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);
};
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!