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 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);
}
客户端:
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 | 服务器