wordpress theme 删除,seo网络营销外包公司,小程序开发工具代理平台,网站备案号是什么样子自从上篇中sokect实现了视频通话#xff0c;但是是使用ws依赖库实现的服务端#xff0c;所以最近再看ws源码#xff0c;不看不知道#xff0c;一看很惊讶。
接下来一点点记录一下#xff0c;如何搭建一个简易的服务端socket#xff0c;来实现上次的视频通讯。
搭建一个…自从上篇中sokect实现了视频通话但是是使用ws依赖库实现的服务端所以最近再看ws源码不看不知道一看很惊讶。
接下来一点点记录一下如何搭建一个简易的服务端socket来实现上次的视频通讯。
搭建一个http服务
首先看一下ws依赖的调用 所以首选我们要创建一个服务器然后监听端口号
这个不难直接使用node自带的http依赖
const http require(http);class MyWebsocket extends EventEmitter {constructor(options) {super(options);const server http.createServer();server.listen(options.port || 8080);}
}module.exports MyWebsocket;这样就启动了一个端口号为8080的http服务了然后这个端口可以自定义所以初始化的时候就传参进来就行。
然后我们继续发现需要用on来监听事件这要如何在node中实现呢
on方法在这里遵循了Node.js EventEmitter模式它允许我们绑定函数到特定的事件上当该事件发生时对应的函数会被执行。
什么意思呢通熟易懂就是继承这个node自带的类EventEmitter 然后你要监听一个connection函数在MyWebsocket中要怎么触发呢
使用 emit 方法来触发你定义的事件并传递任何你想要传递给监听器的数据。
const http require(http);class MyWebsocket extends EventEmitter {constructor(options) {super(options);const server http.createServer();server.listen(options.port || 8080);this.emit(connection, 参数);}
}module.exports MyWebsocket;如何监听客户端socket
然后到了最重要的一步我们最主要的功能就是监听socket那怎么监听客户端来的socket连接
看一下ws的websocket-server.js源码 我们刚刚不是建立了一个http服务吗
监听 upgrade 事件
在 Node.js 中HTTP 服务器可以监听 upgrade 事件来处理 WebSocket 连接或其他需要升级传输层协议的请求。upgrade 事件在客户端发起一个 HTTP 请求并要求升级到其他协议如 WebSocket时触发。 class MyWebsocket extends EventEmitter {constructor(options) {super();options {...options,}const server http.createServer();server.listen(options.port || 8080);this.clients new Set()server.on(upgrade, (req, socket) {this.socket socket; // 存储当前的socket方便后端调用...});}}socket升级协议
然后需要有socket升级协议为什么要有升级协议呢
WebSocket 升级协议WebSocket Upgrade Protocol在 Node.js 中是必要的因为它允许现有的 HTTP 或 HTTPS 服务器与客户端建立持久的、双向的通信连接而这种连接在技术上被称为 WebSocket 连接。
那什么是socket升级协议呢
客户端请求客户端发起一个 HTTP 请求请求头部包含 Upgrade: websocket 和 Connection: Upgrade以及可能的 Sec-WebSocket-Key 和其他 WebSocket 相关的头部信息。服务器响应服务器接收到请求后如果同意升级会在响应中包含 Upgrade: websocket 和 Connection: Upgrade 头部以及一个 Sec-WebSocket-Accept 头部这个头部是服务器对客户端 Sec-WebSocket-Key 的回应。连接升级一旦客户端和服务器都确认了升级它们就会关闭 HTTP 连接同时建立一个新的 WebSocket 连接。这个连接允许双方进行二进制或文本数据的双向通信。
其实就是根据客户端socket连接发过来的请求头返回一个请求头给客户端来建立连接
看一下ws源码的处理 其实就说读取请求头中的sec-websocket-key字段然后加上一个固定的字符串经过 sha1 加密之后转成 base64 的结果就是digest
加密使用node中自带的crypto依赖
const crypto require(crypto);// 也就是用客户端传过来的 key加上一个固定的字符串经过 sha1 加密之后转成 base64 的结果
function hashKey(key) {const sha1 crypto.createHash(sha1);sha1.update(key 258EAFA5-E914-47DA-95CA-C5AB0DC85B11);return sha1.digest(base64);
}这个固定的字符串直接拿ws源码中的 然后就是升级协议的写入
const {EventEmitter
} require(events);
const http require(http);
const crypto require(crypto);const GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11
// 也就是用客户端传过来的 key加上一个固定的字符串经过 sha1 加密之后转成 base64 的结果
function hashKey(key) {const sha1 crypto.createHash(sha1);sha1.update(key GUID);return sha1.digest(base64);
}class MyWebsocket extends EventEmitter {constructor(options) {super(options);const server http.createServer();server.listen(options.port || 8080);server.on(upgrade, (req, socket) {this.socket socket;// socket.setKeepAlive(true);// websocket 升级协议const resHeaders [HTTP/1.1 101 Switching Protocols,Upgrade: websocket,Connection: Upgrade,Sec-WebSocket-Accept: hashKey(req.headers[sec-websocket-key]),,].join(\r\n);socket.write(resHeaders);});}}module.exports MyWebsocket;socket监听传输数据
接下来就说socket监听传输数据和socket关闭
socket.on(data, (data) {console.log(data);
});
socket.on(close, (error) {console.error(close, error)
});然后我们一起看看效果吧 客户端发送的socket数据是 然后看请求头Sec-WebSocket-Accept也对应的上 处理socket传输数据
可以在node中拿到的数据是Buffer的二进制数据首先需要处理的是WebSocket 协议中的数据帧。这里逻辑就有点复杂了。
协议中的数据帧结构是什么样子的
数据帧的结构包括头部Header和负载Payload两部分。以下是数据帧的基本结构
控制位Control Bits FIN1 bit表示这是消息的最后一个片段。如果为1表示这是消息的结束如果为0表示还有后续片段。RSV1、RSV2、RSV3各1 bit保留位用于未来的扩展目前必须设置为0。Opcode4 bits操作码定义了帧的类型。例如0x1 表示文本帧0x2 表示二进制帧0x8 表示关闭连接0x9 表示 Ping 帧0xA 表示 Pong 帧等。Mask1 bit掩码位指示负载数据是否被掩码。客户端发送给服务器的帧必须设置为1表示数据被掩码服务器发送给客户端的帧通常设置为0表示数据未被掩码。
Payload Length7、716、764 bits 7位长度如果值为0-125表示负载数据的长度以字节为单位。716位长度如果值为126接下来的2个字节16位表示负载数据的长度。764位长度如果值为127接下来的8个字节64位表示负载数据的长度。
Masking-Key0或4 bytes 当掩码位Mask为1时存在4字节的掩码密钥Masking-Key。这个密钥用于对负载数据进行掩码处理以防止中间代理服务器缓存污染。
Payload Data负载数据 包含实际要传输的数据。对于文本帧这是UTF-8编码的字符串对于二进制帧这是任意二进制数据。
从上面我们知道需要的数据是负载数据但是数据如果带有掩码是需要解密的
解析帧头 从 bufferData 的第一个字节byte1中读取操作码opcode这是一个4位的值用于指示帧的类型如文本、二进制等。从第二个字节byte2中读取掩码位MASK这是一个1位的值指示是否使用了掩码。
计算有效载荷长度 如果 byte2 的最高位第7位是1表示有效载荷长度为126需要从 bufferData 的第3个字节和第4个字节bufferData.readUInt16BE(2)读取有效载荷的实际长度。如果 byte2 的最高位是0但有效载荷长度为127表示有效载荷长度为64位需要从 bufferData 的第3个字节到第10个字节bufferData.readBigUInt64BE(2)读取有效载荷的实际长度。
处理掩码 如果使用了掩码MASK 为真则从 bufferData 中提取掩码密钥mask key这是一个4字节的值。使用掩码密钥对有效载荷数据进行解密通过 handleMask 函数以获取实际的数据realData。
处理有效载荷 最后函数调用 handleRealData 方法传入操作码和解密后的实际数据进行进一步的处理。 function handleMask(maskBytes, data) {const payload Buffer.alloc(data.length);for (let i 0; i data.length; i) {payload[i] maskBytes[i % 4] ^ data[i];}return payload;
}
const OPCODES {CONTINUE: 0,TEXT: 1,BINARY: 2,CLOSE: 8,PING: 9,PONG: 10,
};class MyWebsocket extends EventEmitter {constructor(options) {...}
// 处理 WebSocket 协议中的数据帧processData(bufferData) {const byte1 bufferData.readUInt8(0); // 第一个字节byte1中读取操作码opcode这是一个4位的值用于指示帧的类型如文本、二进制等。let opcode byte1 0x0f; const byte2 bufferData.readUInt8(1); // 从第二个字节byte2中读取掩码位MASK这是一个1位的值指示是否使用了掩码。const str2 byte2.toString(2);const MASK str2[0];console.log(opcode, opcode)console.log(MASK, mask)let curByteIndex 2;let payloadLength parseInt(str2.substring(1), 2);if (payloadLength 126) {payloadLength bufferData.readUInt16BE(2);curByteIndex 2;} else if (payloadLength 127) {payloadLength bufferData.readBigUInt64BE(2);curByteIndex 8;}console.log(payloadLength, payloadLength)let realData null;if (MASK) {const maskKey bufferData.slice(curByteIndex, curByteIndex 4); // 掩码密钥curByteIndex 4;const payloadData bufferData.slice(curByteIndex, curByteIndex payloadLength);realData handleMask(maskKey, payloadData); // 使用掩码密钥对有效载荷数据进行解密以获取实际的数据realData。} console.log(realData, realData)this.handleRealData(opcode, realData); // 处理有效载荷}
handleRealData(opcode, realDataBuffer) {switch (opcode) {case OPCODES.TEXT: // 文本this.emit(data, realDataBuffer);break;case OPCODES.BINARY: // 二进制this.emit(data, realDataBuffer);break;default:this.emit(close);break;}}handleRealData(opcode, realDataBuffer) {switch (opcode) {case OPCODES.TEXT: // 文本this.emit(data, realDataBuffer);break;case OPCODES.BINARY: // 二进制this.emit(data, realDataBuffer);break;default:this.emit(close);break;}}
}然后调用main.js
const MyWebSocket require(./ws.js);const ws new MyWebSocket({ port: 8000 });
// websocket需要一个服务器如果两个客户端需要通讯需要用服务器转发\ws.on(data, (data) {console.log(receive data: data); // 接受消息
});可以看到存在掩码解密之前数据是bufferData解密之后的数据是realData 这样就成功拿到了客户端传过来的数据了可以看到客户端传过来的是文本使用了掩码效载荷长度为9位这里的9其实就说字符串{“A”:111}的长度。
服务端发消息给客户端
服务端能接收到消息了然后就是将消息再给客户端了所以需要定义一个函数来发送数据 class MyWebsocket extends EventEmitter {constructor(options) {...}...send(data) {let opcode;let buffer;if (Buffer.isBuffer(data)) {opcode OPCODES.BINARY;buffer data;} else if (typeof data string) {opcode OPCODES.TEXT;buffer Buffer.from(data, utf8);} else {console.error(暂不支持发送的数据类型)}this.doSend(opcode, buffer);}doSend(opcode, bufferDatafer) {this.socket.write(encodeMessage(opcode, bufferDatafer));}
}由于我们上面获取传输数据的时候知道socket数据需要支持WebSocket 协议中的数据帧的帧结构
因为根据 WebSocket 协议只有客户端发送给服务器的帧需要掩码。服务器发送给客户端的帧通常不需要掩码。
function encodeMessage(opcode, payload) {let bufferData Buffer.alloc(payload.length 2 0);let byte1 parseInt(10000000, 2) | opcode; // parseInt(130, 2)1 ; 设置 FIN 为 1let byte2 payload.length;bufferData.writeUInt8(byte1, 0); // bufferData.writeUInt8(byte2, 1); // 负载的长度payload.copy(bufferData, 2);return bufferData;
}创建缓冲区 使用 Buffer.alloc 方法创建一个足够大的 Buffer 对象以容纳操作码、有效载荷长度和实际的有效载荷数据。这里假设 payload.length 126所以有效载荷长度只需要1个字节来表示。
设置操作码 byte1 是第一个字节它包含了操作码和 FINFinish标志。这里假设 FIN 标志为 1即消息结束操作码通过 opcode 参数传入。操作码的值决定了消息的类型例如文本0x1或二进制0x2。
设置有效载荷长度 byte2 是第二个字节它包含了有效载荷的长度。由于有效载荷长度小于126所以只需要1个字节来表示。
写入操作码和有效载荷长度 使用 writeUInt8 方法将 byte1 和 byte2 分别写入 bufferData 的第0位和第1位。
复制有效载荷数据 使用 copy 方法将 payload 数据复制到 bufferData 的第2位及之后的位置。 const MyWebSocket require(./ws.js);const ws new MyWebSocket({ port: 8000 });
// websocket需要一个服务器如果两个客户端需要通讯需要用服务器转发\ws.on(data, (data) {console.log(receive data: data); // 接受消息ws.send(data); // 自己给自己发送消息
});客户端接收到的数据 // 创建WebSocket连接
const socketA new WebSocket(ws://localhost:8000);const handleBlobToText (blob) {let reader new FileReader()reader.readAsText(blob, utf-8) // 接收到的是blob数据先转成文本reader.onload async function () {console.log(reader.result)}
}
// A接收B的消息
socketA.onmessage function (event) {console.log(A received:, event.data);handleBlobToText(event.data)
};socket传输大量数据
然后直接将视频的数据传输给服务端然后服务端就挂了 可以看到node端是收到了客户端的数据 报错的原因是超出了范围原因就是我们发送消息给客户端的处理这里出现了问题也就是encodeMessage函数。
我们往前看看到处理socket传输数据中再仔细看看数据帧结构这里有关于负载长度的问题 我们知道我们需要处理的负载就是我们需要传输的数据然后数据量太大是要区分来处理数据的。
很明显上面的encodeMessage只适用于处理0-125的负载长度而发送视频的数据我们可以看看长度为多少 找到问题了需要解决一下接下来改写一下encodeMessage函数
你需要考虑 WebSocket 的最大帧大小限制。WebSocket 协议定义了三种帧类型来表示数据的长度
单字节帧用于长度小于 126 的数据。双字节帧用于长度在 126 到 65535 之间的数据。八字节帧用于长度大于 65535 的数据。
function encodeMessagePerf(options, data) {let offset 2;let dataLength data.length;let payloadLength dataLength;// WebSocket 的最大帧大小限制// 1. 单字节帧用于长度小于 126 的数据。// 2. 双字节帧用于长度在 126 到 65535 之间的数据。// 3. 八字节帧用于长度大于 65535 的数据。if (dataLength 65536) {offset 8;payloadLength 127;} else if (dataLength 125) {offset 2;payloadLength 126;}const target Buffer.allocUnsafe(offset);// 操作码 0x1表示文本帧0x2表示二进制帧0x8表示关闭连接0x9表示ping帧0xA表示pong帧。target[0] options | 0x80; // 设置FINtarget[1] payloadLength; // 负载长度if (payloadLength 126) {target.writeUInt16BE(dataLength, 2);} else if (payloadLength 127) {target[2] target[3] 0;target.writeUIntBE(dataLength, 4, 6);}// 根据 WebSocket 协议只有客户端发送给服务器的帧需要掩码。服务器发送给客户端的帧通常不需要掩码return [target, data];
}writeUInt16BE、writeUIntBE 和 writeUInt8 是 Node.js 中 Buffer 类的三个方法它们的主要区别在于它们写入的值的大小和字节序。
writeUInt16BE 这个方法用于将一个无符号的16位整数即0到65535之间的整数以大端字节序Big Endian写入到 Buffer 对象中。大端字节序意味着高位字节在前低位字节在后。例如如果你要写入的值是 0xABCD使用 writeUInt16BE 方法后Buffer 中的数据将是 0xAB 后跟 0xCD。使用这个方法时你需要指定一个 offset 参数表示从 Buffer 的哪个位置开始写入。如果 offset 超出了 Buffer 的长度或者提供的值不是有效的无符号16位整数行为是未定义的。
writeUInt8 这个方法用于将一个无符号的8位整数即0到255之间的整数写入到 Buffer 对象中。这个方法不涉及字节序因为它只处理一个字节。使用 writeUInt8 方法时你同样需要指定一个 offset 参数。如果 offset 超出了 Buffer 的长度或者提供的值不是有效的无符号8位整数行为同样是未定义的。
writeUIntBE
用于将无符号整数以大端序Big Endian格式写入到 Buffer 对象中它可以处理的整数大小可以达到 48 位6 个字节。这个方法允许你指定要写入的字节长度byteLength这可以是 1、2、3、4、5 或 6 字节。如果 byteLength 大于 2它将写入一个大于 16 位的整数。
总结来说writeUInt16BE 用于写入16位的整数并且遵循大端字节序而 writeUInt8 用于写入8位的整数不涉及字节序。两者都需要一个 offset 参数来指定写入位置。在实际应用中选择哪个方法取决于你需要写入的数据类型和字节序要求。如果你需要写入更大或更小的整数或者需要处理可变长度的整数那么 writeUIntBE 是更合适的选择。
Buffer.allocUnsafe 和 Buffer.alloc 是 Node.js 中用于创建 Buffer 实例的两种方法它们的主要区别在于内存的初始化方式和安全性。
Buffer.allocUnsafe(size) Buffer.allocUnsafe 创建一个指定大小的 Buffer 实例但它不会初始化分配的内存。这意味着新创建的 Buffer 实例可能包含之前内存中的数据这些数据可能是敏感的。因此这个方法被称为“不安全”unsafe。使用 Buffer.allocUnsafe 创建的 Buffer 实例通常比 Buffer.alloc 创建的要快因为它避免了初始化内存的步骤。如果你需要确保 Buffer 中的数据是干净的你应该在使用 Buffer.allocUnsafe 创建实例后使用 Buffer.fill() 方法来填充整个 Buffer或者在写入数据之前完全覆盖它。
Buffer.alloc(size[, fill[, encoding]]) Buffer.alloc 创建一个指定大小的 Buffer 实例并且会用指定的值默认为0初始化整个 Buffer。这确保了新创建的 Buffer 实例不会包含任何旧数据。Buffer.alloc 方法比 Buffer.allocUnsafe 慢因为它需要额外的时间来初始化内存。如果你不需要处理可能包含敏感数据的旧内存或者你打算立即用新数据覆盖整个 Buffer那么使用 Buffer.alloc 是一个更安全的选择。
在实际应用中如果你需要处理敏感数据或者需要确保 Buffer 的内容是可预测的建议使用 Buffer.alloc。如果你对性能有更高的要求并且能够确保在读取或使用 Buffer 之前清除或覆盖其内容那么可以考虑使用Buffer.allocUnsafe。
在ws源码中,使用的是Buffer.allocUnsafe,可能是为了性能优化和内存管理。在 Node.js 中Buffer.allocUnsafe 可能会使用一个内部的内存池来分配 Buffer 实例。这意味着如果创建的 Buffer 大小小于或等于 Buffer.poolSize 的一半那么这些 Buffer 实例可能会共享内存池中的内存。这有助于减少内存分配和垃圾回收的开销。
当然在ws源码中处理数据帧的函数是frame 能看到除了负载数据之外ws中还有很多其他参数比如说处理掩码比如是否设置FIN指定是否可以修改’ data 等等。但是正常使用就是上面的encodeMessagePerf流程了。
接下来就完美解决了数据大量的问题。 如何区分不同客户端
能看到目前只是一个客户端那客户端一多想要控制A客户端发消息给B客户端B客户端发消息给A客户端那这样就要区分不同的客户端了。
那如何区分不同的客户端呢
JavaScript中有一个数据结构能够将不同的数据集区分开来就是Set啦看一下ws的源码 客户端存储在一个Set对象中连接的时候就添加到clients中断开连接了就delete掉这样就能清楚有几个客户端了。 看完之后就动手撸一下试试
class MyWebsocket extends EventEmitter {constructor(options) {super();options {...options,}const server http.createServer();server.listen(options.port || 8080);this.clients new Set()server.on(upgrade, (req, socket) {this.socket socket;socket.setKeepAlive(true);// websocket 升级协议const resHeaders [HTTP/1.1 101 Switching Protocols,Upgrade: websocket,Connection: Upgrade,Sec-WebSocket-Accept: hashKey(req.headers[sec-websocket-key]),,,].join(\r\n);socket.write(resHeaders);socket.on(data, (data) {this.processData(data);});socket.on(close, () {this.clients.delete(socket)console.log(close)})socket.on(end, () {this.clients.delete(socket)console.log(end)})socket.on(error, (err) {console.log(error, err)})if (this.clients) {this.clients.add(socket)}// debuggerconsole.log(this.clients.size, clients.size)});}
}这样子就搭建好了看一下客户端的数量是否正确 能看到数量是正常的然后就是将A客户端的数据发送给B客户端了
ws.on(data, function connection({realDataBuffer, clients}) {// 需要在data这个回调中拿到clients数据console.log(Client connected, realDataBuffer.toString(utf8));// ws.send(data Date.now()); // 发送消息// 将消息发送给所有客户端if (clients) {clients.forEach(function each(client) {if (client ! ws.socket) {ws.send(realDataBuffer); // 客户端接受的是blob格式数据}});}
});这里需要在data的回调中拿到clients数据所以需要传递数据出来。由于clients在constructor中定义的所以可以直接在emit回调的时候将clients传递出来。 这样外面就能访问到clients了然后我们直接这样传递数据但是你以为这就完了吗那你就太天真了 找一下问题我做了一个简单的demo就是A页面发消息给B页面B页面收到了然后B页面发消息给A页面A页面并没有收到然后node端是都能看到AB各自发送的消息的。
其实就是node端发送消息出现了问题仔细看一下下面的代码
ws.on(data, function connection({realDataBuffer, clients}) {console.log(Client connected, realDataBuffer.toString(utf8));// ws.send(data Date.now()); // 发送消息// 将消息发送给所有客户端if (clients) {clients.forEach(function each(client) {if (client ! ws.socket) { // ws.socket是最后连接的客户端不是当前要发消息的客户端ws.send(realDataBuffer); // 客户端接受的是blob格式数据}});}
});原因就是ws.socket是最后连接的客户端不是当前发生消息的客户端。
所以导致我首先刷新的A页面再刷新了B页面ws.socket出现了覆盖此时ws.socket就是B客户端了。
所以后面B页面发送消息给A页面A页面没有收到而是又再次将消息发送给了B页面。
如何将A的数据广发给其他客户端
如何解决呢我们仔细看一下ws是如何将一个客户端的数据发送给除了当前以外的其他客户端。 可以看到这里是先有connection然后才能触发message回调
而且在connection回调中拿到了ws对象这个对象的message回调才会监听到客户端发送过来的消息
也就是说我们上面的data回调需要再包一层然后判断是通过ws这个对象来判断是不是当前需要发送消息的客户端的。
这样就觉得ws其实就是一个客户端对象是clients中的一个对象然后ws的message回调就是接收到当前客户端的消息然后只要将不是当前ws的客户端发送消息就可以了。
所以这里区分成服务端的socket管理和单个客户端的socket管理需要在再新增一个ws对象也需要继承自EventEmitter因为它也有on事件
class SingleData extends EventEmitter {constructor(socket) {super();this.socket socketthis.socket.on(data, (data) { // 传输数据this.processData(data);});this.socket.on(close, () {this.emit(close)})this.socket.on(end, () {this.emit(end)console.log(end)})this.socket.on(error, (err) {this.emit(error, err)})}handleRealData(opcode, realDataBuffer) {switch (opcode) {case OPCODES.TEXT: // 文本this.emit(message, realDataBuffer);break;case OPCODES.BINARY: // 二进制this.emit(message, realDataBuffer);break;default:this.emit(close);break;}}processData(bufferData) {const byte1 bufferData.readUInt8(0);let opcode byte1 0x0f; const byte2 bufferData.readUInt8(1);const str2 byte2.toString(2);const MASK str2[0];let curByteIndex 2;let payloadLength parseInt(str2.substring(1), 2);if (payloadLength 126) {payloadLength bufferData.readUInt16BE(2);curByteIndex 2;} else if (payloadLength 127) {payloadLength bufferData.readBigUInt64BE(2);curByteIndex 8;}let realData null;if (MASK) {const maskKey bufferData.slice(curByteIndex, curByteIndex 4); curByteIndex 4;const payloadData bufferData.slice(curByteIndex, curByteIndex payloadLength);realData handleMask(maskKey, payloadData);} this.handleRealData(opcode, realData);}send(data) {let opcode;let buffer;if (Buffer.isBuffer(data)) {opcode OPCODES.BINARY;buffer data;} else if (typeof data string) {opcode OPCODES.TEXT;buffer Buffer.from(data, utf8);} else {console.log(data)console.error(暂不支持发送的数据类型)}this.doSend(opcode, buffer);}doSend(opcode, bufferDatafer) {// 大量数据let list frame(bufferDatafer)if (list.length 2) {this.socket.cork();this.socket.write(list[0]);this.socket.write(list[1]);this.socket.uncork();} else {this.socket.write(list[0]);}}
}module.exports SingleData;由于这是单个的客户端socket数据所以将接受和发送socket数据帧的处理放到这个对象来。
然后就可以将服务端的socket对象简化成下面这样
const { EventEmitter } require(events);
const http require(http);
const crypto require(crypto);
const SingleData require(./clientSocket.js)class MyWebsocket extends EventEmitter {constructor(options) {super();options {...options,}const server http.createServer();server.listen(options.port || 8080);this.clients new Set()server.on(upgrade, (req, socket) {this.socket socket;socket.setKeepAlive(true);// websocket 升级协议const resHeaders [HTTP/1.1 101 Switching Protocols,Upgrade: websocket,Connection: Upgrade,Sec-WebSocket-Accept: hashKey(req.headers[sec-websocket-key]),,,].join(\r\n);socket.write(resHeaders);const ws new SingleData(socket);ws.on(close, () {this.clients.delete(ws)console.log(close)})ws.on(end, () {this.clients.delete(ws)console.log(end)})ws.on(error, (err) {console.log(error, err)})if (this.clients) {this.clients.add(ws)}this.emit(connection, ws);});}}module.exports MyWebsocket;再看看效果这样就实现了一个简易版的ws源码啦~ 当然ws源码中的处理比上面这个简易版复杂很多发送消息有Sender类接受消息有Receiver类实现双向通讯而且数据处理上也更加谨慎。
如果你想深入研究源码可以看看这篇文章https://juejin.cn/post/6844903850667671560