websocket掩码处理方式

1、数据帧格式概览

下面给出了WebSocket数据帧的统一格式。熟悉TCP/IP协议的同学对这样的图应该不陌生。

  1. 从左到右,单位是比特。比如FINRSV1各占据1比特,opcode占据4比特。
  2. 内容包括了标识、操作代码、掩码、数据、数据长度等。(下一小节会展开)
     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

发布了9 篇原创文章 · 获赞 4 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/yangzai187/article/details/93862980
今日推荐