1、数据帧格式概览
下面给出了WebSocket数据帧的统一格式。熟悉TCP/IP协议的同学对这样的图应该不陌生。
- 从左到右,单位是比特。比如
FIN
、RSV1
各占据1比特,opcode
占据4比特。 - 内容包括了标识、操作代码、掩码、数据、数据长度等。(下一小节会展开)
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+
2、数据帧格式详解
针对前面的格式概览图,这里逐个字段进行讲解,如有不清楚之处,可参考协议规范,或留言交流。
FIN:1个比特。
如果是1,表示这是消息(message)的最后一个分片(fragment),如果是0,表示不是是消息(message)的最后一个分片(fragment)。
RSV1, RSV2, RSV3:各占1个比特。
一般情况下全为0。当客户端、服务端协商采用WebSocket扩展时,这三个标志位可以非0,且值的含义由扩展进行定义。如果出现非零的值,且并没有采用WebSocket扩展,连接出错。
Opcode: 4个比特。
操作代码,Opcode的值决定了应该如何解析后续的数据载荷(data payload)。如果操作代码是不认识的,那么接收端应该断开连接(fail the connection)。可选的操作代码如下:
%x0:表示一个延续帧。当Opcode为0时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。 %x1:表示这是一个文本帧(frame) %x2:表示这是一个二进制帧(frame) %x3-7:保留的操作代码,用于后续定义的非控制帧。 %x8:表示连接断开。 %x9:表示这是一个ping操作。 %xA:表示这是一个pong操作。 %xB-F:保留的操作代码,用于后续定义的控制帧。
Mask: 1个比特。
表示是否要对数据载荷进行掩码操作。从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。
如果服务端接收到的数据没有进行过掩码操作,服务端需要断开连接。
如果Mask是1,那么在Masking-key中会定义一个掩码键(masking key),并用这个掩码键来对数据载荷进行反掩码。所有客户端发送到服务端的数据帧,Mask都是1。
掩码的算法、用途在下一小节讲解。
Payload length:数据载荷的长度,单位是字节。为7位,或7+16位,或1+64位。
假设数Payload length === x,如果
x为0~126:数据的长度为x字节。 x为126:后续2个字节代表一个16位的无符号整数,该无符号整数的值为数据的长度。 x为127:后续8个字节代表一个64位的无符号整数(最高位为0),该无符号整数的值为数据的长度。
此外,如果payload length占用了多个字节的话,payload length的二进制表达采用网络序(big endian,重要的位在前)。
Masking-key:0或4字节(32位)
所有从客户端传送到服务端的数据帧,数据载荷都进行了掩码操作,Mask为1,且携带了4字节的Masking-key。如果Mask为0,则没有Masking-key。
备注:载荷数据的长度,不包括mask key的长度。
Payload data:(x+y) 字节
载荷数据:包括了扩展数据、应用数据。其中,扩展数据x字节,应用数据y字节。
扩展数据:如果没有协商使用扩展的话,扩展数据数据为0字节。所有的扩展都必须声明扩展数据的长度,或者可以如何计算出扩展数据的长度。此外,扩展如何使用必须在握手阶段就协商好。如果扩展数据存在,那么载荷数据长度必须将扩展数据的长度包含在内。
应用数据:任意的应用数据,在扩展数据之后(如果存在扩展数据),占据了数据帧剩余的位置。载荷数据长度 减去 扩展数据长度,就得到应用数据的长度。
3、掩码算法
掩码键(Masking-key)是由客户端挑选出来的32位的随机数。掩码操作不会影响数据载荷的长度。掩码、反掩码操作都采用如下算法:
首先,假设:
original-octet-i:为原始数据的第i字节。 transformed-octet-i:为转换后的数据的第i字节。 j:为i mod 4的结果。 masking-key-octet-j:为mask key第j字节。
算法描述为: original-octet-i 与 masking-key-octet-j 异或后,得到 transformed-octet-i。
j = i MOD 4 transformed-octet-i = original-octet-i XOR masking-key-octet-j
4、掩码样例
场景:
客户端:发送语音文件到服务端,先发送txt消息文件名称"tts";再读物文件,发送bin消息二进制流数据(多帧发送)。
服务端:先接收txt消息,需要接收的文件名称并创建打开;再接收bin消息语音流消息,并写入文件;当接收到最后一帧二进制后,关闭打开的文件。
txt消息生成的 掩码KEY:
the key is 14,51,172,208.
客服端处理消息:"tts" =》122,71,-33
1.客户端生成原始4位掩码
2.将需要发送字符与掩码异或运算,获取计算后的值,用于网络传输。
116(t)与掩码14异或处理,得到异或值:122
116(t)与掩码51异或处理,得到异或值:71
116(s)与掩码172:10101101异或处理,得到异或值:-33
(208:11010000)
3.生成待发送消息:121,71,-33
unsigned char maskKey[4] = {0}; // 掩码
测试:
算法处理:
int webSocket_enPackage(unsigned char *data, unsigned int dataLen, unsigned char *package, unsigned int packageMaxLen, bool isMask, WebsocketData_Type type)
{
unsigned char maskKey[4] = {0}; // 掩码
unsigned char temp1, temp2;
int count;
unsigned int i, len = 0;
if(packageMaxLen < 2)
return -1;
if(type == WDT_MINDATA)
*package++ = 0x00;
else if(type == WDT_TXTDATA)
*package++ = 0x81;
else if(type == WDT_BINDATA)
*package++ = 0x82;
else if(type == WDT_DISCONN)
*package++ = 0x88;
else if(type == WDT_PING)
*package++ = 0x89;
else if(type == WDT_PONG)
*package++ = 0x8A;
else if(type == 100)
*package++ = 0x02;//add by cheyang//return -1;
else if(type == 99)
*package++ = 0x80;//add by cheyang//return -1;
//
if(isMask)
*package = 0x80;
len += 1;
//
if(dataLen < 126)
{
*package++ |= (dataLen&0x7F);
len += 1;
}
else if(dataLen < 65536)
{
if(packageMaxLen < 4)
return -1;
*package++ |= 0x7E;
*package++ = (char)((dataLen >> 8) & 0xFF);
*package++ = (unsigned char)((dataLen >> 0) & 0xFF);
len += 3;
}
else if(dataLen < 0xFFFFFFFF)
{
if(packageMaxLen < 10)
return -1;
*package++ |= 0x7F;
*package++ = 0; //(char)((dataLen >> 56) & 0xFF); // 数据长度变量是 unsigned int dataLen, 暂时没有那么多数据
*package++ = 0; //(char)((dataLen >> 48) & 0xFF);
*package++ = 0; //(char)((dataLen >> 40) & 0xFF);
*package++ = 0; //(char)((dataLen >> 32) & 0xFF);
*package++ = (char)((dataLen >> 24) & 0xFF); // 到这里就够传4GB数据了
*package++ = (char)((dataLen >> 16) & 0xFF);
*package++ = (char)((dataLen >> 8) & 0xFF);
*package++ = (char)((dataLen >> 0) & 0xFF);
len += 9;
}
//
if(isMask) // 数据使用掩码时, 使用异或解码, maskKey[4]依次和数据异或运算, 逻辑如下
{
if(packageMaxLen < len + dataLen + 4)
return -1;
webSocket_getRandomString(maskKey, sizeof(maskKey)); // 随机生成掩码
*package++ = maskKey[0];
*package++ = maskKey[1];
*package++ = maskKey[2];
*package++ = maskKey[3];
len += 4;
if(type == WDT_TXTDATA){
printf("the key is %d,%d,%d,%d.\n",maskKey[0],maskKey[1],maskKey[2],maskKey[3]);
}
for(i = 0, count = 0; i < dataLen; i++)
{
temp1 = maskKey[count];
temp2 = data[i];
if(type == WDT_TXTDATA){
printf("the file name is %d,key:%d,\n",temp2,maskKey[count]);
}
*package++ = (char)(((~temp1)&temp2) | (temp1&(~temp2))); // 异或运算后得到数据
if(type == WDT_TXTDATA){
printf("after key name is %d,\n",(char)(((~temp1)&temp2) | (temp1&(~temp2))));
}
count += 1;
if(count >= sizeof(maskKey)) // maskKey[4]循环使用
count = 0;
}
len += i;
*package = '\0';
}
else // 数据没使用掩码, 直接复制数据段
{
if(packageMaxLen < len + dataLen)
return -1;
memcpy(package, data, dataLen);
package[dataLen] = '\0';
len += dataLen;
}
//
return len;
}
服务端处理消息:122,71,-33 =》 "tts"
1.获取客户端发送的4位掩码和数据消息
2.将收到的字符消息与掩码异或运算,获取源字符值
122与掩码(14)异或处理,得到原字符值:116(t)
71与掩码(51)异或处理,得到原字符值:116(t)
-33与掩码(-84:10101101)异或处理,得到原字符值:115(s)
(-48:11010000)
3.解码获取文件名:116,116,115;及字符:tts
char masking_key[4]; 有符号变量,
算法处理:
int Websocket_Handler::recv_request()
{
printf("Websocket_Handler::recv_request: recv request logic.\n");
char readCache[1024*4] = {0};
int recvsize;
int head_len = 0;
do{
recvsize = 0;
recvsize = read(fd_,readCache,BUFFLEN);
printf("read:size:%d,curslot:%d.\n",recvsize,m_nOffset);
if (recvsize < 0)
{
memset(readCache, 0, sizeof(readCache));
printf("read failure,wait next frame websocket.\n");
return 0;
}else if( 0 == recvsize ){
if( NULL != handle ){
fclose(handle);
}
channel->setDeleted(true);
channel->getLoop().lock()->addTimer(channel,0);
return 0;
}
memcpy(buff_+m_nOffset, readCache, recvsize);
m_nOffset = m_nOffset + recvsize;//set offset
//获取消息头
if( 0 > (head_len = getFrameHeaderInfo(buff_,&header_msg,m_nOffset)) )
{
printf("error is not full frame.\n");
continue;
}
printf("read frame head\tFIN: %d\tOPCODE: %d\tMASK: %d\tPAYLOADLEN: %d\tHeadlen:%d\n.",
header_msg.fin, header_msg.opcode, header_msg.mask, header_msg.payload_length,head_len);
//读取满帧数据校验
if(header_msg.payload_length > m_nOffset- head_len || header_msg.payload_length <= 0 )
{
continue;
}
if( 1 ==header_msg.mask){
umask(buff_+head_len,header_msg.payload_length,header_msg.masking_key);
}
switch(header_msg.opcode){
case 0x00:
printf("recv mid frame.\n");
break;
case 0x01:
{
printf("recv text frame.\n\t%s\n",(buff_+head_len));
if( handle == NULL && header_msg.payload_length <= 1000){
char filename[1024];
sprintf(filename,"./snOfflineVoice_%s.snv",buff_+head_len);
handle = fopen(filename,"a+");
}
m_nOffset = m_nOffset - header_msg.payload_length - head_len;
char buffer[2048*2] = {0};
memcpy(buffer, buff_+header_msg.payload_length+head_len,m_nOffset);
memset(buff_,0,BUFFLEN*2);
memcpy(buff_, buffer,m_nOffset);
return 0;
}
case 0x02:
printf("recv bin frame.\n");
if( handle == NULL){
char filename[1024];
sprintf(filename,"./snOfflineVoice_%02d.snv",header_msg.payload_length);
handle = fopen(filename,"a+");
}
break;
case 0x08:
channel->setDeleted(true);
channel->getLoop().lock()->addTimer(channel,0);
return 0;
case 0x09:
printf("recv ping frame,need send pong frame.\n");
//webSocket_send(fd, (char *)webSocketPackage, retLen, true, WDT_PONG);//自动 ping-pong
return 0;
case 0x0A:
printf("recv pong frame.\n");
return 0;
default:
printf("recv unknow frame.\n");
}
//just one frame or frist of mutl_frame
/*if( (1 ==header_msg.fin && 0 < header_msg.opcode && 3 > header_msg.opcode && handle != NULL )
|| (0 ==header_msg.fin && 0 !=header_msg.opcode && 3 > header_msg.opcode ) ){
char filename[1024];
sprintf(filename,"./snOfflineVoice_%02d.snv",header_msg.payload_length);
handle = fopen(filename,"a+");
if(NULL != handle){
fwrite(buff_+head_len, sizeof(char), header_msg.payload_length, handle);
fflush(handle);
printf("fwrite:size:%d,payload_len:%d.\n",header_msg.payload_length,header_msg.payload_length);
m_nOffset = m_nOffset - header_msg.payload_length - head_len;
char swap[2048] = {0};
memcpy(swap, buff_+header_msg.payload_length+head_len,m_nOffset);
memset(buff_,0,BUFFLEN*2);
memcpy(buff_, swap,m_nOffset);
return 0;//break function
}
}*/
if( NULL != handle){
fwrite(buff_+head_len, sizeof(char), header_msg.payload_length, handle);
fflush(handle);
m_nOffset = m_nOffset - header_msg.payload_length - head_len;
char swap[2048*2] = {0};
memcpy(swap, buff_+header_msg.payload_length+head_len,m_nOffset);
memset(buff_,0,BUFFLEN*2);
memcpy(buff_, swap,m_nOffset);
printf("fwrite:size:%d,m_nOffset:%d.\n",header_msg.payload_length, m_nOffset);
}
if(m_nOffset > 4){
memset(&header_msg,0,sizeof(header_msg));
printf(">>>>>>>>>>check Incomming data frame<<<<<<<<<.\n");
if( 0 > (head_len = getFrameHeaderInfo(buff_,&header_msg,m_nOffset)) )
{
printf("error is not full frame.\n");
continue;
}
if(header_msg.payload_length <= m_nOffset- head_len&& header_msg.payload_length > 0 )
{
printf("read frame head\tFIN: %d\tOPCODE: %d\tMASK: %d\tPAYLOADLEN: %d\tHeadlen:%d.\n",
header_msg.fin, header_msg.opcode, header_msg.mask, header_msg.payload_length,head_len);
if( 1 ==header_msg.mask){
umask(buff_+head_len,header_msg.payload_length,header_msg.masking_key);
}
fwrite(buff_+head_len, sizeof(char), header_msg.payload_length, handle);
fflush(handle);
printf("fwrite more frame:size:%d,m_nOffset:%d.\n",header_msg.payload_length, m_nOffset);
m_nOffset = m_nOffset -header_msg.payload_length-head_len;
char swap[2018] = {0};
memcpy(swap, buff_+header_msg.payload_length+head_len,m_nOffset);
memset(buff_,0,BUFFLEN*2);
memcpy(buff_, swap,m_nOffset);
}
else{
printf(" Lack of data, need read more data.\n");
continue;
}
}
if(1 == header_msg.fin && NULL != handle &&
( 0 == header_msg.opcode || 2 == header_msg.opcode) ){
fclose(handle);
handle = NULL;
m_nOffset = 0;
memset(buff_,0,BUFFLEN*2);
printf("is last frame, need close file.\n");
return 0;
}
printf("read data again.\n");
}while(1);
printf("wait next request websocket .\n");
return 0;
}
int Websocket_Handler::getFrameHeaderInfo(char *buff,frame_head* head,int curSize)
{
char one_char;
int head_len = 0;
one_char = buff[0];
head->fin = (one_char & 0x80) == 0x80;
head->opcode = one_char & 0x0F;
one_char = buff[1];
head->mask = (one_char & 0x80) == 0X80;
/*get payload length*/
head->payload_length = one_char & 0x7F;
if (head->payload_length == 126)
{
char extern_len[2];
extern_len[0] = buff[2];
extern_len[1] = buff[3];
head->payload_length = (extern_len[0]&0xFF) << 8 | (extern_len[1]&0xFF);
head_len = 4;
}
else if (head->payload_length == 127)
{
char extern_len[8];
//wait to doing
//...
inverted_string(extern_len,8);
memcpy(&(head->payload_length),extern_len,8);
head_len = 10;
}
else if(head->payload_length == 0)
{
printf("read payload_length is 0,close connect.");
return -1;
}
else if(head->payload_length < 126)
{
printf("read payload_length is less 126, is little frame.\n");
head_len = 2;
}
if(head->mask ==1 ){
if(curSize <= 7 )
{
printf("check buff size error.\n");
return -1;
}
head->masking_key[0] = buff[head_len+0];
head->masking_key[1] = buff[head_len+1];
head->masking_key[2] = buff[head_len+2];
head->masking_key[3] = buff[head_len+3];
return head_len +4;
}
return head_len;
}
int Websocket_Handler::handlerconn(){
if(status_ == WEBSOCKET_UNCONNECT){
int len = read(fd_,buff_,BUFFLEN*2);
if (len<=0)
return -1;
return handshark();
}
printf("Websocket_Handler::handlerconn: begin handlerconn logic.\n");
recv_request();
return 0;
//int ilenData = 0;
//request_->getReqData(buff_out,buff_len);
snprintf(buff_out,2048,"%s",buff_);
//send_frame_head();
//request_->print();
//respond client
channel->setRevents(EPOLLOUT|EPOLLET);
channel->getLoop().lock()->updatePoller(channel);
channel->setWritehandler(bind(&Websocket_Handler::send_respond,this));
printf("process logic end.\n");
memset(buff_, 0, sizeof(buff_));
return 0;
}
void Websocket_Handler::umask(char *data,int len,char *mask)
{
int i;
for (i=0;i<len;++i){
*(data+i) ^= *(mask+(i%4));
}
}
other:
异或的特性及应用:
1.使特定位翻转
假设有01111010,想使其低4位翻转,即1变为0,0变为1。可以将它与00001111进行^运算,即
结果值的低4位正好是原数低4位的翻转。要使哪几位翻转就将与其^运算的该几位置为1即可。这是因为原数中值为1的位与1进行^运算得0,原数中的位值0与1进行^运算的结果得1
2.与自身异或为0,与0异或不变
假设3^3,即011^011,结果为000.
3^0,011^000,结果为011,还是3.
用此特性可完成两个数a,b的交换且不需要临时变量
a = a^b //此时给a赋值a^b
b = a^b //此时a = a^b 则 b = a^b^b. 得b = a
a = a^b //此时a = a^b, b = a. 则 a = a^b^a. 得a = b.完成交换
websocket简介:https://www.cnblogs.com/cyblogs/p/11099193.html