tcp是基于字节流的,udp是基于报文即数据包的,所以tcp会产生一个叫做粘包的问题,而udp不会产生。
我们这节主要讨论粘包问题:
先看一下粘包问题的原因:
总结如下:
1、应用进程的缓冲区和Socket缓冲区的大小不一定相吻合。
2、tcp传输段有mss限制。
3、链路层有个mtu限制。
粘包的解决方案:
1、设置定长包,定长接受。
2、在包尾加上\r\n,例如ftp就是这样实现的。
3、包头加上包体长度。
4、更复杂的应用层协议。
下面就封装一下readn和writen来实现解决粘包问题,改进一下先前的那个回射程序:
服务器端:
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) struct packet //新定义包的结构,包体长度和包体 { int len; char buf[1024]; }; ssize_t readn(int fd, void *buf, size_t count) //readn的封装 { size_t nleft = count; ssize_t 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 count - nleft; bufp += nread; nleft -= nread; } return count; } ssize_t writen(int fd, const void *buf, size_t count)//writen的封装 { size_t nleft = count; ssize_t nwritten; char *bufp = (char*)buf; while (nleft > 0) { if ((nwritten = write(fd, bufp, nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nwritten == 0) continue; bufp += nwritten; nleft -= nwritten; } return count; } void do_service(int conn) { struct packet recvbuf; int n; while (1) { memset(&recvbuf, 0, sizeof(recvbuf)); int ret = readn(conn, &recvbuf.len, 4); //先接受包头长度 if (ret == -1) ERR_EXIT("read"); else if (ret < 4) { printf("client close\n"); break; } n = ntohl(recvbuf.len); //这里有个字节序的转换,因为是要在网络上传输 ret = readn(conn, recvbuf.buf, n); //再接受包体 if (ret == -1) ERR_EXIT("read"); else if (ret < n) { printf("client close\n"); break; } fputs(recvbuf.buf, stdout); writen(conn, &recvbuf, 4+n); //回写要包体长度加包体 } } int main(void) { int listenfd; if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) /* if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)*/ ERR_EXIT("socket"); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5188); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); /*servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");*/ /*inet_aton("127.0.0.1", &servaddr.sin_addr);*/ int on = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) ERR_EXIT("setsockopt"); if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) ERR_EXIT("bind"); if (listen(listenfd, SOMAXCONN) < 0) ERR_EXIT("listen"); struct sockaddr_in peeraddr; socklen_t peerlen = sizeof(peeraddr); int conn; pid_t pid; while (1) { if ((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0) ERR_EXIT("accept"); printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); pid = fork(); if (pid == -1) ERR_EXIT("fork"); if (pid == 0) { close(listenfd); do_service(conn); exit(EXIT_SUCCESS); } else close(conn); } return 0; }
客户端:
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) struct packet { int len; char buf[1024]; }; ssize_t readn(int fd, void *buf, size_t count) { size_t nleft = count; ssize_t 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 count - nleft; bufp += nread; nleft -= nread; } return count; } ssize_t writen(int fd, const void *buf, size_t count) { size_t nleft = count; ssize_t nwritten; char *bufp = (char*)buf; while (nleft > 0) { if ((nwritten = write(fd, bufp, nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nwritten == 0) continue; bufp += nwritten; nleft -= nwritten; } return count; } int main(void) { int sock; if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) ERR_EXIT("socket"); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5188); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) ERR_EXIT("connect"); struct packet sendbuf; struct packet recvbuf; memset(&sendbuf, 0, sizeof(sendbuf)); memset(&recvbuf, 0, sizeof(recvbuf)); int n; while (fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin) != NULL) { n = strlen(sendbuf.buf); sendbuf.len = htonl(n); writen(sock, &sendbuf, 4+n); int ret = readn(sock, &recvbuf.len, 4); if (ret == -1) ERR_EXIT("read"); else if (ret < 4) { printf("client close\n"); break; } n = ntohl(recvbuf.len); ret = readn(sock, recvbuf.buf, n); if (ret == -1) ERR_EXIT("read"); else if (ret < n) { printf("client close\n"); break; } fputs(recvbuf.buf, stdout); memset(&sendbuf, 0, sizeof(sendbuf)); memset(&recvbuf, 0, sizeof(recvbuf)); } close(sock); return 0; }
这种解决粘包的方法特别实在网络中传输的时候十分必要。