当前位置: 首页 > 后端技术 > Node.js

基于TCP实现一个简单的聊天室

时间:2023-04-03 19:15:09 Node.js

阅读原序TCP是传输层协议。在NodeJS中,基于TCP的核心模块是net,http和https模块都是基于net实现的。下面先简单介绍下net的使用,然后基于net实现一个简单的聊天室。net模块的基本使用1.使用net创建网络服务方法一:constnet=require("net");//创建TCP服务constserver=net.createServer(function(socket){//......});server.listen(3000);方法二:constnet=require("net");//创建一个TCP服务constserver=net.createServer();//监听连接server.on("连接",函数(套接字){//......});服务器.listen(3000);以上两种创建网络服务的方式,第二种比较常用,回调函数的参数都是socket,在产生连接时执行,每产生一个连接就产生一个socket。我们也可以把套接字理解为客户端。如果现在用浏览器连接这个服务,可以成功收到请求,但是浏览器使用的是http协议,不识别,所以不会有响应。2.使用TCP模拟httpconstnet=require("net");//创建TCP服务constserver=net.createServer();//监听连接server.on("connection",function(socket){//设置编码socket.setEncoding("utf8");//读取请求消息socket.on("data",function(data){console.log(data);});//返回响应消息给浏览器socket.write(`HTTP/1.1200okContent-Length:5hello`);});server.listen(3000);//GET/favicon.icoHTTP/1.1//Host:localhost:3000//Connection:keep-alive//Pragma:no-cache//Cache-Control:no-cache//......省略后面socket是一个可读可写的流Duplex(双工流),所以可以从浏览器读取请求信息,响应信息即可书面。模拟http时,必须遵循http协议规则。每行前不允许有空格或制表符,响应头和响应文本之间需要一个空行。此时启动服务,使用浏览器访问localhost:3000,即可在控制台打印请求信息,并在浏览器中显示hello。在命令窗口使用curl发送请求可以查看http的header信息。输入命令为curl-vhttp://.......,默认命令行窗口不支持curl命令,系统请到curl官网下载对应版本,在windows系统下,解压后下载的压缩包,将curl.exe和ca-bundle.crt复制到C:\Windows\System32或将文件夹添加到系统环境变量中。3、server和socket的属性和方法TCP创建的服务server和连接中的socket都有一些属性、方法和事件。我们将通过下面的例子来介绍它。constnet=require("net");//创建一个TCP服务器constserver=net.createServer();server.on("connection",function(socket){//客户端的ip+端口号letkey=socket.remoteAddress+socket.remotePort;server.getConnetions(function(err,count){socket.write(`目前有${count}人,总人数为${server.maxConnections}人。`);});socket.on("data",function(data){//设置编码socket.setEncoding("utf8");//关闭客户端//socket.end();//关闭服务器//server.close();server.unref();});});//最大连接数server.maxConnections=3;server.on("close",function(){console.log("serverclose");});server.on("error",function(err){if(err.code==="EADDRINUSE"){server.listen(err.port+1);}});server.listen(3000,function(){console.log("服务器启动3000");});socket.remoteAddress属性获取客户端的IP地址。socket.remotePort属性,获取客户端的端口号。socket.setEncoding方法,设置编码格式。socket.write方法,向客户端写入内容,写入内容的值只能是字符串或Buffer。socket.end方法断开相应的客户端并返回信息。返回内容的值只能是字符串或Buffer。套接字可以监听结束事件,并在客户端关闭时触发并执行回调。socket.destroy方法用于销毁当前客户端对应的socket对象。server.maxConnections属性是当前服务器允许的最大连接数。它是一个数值。当连接数超过设定值时,新客户端将无法连接到服务器。server.getConnetions方法,获取当前连接数,参数为回调函数,回调函数有两个参数err(错误)和count(当前连接数),异步执行。server.close方法关闭服务器,并不是真正关闭服务器,而是不允许新的连接,直到所有连接断开,服务器自动关闭。server.unref方法是关闭服务器的另一种形式,它不会阻塞新的连接,并在所有连接断开时自动关闭服务器。server.listen方法,监听端口号,支持传入回调,服务启动后执行。服务器的关闭事件,参数为回调函数,异步执行,服务器关闭时触发。服务器的错误事件,参数为回调函数,回调函数的参数为??err(错误对象),异步执行,在服务器启动或运行时出错时触发。在webpack中,如果启动了webpack-dev-server,端口号被占用,端口号会自动+1。我们可以使用err错误对象来模拟。err事件对象上有很多属性,code属性值为EADDRINUSE,表示端口号被占用,所以判断code值后,再次调用server.listen,传入重新计算的端口号。要想看到上面代码的效果,需要客户端的支持。本文中模拟客户端访问服务端的方式有3种,你可以使用一种。创建客户端验证我们自己实现的TCP服务器需要客户端访问,简单的聊天室也需要用户和客户端,也就是本文的主题,所以我介绍一下创建客户端的方法。您可以使用net模块创建客户端并启用对服务器的访问;Mac上直接在命令窗口执行brewinstalltelnet安装telnet,安装完成后输入telnetlocalhost3000即可访问上述服务器;在Windows上,telnet收到的服务器响应会变成乱码,可以使用Xshell、PuTTY等客户端工具。使用net创建客户端的代码如下://client:client.jsconstnet=require("net");//创建客户端letclient=net.createConnection({port:3000});//向服务器发送消息client.write("s:username:message");为了方便本文中PuTTY工具的使用,Windows系统在使用前需要打开Telnet服务器和客户端,通过控制面板→打开或关闭Windows功能→勾选Telnet服务器和客户端。PuTTY的界面如下。Connectiontype(连接类型)默认为SSH。我们之所以使用Raw而不是其他类型,是因为其他方法在连接服务器时会发送窗口信息,而我们不需要这些数据。单击界面底部的“打开”按钮以创建客户端连接。客户端窗口如下,输入回车即可向服务器发送消息。现在所有的准备工作都准备好了,下面就是我们的正题了,使用net模块实现一个TCP服务,使用PuTTY作为客户端实现一个简单的聊天室。实现一个简单的聊天室1.定义聊天室规则聊天室主要有四个功能,都需要输入相应的命令。显示在线用户:命令为l;重命名:聊天室默认用户名为匿名用户,重命名命令为r:newname;私聊:私聊的参数是聊天对象的名称和消息内容,命令为s:username:message;广播:发送的消息除了自己以外的所有人都可以收到,命令为b:message。存储所有client时,以client的ip+port作为用户的唯一标识。2.服务构建//Server:server.jsconstnet=require("net");//处理输入命令模块constprocessInstructs=require("./process-instructs");constserver=net.createServer();//创建服务letclient={};//客户端让端口=3000;//端口号//监听连接server.on("connection",socket=>{//客户端的ip+端口号作为存储客户端唯一标识letkey=socket.remoteAddress+socket.remotePort;//添加客户端到客户端storeclient[key]={username:"anonymous",socket};//欢迎函数server.getConnections((err,count)=>{socket.write(`欢迎加入!目前有${count}人。\r\n`);});//设置编码socket.setEncoding("utf8");//监听用户输入socket.on("data",data=>{//由于输入的是message,回车确认,所以需要处理message中的回车data=data.replace(/\r\n/,"");//处理输入并响应processInstructs(client,key,data);});//client主动关闭后,清除serverclient存储中的client,并销毁对应的socketsocket.on("end",()=>{socket.destroy();deleteclient[key];});});//监听端口号server.listen(port,()=>{console.log(`serverstart${port}`);});在上面的服务构建中,创建了一个client对象,用于存储聊天室的客户Terminal和信息,client使用ip+port作为存储的唯一标识,用户名默认为“匿名”,设置了欢迎功能,并显示当前在线号码,监听用户输入,处理消息中的回车。process-instructs处理指令,最后处理离开的用户,目的是防止其他人在离开后使用私聊或广播功能通知此人,因找不到对应的socket而出错。3.处理指令模块process-instructs//文件:process-instructs.js//引入处理不同指令的功能函数const{list,rename,private,broadcast}=require("./instructs");module.exports=函数(客户端,密钥,数据){让dataArr=data.split(“:”);//不同的指令调用不同的处理方法switch(dataArr[0]){case"l":list(client,key);休息;案例“r”:重命名(客户端,密钥,dataArr);休息;case"s":private(client,key,dataArr);休息;默认值:socket.write("命令错误\r\n");}};在上面的指令处理中,针对不同的指令介绍了instructs模块对应的处理方法。4.指令处理方法模块instructs//文件:instructs.js//处理l条指令,显示在线用户exports.list=function(client,key){//获取当前socketletsocket=client[key].socket;//写入信息socket.write("当前用户列表:\r\n");Object.values(client).forEach(p=>{socket.write(`${p.username}\r\n`);});};//处理r命令,用户重命名exports.rename=函数(客户端,密钥,dataArr){letnewName=dataArr[1];//更新对应socket的新用户名并通知client[key].username=newName;client[key].socket.write(`Thenewusernameis:${newName}\r\n`);};//处理s命令,私聊exports.private=function(){Object.keys(client).forEach(c=>{if(client[c].username===dataArr[1]){client[c].socket.write(`${client[key].username}:${dataArr[2]}\r\n`);}});};//处理b命令,广播exports.broadcast=function(){Object.keys(client).forEach(c=>{if(c!==key){client[c].socket.write(`${client[key].username}:${dataArr[1]}\r\n`);}});};显示在线用户功能的思路是将客户端所有在线用户的用户名循环写入当前socket,重命名功能的思路是获取输入的新用户名并替换客户端用户名中对应的一个,并返回当前新用户名已成功设置到当前套接字的消息。私聊功能的思路是在client中循环遍历所有client。当用户名与发送的用户名相同时,将消息写入用户名对应的socket。广播函数的思想是循环客户端,把消息写给除自己以外的所有客户端。总结本文重点了解多人聊天功能的开发思路,以及NodeJS中TCP传输对应的net模块的使用。实际上,本文中聊天室中的代码在用户同名时并没有做任何事情。通常,id应该用作唯一标识符,而不是指定用户名。其实在NodeJS开发中很少直接使用net。大部分情况下都是用http和https来代替的,但是要知道它们都是基于net封装的,知道net在使用http和https的时候会更加得心应手。