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

nodeimplementsawebsocketserver

时间:2023-04-04 01:00:45 Node.js

翻译了之前的文章Implementingawebsocketserver-theory,简单介绍了理论基础。本来打算放在一起的,但是感觉太长了,大家可能看不懂。但是拆机发现,难免要提到理论部分。让我们简单回顾一下它的用处。在具体代码实现Websockt的基本通信过程之前,我们需要先大致梳理一下思路。复习websockets的理论部分。简单的websocket流程如下(这里不说详细流程,简单描述一下)客户端发送握手请求,服务端响应,处理握手返回客户端,验证通过后服务器发送数据,服务器接收并处理数据,然后返回给客户端客户端接收服务器的推送作为服务器,我们的主要关注点需要放在第2步和第4步。响应和处理握手虽然websocket可以实现服务器推送,前提是连接已经建立。客户端仍然需要发起Websocket握手请求。现在我们要响应握手请求,我们需要了解请求。ClientHandshakeRequest客户端的握手请求是一个标准的HTTP请求,大致像下面的例子。GET/HTTP/1.1//HTTP版本必须是1.1以上,请求方式为GETHost:localhost:8081//本地项目连接:UpgradePragma:no-cacheCache-Control:no-cacheUpgrade:websocket//指定websocket协议来源:http://192.168.132.170:8000Sec-WebSocket-Version:13//VersionUser-Agent:Mozilla/5.0(Macintosh;IntelMacOSX10_13_1)AppleWebKit/537.36(KHTML,likeGecko)Chrome/62.0.3202.94Safari/537.36Accept-Encoding:gzip,deflate,brAccept-Language:zh-CN,zh;q=0.9,en;q=0.8Cookie:optimizelyEndUserId=oeu1505722530441r0.5993643212774391;6z8fnZfN3w==//握手返回基于keySec-WebSocket-Extensions:permessage-deflate;client_max_window_bits实际例子中的请求头如上所列,内容由浏览器生成。需要注意的部分如下。HTTP版本必须是1.1以上,请求方式为GETConnection:UpgradeUpgrade:websocket//指定websocketSec-WebSocket-Key的key服务器处理握手的依据我们的服务器需要注意以上四点在处理握手时。对握手请求的响应服务器根据websocket是否必须请求header分为以下两种情况:不满足,作为http请求响应。满足,根据websocket指定的数据格式进行分析处理,响应返回格式HTTP/1.1101SwitchingProtocolsUpgrade:websocketConnection:UpgradeSec-WebSocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=请注意每个header以rn和结尾在最后一个额外的rn之后添加。这里的Sec-WebSocket-Accept是根据请求头中的Sec-WebSocket-Key生成的。规则如下:Sec-WebSocket-Key与“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”链接,通过SHA-1哈希得到结果,然后返回结果的base64编码。代码如下://指定拼接字符varws_key='258EAFA5-E914-47DA-95CA-C5AB0DC85B11';//生成对应的keyfunctiongetAccpectKey(rSWKey){returncrypto.createHash('sha1').update(rSWKey+ws_key)。digest('base64')}functionhandShake(socket,headers){varreqSWKey=headers['Sec-WebSocket-Key'],resSWKey=getAccpectKey(reqSWKey)socket.write('HTTP/1.1101切换协议\r\n');socket.write('升级:websocket\r\n');socket.write('连接:升级\r\n');socket.write('Sec-WebSocket-Accept:'+resSWKey+'\r\n');socket.write('\r\n');}这样即使我们的握手协议完成了,此时也会触发客户端的websocket的onopen事件,即打开websocket,并且通信可以被客户端解析发送帧格式握手协议完成后,我们要解析数据,仍然需要取出这个帧格式。格式:012301234567890123456789012345678901+-+-+-+-+------+-+------------+-----------------------------+|F|R|R|R|操作码|M|有效载荷长度|扩展有效载荷长度||我|S|S|S|(4)|一个|(7)|(16/64)||N|V|V|V||S||(如果负载len==126/127)|||1|2|3||K|||+-+-+-+-+------+-+------------+---------------+|扩展有效负载长度继续,如果有效负载len==127|+--------------+--------------------------------+||屏蔽密钥,如果MASK设置为1|+----------------------------+------------------------------+|掩码键(续)|有效载荷数据|+-------------------------------------------------+:有效载荷数据继续......:+--------------------------------+|有效负载数据继续...|+--------------------------------------------------------------+从客户端发送到服务器的每个数据帧都遵循上述格式MASK位:仅指示信息是否进行了屏蔽。来自客户端的消息必须被处理,所以我们应该将它设置为1opcode字段来定义如何解析有效数据:0x0继续处理0x1文本(必须是UTF-8编码)0x2二进制和其他称为控制代码的数据。0x3-0x70xB-0xF该版本WebSockets无意义的FIN表示是否为数据采集的最后一条消息。如果为0,则服务器继续监视消息并等待消息的其余部分。否则服务器认为消息已经发送完毕。Payloadlen:有效数据长度Payloadlen<126,即真实长度为126,说明真实长度大于125,后面2个字节的值为真实长度127,真实长度大于65535,最后8个字节是真正长度的解析数据所谓解析数据,必须在上述格式的基础上,按照一定的规则进行处理。以下是处理规则。获取有效数据长度获取掩码,按照规则反序列化数据直接看代码应该更清楚。//解析接收到的数据帧functiondecodeFrame(buffer){/***>>>7右移操作,即字节右移7位,目的是只取第一个的值bit*10010030====>00000001*&按位与1为1*15二进制表示为:00001111,运算后前四位为0,得到后四位的值*11011000&00001111===》00001000**/varfBite=buffer[0],/***获取Fin的值,*1传输结束*0继续监听*/Fin=fBite>>>7,/***获取opcode的值,opcode是fBite*&Bitwise的4-7位和1一样的1*15二进制表示是:00001111,运算后前四位为0,后四位的值为obtained*/opcode=buffer[0]&15,/***获取有效数据长度*/len=buffer[1]&127,//是否进行mask处理,客户端请求必须为1Mask=buffer[1]>>>7、maskKey=null//获取数据长度//实际长度大于125,读取接下来的2个字节if(len==126){len=buffer.readUInt16BE(2)}elseif(len==127){//真实长度大于65535,读取接下来的8个字节len=buffer.readUInt64BE(2)}//判断是否进行mask处理Mask&&(maskKey=buffer.slice(2,5))/***反掩码处理*循环遍历加密后的字节(八位字节,文本数据的单位),并与(i%4)位掩码字部分组合(即i除以4取余)进行异或运算*/if(Mask){for(vari=2;i65535?10:(len>125?4:2),buf=newBuffer(len+payload_len)/***第一个字节,0x81=10000001*对应的Fin是1opcodeis001maskis0*表示返回的数据为txt,文本已经结束,没有经过mask处理*/buf[0]=0x81/***根据实??际数据设置payload_len位length*/if(payload_len==2){buf[1]=len}elseif(payload_len==4){buf[1]=126;buf.writeUInt16BE(payload_len,2);}else{buf[1]=127;buf.writeUInt32BE(payload_len>>>32,2);buf.writeUInt32BE(payload_len&0xFFFFFFFF,6);}buf.write(data,payload_len);returnbuf;}Heartbeatresponse当收到的opcode为9时,是一个ping请求,返回和valid完全一样的datapong就可以了。Pings的opcode是0x9,pong是0xA,所以可以直接如下//pingrequestif(opcode==9){console.log("pingresponse");/***pingpong的最大长度是125,所以可以直接拼接*数据的前两位是10001010+数据长度*即发送后的pong响应,数据必须小于125*/socke.write(Buffer.concat([newBuffer([0x8A,data.length]),data]))}结论至此,一个websocketserver的简单实现就完成了。请参阅它以获取更多详细信息。当然,成熟的websocket库在处理各种情况方面还是比较完善的,推荐大家使用。这里只是简单的练习,更多的是为了满足自己的好奇心。进步