lwbaptx

TCP的封包和解包

TCP是个“流”协议,所谓流,就是没有界限的一串数据。大家可以将其想象河里的流水,是连成一片的,其间是没有分界线的。

但一般通信程序开发是需要定义一个个相互独立的数据包的,比如用于登录的数据包、用于注销的数据包等。由于TCP“流”的特性以及网络状况,在进行数据传输时假设我们连续调用两次send分别发送两段数据data1和data2,在接收端有以下几种接收情况(当然不止这几种情况,这里只列出了有代表性的情况)。
(1)先接收到data1,然后接收到data2。
(2)先接收到data1的部分数据,然后接收到data1余下的部分以及data2的全部。
(3)先接收到了data1的全部数据和data2的部分数据,然后接收到了data2的余下的数据。
(4)一次性接收到了data1和data2的全部数据。
对于(1)这种情况正是我们需要的,不再做讨论。
对于(2)、(3)和(4)的情况就是常说的“粘包”,就需要把接收到的数据进行拆包,拆成一个个独立的数据包;而为了拆包就必须在发送端进行封包。

对于UDP来说就不存在拆包的问题,因为UDP是个”数据包”协议,也就是两段数据间是有界限的,在接收端要么接收不到数据要么就是接收一段完整的数据,不会少接收也不会多接收。

为什么会出现(2)、(3)和(4)的情况呢,有以下几点原因。
1.由Nagle算法造成的发送端的粘包。Nagle算法是一种改善网络传输效率的算法,但也可能造成困扰。简单来说,当要提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间,看看在等待期间是否还有要发送的数据,若有则会一次把多段数据发送出去。像(3)和(4)的情况就有可能是Nagle算法造成的。
2.接收端接收不及时造成的接收端粘包。TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时取出TCP的数据,就会造成TCP缓冲区中存放了多段数据。
“粘包”可发生在发送端也可发生在接收端。最初遇到”粘包”的问题时,大家可能觉得可以在两次send之间调用sleep来休眠一小段时间,以此来解决。这个解决方法的缺点是显而易见的:使传输效率大大降低,而且也并不可靠。对数据包进行封包和拆包,就能解决这个问题。

封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(以后讲过滤非法包时会加上“包尾”内容)。
包头其实上是个大小固定的结构体,其中有个结构体成员变量表示包体的长度,这是个很重要的变量,其他的结构体成员可根据需要自己定义。根据固定的包头长度以及包头中含有的包体长度的变量值就能正确的拆分出一个完整的数据包。利用底层的缓冲区来进行拆包时,由于TCP也维护了一个缓冲区,所以可以利用TCP的缓冲区来缓存发送的数据,这样一来就不需要为每一个连接分配一个缓冲区了,对于利用缓冲区来拆包,也就是循环不停地接收包头给出的数据,直到收够为止,这就是一个完整的TCP包。为了解决“粘包”的问题,大家通常会在所发送的内容前,加上发送内容的长度,所以对方就会先收4 Byte,解析获得接下来需要接收的长度,再进行收包。

下面是一个收发字符串的例子,在字符串前面发送一个4字节大小的长度。
server.cpp

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>

int process_recv(int socket, char* pchBuf, size_t len) {
int bytes_read_total = 0;
while (bytes_read_total < len) {
int bytes_read_once = -1;
do {
bytes_read_once = read(socket, pchBuf, len - bytes_read_total);
} while( (bytes_read_once < 0) && (errno == EINTR));

if (bytes_read_once < 0) {
return bytes_read_once;
} else if (bytes_read_once == 0)
return bytes_read_total;
bytes_read_total += bytes_read_once;
pchBuf += bytes_read_once;
}
}

int main(int argc, char ** argv) {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) {
perror("create error");
return -1;
}

struct sockaddr_in listen_addr;
listen_addr.sin_family = AF_INET;
listen_addr.sin_port = htons(16666);
listen_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(listen_addr.sin_zero), 0);
if (bind(listen_fd, (struct sockaddr*)&listen_addr, sizeof(struct sockaddr)) == -1) {
perror("bind error");
return -1;
}

if (listen(listen_fd, 10) == -1) {
perror("listen error");
return -1;
}

while (true) {
unsigned int sin_size = sizeof(listen_addr);
int accept_fd = accept(listen_fd, (struct sockaddr*)&listen_addr, &sin_size);
if (accept_fd < 0) {
close(listen_fd);
printf("accept error\n");
return -1;
}

//read len
char msg_buff[10];
int read_len = process_recv(accept_fd, msg_buff, sizeof(int));
if (read_len < 0) {
printf("read len error\n");
return -1;
}
int recv_len = (int) ntohl(*(int*)msg_buff); //change byte order
printf("recv body len: %d\n", recv_len);

//read body
read_len = process_recv(accept_fd, msg_buff, recv_len);
if (read_len < 0) {
printf("read body error\n");
return -1;
}
msg_buff[recv_len] = '\0';
printf("recv body data: %s\n", msg_buff);
close(accept_fd);
}

close(listen_fd);
return 0;
}

client.cpp

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
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

int process_send(int socket, char* pchBuf, size_t len) {
int bytes_send_total = 0;
if(len == 0)
return 0;
while(bytes_send_total < len) {
int bytes_send_once = -1;
do {
bytes_send_once = send(socket, pchBuf, len - bytes_send_total, 0);
} while((bytes_send_once < 0) && (errno == EINTR));

if(bytes_send_once < 0) {
return bytes_send_total;
}

bytes_send_total += bytes_send_once;
pchBuf += bytes_send_once;
}
return bytes_send_total;
}

int main(int argc, char * argv[]) {
if (argc < 2) {
printf("Uasge: client [server IP address] \n");
return -1;
}

struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(16666);
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
int conn_fd = socket(AF_INET, SOCK_STREAM, 0);
if (conn_fd < 0) {
printf("create error\n");
return -1;
}

if (connect(conn_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
printf("connect error\n");
return -1;
}

const char* send_msg = "0123456789";
int msg_len = strlen(send_msg);
int packet_len = sizeof(int) + msg_len;
char* pBuff = new char[packet_len];
*(int*)(pBuff) = htonl(msg_len);
memcpy(pBuff + sizeof(int), send_msg, msg_len);
int send_len = process_send(conn_fd, pBuff, packet_len);
if (send_len < 0) {
printf("send error\n");
close(conn_fd);
return -1;
} else {
printf("send success, len: %d msg: %s\n", send_len, send_msg);
}
close(conn_fd);
return 0;
}

编译运行

1
2
3
4
g++ -o server server.cpp
g++ -o client client.cpp
./server
./client 127.0.0.1

client输出结果

1
send success, len: 14 msg: 0123456789

server输出结果

1
2
recv body len: 10
recv body data: 0123456789