解决WinSock中发送、接收多包问题
最近在写自己的开发库写到Socket时遇到一个很头疼的问题,那就是在发送时发送内容可能会比缓冲区大,而在接收时又不好判断什么时候接收完数据。所以写了一种发送时分割发送和分包接收后拼接的解决方案。而接收时判断数据是否传输结束,我用了select。这里以阻塞式为例子。
首先,我们需要定义一个常量,那就是我们分包时每个包的大小。如下:
#define EACH_PACK_SIZE 1024 // 单个数据包大小
在发送时,我采用了分割字符串进行分包发送的方法发送数据。
bool SendPacket(SOCKET SendSock, const char * pContent) { unsigned int unPackSum = strlen(pContent) / EACH_PACK_SIZE; // 计算分包数量 if (strlen(pContent) % EACH_PACK_SIZE != 0) unPackSum += 1; char **szSendPack = (char **)calloc(unPackSum, sizeof(char)); // 分配内存用以存放分包内容 for (size_t i = 0; i < unPackSum; i++) { szSendPack[i] = (char *)calloc(EACH_PACK_SIZE + 1, sizeof(char)); // 分配内存给每个分包 strncpy_s(szSendPack[i], EACH_PACK_SIZE + 1, pContent + i * EACH_PACK_SIZE, EACH_PACK_SIZE); // 读入分包至数组 } for (size_t i = 0; i < unPackSum; i++) { ErrorCode = send(SendSock, szSendPack[i], strlen(szSendPack[i]), 0); // 发送数据包 if (ErrorCode == SOCKET_ERROR) // 发送失败 { closesocket(SendSock); return false; } } for (size_t i = 0; i < unPackSum; i++) // 释放内存 free(szSendPack[i]); return true; }
而对于接收数据,由于接收数据长度不定,因此用char二维数组是不方便的,因此我选择了vector来做容器。因此,在使用前应当引用:
#include<vector> using namespace std;
引用后,我们就可以这样处理接收数据了。
char * RecvPacket(SOCKET RecvSock) { vector<char *> vecRecvPack; // 存放接收分包 bool bEmptyPack = false; fd_set crfd; timeval td = { 5, 0 }; while (true) { FD_ZERO(&crfd); FD_SET(RecvSock, &crfd); select(0, &crfd, NULL, NULL, &td); if (FD_ISSET(RecvSock, &crfd)) { bEmptyPack = false; char *szRecvTmp = (char *)calloc(EACH_PACK_SIZE + 1, sizeof(char)); // 分配内存用以缓冲分包 ErrorCode = recv(RecvSock, szRecvTmp, EACH_PACK_SIZE, 0); vecRecvPack.push_back(szRecvTmp); // 放入分包至数组 FD_ZERO(&crfd); td = { 0, 0 }; FD_SET(RecvSock, &crfd); select(0, &crfd, NULL, NULL, &td); if (FD_ISSET(RecvSock, &crfd)) continue; else break; } else { bEmptyPack = true; } } if (bEmptyPack){ //空数据包丢弃连接 return char(); } char *szRecvPack = (char *)calloc(EACH_PACK_SIZE * vecRecvPack.size() + 1, sizeof(char)); // 合成分包 for (size_t i = 0; i < vecRecvPack.size(); i++) { strncpy_s(szRecvPack + (i * EACH_PACK_SIZE), EACH_PACK_SIZE + 1, vecRecvPack[i], EACH_PACK_SIZE); // 读入分包内容至字符串 } return szRecvPack; }
至此,分包发送和接收就完成了。