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会根据消息长度自动分包)的分包会有很几种情况。
情况一:分两次发送,第一次发送的数据包含第二个包的包信息。
情况二:第一次发送和第二次发送正好是两个包的大小
情况三:第一次发送的信息大小 < 第一个包的大小,第二次发送的消息 > 第二个包的大小(包含第一个包一部分消息)
三、为什么要进行粘包?
这里的粘包也对应上面的两层分包意思,第一层粘包是把自定义分开发送的数据(8+8+8+1)重新粘在一起组成25k的原数据。第二层粘包是由于TCP数据段在发送的时候超过MSS,协议会自动的分包,所以也得把它粘起来组成一个完整的包。解析出我们正确想要的数据。
我狠话不多,反正上面噼里啪啦一大堆了,我们接下来开始实战了。
四、发送TCP数据包的格式(怎么分包)
而最重要的是头部,我们把我们需要用到的一些字段信息存放在头部发送过去,方便接收端进行接收。这里我就写简单一点,头部我存放的是总包数(比如25k的包就分成8+8+8+1,也就是总包数为4),但这三个8k的数据是略小于8k的,因为是一个包(头部+数据),还含有头部大小。该包序号:记录当前包是第几个包。该包大小:方便接收端判断是否已经接收数据完成。
(头部所有字段都是int类型)
所以我们可以自定义成4个包:
五、怎么粘包
我们先处理TCP自己分的包粘在一起,我们先将recv接受的数据appand添加到string类型的变量,然后判断string变量的长度和头部中的包大小进行比较,如果string变量的长度大于或等于包大小,说明该包已经接收完,就进行处理。如果小于则不做处理。
然后再处理我们自己分的包,如果头部的当前包序号等于总包数,说明这个包是最后一个包了,否则也添加到一个string变量里。
六、代码演示
小编只是简单的做一下分包的粘包的代码演示,不涉及太难得逻辑,服务端只处理粘包,客户端只分包。
服务端:
using 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 Bufferchar* 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);}
客户端:
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 | 服务器
