1、net模块基本API

要使用Node.js的net模块实现一个命令行聊天室,就必须先了解NET模块的API使用。NET模块API分为两大类:Server和Socket类、工厂方法。

Server类如下图所示:



net.Server类可以用来创建一个TCP或本地服务器,继承了EventEmitter。

Socket类如下:



net.Socket类一般用创建一个socket客户端或者是net.Server connection事件的参数。

工厂方法如下:



以上三个图展示了API的使用,其实NET模块的内部原理和C++网络编程差不多的,都是以下步骤。

服务端:

  1. 创建socket套接字
  2. 绑定IP和端口
  3. 启动监听
  4. 等待客户端连接
  5. 与客户端进行通信
  6. 关闭socket

客户端:

  1. 创建socket套接字
  2. 连接server服务器
  3. 与服务器进行通信
  4. 关闭socket

如下图所示:

2、聊天室的设计和实现

上面学习了NET模块API的使用,接下来便开始实现命令行聊天室,我们不需要做的很复杂,只需实现如下功能即可:

  1. 用户自定义昵称,不可更改
  2. 当有新的用户进入聊天室,或者用户离开聊天室,广播给其他用户
  3. 用户发送信息,需广播给其他用户
  4. 客户端与服务端建立心跳机制
  5. 用户输入'exit'或者'quit'可以退出聊天室

确定功能之后,便开始代码的编写。这里我就不一步步分析,直接上代码了,首先是服务端:

Server:

const net = require('net');
const server = net.createServer();
const clients = {};//保存客户端的连接
var client = null;//当前客户连接
var uid = 0;
server.on('connection',(socket)=>{
//启动心跳机制
var isOnline = !0;
var keepAliveTimer = socket.timer = setInterval(()=>{
if(!isOnline){
client = socket;
quit(socket.nick);
return;
}
if(socket.writable){
isOnline = !1;
socket.write('::');
}else{
client = socket;
quit(socket.nick);
}
},3000);
socket.on('end',()=>{
console.log(`client disconnected.\n\r`);
socket.destroy();
});
socket.on('error',(error)=>{
console.log(error.message);
});
socket.on('data',(chunk)=>{
client = socket;
var msg = JSON.parse(chunk.toString());
if(msg.cmd=='keep'){
isOnline = !0;
return;
}
dealMsg(msg);
});
});
server.on('error',(err)=>{
console.log(err);
});
server.on('listening',()=>{
console.log(`listening on ${server.address().address}:${server.address().port}\n\r`);
});
server.listen(8060);//启动监听
/**
* 处理用户信息
*/
function dealMsg(msg){
const cmd = msg.cmd;
const funs = {
'login':login,
'chat':chat,
'quit':quit,
'exit':quit
};
if(typeof funs[cmd] !== 'function') return !1;
funs[cmd](msg);
}
/**
* 释放连接资源
*/
function freeConn(conn){
conn.end();
delete clients[conn.uuid];
conn.timer&&clearInterval(conn.timer);
}
/**
* 用户首次进入聊天室
*/
function login(msg){
var uuid = '';
uuid = getRndStr(15)+(++uid);//产生用户ID
client.write(`欢迎你,${msg.nick}:这里总共有${Object.keys(clients).length}个小伙伴在聊天.\r\n`)
client.nick = msg.nick;
client.uuid = uuid;
clients[uuid] = client;
broadcast(`系统:${msg.nick}进入了聊天室.`); }
/**
* 广播消息
*/
function broadcast(msg){
Object.keys(clients).forEach((uuid)=>{
if((clients[uuid]!=client)& clients[uuid].writable){
clients[uuid].write(msg);
}
});
}
/**
* 退出聊天室
*/
function quit(nick){
var message = `小伙伴${nick}退出了聊天室.`;
broadcast(message);
freeConn(client);
} function chat(msg){
if(msg.msg.toLowerCase()=='quit'||msg.msg.toLowerCase()=='exit'){
quit(msg.nick);
return ;
}
var message = `${msg.nick}说:${msg.msg}`;
broadcast(message);
}
/**
* 随机指定长度(len)的字符串
*/
function getRndStr(len=1){
var rndStr = '';
for (; rndStr.length < len; rndStr += Math.random().toString(36).substr(2));
return rndStr.substr(0, len);
}

客户端代码如下:

client:

const net = require('net');
const cout = process.stdout;
const cin = process.stdin; var client = null;
var nick = ''; cout.write(`请输入昵称:`);
//监听命令行输入
cin.on('data',(chunk)=>{
if(chunk.toString()!='\r\n'){
if(client === null){
nick = (chunk+'').replace(/[\r\n]/ig,"");
createClient();
}else{
msg = (chunk+'').replace(/[\r\n]/ig,"");
client.write(JSON.stringify({
cmd: 'chat',
msg: msg,
nick: nick
}));
//如果输入是exit或quit则断开连接并退出
if(msg.toLowerCase() == 'exit' || msg.toLowerCase() == 'quit'){
client.end();
cin.end();
return;
}
cout.write(`你说:${msg}\n\r`);
}
}else{
cout.write(`请输入昵称:`);
}
}); function addListener(client) {
client.on('connect', () => {
cout.write(`已连接到服务器\n\r`);
client.write(JSON.stringify({
cmd: 'login',
msg: 'hello server',
nick: nick
}));
});
client.on('end', (chunk) => {
cout.write(`与服务器断开连接.\n\r`);
});
client.on('data', (chunk) => {
//如果是心跳信息则回应keep命令
if(chunk.toString()=='::'){
client.write(JSON.stringify({
cmd: 'keep',
msg: '',
nick: nick
}));
return ;
}
cout.write(`${chunk}\n\r`);
});
client.on('error', (err) => {
cout.write(`an error has occured.\n\r${err}`);
});
}
/**
* 创建socket并连接服务器
*/
function createClient(){
console.log('\033[2J');//清屏操作
cout.write(`输入'EXIT OR QUIT'退出聊天室.\r\n`);
client = new net.Socket()
client.connect({port:8060/*,host:'1.1.1.69'*/});
addListener(client);
}

执行结果如下如下:

到此,一个命令行聊天室便做完了。

利用Node.js的Net模块实现一个命令行多人聊天室的更多相关文章

  1. node.js中使用yargs来处理命令行参数

    yargs库能够方便的处理命令行参数. 一.安装 yargs npm install yargs --save 二.读取命令行参数 const yargs = require('yargs'); le ...

  2. Node.js实战5:操作系统与命令行。

    Nodejs有一些内置的方法可以查询操作系统信息: 如: process.arch获取到系统是32位还是64位, process.platform可获取系统的类型. 例程: console.log(p ...

  3. 在nginx服务器里面搭建好node.js本地服务器后,利用Node.js的FS模块,实现简单数据的写入和读取

    先在server.js里面引入: var fs = require('fs');   然后写入  // 往writeme.txt文件 写入一些内容     fs.writeFile('./writem ...

  4. 【Node.js】利用node.js搭建服务器并访问静态网页

    node.js是一门服务端的语言,下面讲讲如何利用node.js提供给我们的api来搭建服务器,并且访问静态网页 项目结构如下 ------------------------------------ ...

  5. 浅析 Node.js 的 vm 模块以及运行不信任代码

    在一些系统中,我们希望给用户提供插入自定义逻辑的能力,除了 RPC 和 REST 之外,运行客户提供的代码也是比较常用的方法,好处是可以极大地减少在网络上的耗时.JavaScript 是一种非常流行而 ...

  6. Node.js的Formidable模块的使用

    今天总结了下Node.js的Formidable模块的使用,下面做一些简要的说明. 1)     创建Formidable.IncomingForm对象 var form = new formidab ...

  7. Node.js 教程 04 - 模块系统

    前言: Node.js的模块系统类似于C/C++的文件引用,可以声明对象,也可以定义类 创建对象. 大家这么理解,就简单了. 定义: 为了让Node.js的文件可以相互调用,Node.js提供了一个简 ...

  8. Node.js的cluster模块——Web后端多进程服务

    众所周知,Node.js是单线程的,一个单独的Node.js进程无法充分利用多核.Node.js从v0.6.0开始,新增cluster模块,让Node.js开发Web服务时,很方便的做到充分利用多核机 ...

  9. Node.js入门:模块机制

    CommonJS规范      早在Netscape诞生不久后,JavaScript就一直在探索本地编程的路,Rhino是其代表产物.无奈那时服务端JavaScript走的路均是参考众多服务器端语言来 ...

随机推荐

  1. 从直播编程到直播教育:LiveEdu.tv开启多元化的在线学习直播时代

    2015年9月,一个叫Livecoding.tv的网站在互联网上引起了编程界的注意.缘于Pingwest品玩的一位编辑在上网时无意中发现了这个网站,并写了一篇文章<一个比直播睡觉更奇怪的网站:直 ...

  2. NodeJs在Linux下使用的各种问题

    环境:ubuntu16.04 ubuntu中安装NodeJs 通过apt-get命令安装后发现只能使用nodejs,而没有node命令 如果想避免这种情况请看下面连接的这种安装方式: 拓展见:Linu ...

  3. Asp.net Core中使用Session

    前言 2017年就这么悄无声息的开始了,2017年对我来说又是特别重要的一年. 元旦放假在家写了个Asp.net Core验证码登录, 做demo的过程中遇到两个小问题,第一是在Asp.net Cor ...

  4. ASP.NET MVC5+EF6+EasyUI 后台管理系统(74)-微信公众平台开发-自定义菜单

    系列目录 引言 1.如果不借用Senparc.Weixin SDK自定义菜单,编码起来,工作量是非常之大 2.但是借助SDK似乎一切都是简单得不要不要的 3.自定义菜单无需要建立数据库表 4.自定义菜 ...

  5. C#使用Aspose.Cells导出Excel简单实现

    首先,需要添加引用Aspose.Cells.dll,官网下载地址:http://downloads.aspose.com/cells/net 将DataTable导出Xlsx格式的文件下载(网页输出) ...

  6. BZOJ3198[SDOI2013]SPRING

    Description Input Output Sample Input 3 3 1 2 3 4 5 6 1 2 3 0 0 0 0 0 0 4 5 6 Sample Output 2 HINT 题 ...

  7. ASP.NET Core "完整发布,自带运行时" 到jexus

    一.阅读前须知   1.使用 jexus整合asp.net core的优点:       1)支持多站点,同一端口可以同时支持任何多的asp.net core应用程序:      2)应用程序启动.停 ...

  8. Redola.Rpc 的一个小目标

    Redola.Rpc 的一个小目标 Redola.Rpc 的一个小目标:20000 tps. Concurrency level: 8 threads Complete requests: 20000 ...

  9. Floyd-Warshall 全源最短路径算法

    Floyd-Warshall 算法采用动态规划方案来解决在一个有向图 G = (V, E) 上每对顶点间的最短路径问题,即全源最短路径问题(All-Pairs Shortest Paths Probl ...

  10. 基于 SailingEase WinForm Framework 开发优秀的客户端应用程序(目录)

    本系统文章将详细阐述客户端应用程序的设计理念,实现方法. 本系列文章以  SailingEase WinForm Framework 为基础进行设计并实现,但其中的设计理念及方法,亦适用于任何类型的客 ...