利用Node.js的Net模块实现一个命令行多人聊天室
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++网络编程差不多的,都是以下步骤。
服务端:
- 创建socket套接字
- 绑定IP和端口
- 启动监听
- 等待客户端连接
- 与客户端进行通信
- 关闭socket
客户端:
- 创建socket套接字
- 连接server服务器
- 与服务器进行通信
- 关闭socket
如下图所示:

2、聊天室的设计和实现
上面学习了NET模块API的使用,接下来便开始实现命令行聊天室,我们不需要做的很复杂,只需实现如下功能即可:
- 用户自定义昵称,不可更改
- 当有新的用户进入聊天室,或者用户离开聊天室,广播给其他用户
- 用户发送信息,需广播给其他用户
- 客户端与服务端建立心跳机制
- 用户输入'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模块实现一个命令行多人聊天室的更多相关文章
- node.js中使用yargs来处理命令行参数
yargs库能够方便的处理命令行参数. 一.安装 yargs npm install yargs --save 二.读取命令行参数 const yargs = require('yargs'); le ...
- Node.js实战5:操作系统与命令行。
Nodejs有一些内置的方法可以查询操作系统信息: 如: process.arch获取到系统是32位还是64位, process.platform可获取系统的类型. 例程: console.log(p ...
- 在nginx服务器里面搭建好node.js本地服务器后,利用Node.js的FS模块,实现简单数据的写入和读取
先在server.js里面引入: var fs = require('fs'); 然后写入 // 往writeme.txt文件 写入一些内容 fs.writeFile('./writem ...
- 【Node.js】利用node.js搭建服务器并访问静态网页
node.js是一门服务端的语言,下面讲讲如何利用node.js提供给我们的api来搭建服务器,并且访问静态网页 项目结构如下 ------------------------------------ ...
- 浅析 Node.js 的 vm 模块以及运行不信任代码
在一些系统中,我们希望给用户提供插入自定义逻辑的能力,除了 RPC 和 REST 之外,运行客户提供的代码也是比较常用的方法,好处是可以极大地减少在网络上的耗时.JavaScript 是一种非常流行而 ...
- Node.js的Formidable模块的使用
今天总结了下Node.js的Formidable模块的使用,下面做一些简要的说明. 1) 创建Formidable.IncomingForm对象 var form = new formidab ...
- Node.js 教程 04 - 模块系统
前言: Node.js的模块系统类似于C/C++的文件引用,可以声明对象,也可以定义类 创建对象. 大家这么理解,就简单了. 定义: 为了让Node.js的文件可以相互调用,Node.js提供了一个简 ...
- Node.js的cluster模块——Web后端多进程服务
众所周知,Node.js是单线程的,一个单独的Node.js进程无法充分利用多核.Node.js从v0.6.0开始,新增cluster模块,让Node.js开发Web服务时,很方便的做到充分利用多核机 ...
- Node.js入门:模块机制
CommonJS规范 早在Netscape诞生不久后,JavaScript就一直在探索本地编程的路,Rhino是其代表产物.无奈那时服务端JavaScript走的路均是参考众多服务器端语言来 ...
随机推荐
- jQuery之ajax实现篇
jQuery的ajax方法非常好用,这么好的东西,你想拥有一个属于自己的ajax么?接下来,我们来自己做一个简单的ajax吧. 实现功能 由于jq中的ajax方法是用了内置的deferred模块,是P ...
- Java 征途:行者的地图
前段时间应因缘梳理了下自己的 Java 知识体系, 成文一篇望能帮到即将走进或正在 Java 世界跋涉的程序员们. 第一张,基础图 大约在 2003 年我开始知道 Java 的(当时还在用 Delph ...
- excel 日期/数字格式不生效需要但双击才会生效的解决办法
原因: Excel2007设置过单元格格式后,并不能立即生效必须挨个双击单元格,才能生效.数据行很多.效率太低. 原因:主要是一些从网上拷贝过来的日期或数字excel默认为文本格式或特殊-中文数字格式 ...
- Android笔记——Button点击事件几种写法
Button点击事件:大概可以分为以下几种: 匿名内部类 定义内部类,实现OnClickListener接口 定义的构造方法 用Activity实现OnClickListener接口 指定Button ...
- git 命令
切换仓库地址: git remote set-url origin xxx.git切换分支:git checkout name撤销修改:git checkout -- file删除文件:git rm ...
- Android性能优化之利用Rxlifecycle解决RxJava内存泄漏
前言: 其实RxJava引起的内存泄漏是我无意中发现了,本来是想了解Retrofit与RxJava相结合中是如何通过适配器模式解决的,结果却发现了RxJava是会引起内存泄漏的,所有想着查找一下资料学 ...
- windows+nginx+iis+redis+Task.MainForm构建分布式架构 之 (nginx+iis构建服务集群)
本次要分享的是利用windows+nginx+iis+redis+Task.MainForm组建分布式架构,由标题就能看出此内容不是一篇分享文章能说完的,所以我打算分几篇分享文章来讲解,一步一步实现分 ...
- 【Web动画】SVG 实现复杂线条动画
在上一篇文章中,我们初步实现了一些利用基本图形就能完成的线条动画: [Web动画]SVG 线条动画入门 当然,事物都是朝着熵增焓减的方向发展的,复杂线条也肯定比有序线条要多. 很多时候,我们无法人工去 ...
- HDU1671——前缀树的一点感触
题目http://acm.hdu.edu.cn/showproblem.php?pid=1671 题目本身不难,一棵前缀树OK,但是前两次提交都没有成功. 第一次Memory Limit Exceed ...
- PHP设计模式(一)简单工厂模式 (Simple Factory For PHP)
最近天气变化无常,身为程序猿的寡人!~终究难耐天气的挑战,病倒了,果然,程序猿还需多保养自己的身体,有句话这么说:一生只有两件事能报复你:不够努力的辜负和过度消耗身体的后患.话不多说,开始吧. 一.什 ...