tcp是流式传输协议,接收端和发送端的 收发时间延时,此时会出现粘包现象。
比如:再不考虑内核双向缓冲区的延时发送问题,客户端假设1s 发送100k,循环发,服务器每2s收一次数据,此时服务器收到了200k,出现了消息粘包,服务器也不知道客户端的消息分组情况,所以我们需要和客户端协商一个解决方案,类型tcp底层封装思想一下定义为 消息头和消息体:
下面伪代码展示一下:
发送端:
void sendMsg(int fd, char* sendData)
{
/*这里的sendData数据体他是字符串形式的 不需要考虑字节对齐问题,
sendData处理后的数据我们可以通过protobuf工具进行序列化得到一个字符串
*/
//在每条消息发送前面我们和服务器协商好定义一个int类型4字节的消息头里面存储消息实际长度
int allLen = strlen(sendData) + 4; //要发送的消息总长度 4就是消息头的大小,内容是要发送实际数据
char* netData = new char[allLen]; //一条完整消息 包含消息体和消息头
memset(netData, 0 sizeof(netData));
uint32_t head = strlen(sendData); //头保存消息体数据长度
//int 是4字节的数据 由于不同机器要考虑到网络传输的字节对齐问题,而字符串不用考虑,所以需要将主机字节序转换为网络字节序
head = htonl(head); //head 对应4字节所以用 htohl()
memstrcpy(netData , &head, 4); //将消息头数据先拷贝要发送的数据包里面
//然后将消息体实际数据拷贝发送包里面
memstrcpy(netData +4, sendData, strlen(sendData));
send(fd, netData, allLen, 0); //将数据发送给接收端
}
接收端:
#define RECVSIZE 40960000
char tmpbuffer[RECVSIZE] = {0}; //超大缓冲区
void recvMsg(fd, char* recvData)
{
//这里准确的做法是应该用个缓冲区一次性接收所有数据 防止的不完整
int len = recv(fd, tmpbuffer+strlen(tmpbuffer), 40960000, 0) //这里tmpbuffer+strlen(tmpbuffer) 因为有没有满足一条完整消息 而留下的数据此时要接着缓冲区后面的位置继续写
if( len > 4) //>4先取头信息 看消息体长度够不够
{
uint32_t head = 0;
memstrcpy(&head, tmpbuffer, 4)
head = ntohl(head); //将读到的数据网络字节序转换为本机字节序 head 里面的大小就是消息体内容的长度
int readLen = 0 //已经读取的数据
while(true) //通过循环将消息拆分一条条处理完
{
if(len >= head + 4) //满足一条完整的数据, 拆包过程
{
char* lineData = new char[head]
readLen + = 4 //如果满足了head + 4 <= len 才可以设置readLen加,要不然丢失头数据信息
memstrcpy(lineData, tmpbuffer+readLen , head) //取数据从缓冲区的头依次取数据
readLen += head //4+head 读走了一条完整数据的长度
len -= readLen //剩下的数据长度
//将lineData通过protocol反序列化
//处理对应得了逻辑
delete [] lineData;
lienData = NULL;
}
else //不满足一条完整的数据
{
memstrcpy(tmpbuffer, tmpbuffer+readLen, len-readLen); //这里将剩下数据拷回临时缓冲区起始地址
tmpbuffer[readLen+1] = '\0'; //strlen 遇到\0停止
break;
}
memstrcpy(&head, tmpbuffer+readLen , 4)
head = ntohl(head); //将读到的数据网络字节序转换为本机字节序 head 里面的大小就是消息体内
}
}
}