前言
OSI网络模型分七层,由下至上分别为:物理层
、数据链路层
、网络层
、传输层
、会话层
、表示层
、应用层
,TCP协议处于传输层。
Node.js中TCP
Node.js中的 net
模块提供了对TCP协议的封装,使用 net
模块可以轻松的构建一个TCP服务器,或构建一个连接TCP服务器的客户端。
net模块创建一个服务器
let net = require('net')
let server = net.createServer()
server.on('connection', function (socket) {
// socket套接字 会话,http有请求 响应
console.log('connection success')
})
server.listen(3000, function () {
console.log('server start at 3000')
})
复制代码
这样就创建了一个简单的服务。启动在3000端口,并且会监听客户端的连接。socket
表示套接字,是一个可读可写的双工流,可以理解为客户端和服务端之间的会话。
接收数据
socket.setEncoding('utf8')
socket.on('data', function (data) {
console.log(data)
})
复制代码
响应数据
socket.write('nice to meet you')
复制代码
关闭客户端
socket.end('end...') // 关闭客户端
复制代码
关闭服务端
server.close(); // 如果触发close事件就不会再接收新的请求了
server.unref(); // 也表示关闭 ,没有客户端连接会自己关闭(不会触发close事件)
server.on('close', function () {
console.log('server closed')
})
复制代码
close
调用后, 服务端不再接收新的请求,当没有客户端连接,会触发close
事件,并关闭服务端
unref
调用后,服务端继续接受新的请求,当没有客户端连接,不会触发close
事件,并关闭服务端。
设置最大连接数
server.maxConnections = 2; // 设置最大连接数,超过数量不能连接
复制代码
聊天室的实现
有了上面的知识,我们可以动手实现一个聊天室。
需求
- 当前用户在线数,最多可连接的用户数;
- 输入
l:
, 查看当前用户列表; - 输入
s:zs: hello
, 向张三发送消息; - 输入
r: ls
, 给自己重命名为ls; - 输入
b: nice to meet you
, 向其他用户广播消息
创建服务
let net = require('net');
let server = net.createServer((socket) => {
let key = socket.remoteAddress + socket.remotePort
console.log(key)
})
server.listen(3000, function () {
console.log('server start at 3000')
})
复制代码
缓存客户端信息
let client = {}
let server = net.createServer((socket) => {
// 显示欢迎信息
server.maxConnections = 3;
server.getConnections(function (err, count) {
socket.write(`欢迎到来,当前用户 ${count},总容纳 ${server.maxConnections}\r\n`)
})
// 缓存用户
let key = socket.remoteAddress + socket.remotePort
client[key] = {nickname: '匿名',socket}
})
复制代码
缓存客户端信息到 client
中,以客户的ip+port作为key值,nickname默认为匿名,并保存回话socket。
处理客户端输入的指令
socket.setEncoding('utf8')
socket.on('data', function (chunk) {
chunk = chunk.replace(/\r\n/,'')
let [command, target, content] = chunk.split(':')
switch (command) {
case 'l': //查看用户列表
showList(socket);
break;
case 's': //私聊
charTo(target,content, client[key].nickname);
break;
case 'r': //重命名
rename(key, target);
break;
case 'b': //想其他用户广播
broadcast(key, target, client[key].nickname);
break;
default:
break;
}
})
复制代码
处理函数的实现
showList
函数的实现
function showList(socket) {
let users = []
Object.values(client).forEach(user => {
users.push(user.nickname)
})
socket.write(`当前用户列表:\r\n ${users.join('\r\n')} \r\n`)
}
复制代码
charTo
函数的实现
function charTo(target, content, source) {
let targetSocket;
Object.values(client).forEach(user => {
if(user.nickname === target) {
targetSocket = user.socket
}
})
targetSocket.write(`${source}:${content}\r\n`)
}
复制代码
rename
函数的实现
function rename(key, content) {
client[key].nickname = content;
client[key].socket.write(`重命名为: ${content}\r\n`)
}
复制代码
broadcast
函数的实现
function broadcast(key, content, nickname) {
Object.keys(client).forEach(user => {
if(user !== key) {
client[user].socket.write(`${nickname}: ${content}\r\n`)
}
})
}
复制代码
成果检验
我们的建议聊天室就做好了,大家可以使用 PuTTY
这个软件作为客户端发送tcp请求;
下面是我测试的结果。
结语
自己实现一个聊天室的过程还是挺有意思的,欢迎喜欢捣鼓的同学多多交流。