转载 https://blog.csdn.net/yangzai187/article/details/93905594
问题:
接收客户端消息处理时,遇到这样情况;接收第一帧数据时正常的,后面再次接受解析数据帧时,发现解析的消息是异常、缺失的,导致服务端不能正确接收消息。
查了相关资料,发现tcp再传输数据时,发送消息并非一包一包发送,存在粘包、拆包的情况。
粘包、拆包表现形式
现在假设客户端向服务端连续发送了两个数据包,用packet1和packet2来表示,那么服务端收到的数据可以分为三种,现列举如下:
第一种情况(正常情况)
接收端正常收到两个数据包,即没有发生拆包和粘包的现象,此种情况不在本文的讨论范围内。
第二种情况(粘包:两帧数据放在一个tcp消息包中)
接收端只收到一个数据包,由于TCP是不会出现丢包的,所以这一个数据包中包含了发送端发送的两个数据包的信息,这种现象即为粘包。这种情况由于接收端不知道这两个数据包的界限,所以对于接收端来说很难处理。
第三种情况(拆包:一帧数据被拆分在两个tcp消息包中)
这种情况有两种表现形式,如下图。接收端收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来一块,这种情况即发生了拆包和粘包。这两种情况如果不加特殊处理,对于接收端同样是不好处理的。
粘包、拆包发生原因
发生TCP粘包或拆包有很多原因,现列出常见的几点,可能不全面,欢迎补充,
1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。
3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。
4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。
等等。
粘包、拆包解决办法
通过以上分析,我们清楚了粘包或拆包发生的原因,那么如何解决这个问题呢?解决问题的关键在于如何给每个数据包添加边界信息,常用的方法有如下几个:
1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
2、发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。
等等。
样例程序
我将在程序中使用两种方法来解决粘包和拆包问题,固定数据包长度和添加长度首部,这两种方法各有优劣。
固定数据包长度传输效率一般,尤其是在要发送的数据长度长短差别很大的时候效率会比较低,但是编程实现比较简单;
添加长度首部虽然可以获得较高的传输效率,冗余信息少且固定,但是编程实现较为复杂。
websocket是包含消息头部的,所以样例程序采用首部验证方法
固定数据包长度
这种处理方式的思路很简单,发送端在发送实际数据前先把数据封装为固定长度,然后在发送出去,接收端接收到数据后按照这个固定长度进行拆分即可。处理省略。。。
添加长度首部
这种方式的处理较上面提到的方式稍微复杂一点。在发送端需要给待发送的数据添加固定的首部,然后再发送出去,然后在接收端需要根据这个首部的长度信息进行数据包的组合或拆分,发送端程序如下:
#include "websocket_common.h"
#include
#include
#include // 使用 malloc, calloc等动态分配内存方法
#include // 获取系统时间
#include
#include // 非阻塞
#include
#include // inet_addr()
#include // close()
#include // 文件IO操作
#include //
#include
#include
#include
#include // gethostbyname, gethostbyname2, gethostbyname_r, gethostbyname_r2
#include
#include
#include
#include
#include // SIOCSIFADDR
//==============================================================================================
//======================================== 设置和工具部分 =======================================
//==============================================================================================
// 连接服务器
#define WEBSOCKET_LOGIN_CONNECT_TIMEOUT 1000 // 登录连接超时设置 1000ms
#define WEBSOCKET_LOGIN_RESPOND_TIMEOUT (1000 + WEBSOCKET_LOGIN_CONNECT_TIMEOUT) // 登录等待回应超时设置 1000ms
// 发收
// 生成握手key的长度
#define WEBSOCKET_SHAKE_KEY_LEN 16
//==================== delay ms ====================
void webSocket_delayms(unsigned int ms)
{
struct timeval tim;
tim.tv_sec = ms/1000;
tim.tv_usec = (ms%1000)*1000;
select(0, NULL, NULL, NULL, &tim);
}
//-------------------- IP控制 --------------------
int netCheck_setIP(char *devName, char *ip)
{
struct ifreq temp;
struct sockaddr_in *addr;
int fd, ret;
//
strcpy(temp.ifr_name, devName);
if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return -1;
//
addr = (struct sockaddr_in *)&(temp.ifr_addr);
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = inet_addr(ip);
ret = ioctl(fd, SIOCSIFADDR, &temp);
//
close(fd);
if(ret < 0)
return -1;
return 0;
}
void netCheck_getIP(char *devName, char *ip)
{
struct ifreq temp;
struct sockaddr_in *addr;
int fd, ret;
//
strcpy(temp.ifr_name, devName);
if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return;
ret = ioctl(fd, SIOCGIFADDR, &temp);
close(fd);
if(ret < 0)
return;
//
addr = (struct sockaddr_in *)&(temp.ifr_addr);
strcpy(ip, inet_ntoa(addr->sin_addr));
//
// return ip;
}
//==================== 域名转IP ====================
typedef struct{
pthread_t thread_id;
char ip[256];
bool result;
bool actionEnd;
}GetHostName_Struct;
//
void *websocket_getHost_fun(void *arge)
{
int ret;
//int i;
char buf[1024];
struct hostent host_body, *host = NULL;
struct in_addr **addr_list;
GetHostName_Struct *gs = (GetHostName_Struct *)arge;
/* 此类方法不可重入! 即使关闭线程
if((host = gethostbyname(gs->ip)) == NULL)
//if((host = gethostbyname2(gs->ip, AF_INET)) == NULL)
{
gs->actionEnd = true;
return NULL;
}*/
if(gethostbyname_r(gs->ip, &host_body, buf, sizeof(buf), &host, &ret))
{
gs->actionEnd = true;
return NULL;
}
if(host == NULL)
{
gs->actionEnd = true;
return NULL;
}
addr_list = (struct in_addr **)host->h_addr_list;
//printf("ip name : %s\r\nip list : ", host->h_name);
//for(i = 0; addr_list[i] != NULL; i++) printf("%s, ", inet_ntoa(*addr_list[i])); printf("\r\n");
if(addr_list[0] == NULL)
{
gs->actionEnd = true;
return NULL;
}
memset(gs->ip, 0, sizeof(gs->ip));
strcpy(gs->ip, (char *)(inet_ntoa(*addr_list[0])));
gs->result = true;
gs->actionEnd = true;
return NULL;
}
//
int websocket_getIpByHostName(char *hostName, char *backIp)
{
int i, timeOut = 1;
GetHostName_Struct gs;
if(hostName == NULL)
return -1;
else if(strlen(hostName) < 1)
return -1;
//----- 开线程从域名获取IP -----
memset(&gs, 0, sizeof(GetHostName_Struct));
strcpy(gs.ip, hostName);
gs.result = false;
gs.actionEnd = false;
if (pthread_create(&gs.thread_id, NULL, (void *)websocket_getHost_fun, &gs) < 0)
return -1;
i = 0;
while(!gs.actionEnd)
{
if(++i > 10)
{
i = 0;
if(++timeOut > 1000)
break;
}
webSocket_delayms(1000);// 1ms延时
}
// pthread_cancel(gs.thread_id);
pthread_join(gs.thread_id, NULL);
if(!gs.result)
return -timeOut;
//----- 开线程从域名获取IP -----
memset(backIp, 0, strlen(backIp));
strcpy(backIp, gs.ip);
return timeOut;
}
//==================== 加密方法BASE64 ====================
//base64编/解码用的基础字符集
const char websocket_base64char[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/*******************************************************************************
* 名称: websocket_base64_encode
* 功能: ascii编码为base64格式
* 形参: bindata : ascii字符串输入
* base64 : base64字符串输出
* binlength : bindata的长度
* 返回: base64字符串长度
* 说明: 无
******************************************************************************/
int websocket_base64_encode( const unsigned char *bindata, char *base64, int binlength)
{
int i, j;
unsigned char current;
for ( i = 0, j = 0 ; i < binlength ; i += 3 )
{
current = (bindata[i] >> 2) ;
current &= (unsigned char)0x3F;
base64[j++] = websocket_base64char[(int)current];
current = ( (unsigned char)(bindata[i] << 4 ) ) & ( (unsigned char)0x30 ) ;
if ( i + 1 >= binlength )
{
base64[j++] = websocket_base64char[(int)current];
base64[j++] = '=';
base64[j++] = '=';
break;
}
current |= ( (unsigned char)(bindata[i+1] >> 4) ) & ( (unsigned char) 0x0F );
base64[j++] = websocket_base64char[(int)current];
current = ( (unsigned char)(bindata[i+1] << 2) ) & ( (unsigned char)0x3C ) ;
if ( i + 2 >= binlength )
{
base64[j++] = websocket_base64char[(int)current];
base64[j++] = '=';
break;
}
current |= ( (unsigned char)(bindata[i+2] >> 6) ) & ( (unsigned char) 0x03 );
base64[j++] = websocket_base64char[(int)current];
current = ( (unsigned char)bindata[i+2] ) & ( (unsigned char)0x3F ) ;
base64[j++] = websocket_base64char[(int)current];
}
base64[j] = '\0';
return j;
}
/*******************************************************************************
* 名称: websocket_base64_decode
* 功能: base64格式解码为ascii
* 形参: base64 : base64字符串输入
* bindata : ascii字符串输出
* 返回: 解码出来的ascii字符串长度
* 说明: 无
******************************************************************************/
int websocket_base64_decode( const char *base64, unsigned char *bindata)
{
int i, j;
unsigned char k;
unsigned char temp[4];
for ( i = 0, j = 0; base64[i] != '\0' ; i += 4 )
{
memset( temp, 0xFF, sizeof(temp) );
for ( k = 0 ; k < 64 ; k ++ )
{
if ( websocket_base64char[k] == base64[i] )
temp[0]= k;
}
for ( k = 0 ; k < 64 ; k ++ )
{
if ( websocket_base64char[k] == base64[i+1] )
temp[1]= k;
}
for ( k = 0 ; k < 64 ; k ++ )
{
if ( websocket_base64char[k] == base64[i+2] )
temp[2]= k;
}
for ( k = 0 ; k < 64 ; k ++ )
{
if ( websocket_base64char[k] == base64[i+3] )
temp[3]= k;
}
bindata[j++] = ((unsigned char)(((unsigned char)(temp[0] << 2))&0xFC)) | \
((unsigned char)((unsigned char)(temp[1]>>4)&0x03));
if ( base64[i+2] == '=' )
break;
bindata[j++] = ((unsigned char)(((unsigned char)(temp[1] << 4))&0xF0)) | \
((unsigned char)((unsigned char)(temp[2]>>2)&0x0F));
if ( base64[i+3] == '=' )
break;
bindata[j++] = ((unsigned char)(((unsigned char)(temp[2] << 6))&0xF0)) | \
((unsigned char)(temp[3]&0x3F));
}
return j;
}
//==================== 加密方法 sha1哈希 ====================
typedef struct SHA1Context{
unsigned Message_Digest[5];
unsigned Length_Low;
unsigned Length_High;
unsigned char Message_Block[64];
int Message_Block_Index;
int Computed;
int Corrupted;
} SHA1Context;
#define SHA1CircularShift(bits,word) ((((word) << (bits)) & 0xFFFFFFFF) | ((word) >> (32-(bits))))
void SHA1ProcessMessageBlock(SHA1Context *context)
{
const unsigned K[] = {0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6 };
int t;
unsigned temp;
unsigned W[80];
unsigned A, B, C, D, E;
for(t = 0; t < 16; t++)
{
W[t] = ((unsigned) context->Message_Block[t * 4]) << 24;
W[t] |= ((unsigned) context->Message_Block[t * 4 + 1]) << 16;
W[t] |= ((unsigned) context->Message_Block[t * 4 + 2]) << 8;
W[t] |= ((unsigned) context->Message_Block[t * 4 + 3]);
}
for(t = 16; t < 80; t++)
W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
A = context->Message_Digest[0];
B = context->Message_Digest[1];
C = context->Message_Digest[2];
D = context->Message_Digest[3];
E = context->Message_Digest[4];
for(t = 0; t < 20; t++)
{
temp = SHA1CircularShift(5,A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0];
temp &= 0xFFFFFFFF;
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t = 20; t < 40; t++)
{
temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1];