Websocket协议详解及简单实例代码
Websocket协议详解
关于websocket的协议是用来干嘛的,请参考其他文章。
WebSocket关键词
HTML5协议,实时,全双工通信,长连接
WebSocket比传统Http的好处
- 客户端与服务端只建立一个TCP连接,可以使用更少的连接
- WebSocket的服务端可以将数据推送到客户端,如实时将证券信息反馈到客户端(这个很关键),实时天气数据,比http请求响应模式更灵活
- 更轻量的协议头,减少数据传送量
数据帧格式
下图为手工打造的数据帧格式
/** *fin|masked|| *srv1|length|| *srv2|(7bit|mask数据|payload *srv3|7+2字节|4字节|真实数据 opcode|7+64字节|| *(4bit) */
作以下说明:
1.前8个bit(一个字节)
—fin:是否数据发送完成,为1发送完成为0发送未完。
—srv1,srv2,srv3:留作后用
—opcode:数据类型操作码,4bit表示,其中
TEXT:1,text类型的字符串
BINARY:2,二进制数据,通常用来保存图片
CLOSE:8,关闭连接的数据帧。
PING:9,心跳检测。ping
PONG:10,心跳检测。pong
varevents=require('events'); varhttp=require('http'); varcrypto=require('crypto'); varutil=require('util'); /** *数据类型操作码TEXT字符串 *BINARY二进制数据常用来保存照片 *PING,PONG用作心跳检测 *CLOSE关闭连接的数据帧(有很多关闭连接的代码1001,1009,1007,1002) */ varopcodes={ TEXT:1, BINARY:2, CLOSE:8, PING:9, PONG:10 }; varWebSocketConnection=function(req,socket,upgradeHead){ "usestrict"; varself=this; varkey=hashWebSocketKey(req.headers['sec-websocket-key']); /** *写头 */ socket.write('HTTP/1.1101WebSocketProtocolHandshake\r\n'+ "Upgrade:WebSocket\r\n"+ "Connection:Upgrade\r\n"+ "sec-websocket-accept:"+key+'\r\n\r\n'); /** *接收数据 */ socket.on('data',function(buf){ self.buffer=Buffer.concat([self.buffer,buf]); while(self._processBuffer()){ } }); socket.on('close',function(had_error){ if(!self.closed){ self.emit("close",1006); self.closed=true; } }); this.socket=socket; this.buffer=newBuffer(0); this.closed=false; }; //websocket连接继承事件 util.inherits(WebSocketConnection,events.EventEmitter); /* 发送数据函数 **/ WebSocketConnection.prototype.send=function(obj){ "usestrict"; varopcode; varpayload; if(Buffer.isBuffer(obj)){ opcode=opcodes.BINARY; payload=obj; }elseif(typeofobj){ opcode=opcodes.TEXT; //创造一个utf8的编码可以被编码为字符串 payload=newBuffer(obj,'utf8'); }else{ thrownewError('cannotsendobject.MustbestringofBuffer'); } this._doSend(opcode,payload); }; /* 关闭连接函数 **/ WebSocketConnection.prototype.close=function(code,reason){ "usestrict"; varopcode=opcodes.CLOSE; varbuffer; if(code){ buffer=newBuffer(Buffer.byteLength(reason)+2); buffer.writeUInt16BE(code,0); buffer.write(reason,2); }else{ buffer=newBuffer(0); } this._doSend(opcode,buffer); this.closed=true; }; WebSocketConnection.prototype._processBuffer=function(){ "usestrict"; varbuf=this.buffer; if(buf.length<2){ return; } varidx=2; varb1=buf.readUInt8(0);//读取数据帧的前8bit varfin=b1&0x80;//如果为0x80,则标志传输结束 varopcode=b1&0x0f;//截取第一个字节的后四位 varb2=buf.readUInt8(1);//读取数据帧第二个字节 varmask=b2&0x80;//判断是否有掩码,客户端必须要有 varlength=b2|0x7f;//获取length属性也是小于126数据长度的数据真实值 if(length>125){ if(buf.length<8){ return;//如果大于125,而字节数小于8,则显然不合规范要求 } } if(length===126){//获取的值为126,表示后两个字节用于表示数据长度 length=buf.readUInt16BE(2);//读取16bit的值 idx+=2;//+2 }elseif(length===127){//获取的值为126,表示后8个字节用于表示数据长度 varhighBits=buf.readUInt32BE(2);//(1/0)1111111 if(highBits!=0){ this.close(1009,"");//1009关闭代码,说明数据太大 } length=buf.readUInt32BE(6);//从第六到第十个字节为真实存放的数据长度 idx+=8; } if(buf.length<idx+4+length){//不够长4为掩码字节数 return; } varmaskBytes=buf.slice(idx,idx+4);//获取掩码数据 idx+=4;//指针前移到真实数据段 varpayload=buf.slice(idx,idx+length); payload=unmask(maskBytes,payload);//解码真实数据 this._handleFrame(opcode,payload);//处理操作码 this.buffer=buf.slice(idx+length);//缓存buffer returntrue; }; /** *针对不同操作码进行不同处理 *@param操作码 *@param数据 */ WebSocketConnection.prototype._handleFrame=function(opcode,buffer){ "usestrict"; varpayload; switch(opcode){ caseopcodes.TEXT: payload=buffer.toString('utf8');//如果是文本需要转化为utf8的编码 this.emit('data',opcode,payload);//Buffer.toString()默认utf8这里是故意指示的 break; caseopcodes.BINARY://二进制文件直接交付 payload=buffer; this.emit('data',opcode,payload); break; caseopcodes.PING://发送ping做响应 this._doSend(opcodes.PING,buffer); break; caseopcodes.PONG://不做处理 break; caseopcodes.CLOSE://close有很多关闭码 letcode,reason;//用于获取关闭码和关闭原因 if(buffer.length>=2){ code=buffer.readUInt16BE(0); reason=buffer.toString('utf8',2); } this.close(code,reason); this.emit('close',code,reason); break; default: this.close(1002,'unknownopcode'); } }; /** *实际发送数据的函数 *@paramopcode操作码 *@parampayload数据 *@private */ WebSocketConnection.prototype._doSend=function(opcode,payload){ "usestrict"; this.socket.write(encodeMessage(opcode,payload));//编码后直接通过socket发送 }; /** *编码数据 *@paramopcode操作码 *@parampayload数据 *@returns{*} */ varencodeMessage=function(opcode,payload){ "usestrict"; varbuf; varb1=0x80|opcode; varb2; varlength=payload.length; if(length<126){ buf=newBuffer(payload.length+2+0); b2|=length; //buffer,offset buf.writeUInt8(b1,0);//读前8bit buf.writeUInt8(b2,1);//读8―15bit //Buffer.prototype.copy=function(targetBuffer,targetStart,sourceStart,sourceEnd){ payload.copy(buf,2)//复制数据,从2(第三)字节开始 }elseif(length<(1<<16)){ buf=newBuffer(payload.length+2+2); b2|=126; buf.writeUInt8(b1,0); buf.writeUInt8(b2,1); buf.writeUInt16BE(length,2) payload.copy(buf,4); }else{ buf=newBuffer(payload.length+2+8); b2|=127; buf.writeUInt8(b1,0); buf.writeUInt8(b2,1); buf.writeUInt32BE(0,2) buf.writeUInt32BE(length,6) payload.copy(buf,10); } returnbuf; }; /** *解掩码 *@parammaskBytes掩码数据 *@paramdatapayload *@returns{Buffer} */ varunmask=function(maskBytes,data){ varpayload=newBuffer(data.length); for(vari=0;i<data.length;i++){ payload[i]=maskBytes[i%4]^data[i]; } returnpayload; }; varKEY_SUFFIX='258EAFA5-E914-47DA-95CA-C5ABoDC85B11'; /*equalstocrypto.createHash('sha1').update(key+'KEY_SUFFIX').digest('base64') **/ varhashWebSocketKey=function(key){ "usestrict"; varsha1=crypto.createHash('sha1'); sha1.update(key+KEY_SUFFIX,'ascii'); returnsha1.digest('base64'); }; exports.listen=function(port,host,connectionHandler){ "usestrict"; varsrv=http.createServer(function(req,res){ }); srv.on('upgrade',function(req,socket,upgradeHead){ "usestrict"; varws=newWebSocketConnection(req,socket,upgradeHead); connectionHandler(ws); }); srv.listen(port,host); };
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!