TCP 粘包
TCP 粘包
由于TCP本身是一种基于流的,安全可靠的传输协议,所以发送方与接受方每次处理数据的量以及数据的处理频率可以不同
这就导致接收方可能无法完整解析一个数据包,存在数据包粘连问题
Problems
- 理想状态下接受到了发送方发送的完整数据包
- 接受到了发送方发来的N个不等长数据包,导致接收方无法拆分
- 接受到了发送发来的一个N个完整数据包+一个不完整数据包,无法拆分
Reason
- 发送方与接收方对数据的发送与接受速率,每次处理的量不对等
- 接收方不知道每个数据包的起始位置与数据包的大小,无法对当前接收到的数据进行拆分
Solutions
- 使用标准的应用层协议(http,https)封装不定长的数据包
- 将传输的数据包添加包头(包头大小自定,一般为4byte),包头存有数据包的总字节数(不包括包头大小),形成一个完整的数据块
Code
Problem
发送方逐行读取txt文件中的内容进行发送,接收方由于不知道完整数据包大小导致无法设置接收缓存的大小来接收并处理完整数据包
1
2
3
4
5
6
7
8
9
10
11
12 发送方:
Welcome
to
China!
理想接收方:
Welcome
to
China!
实际接收方:
we
lc
om.
Sending end
动态分配一段 N+4 的内存,一般为字符数组
前4个字节存放包头,注意包头需要进行网络字节序(4byte完整解析)的转换
后N个字节存放完整的数据包
发送该数据块,并每次检测是否还有包头指定的剩余字节未发送
若未完成,此次发送未结束,继续发送剩余字节数
若完成,即完成一个数据块发送
释放申请的 N+4 内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 //buf 待发送字符数组 size 待发送数据包长度(由于发送的时候包头一块发 所以这里一般是包头+数据包长度)
int _send(SOCKET fd,const char* buf,int size) {
//位置记录指针,记录下次发送的位置
const char* p = buf;
//count记录剩余需要发送的字节数
int count = size;
//发送完整数据包
while(count>0) {
int len = send(fd,p,count,0);
if(len<0) {
closesocket(fd);
return -1;
}else if(len==0) {
continue;
}else {
//count记录剩余需要发送的字节数
count-=len;
//p指针记录下次发送字节的位置
p+=len;
}
}
return size;
}
//数据发送部分
while (1) {
string buff;
int sign;
while (getline(in, buff)) {
//包头设置
int len = buff.length();
int biglen = htonl(len);
//数据块设置 数据包+包头
char* buf = (char*)malloc(4 + len);
memcpy(buf, (char*)&biglen, 4);
memcpy(buf+4, &buff, len);
sign = _send(c_socket, buf, len + 4);
if (sign > 0) {
cout << "数据已发送" << endl;
}
else if (sign == 0) {
cout << "服务端断开连接!" << endl;
break;
}
else {
cout << "数据发送失败" << endl;
break;
}
}
}
Receiving end
先检测收到字节流的首4个字节,提取出包头存放的数据包长度len,注意主机字节序的转换
动态申请对应大小的内存,完成对len长度完整数据包的接受,并每次检测是否还有包头指定的剩余字节未接受
若未完成,此次接受未结束,继续接受剩余字节数
若完成,即完成一个数据块接受
处理完整数据包
释放申请的堆内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 //buf 用于接收的数据包字符数组 size 待接收数据包长度
int _recv(SOCKET fd,char* buf,int size) {
//位置记录指针,记录下次存放的位置
char* p = buf;
//count记录剩余需要接收的字节数
int count = size;
//接收完整数据包
while(count>0) {
int len = recv(fd,p,count,0);
if(len<0) {
closesocket(fd);
return -1;
}else if(len==0) {
//返回已经接收的字节数
return size-count;
}else {
//count记录剩余需要接收的字节数
count-=len;
//p指针记录下次接收字节的位置
p+=len;
}
}
return size;
}
//数据接收
while (1) {
//先接收包头并提取出数据包大小
int biglen;
int len;
_recv(p->fd, (char*)&biglen, 4);
//字节序转换
len = ntohl(biglen);
//动态分配完整数据包大小的内存
char* buff = (char*)malloc(len);
//接收数据包
int sign = _recv(p->fd, buff, len);
if (sign > 0) {
//对接收数据的处理
cout << buff << endl;;
}
else if (sign == 0) {
cout << "客户端" << inet_ntoa(p->addr.sin_addr) << "断开连接!" << endl;
p->fd = -1;
break;
}
else {
cout << "读取客户端" << inet_ntoa(p->addr.sin_addr) << "数据失败!" << endl;
p->fd = -1;
break;
}
}
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 YunDid's Blog!
评论