1、前言
这里不得不先说下TCP和websocket通讯的区别:
区别
按照OSI网络分层模型,IP是网络层协议,TCP是传输层协议,而HTTP是应用层的协议。在这三者之间,SPDY和WebSocket都是与HTTP相关的协议,而TCP是HTTP底层的协议。
WebSocket则提供使用一个TCP连接进行双向通讯的机制,包括网络协议和API,以取代网页和服务器采用HTTP轮询进行双向通讯的机制。
本质上来说,WebSocket是不限于HTTP协议的,但是由于现存大量的HTTP基础设施,代理,过滤,身份认证等等,WebSocket借用HTTP和HTTPS的端口。
由于使用HTTP的端口,因此TCP连接建立后的握手消息是基于HTTP的,由服务器判断这是一个HTTP协议还是WebSocket协议。
WebSocket连接除了建立和关闭时的握手,数据传输和HTTP没丁点关系了。
一句话总结下
websocket建立握手是通过http的,后面真正传输不再进行HTTP的交互,而是开始websocket的数据帧协议。实现客户端与服务端的数据交换
那么使用Nodejs创建一个TCP服务器呢?
2、实现要求
服务器可以向客户端群发信息,也要支持定向发送信息
3、服务端简单实现
net模块是Nodejs内置的基础网络模块,通过使用net,可以创建一个简单的tcp服务器
server.listen(port[,host][,backlog][,callback])
port参数为需要监听的端口号,参数值为0的时候将随机分配一个端口号。
host为服务器地址。
backlog为等待队列的最大长度
callback为回调函数
回调事件:
connection:当前新的链接创建时触发,回调函数的参数为socket连接对象,同net.createServer的第二个函数参数。
close:TCP服务器关闭的时候触发,回调函数没有参数
error:TCP服务器发生错误的时候触发,回调函数的参数为error对象
listening:调用server.listen时触发的回调
// 引入NET
const net = require('net');
// 创建TCP服务器
const server = net.createServer(function (client) {
console.log('someones connects');
// 接收客户端的数据
client.on('data', function (data) {
setTimeout(() => {
client.write('服务端发送通知');
}, 3000);
console.log('服务端接收到来自客户端的数据:', data.toString('utf-8'));
});
// 客户端连接关闭
client.on('close', function (err) {
console.log('客户端下线');
});
// 客户端连接错误
client.on('error', function (err) {
console.log('客户端连接错误');
});
});
// 监听客户端的连接
server.listen(
{
port: 9001,
host: '127.0.0.1',
},
function () {
console.log('server start listening');
}
);
//设置监听时的回调函数
server.on('listening', function () {
const { address, port } = server.address();
console.log('server is running: ' + address + ':' + port);
});
//设置关闭时的回调函数
server.on('close', function () {
console.log('sever closed');
});
//设置出错时的回调函数
server.on('error', function () {
console.log('sever error');
});
4、客户端简单实现
const net = require('net');
const sock = net.connect(
{
port: 9001,
host: '127.0.0.1',
},
function () {
console.log('connected to server!');
}
);
// 连接成功
sock.on('connect', function () {
console.log('connect success');
// 向服务端发送数据
sock.write("测试数据", 'utf8');
});
// 接收来自服务端的信息
sock.on('data', function (data) {
console.log('接收到来自服务端的信息:', data.toString('utf-8'));
});
// 有错误发生调用的事件
sock.on('error', function (e) {
console.log('socket error', e);
});
// socket关闭的事件
sock.on('close', function () {
console.log('socket close');
});
// 对方发送了关闭数据包过来的事件
sock.on('end', function () {
console.log('socket end');
});
5、逻辑实现
上面的简单例子,已经实现了群发功能,服务端发送消息后,所有建立连接的客户端都能收到信息。
那么如果一对一发送消息呢?
由于每个客户端和服务端之间的连接没有一个别名,我们只能获取到客户端的IP和PORT,所以索性我们把IP+PORT拼接为SessionId作为唯一ID
,
返回把所有Client实例存储起来
// 自定义客户端连接SESSIONID
const { remoteAddress, remotePort } = client;
client.name = `${remoteAddress}:${remotePort}`;
/**
* 存储客户端,一对多,或者一对一,广播
* clients = [{name: 客户端别名, sessionId: 唯一会话ID,client:客户端连接实例}]
**/
const clients = [];
// 存储
function storeClient(client, name = '') {
if (!client || !client.remoteAddress || !client.remotePort) return;
const { remoteAddress, remotePort } = client;
const sessionId = `${remoteAddress}:${remotePort}`;
const existClient = clients.find(m => m.sessionId == sessionId);
// 如果不存在,存储实例
if (!existClient) {
clients.push({
name,
sessionId,
client,
});
} else {
// 如果存在,更新
existClient.client = client;
existClient.name = name;
}
}
如果服务器想通过别名去发送消息呢,该怎么做?
客户端可以在连接成功的第一时间,同步别名给服务器
,这样服务器就知道客户端的别名了
// 连接成功调用的事件
sock.on('connect', function () {
console.log('connect success');
// 同步服务端,客户端基础信息
const info = {
type: 'sync',
clientName: 'Client-1',
message: '同步基础信息',
};
const message = `${info.clientName}向服务端同步基础信息${info.message}`;
sock.write(JSON.stringify(info), 'utf8');
});
6、完整代码
服务端
const net = require('net');
/**
* 存储客户端,一对多,或者一对一,广播
* clients = [{name: 客户端别名, sessionId: 唯一会话ID,client:客户端连接实例}]
**/
const clients = [];
// 创建
const server = net.createServer(function (client) {
// 自定义客户端连接SESSIONID
// 或者可以在客户端定义UUID,同步信息过来
const { remoteAddress, remotePort } = client;
client.name = `${remoteAddress}:${remotePort}`;
// 存储
storeClient(client);
console.log('[客户端已接入]: ', client.name);
console.log(
'[目前已接入的所有客户端]: ',
clients.map(m => m.sessionId)
);
// 监听来自客户端的数据
client.on('data', function (data) {
try {
// 一定要约束固定的消息模板,这里data约定是JSON格式的数据
const _data = JSON.parse(data);
const { type, clientName, message } = _data;
if (type == 'sync') {
// 同步信息
storeClient(client, clientName);
} else if (type == 'info') {
// 消息互传
}
console.log(
'服务端接收到来自客户端' + client.name + '的数据',
message.toString('utf-8')
);
} catch (error) {}
});
// 客户端断开连接的时候处理,
// 提示用户断线离开了,删除客户端连接池
client.on('close', function () {
const index = clients.findIndex(m => m.sessionId == client.name);
if (index >= 0) clients.splice(index, 1);
console.log(
'【all clients】',
clients.map(m => m.sessionId)
);
console.log(client.name, '下线了');
});
// 客户端连接错误
client.on('error', function (err) {
console.log(client.name + ' 连接错误', err);
});
});
// 广播
const broadcast = ({ clientName, message }) => {
// 定向广播
if (clientName) {
const client = clients.find(m => m.name == clientName);
client && client.client.write(message);
} else {
// 群发
clients.map(m => {
m.client.write(message);
});
}
};
// 测试
let testTimeout = null;
server.on('connection', function () {
clearTimeout(testTimeout);
testTimeout = setTimeout(() => {
console.log('通知所有人:来领物资了');
broadcast({ message: '\n\r大家来领物资了' });
console.log('通知Client-1: 赶紧来做核酸了, 就差你了');
broadcast({ clientName: 'Client-1', message: '\n\r赶紧来做核酸了, 就差你了' });
console.log('通知Client-2: 一会儿上门做核酸');
broadcast({ clientName: 'Client-2', message: '\n\r一会儿上门做核酸' });
}, 10000);
});
server.on('listening', function () {
console.log('等待连接...');
});
// 端口监听
server.listen({
port: 9000,
host: '127.0.0.1',
});
// 监听发生错误的时候调用
server.on('error', function () {
console.log('listen error');
});
server.on('close', function () {
console.log('server stop listener');
});
// 存储
function storeClient(client, name = '') {
if (!client || !client.remoteAddress || !client.remotePort) return;
const { remoteAddress, remotePort } = client;
const sessionId = `${remoteAddress}:${remotePort}`;
const existClient = clients.find(m => m.sessionId == sessionId);
// 如果不存在,存储实例
if (!existClient) {
clients.push({
name,
sessionId,
client,
});
} else {
// 如果存在,更新
existClient.client = client;
existClient.name = name;
}
}
客户端Client-1
const net = require('net');
// net.Socket,
const sock = net.connect(
{
port: 9000,
host: '127.0.0.1',
},
function () {
console.log('connected to server!');
}
);
// 连接成功调用的事件
sock.on('connect', function () {
console.log('connect success');
// 同步服务端,客户端基础信息
const info = {
type: 'sync',
clientName: 'Client-1',
message: '同步基础信息',
};
const message = `${info.clientName}向服务端同步基础信息${info.message}`;
console.log(message);
sock.write(JSON.stringify(info), 'utf8');
// 延迟发送数据
setTimeout(() => {
// 消息模板
const info = {
type: 'info',
clientName: 'Client-1',
message: '1111',
};
const message = `${info.clientName}向服务端发送信息:${info.message}`;
console.log(message);
sock.write(JSON.stringify(info), 'utf8');
}, 3000);
});
// 当有数据发生的时候,调用;
sock.on('data', function (data) {
console.log('Client-1 接收到来自服务端的信息:', data.toString('utf-8'));
});
// 有错误发生调用的事件
sock.on('error', function (e) {
console.log('error', e);
});
// socket关闭的事件
sock.on('close', function () {
console.log('close');
});
// 对方发送了关闭数据包过来的事件
sock.on('end', function () {
console.log('end');
});
客户端Client-2
const net = require('net');
// net.Socket,
const sock = net.connect(
{
port: 9000,
host: '127.0.0.1',
},
function () {
console.log('connected to server!');
}
);
// 连接成功调用的事件
sock.on('connect', function () {
console.log('connect success');
// 同步服务端,客户端基础信息
const info = {
type: 'sync',
clientName: 'Client-2',
message: '同步基础信息',
};
const message = `${info.clientName}向服务端同步基础信息${info.message}`;
console.log(message);
sock.write(JSON.stringify(info), 'utf8');
// 延迟发送数据
setTimeout(() => {
// 消息模板
const info = {
type: 'info',
clientName: 'Client-2',
message: '222',
};
const message = `${info.clientName}向服务端发送信息:${info.message}`;
console.log(message);
sock.write(JSON.stringify(info), 'utf8');
}, 3000);
});
// 当有数据发生的时候,调用;
sock.on('data', function (data) {
console.log('Client-2 接收到来自服务端的信息:', data.toString('utf-8'));
});
// 有错误发生调用的事件
sock.on('error', function (e) {
console.log('error', e);
});
// socket关闭的事件
sock.on('close', function () {
console.log('close');
});
// 对方发送了关闭数据包过来的事件
sock.on('end', function () {
console.log('end');
});