网络编程之TCP粘包问题

网络编程之TCP粘包问题,介绍粘包问题产生的原因以及解决办法。


粘包问题产生的原因?

TCP是基于字节流传输的,只维护发送出去多少,确认了多少,没有维护消息与消息之间的边界,因而可能导致粘包问题。


粘包解决方案

粘包问题本质上要在应用层维护消息与消息的边界。

  • 定长包
  • 包尾\r\n(ftp)
  • 包头加上包头长度
  • 更复杂的应用层协议

readn()和writen()

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/*server.c*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
ssize_t readn(int fd, void *buf, size_t n) {
int nleft = n;
int nread;
char *bufp = (char *)buf;
while (nleft > 0) {
if((nread = read(fd, bufp, nleft)) < 0) {
if (errno == EINTR)
continue;
return -1;
}
else if (nread == 0) {
return n - nleft;
}
nleft -= nread;
bufp += nread;
}
return n;
}
ssize_t writen(int fd, void *buf, size_t n) {
int nleft = n;
int nwrite;
char *bufp = (char *)buf;
while (nleft > 0) {
if((nwrite = write(fd, buf, nleft)) < 0) {
if (errno == EINTR)
continue;
return -1;
}
else if (nwrite == 0) {
return n - nleft;
}
nleft -= nwrite;
buf += nwrite;
}
return n;
}
void handler(int arg) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}
int main() {
// signal(SIGCHLD, SIG_IGN); // 避免僵尸进程
signal(SIGCHLD, handler);
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
ERR_EXIT("socket");
}
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(5534);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
int on = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
ERR_EXIT("setsockopt");
}
if (bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0) {
ERR_EXIT("bind");
}
if (listen(sockfd, SOMAXCONN) < 0) {
ERR_EXIT("listen");
}
int conn;
pid_t pid;
struct sockaddr_in clientaddr;
int clientaddrlen = sizeof(clientaddr);
while (1) {
if ((conn = accept(sockfd, (struct sockaddr*)&clientaddr, &clientaddrlen)) < 0) {
ERR_EXIT("accept");
}
printf("ip=%s, port=%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
if ((pid = fork()) < 0) {
ERR_EXIT("fork");
} else if (pid == 0) { // 子进程
close(sockfd);
while(1) {
char recvbuf[1024] = {0};
int ret;
if ((ret = readn(conn, recvbuf, 10)) < 0) {
ERR_EXIT("readn");
} else if (ret == 0) {
printf("client close\n");
break;
}
fputs(recvbuf, stdout);
writen(conn, recvbuf, 10);
memset(recvbuf, 0, sizeof(recvbuf));
}
close(conn);
exit(EXIT_SUCCESS);
} else { // 父进程
close(conn);
}
}
return 0;
}
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/*client.c*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0) \
ssize_t readn(int fd, void *buf, size_t n) {
int nleft = n;
int nread;
char *bufp = (char *)buf;
while (nleft > 0) {
if((nread = read(fd, bufp, nleft)) < 0) {
if (errno == EINTR)
continue;
return -1;
}
else if (nread == 0) {
return n - nleft;
}
nleft -= nread;
bufp += nread;
}
return n;
}
ssize_t writen(int fd, void *buf, size_t n) {
int nleft = n;
int nwrite;
char *bufp = (char *)buf;
while (nleft > 0) {
if((nwrite = write(fd, bufp, nleft)) < 0) {
if (errno == EINTR)
continue;
return -1;
}
else if (nwrite == 0) {
return n - nleft;
}
nleft -= nwrite;
bufp += nwrite;
}
return n;
}
int main() {
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
ERR_EXIT("socket");
}
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(5534);
serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0) {
ERR_EXIT("connect");
}
char sendbuf[1024] = {0};
char recvbuf[1024] = {0};
while (fgets(sendbuf, sizeof(sendbuf), stdin) != 0) {
writen(sockfd, sendbuf, 10);
int ret;
if ((ret = readn(sockfd, recvbuf, 10)) < 0) {
ERR_EXIT("readn");
} else if (ret == 0) {
printf("server close\n");
break;
}
fputs(recvbuf, stdout);
memset(sendbuf, 0, sizeof(sendbuf));
memset(recvbuf, 0, sizeof(recvbuf));
}
close(sockfd);
return 0;
}

readline()函数

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
ssize_t recv_peek(int sockfd, void *buf, size_t len) {
while (1) {
int ret = recv(sockfd, buf, len, MSG_PEEK);
if (ret < 0 && errno == EINTR) {
continue;
}
return ret;
}
}
ssize_t readline(int sockfd, void *buf, size_t maxline) {
int ret;
int nread;
char *bufp = (char*)buf;
int nleft = maxline;
while (1) {
ret = recv_peek(sockfd, bufp, nleft);
if (ret < 0) {
return ret;
} else if (ret == 0) {
return ret;
}
nread = ret;
int i;
for (i=0; i<nread; ++i) {
if (bufp[i] == '\n') {
ret = readn(sockfd, bufp, i+1);
if (ret != i+1) {
exit(EXIT_FAILURE);
}
return ret;
}
}
if (nread > nleft) {
exit(EXIT_FAILURE);
}
nleft -= nread;
ret = readn(sockfd, bufp, nread);
if (ret != nread) {
exit(EXIT_FAILURE);
}
bufp += nread;
}
return -1;
}

包头加上包长度

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/*server.c*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
struct packet {
int len;
char buf[1024];
};
void handler(int arg) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}
int main() {
// signal(SIGCHLD, SIG_IGN); // 避免僵尸进程
signal(SIGCHLD, handler);
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
ERR_EXIT("socket");
}
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(5534);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
int on = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
ERR_EXIT("setsockopt");
}
if (bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0) {
ERR_EXIT("bind");
}
if (listen(sockfd, SOMAXCONN) < 0) {
ERR_EXIT("listen");
}
int conn;
pid_t pid;
struct sockaddr_in clientaddr;
int clientaddrlen = sizeof(clientaddr);
while (1) {
if ((conn = accept(sockfd, (struct sockaddr*)&clientaddr, &clientaddrlen)) < 0) {
ERR_EXIT("accept");
}
printf("ip=%s, port=%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
if ((pid = fork()) < 0) {
ERR_EXIT("fork");
} else if (pid == 0) { // 子进程
close(sockfd);
while(1) {
struct packet recv_packet;
int ret;
memset(&recv_packet, 0, sizeof(recv_packet));
if ((ret = read(conn, &recv_packet.len, sizeof(int))) < 0) {
ERR_EXIT("read len");
} else if (ret == 0) {
printf("client close\n");
break;
}
int n = ntohl(recv_packet.len);
if ((ret = read(conn, recv_packet.buf, n)) < 0) {
ERR_EXIT("read buf");
}
fputs(recv_packet.buf, stdout);
write(conn, &recv_packet, 4+n);
}
close(conn);
exit(EXIT_SUCCESS);
} else { // 父进程
close(conn);
}
}
return 0;
}
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
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0) \
struct packet {
int len;
char buf[1024];
};
int main() {
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
ERR_EXIT("socket");
}
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(5534);
serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0) {
ERR_EXIT("connect");
}
struct packet send_packet;
struct packet recv_packet;
while (fgets(send_packet.buf, sizeof(send_packet.buf), stdin) != 0) {
int n = strlen(send_packet.buf);
send_packet.len = htonl(n);
write(sockfd, &send_packet, 4+n);
int ret;
if ((ret = read(sockfd, &recv_packet, 4+n)) < 0) {
ERR_EXIT("readn");
} else if (ret == 0) {
printf("server close\n");
break;
}
fputs(recv_packet.buf, stdout);
memset(&send_packet, 0, sizeof(send_packet));
memset(&recv_packet, 0, sizeof(recv_packet));
}
close(sockfd);
return 0;
}