TCP IP协议是流协议,对上层协议来讲是没有边界的,主机A发送两个消息M1和M2,如下图所示:
主机A发送了M1和M2,主机B在接收时有4种情况:
1、先收了M1,又收了M2
2、M1、M2一起收到了
3、M1和M2的一部分一起收到的,又收到了M2的一部分
4、先收到了M1的一部分,然后M1的下一部分和M2一起收到
说明:
tcp字节流无边界
udp消息是基于数据报的,是有边界的,可以不处理
对等方一次读操作,不能保证完全把消息读完
对方接收数据包的个数是不确定的
应用程序发数据时,先把数据写到socket的缓冲区里面,缓冲区的大小也是有规定的,当缓冲区写到一定程度,这时候TCP IP协议开始往对等方发数据。IP层有MSS最大数据报限制,如果数据包大于了MSS,则IP层会对数据分片,到对等方再进行组合。在链路层有MTU最大传输单元限制。
产生粘包的原因:
1、套接字本身有缓冲区(发送缓冲区、接受缓冲区)
2、tcp传送端的mss大小限制
3、链路层的MTU限制,如果数据包大于MTU,则要在IP层进行分片,导致消息分割
4、tcp的流量控制和拥塞控制,也可能导致粘包
5、tcp延迟发送机制
我们前几篇博客中的read函数是有bug的,但是我们的实验都是在局域网(在一个机器上)进行的,包传输较快,所以没有凸显出来。也就是在局域网上传输较快,先发送的包也先接收到了,没有出现粘包的现象。但是在公网传输时,延迟较大,如果我们不对流式数据包不进行处理,这时可能就会出现我们上面说的粘包现象了。真正的商用软件一定会进行粘包处理。
包之间没有边界,我们可以人为的造边界。
目前有以下处理方法:
1、在包之间加\r\n,ftp就是这样处理的。
2、在包之间加自定义报文。例如,在报文头之前加4个字节,指示后面的报文大小。
3、定长包
4、更复杂的应用层协议
我们使用在包头加上四字节自定义报文的方式解决粘包问题,直接给出如下的程序:
服务器端:
1 #include <sys/types.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <stdio.h> 5 #include <string.h> 6 #include <errno.h> 7 #include <arpa/inet.h> 8 #include <sys/socket.h> 9 #include <netinet/in.h> 10 #include <sys/socket.h> 11 #include <netinet/ip.h> /* superset of previous */ 12 13 ssize_t readn(int fd, void *buf, size_t count) 14 { 15 size_t nleft = count; 16 ssize_t nread; 17 18 char *bufp = (char*)buf; 19 20 while(nleft > 0) 21 { 22 if( (nread = read(fd, bufp, nleft)) < 0 ) 23 { 24 if(errno == EINTR) 25 { 26 continue; 27 } 28 29 return -1; 30 } 31 else if(nread == 0) 32 { 33 return count - nleft; 34 } 35 36 bufp += nread; 37 nleft -= nread; 38 } 39 40 return count; 41 } 42 43 ssize_t writen(int fd, const void *buf, size_t count) 44 { 45 size_t nleft = count; 46 ssize_t nwritten; 47 48 char *bufp = (char*)buf; 49 50 while(nleft > 0) 51 { 52 if( (nwritten = write(fd, bufp, nleft)) < 0) 53 { 54 if(errno == EINTR) 55 { 56 continue; 57 } 58 59 return -1; 60 } 61 else if(nwritten == 0) 62 { 63 continue; 64 } 65 66 bufp += nwritten; 67 nleft -= nwritten; 68 } 69 70 return count; 71 } 72 73 struct packet 74 { 75 int len; 76 char buf[1024]; 77 }; 78 79 int main() 80 { 81 int sockfd = 0; 82 sockfd = socket(AF_INET, SOCK_STREAM, 0); 83 84 if(sockfd == -1) 85 { 86 perror("socket error"); 87 exit(0); 88 } 89 90 struct sockaddr_in addr; 91 addr.sin_family = AF_INET; 92 addr.sin_port = htons(8001); 93 inet_aton("192.168.31.128", &addr.sin_addr); 94 //addr.sin_addr.s_addr = inet_addr("192.168.6.249"); 95 //addr.sin_addr.s_addr = INADDR_ANY; 96 97 int optval = 1; 98 if( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) 99 { 100 perror("setsockopt error"); 101 exit(0); 102 } 103 104 if( bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) 105 { 106 perror("bind error"); 107 exit(0); 108 } 109 110 if(listen(sockfd, SOMAXCONN) < 0) 111 { 112 perror("listen error"); 113 exit(0); 114 } 115 116 struct sockaddr_in peeraddr; 117 socklen_t peerlen; 118 119 pid_t pid; 120 int conn = 0; 121 122 while(1) 123 { 124 conn = accept(sockfd, (struct sockaddr *)&peeraddr, &peerlen); 125 if(conn == -1) 126 { 127 perror("accept error"); 128 exit(0); 129 } 130 131 char *p = NULL; 132 int peerport = 0; 133 p = inet_ntoa(peeraddr.sin_addr); 134 peerport = ntohs(peeraddr.sin_port); 135 136 printf("peeraddr = %s\n peerport = %d\n", p, peerport); 137 138 pid = fork(); 139 if(pid == -1) 140 { 141 perror("fork error"); 142 exit(0); 143 } 144 145 if(pid == 0) 146 { 147 struct packet recvbuf; 148 int n; 149 int ret = 0; 150 151 close(sockfd); 152 153 while(1) 154 { 155 memset(&recvbuf, 0, sizeof(struct packet)); 156 ret = readn(conn, &recvbuf.len, 4); 157 158 if(ret == -1) 159 { 160 printf("client closed \n"); 161 exit(0); 162 } 163 else if(ret < 4) 164 { 165 perror("read error"); 166 break; 167 } 168 169 n = ntohl(recvbuf.len); 170 ret = readn(conn, recvbuf.buf, n); 171 172 if(ret == -1) 173 { 174 perror("readn error"); 175 exit(0); 176 } 177 else if(ret < n) 178 { 179 printf("client closed\n"); 180 break; 181 } 182 183 fputs(recvbuf.buf, stdout); 184 185 writen(conn, &recvbuf, 4+n); 186 } 187 } 188 189 close(conn); 190 191 } 192 193 close(conn); 194 close(sockfd); 195 196 return 0; 197 }
客户端:
1 #include <sys/types.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <stdio.h> 5 #include <string.h> 6 #include <errno.h> 7 #include <arpa/inet.h> 8 #include <sys/socket.h> 9 #include <netinet/in.h> 10 #include <sys/socket.h> 11 #include <netinet/ip.h> /* superset of previous */ 12 13 ssize_t readn(int fd, void *buf, size_t count) 14 { 15 size_t nleft = count; 16 ssize_t nread; 17 18 char *bufp = (char*)buf; 19 20 while(nleft > 0) 21 { 22 if( (nread = read(fd, bufp, nleft)) < 0 ) 23 { 24 if(errno == EINTR) 25 { 26 continue; 27 } 28 29 return -1; 30 } 31 else if(nread == 0) 32 { 33 return count - nleft; 34 } 35 36 bufp += nread; 37 nleft -= nread; 38 } 39 40 return count; 41 } 42 43 ssize_t writen(int fd, const void *buf, size_t count) 44 { 45 size_t nleft = count; 46 ssize_t nwritten; 47 48 char *bufp = (char*)buf; 49 50 while(nleft > 0) 51 { 52 if( (nwritten = write(fd, bufp, nleft)) < 0) 53 { 54 if(errno == EINTR) 55 { 56 continue; 57 } 58 59 return -1; 60 } 61 else if(nwritten == 0) 62 { 63 continue; 64 } 65 66 bufp += nwritten; 67 nleft -= nwritten; 68 } 69 70 return count; 71 } 72 73 struct packet 74 { 75 int len; 76 char buf[1024]; 77 }; 78 79 int main() 80 { 81 int sockfd = 0; 82 sockfd = socket(AF_INET, SOCK_STREAM, 0); 83 84 struct sockaddr_in addr; 85 addr.sin_family = AF_INET; 86 addr.sin_port = htons(8001); 87 inet_aton("192.168.31.128", &addr.sin_addr); 88 //addr.sin_addr.s_addr = inet_addr("192.168.31.128"); 89 90 if( connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1 ) 91 { 92 perror("connect error"); 93 exit(0); 94 } 95 96 struct packet sendbuf; 97 struct packet recvbuf; 98 memset(&recvbuf, 0, sizeof(struct packet)); 99 memset(&sendbuf, 0, sizeof(struct packet)); 100 int ret = 0; 101 int n = 0; 102 while(fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin) != NULL) 103 { 104 n = strlen(sendbuf.buf); 105 sendbuf.len = htonl(n); 106 107 writen(sockfd, &sendbuf, 4+n); 108 109 ret = readn(sockfd, &recvbuf.len, 4); 110 111 if(ret == -1) 112 { 113 perror("readn error"); 114 exit(0); 115 } 116 else if(ret < 4) 117 { 118 printf("server close\n"); 119 } 120 121 n = ntohl(recvbuf.len); 122 123 ret = readn(sockfd, recvbuf.buf, n); 124 125 if(ret == -1) 126 { 127 perror("readn error"); 128 exit(0); 129 } 130 else if(ret < n) 131 { 132 printf("client close\n"); 133 break; 134 } 135 136 fputs(recvbuf.buf, stdout); 137 memset(&recvbuf, 0, sizeof(struct packet)); 138 memset(&sendbuf, 0, sizeof(struct packet)); 139 140 } 141 142 close(sockfd); 143 144 return 0; 145 }
最重要的就是readn和writen函数,readn先读取4字节,然后根据这四字节的内容确定继续读取后面数据的大小。writen是写4+n字节,n是真正有用的数据,4字节是包头,如果写的过程中不出错,则writen一定会将4+n字节写完。如果writen返回0,那么可能是真的没有写进去,也可能是对端已经关闭,这时候我们重写一次,如果是对端关闭,则这次写writen就会返回小于0的数了。返回小于零的数可能是由于对端关闭,也可能是被中断唤醒,所以我们要判断一下errno,如果是被中断的,则再次尝试写入,如果是对端关闭,则writen就直接出错返回了(返回-1)。
下面我们使用第二种解决方案,在数据包的后面加上'\n',这样的话接收端就要解析数据查找'\n',我们之前用的都是read读数据,如果解析时还用read的话,就要一个字节一个字节的读取并解析,需要多次调用read,效率很低,为了提高效率,这时候可以选择recv函数。原型如下:
ssize_t recv(int s, void *buf, size_t len, int flags)
与read相比,recv函数只能用于套接字文件描述符。而且多了一个flags参数。flags常用的参数有以下两个:
MSG_OOB:带外数据,紧急指针
MSG_PEEK:数据包的“偷窥”,提前预读,当设置成“偷窥”模式时,可以判断数据包的长度和内容。相当于提前读缓冲区,但是并没有将数据清走。read函数读的时候也会清缓冲区。
用fgets读键盘,客户端从键盘输入数据时默认会带一个'\n',这是fgets自动加的。
示例程序如下:
服务器端:
1 #include <sys/types.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <stdio.h> 5 #include <string.h> 6 #include <errno.h> 7 #include <arpa/inet.h> 8 #include <sys/socket.h> 9 #include <netinet/in.h> 10 #include <sys/socket.h> 11 #include <netinet/ip.h> /* superset of previous */ 12 13 ssize_t readn(int fd, void *buf, size_t count) 14 { 15 size_t nleft = count; 16 ssize_t nread; 17 18 char *bufp = (char*)buf; 19 20 while(nleft > 0) 21 { 22 if( (nread = read(fd, bufp, nleft)) < 0 ) 23 { 24 if(errno == EINTR) 25 { 26 continue; 27 } 28 29 return -1; 30 } 31 else if(nread == 0) 32 { 33 return count - nleft; 34 } 35 36 bufp += nread; 37 nleft -= nread; 38 } 39 40 return count; 41 } 42 43 ssize_t writen(int fd, const void *buf, size_t count) 44 { 45 size_t nleft = count; 46 ssize_t nwritten; 47 48 char *bufp = (char*)buf; 49 50 while(nleft > 0) 51 { 52 if( (nwritten = write(fd, bufp, nleft)) < 0) 53 { 54 if(errno == EINTR) 55 { 56 continue; 57 } 58 59 return -1; 60 } 61 else if(nwritten == 0) 62 { 63 continue; 64 } 65 66 bufp += nwritten; 67 nleft -= nwritten; 68 } 69 70 return count; 71 } 72 73 ssize_t recv_peek(int sockfd, void *buf, size_t len) 74 { 75 while(1) 76 { 77 int ret = recv(sockfd, buf, len, MSG_PEEK); 78 if(ret == -1 && errno == EINTR) 79 continue; 80 return ret; 81 } 82 } 83 84 ssize_t readline(int sockfd, void *buf, size_t maxline) 85 { 86 int ret; 87 int nread; 88 char *bufp = (char*)buf; 89 int nleft = maxline; 90 91 while(1) 92 { 93 ret = recv_peek(sockfd, bufp, nleft); 94 if(ret < 0) 95 { 96 return ret; 97 } 98 else if(ret == 0) 99 { 100 return ret; 101 } 102 103 nread = ret; 104 int i; 105 for(i = 0; i < nread; i++) 106 { 107 if(bufp[i] == '\n') 108 { 109 ret = readn(sockfd, bufp, i+1); 110 if(ret != i+1) 111 { 112 perror("readn error"); 113 exit(0); 114 } 115 116 return ret; 117 } 118 } 119 120 if(nread > nleft) 121 { 122 perror("FAILURE"); 123 exit(0); 124 } 125 126 nleft -= nread; 127 ret = readn(sockfd, bufp, nread); 128 if(ret != nread) 129 { 130 perror("readn error"); 131 exit(0); 132 } 133 bufp += nread; 134 } 135 136 return -1; 137 } 138 139 140 int main() 141 { 142 int sockfd = 0; 143 sockfd = socket(AF_INET, SOCK_STREAM, 0); 144 145 if(sockfd == -1) 146 { 147 perror("socket error"); 148 exit(0); 149 } 150 151 struct sockaddr_in addr; 152 addr.sin_family = AF_INET; 153 addr.sin_port = htons(8001); 154 inet_aton("192.168.31.128", &addr.sin_addr); 155 //addr.sin_addr.s_addr = inet_addr("192.168.6.249"); 156 //addr.sin_addr.s_addr = INADDR_ANY; 157 158 int optval = 1; 159 if( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) 160 { 161 perror("setsockopt error"); 162 exit(0); 163 } 164 165 if( bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) 166 { 167 perror("bind error"); 168 exit(0); 169 } 170 171 if(listen(sockfd, SOMAXCONN) < 0) 172 { 173 perror("listen error"); 174 exit(0); 175 } 176 177 struct sockaddr_in peeraddr; 178 socklen_t peerlen; 179 180 pid_t pid; 181 int conn = 0; 182 183 while(1) 184 { 185 conn = accept(sockfd, (struct sockaddr *)&peeraddr, &peerlen); 186 if(conn == -1) 187 { 188 perror("accept error"); 189 exit(0); 190 } 191 192 char *p = NULL; 193 int peerport = 0; 194 p = inet_ntoa(peeraddr.sin_addr); 195 peerport = ntohs(peeraddr.sin_port); 196 197 printf("peeraddr = %s\n peerport = %d\n", p, peerport); 198 199 pid = fork(); 200 if(pid == -1) 201 { 202 perror("fork error"); 203 exit(0); 204 } 205 206 if(pid == 0) 207 { 208 char recvbuf[1024]; 209 int ret = 0; 210 211 close(sockfd); 212 213 while(1) 214 { 215 memset(&recvbuf, 0, sizeof(recvbuf)); 216 ret = readline(conn, recvbuf, 1024); 217 218 if(ret == 0) 219 { 220 printf("client closed \n"); 221 break; 222 } 223 else if(ret == -1) 224 { 225 perror("readline error"); 226 break; 227 } 228 229 fputs(recvbuf, stdout); 230 231 writen(conn, recvbuf, strlen(recvbuf)); 232 } 233 } 234 235 close(conn); 236 237 } 238 239 close(conn); 240 close(sockfd); 241 242 return 0; 243 }
客户端:
1 #include <sys/types.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <stdio.h> 5 #include <string.h> 6 #include <errno.h> 7 #include <arpa/inet.h> 8 #include <sys/socket.h> 9 #include <netinet/in.h> 10 #include <sys/socket.h> 11 #include <netinet/ip.h> /* superset of previous */ 12 13 ssize_t readn(int fd, void *buf, size_t count) 14 { 15 size_t nleft = count; 16 ssize_t nread; 17 18 char *bufp = (char*)buf; 19 20 while(nleft > 0) 21 { 22 if( (nread = read(fd, bufp, nleft)) < 0 ) 23 { 24 if(errno == EINTR) 25 { 26 continue; 27 } 28 29 return -1; 30 } 31 else if(nread == 0) 32 { 33 return count - nleft; 34 } 35 36 bufp += nread; 37 nleft -= nread; 38 } 39 40 return count; 41 } 42 43 ssize_t writen(int fd, const void *buf, size_t count) 44 { 45 size_t nleft = count; 46 ssize_t nwritten; 47 48 char *bufp = (char*)buf; 49 50 while(nleft > 0) 51 { 52 if( (nwritten = write(fd, bufp, nleft)) < 0) 53 { 54 if(errno == EINTR) 55 { 56 continue; 57 } 58 59 return -1; 60 } 61 else if(nwritten == 0) 62 { 63 continue; 64 } 65 66 bufp += nwritten; 67 nleft -= nwritten; 68 } 69 70 return count; 71 } 72 73 ssize_t recv_peek(int sockfd, void *buf, size_t len) 74 { 75 while(1) 76 { 77 int ret = recv(sockfd, buf, len, MSG_PEEK); 78 if(ret == -1 && errno == EINTR) 79 continue; 80 return ret; 81 } 82 } 83 84 ssize_t readline(int sockfd, void *buf, size_t maxline) 85 { 86 int ret; 87 int nread; 88 char *bufp = (char*)buf; 89 int nleft = maxline; 90 91 while(1) 92 { 93 ret = recv_peek(sockfd, bufp, nleft); 94 if(ret < 0) 95 { 96 return ret; 97 } 98 else if(ret == 0) 99 { 100 return ret; 101 } 102 103 nread = ret; 104 int i; 105 for(i = 0; i < nread; i++) 106 { 107 if(bufp[i] == '\n') 108 { 109 ret = readn(sockfd, bufp, i+1); 110 if(ret != i+1) 111 { 112 perror("readn error"); 113 exit(0); 114 } 115 116 return ret; 117 } 118 } 119 120 if(nread > nleft) 121 { 122 perror("FAILURE"); 123 exit(0); 124 } 125 126 nleft -= nread; 127 ret = readn(sockfd, bufp, nread); 128 if(ret != nread) 129 { 130 perror("readn error"); 131 exit(0); 132 } 133 bufp += nread; 134 } 135 136 return -1; 137 } 138 139 int main() 140 { 141 int sockfd = 0; 142 sockfd = socket(AF_INET, SOCK_STREAM, 0); 143 144 struct sockaddr_in addr; 145 addr.sin_family = AF_INET; 146 addr.sin_port = htons(8001); 147 inet_aton("192.168.31.128", &addr.sin_addr); 148 //addr.sin_addr.s_addr = inet_addr("192.168.31.128"); 149 150 if( connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1 ) 151 { 152 perror("connect error"); 153 exit(0); 154 } 155 156 char sendbuf[1024] = {0}; 157 char recvbuf[1024] = {0}; 158 int ret = 0; 159 int n = 0; 160 while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) 161 { 162 writen(sockfd, sendbuf, strlen(sendbuf)); 163 164 ret = readline(sockfd, recvbuf, sizeof(recvbuf)); 165 166 if(ret == -1) 167 { 168 perror("readline error"); 169 exit(0); 170 } 171 else if(ret == 0) 172 { 173 printf("server close\n"); 174 break; 175 } 176 177 fputs(recvbuf, stdout); 178 memset(recvbuf, 0, sizeof(recvbuf)); 179 memset(sendbuf, 0, sizeof(sendbuf)); 180 181 } 182 183 close(sockfd); 184 185 return 0; 186 }
主要的函数就是readline函数,该函数从套接字缓冲区读取maxline长度的数据,readline调用了recv_peek,recv_peek返回实际读取的数据长度,然后在readline函数中判断这些数据中是否有'\n',如果有'\n'的话,就用readn函数真正的将数据读出来,然后直接返回。如果没有'\n',则会跳到120行判断一下recv_peek读取的长度,然后用readn将这些长度的数据读出来,然后移动缓冲区指针,接着再次调用recv_peek去缓冲区读数据,直到读到'\n'为止。或者读满整个maxline长度就返回。
执行结果如下: