Socket分包,封包,粘包

Posted C程序技术栈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Socket分包,封包,粘包相关的知识,希望对你有一定的参考价值。


一、简单了解TCP



二、为什么要分包?


    这里的分包我表示的是两层意思,第一层意思:比如我们定义每一次发送的数据大小为8k(因为在真正的项目编程中基本都是要进行封装的,所以发送的大小基本固定),那如果我们要发送一个25k的数据,我们是不是得分成8+8+8+1四个包发送,前三个包都是8k,最后一个包是小于8k;第二层意思:我记得在大一的计算机网络中讲过,TCP是以段(Segment)为单位发送数据的,建立TCP链接后,有一个最大消息长度(MSS).如果应用层数据包超过MSS,就会把应用层数据包拆分,分成两个段来发送.这个时候接收端的应用层就要拼接这两个TCP包,才能正确处理数据。相关的,路由器有一个MTU( 最大传输单元)一般是1500字节,除去IP头部20字节,留给TCP的就只有MTU-20字节。所以一般TCP的MSS为MTU-20=1460字节,当应用层数据超过1460字节时,TCP会分多个数据包来发送。

第二层意思(TCP会根据消息长度自动分包)的分包会有很几种情况。

情况一:分两次发送,第一次发送的数据包含第二个包的包信息。


情况二:第一次发送和第二次发送正好是两个包的大小


Socket分包,封包,粘包

情况三:第一次发送的信息大小 < 第一个包的大小,第二次发送的消息 > 第二个包的大小(包含第一个包一部分消息)

Socket分包,封包,粘包



三、为什么要进行粘包?


    这里的粘包也对应上面的两层分包意思,第一层粘包是把自定义分开发送的数据(8+8+8+1)重新粘在一起组成25k的原数据。第二层粘包是由于TCP数据段在发送的时候超过MSS,协议会自动的分包,所以也得把它粘起来组成一个完整的包。解析出我们正确想要的数据。




我狠话不多,反正上面噼里啪啦一大堆了,我们接下来开始实战了。


四、发送TCP数据包的格式(怎么分包)

Socket分包,封包,粘包

而最重要的是头部,我们把我们需要用到的一些字段信息存放在头部发送过去,方便接收端进行接收。这里我就写简单一点,头部我存放的是总包数(比如25k的包就分成8+8+8+1,也就是总包数为4),但这三个8k的数据是略小于8k的,因为是一个包(头部+数据),还含有头部大小。该包序号:记录当前包是第几个包。该包大小:方便接收端判断是否已经接收数据完成。

(头部所有字段都是int类型)

所以我们可以自定义成4个包:

Socket分包,封包,粘包

Socket分包,封包,粘包


五、怎么粘包


    我们先处理TCP自己分的包粘在一起,我们先将recv接受的数据appand添加到string类型的变量,然后判断string变量的长度和头部中的包大小进行比较,如果string变量的长度大于或等于包大小,说明该包已经接收完,就进行处理。如果小于则不做处理。

    然后再处理我们自己分的包,如果头部的当前包序号等于总包数,说明这个包是最后一个包了,否则也添加到一个string变量里。


六、代码演示

    

    小编只是简单的做一下分包的粘包的代码演示,不涉及太难得逻辑,服务端只处理粘包,客户端只分包。


服务端:

#include<iostream>#include<winsock.h>#include <string>#pragma comment(lib,"ws2_32.lib")#define BUFFER_SIZE 8*1024using namespace std;void initialization();int main() {
int send_len = 0; int recv_len = 0; int len = 0; SOCKET s_server; SOCKET s_accept; SOCKADDR_IN server_addr; SOCKADDR_IN accept_addr;
initialization(); server_addr.sin_family = AF_INET; server_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(8888);
s_server = socket(AF_INET, SOCK_STREAM, 0); if (bind(s_server, (SOCKADDR *)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) { cout << "Bind Error : "<<GetLastError() << endl; WSACleanup(); return 0; }
if (listen(s_server, SOMAXCONN) < 0) { cout << "Listen Error : " << GetLastError() << endl; WSACleanup(); return 0; }
cout << "Listen port : 8888 ..." << endl;
len = sizeof(SOCKADDR); s_accept = accept(s_server, (SOCKADDR *)&accept_addr, &len); if (s_accept == SOCKET_ERROR) { cout << "Accept Error : "<<GetLastError() << endl; WSACleanup(); return 0; }
//Recv Buffer char* recvBuffer = new char[BUFFER_SIZE]; string stringRecvData; string TotalData;
while (1) { ::memset(recvBuffer, 0, BUFFER_SIZE); recv_len = recv(s_accept, recvBuffer, BUFFER_SIZE, 0); if (recv_len < 0) { cout << "Client Out!" << endl; break; } else { //将recv接受的数据appand添加到string类型的变量 stringRecvData.append(recvBuffer, recv_len); int TotalCount = 0; //总包数 int CurrentNum = 0;//当前序号 int DataLen = 0;//该包大小 ::memcpy(&TotalCount, stringRecvData.c_str(), sizeof(int)); ::memcpy(&CurrentNum, stringRecvData.c_str()+ sizeof(int), sizeof(int)); ::memcpy(&DataLen, stringRecvData.c_str()+ sizeof(int)+ sizeof(int), sizeof(int)); //判断string变量的长度和头部中的包大小进行比较 //如果string变量的长度大于或等于包大小,说明该包已经接收完,就进行处理 if (stringRecvData.length() > DataLen || stringRecvData.length() == DataLen) { //得到该包的数据 string PacketData(stringRecvData.c_str() + sizeof(int)*3,DataLen - sizeof(int) * 3); //sizeof(int) * 3是头部大小
TotalData.append(PacketData);
std::cout << "TotalCount:"<< TotalCount << std::endl; std::cout << "CurrentNum:"<< CurrentNum << std::endl; std::cout << "PacketData Len:" << PacketData.length() << std::endl;
//开始处理我们自个分的包 //如果头部的当前包序号等于总包数,说明这个包是最后一个包了 if (CurrentNum==TotalCount) { std::cout << "Total Data Len:" << TotalData.length() << std::endl; TotalData.clear(); }
stringRecvData = stringRecvData.substr(DataLen, stringRecvData.length() - DataLen);//处理完一个包就删除该包 }
}
} delete[]recvBuffer; recvBuffer = NULL; closesocket(s_server); closesocket(s_accept); WSACleanup(); return 0;}void initialization() { WORD w_req = MAKEWORD(2, 2); WSADATA wsadata; WSAStartup(w_req, &wsadata);}


客户端:

#include<iostream>#include<winsock.h>#pragma comment(lib,"ws2_32.lib")using namespace std;void initialization();int main() { int send_len = 0; int recv_len = 0;
SOCKET s_server; SOCKADDR_IN server_addr; initialization(); server_addr.sin_family = AF_INET; server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); server_addr.sin_port = htons(8888); s_server = socket(AF_INET, SOCK_STREAM, 0); if (connect(s_server, (SOCKADDR *)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) { cout << "服务器连接失败!" << endl; WSACleanup(); return 0; }
//发送的数据 string senStr = "lkrtlksdrtklsdtldytsdrhtlkdfhtgg9845e45y698aep057ersitgldrsgtlzdtgidlftvodfghslidtidlksdterp35df3g7df34gir694904e57969694756"; senStr = senStr + senStr + senStr + senStr + senStr + senStr + senStr + senStr + senStr + senStr + senStr; senStr = senStr + senStr + senStr + senStr + senStr + senStr + senStr + senStr + senStr + senStr + senStr; senStr = senStr + senStr;

//加那么多遍是为了让数据长度够我们分包 std::cout<< "Send Data Lenght : "<<senStr.length() << std::endl; //计算总包数 int TotalCount = ceil(senStr.length()/((double)(8192-sizeof(int)*3))); //总包数
//如果只有一个包 if (TotalCount==1) { int CurrentNum = 1;//当前序号 int DataLen = senStr.length()+sizeof(int)*3;//该包大小 char* sendBuffer = new char[DataLen]; memset(sendBuffer, 0, DataLen); memcpy(sendBuffer, &TotalCount, sizeof(int)); memcpy(sendBuffer+ sizeof(int), &CurrentNum, sizeof(int)); memcpy(sendBuffer+ sizeof(int)+ sizeof(int), &DataLen, sizeof(int)); memcpy(sendBuffer+ sizeof(int)+ sizeof(int)+ sizeof(int), senStr.c_str(), senStr.length()); send(s_server, sendBuffer, DataLen, 0); delete[]sendBuffer; sendBuffer = NULL; } //否则进行分包 else { string sendStr; for (int i = 1; i < TotalCount + 1; i++) { int CurrentNum = i;//当前序号 //最后一个包 if (i==TotalCount) { sendStr = senStr.substr((i - 1)*(8192 - sizeof(int)), senStr.length()- (i - 1)*(8192 - sizeof(int))); } else { sendStr = senStr.substr((i - 1)*(8192 - sizeof(int)), 8192 - sizeof(int));
}
int DataLen = sendStr.length() + sizeof(int) * 3;
std::cout << "TotalCount:" << TotalCount << std::endl; std::cout << "CurrentNum:" << CurrentNum << std::endl; std::cout << "PacketData Len:" << sendStr.length() << std::endl;
char* sendBuffer = new char[DataLen]; memset(sendBuffer, 0, DataLen); memcpy(sendBuffer, &TotalCount, sizeof(int)); memcpy(sendBuffer + sizeof(int), &CurrentNum, sizeof(int)); memcpy(sendBuffer + sizeof(int) + sizeof(int), &DataLen, sizeof(int)); memcpy(sendBuffer + sizeof(int) + sizeof(int) + sizeof(int), sendStr.c_str(), sendStr.length()); send(s_server, sendBuffer, DataLen, 0); delete[]sendBuffer; sendBuffer = NULL; sendStr.clear(); Sleep(10); } }
closesocket(s_server); WSACleanup();
while (true) {
} return 0;}void initialization() {
WORD w_req = MAKEWORD(2, 2); WSADATA wsadata; WSAStartup(w_req, &wsadata);

}


演示结果:


发送30008byte大小的数据,分成四个包发送,服务端也能正常的接收!




喜欢C/C++编程技术的记得关注哈!

职场知识|管理知识|经济知识|阅读分享

C/C++基础 | 指针 | 面向对象

Qt | Window编程 

MFC | 服务器


以上是关于Socket分包,封包,粘包的主要内容,如果未能解决你的问题,请参考以下文章

简易游戏服务器—分包与粘包问题

解决粘包问题

socket解决半包粘包问题

Netty入门解决TCP粘包/分包的实例

TCP网络通讯如何解决分包粘包问题(有模拟代码)

TCP粘包问题分析和解决(全)