粘包
连续两次send两端数据data1和data2,接收端有几种接收情况
1、先data1,在data2
2、先data1的部分数据,在data1余下的部分以及data2
3、先data1的全部和data2的部分,然后接受data2的余下数据
4、一次性接受data1和data2的全部数据
234就是长说的“粘包”,就需要把接受的数据进行拆包,拆成一个个独立的数据包,而为了拆包必须在发送端进行封包
原因
1、由Nagle算法造成发送端的粘包,当提交一段数据给TCP发送时,TCP不立刻发送此段数据,而是等待一小短时间,看看等待期间是否还有要发送的数据,有的话就一次性发送。上述3、4就是这个原因。
2、接收端接受不及时造成的接收端粘包,TCP会把接受到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层取数据不及时,就会造成缓冲区存放很多数据
解决方法
最初就是在两次send之间调用sleep来休眠一小段时间,但是效率大大降低,而且也不可靠。
对数据包进行封包和拆包就能解决这个问题
封包和拆包
封包
给一段诗句加上包头,这样数据包就变成包头+包体两部分,包头其实就是大小固定的结构体,其中有个结构体变量表示包体的长度,这是个很重要的变量,其他的结构体成员可以根据自己定义。
拆包
利用底层的缓冲区进行拆包,由于TCP也维护了一个缓冲区,可以利用TCP的缓冲区进行缓存发送的数据,这样就不用为每一个连接分配一个缓冲区了,利用缓冲区来拆包,循环接受包头给出的数据,直到收购位置。
解决粘包
为了解决粘包问题,通常会在发送内容前,加上发送内容的长度,所以对方会先收到4Byte,解析获得接下来需要接受的长度,在进行收包
发送与接受一个字符串
客户端
发送的数据前面4个字节表示这个字符串的大小
ssize_t writeLen;
char *sendMsg = "0123456789";
int tLen=strlen(sendMsg);
printf("tLen:%d\n" ,tLen);
int iLen=0;
char * pBuff= new char [100];
*(int*)(pBuff+iLen)= htonl(tLen);
iLen+=sizeof( int);
memcpy(pBuff+iLen,sendMsg,tLen);
iLen+=tLen;
writeLen= MySend(connfd, pBuff, iLen);
把字符串的长度转换成网络字节序,由于发送的内容是字符串,无需转换成网络字节序,直接加到后面就行,发送时间,由于事先不知道发送的诗句有多大,写了下面的函数,一次发不完,可以接着发送,直到发完指定的长度为止
int MySend( int iSock, char * pchBuf, size_t tLen){
int iThisSend;
unsigned int iSended=0;
if(tLen == 0)
return(0);
while(iSended<tLen){
do{
iThisSend = send(iSock, pchBuf, tLen-iSended, 0);
} while((iThisSend<0) && (errno==EINTR));
if(iThisSend < 0){
return(iSended);
}
iSended += iThisSend;
pchBuf += iThisSend;
}
return(tLen);
}
服务端
首先需要先接受4个字节,并转换成主机序,才知道接下来要接受多少的数据
ssize_t readLen = MyRecv(acceptfd, recvMsg, sizeof( int));
if (readLen < 0) {
printf("read failed\n" );
return -1;
}
int len=( int)ntohl(*( int*)recvMsg);
printf("len:%d\n",len);
readLen = MyRecv(acceptfd, recvMsg, len);
不知道会接受多少字节,写一个函数,用于循环接受,直到接受完指定数量为止。
int MyRecv( int iSock, char * pchBuf, size_t tCount){
size_t tBytesRead=0;
int iThisRead;
while(tBytesRead < tCount){
do{
iThisRead = read(iSock, pchBuf, tCount-tBytesRead);
} while((iThisRead<0) && (errno==EINTR));
if(iThisRead < 0){
return(iThisRead);
}else if (iThisRead == 0)
return(tBytesRead);
tBytesRead += iThisRead;
pchBuf += iThisRead;
}
}
发送与接受一个结构体
结构体
struct Header {
int num ;//包id
int index ;//学生编号
};
struct PkgContent {
char sex ;//性别
int score ;//分数
char address [100];//地址
int age;
};
struct Pkg {
Header head;
PkgContent content ;
};
客户端
Pkg mypkg;
mypkg.head.num=1;
mypkg.head.index=10001;
mypkg.content.sex='m';
mypkg.content.score=90;
char * temp="guangzhou and shanghai";
strncpy(mypkg.content.address,temp,sizeof(mypkg.content.address));
mypkg.content.age=18;
ssize_t writeLen;
//计算结构提的大熊啊
int tLen=sizeof(mypkg);
printf("tLen:%d\n" ,tLen);
int iLen=0;
char * pBuff= new char [1000];
//对每个元素进行网络字节序的转换,然后放到一个char数组
*(int*)(pBuff+iLen)= htonl(tLen);
iLen+=sizeof( int);
*(int*)(pBuff+iLen)= htonl(mypkg.head.num);
iLen+=sizeof( int);
*(int*)(pBuff+iLen)= htonl(mypkg.head.index);
iLen+=sizeof( int);
memcpy(pBuff+iLen,&mypkg.content.sex,sizeof( char));
iLen+=sizeof( char);
*(int*)(pBuff+iLen)= htonl(mypkg.content.score);
iLen+=sizeof( int);
memcpy(pBuff+iLen,mypkg.content.address,sizeof(mypkg.content.address));
iLen+=(sizeof(mypkg.content.address));
*(int*)(pBuff+iLen)= htonl(mypkg.content.age);
iLen+=sizeof( int);
//发送代码
writeLen= MySend(connfd, pBuff, iLen);
if (writeLen < 0) {
printf("write failed\n" );
close(connfd);
return 0;
}
服务端
先接受4个字节的包,然后转换成主机序,得到接下来需要接受的长度
ssize_t readLen = MyRecv(acceptfd, recvMsg, sizeof(int));
if (readLen < 0) {
printf("read failed\n" );
return -1;
}
int len=(int)ntohl(*(int*)recvMsg);
printf("len:%d\n",len);
readLen = MyRecv(acceptfd, recvMsg, len);
if (readLen < 0) {
printf("read failed\n" );
return -1;
}
然后将每个元素解析出来
memcpy(&RecvPkg.head.num , pBuff + iLen, sizeof( int));
iLen += sizeof(int);
RecvPkg. head. num = ntohl(RecvPkg.head.num);
printf("RecvPkg.head.num:%d\n" ,RecvPkg.head.num);
memcpy(&RecvPkg.head.index , pBuff + iLen, sizeof( int));
iLen += sizeof(int);
RecvPkg. head. index = ntohl(RecvPkg.head.index);
printf("RecvPkg.head.index:%d\n" ,RecvPkg.head.index);
memcpy(&RecvPkg.content.sex , pBuff + iLen, sizeof( char));
iLen += sizeof(char);
printf("RecvPkg.content.sex:%c\n" ,RecvPkg.content.sex);
memcpy(&RecvPkg.content.score , pBuff + iLen, sizeof( int));
iLen += sizeof(int);
RecvPkg. content.score = ntohl(RecvPkg. content.score );
printf("RecvPkg.content.score:%d\n" ,RecvPkg.content.score);
memcpy(&RecvPkg.content.address, pBuff + iLen, sizeof(RecvPkg.content.address ));
iLen += sizeof(RecvPkg.content.address);
printf("RecvPkg.content.address:%s\n" ,RecvPkg.content.address);
memcpy(&RecvPkg.content.age , pBuff + iLen, sizeof( int));
iLen += sizeof(int);
RecvPkg.content.age = ntohl(RecvPkg.content.age );
printf("RecvPkg.content.age:%d\n" ,RecvPkg.content.age);
现在可以用protobuff等自动生成解析数据的函数
粉丝福利, 免费领取C/C++ 开发学习资料包、技术视频/项目代码,1000道大厂面试题,内容包括(C++基础,网络编程,数据库,中间件,后端开发/音视频开发/Qt开发/游戏开发/Linuxn内核等进阶学习资料和最佳学习路线)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓