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

1、net模块基本API
要使用Node.js的net模块实现一个命令行聊天室,就必须先了解NET模块的API使用。NET模块API分为两大类:Server和Socket类、工厂方法。
Server类如下图所示:
server类
net.Server类可以用来创建一个TCP或本地服务器,继承了EventEmitter。
Socket类如下:
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);
}

执行结果如下如下:

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

posted @ 2016-12-22 17:19  SniffRose  阅读(2408)  评论(2编辑  收藏  举报