TCP与UDP传输数据的区别
TCP是基于字节流的传输服务,无边界。而UDP是基于消息的传输服务,传送数据报有边界。
TCP产生粘包问题的原因

粘包问题主要是因为传输数据没有被接收方完全接收。可能是因为MSS的大小与MTU、或者套接口发送缓冲区的长度。
粘包问题的解决方案
缺点
增加网络的负担,因为无论你想要发送的是多大,使用的空间都是指定的定长字节。可以通过自己定义包的结构来优化
readn,writen
readn方法
struct packet{int len;char buf[1024];}ssize_t readn(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。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;//这里传输的是整个函数已经接收了多少个。} else {bufp += nread;//更新nleft -= nread;}}return count;}
writen方法
可以读取指定长度的数据。然后使用特殊的报文结构,添加前四个字节的包头来表示包体的长度,用于解决粘包问题。
struct packet{int len;char buf[1024];}ssize_t writen(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。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 (nread == 0){continue;//如果等于零,对于write操作等于无事发生} else {bufp += nwritten;//更新nleft -= nwritten;}}return count;}
回射客户/服务器
服务器
#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <stdlib.h>#include <stdio.h>#include <errno.h>#include <netinet/in.h>#include <arpa/inet.h>#include <string.h>struct packet{int len;char buf[1024];}ssize_t readn(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。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;//这里传输的是整个函数已经接收了多少个。} else {bufp += nread;//更新nleft -= nread;}}return count;}ssize_t writen(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。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 (nread == 0){continue;//如果等于零,对于write操作等于无事发生} else {bufp += nwritten;//更新nleft -= nwritten;}}return count;}void do_service(int conn){struct packet recvbuf;while(1){memset(&recvbuf, 0, sizeof(recvbuf));int ret = readn(conn, &recvbuf.len, 4);//这里使用readn,先接收四个字节的包头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");close(conn);break;}fputs(recvbuf.buf, stdout);writen(conn, &recvbuf, 4+n);//这里使用writen,如果客户端不使用同样的接口会导致无法接收}}}int main(){int listenfd;if(listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) < 0)//其实前两个已经表示为是tcp协议了,第三个参数可以填0{ERR_EXIT("socket");//报错误的宏}struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(servaddr));//全部填充0servaddr.sin_family = AF_INET;servaddr.sin_prot = 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);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){//这里返回值是文件描述符,在没有客户端连接时accept会阻塞ERR_EXIT("accept");}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 <stdlib.h>#include <stdio.h>#include <errno.h>#include <netinet/in.h>#include <arpa/inet.h>#include <string.h>struct packet{int len;char buf[1024];}ssize_t readn(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。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;//这里传输的是整个函数已经接收了多少个。} else {bufp += nread;//更新nleft -= nread;}}return count;}ssize_t writen(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。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 (nread == 0){continue;//如果等于零,对于write操作等于无事发生} else {bufp += nwritten;//更新nleft -= nwritten;}}return count;}int main(){int sock;if(sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) < 0)//其实前两个已经表示为是tcp协议了,第三个参数可以填0{ERR_EXIT("socket");//报错误的宏}struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(servaddr));//全部填充0servaddr.sin_family = AF_INET;servaddr.sin_prot = 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;char recvbuf[1024] = {0};char sendbuf[1024] = {0};while(fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin)!= NULL){//此处使用结构体里面的成员n = strlen(sendbuf.buf);sendbuf.len = htonl(n);//此处多此一举是为了统一字节序转换为网络字节序,此处是包体的长度//writen(sock, sendbuf, strlen(sendbuf));//改成writen,这里改成sizeof定长包就能与服务器通信了writen(sock, sendbuf, 4+n);//包头有四个字节int ret = readn(sock, &recvbuf.len, 4);//这里使用readn,先接收四个字节的包头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;}
