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长度就返回。

执行结果如下:

 

  

posted on 2018-07-30 23:28  周伯通789  阅读(267)  评论(0编辑  收藏  举报