image.png

TCP与UDP传输数据的区别

TCP是基于字节流的传输服务,无边界。而UDP是基于消息的传输服务,传送数据报有边界。

TCP产生粘包问题的原因

image.png
粘包问题主要是因为传输数据没有被接收方完全接收。可能是因为MSS的大小与MTU、或者套接口发送缓冲区的长度。

粘包问题的解决方案

image.png

缺点

增加网络的负担,因为无论你想要发送的是多大,使用的空间都是指定的定长字节。可以通过自己定义包的结构来优化

readn,writen

封装两个函数,来确定字节数的读操作,与写操作。

readn方法

  1. struct packet{
  2. int len;
  3. char buf[1024];
  4. }
  5. ssize_t readn(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。
  6. size_t nleft = count;//剩余字节数
  7. ssize_t nread;//已接受的字节数
  8. char *bufp = (char *)buf;
  9. while(nleft > 0){
  10. if(nread = read(fd, bufp, nleft) < 0){
  11. if(errno == EINTR)
  12. //这里有两种情况,可能是信号中断,并不被视为错误
  13. continue;
  14. return -1;//视为错误
  15. } else if (nread == 0){
  16. return count - nleft;//这里传输的是整个函数已经接收了多少个。
  17. } else {
  18. bufp += nread;//更新
  19. nleft -= nread;
  20. }
  21. }
  22. return count;
  23. }

writen方法

可以读取指定长度的数据。然后使用特殊的报文结构,添加前四个字节的包头来表示包体的长度,用于解决粘包问题。

  1. struct packet{
  2. int len;
  3. char buf[1024];
  4. }
  5. ssize_t writen(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。
  6. size_t nleft = count;//剩余字节数
  7. ssize_t nwritten;//已发送的字节数
  8. char *bufp = (char *)buf;
  9. while(nleft > 0){
  10. if(nwritten = write(fd, bufp, nleft) < 0){
  11. if(errno == EINTR)
  12. continue;//这里有两种情况,可能是信号中断,并不被视为错误
  13. return -1;//视为错误
  14. } else if (nread == 0){
  15. continue;//如果等于零,对于write操作等于无事发生
  16. } else {
  17. bufp += nwritten;//更新
  18. nleft -= nwritten;
  19. }
  20. }
  21. return count;
  22. }

回射客户/服务器

服务器

  1. #include <unistd.h>
  2. #include <sys/types.h>
  3. #include <sys/socket.h>
  4. #include <stdlib.h>
  5. #include <stdio.h>
  6. #include <errno.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #include <string.h>
  10. struct packet{
  11. int len;
  12. char buf[1024];
  13. }
  14. ssize_t readn(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。
  15. size_t nleft = count;//剩余字节数
  16. ssize_t nread;//已接受的字节数
  17. char *bufp = (char *)buf;
  18. while(nleft > 0){
  19. if(nread = read(fd, bufp, nleft) < 0){
  20. if(errno == EINTR)
  21. //这里有两种情况,可能是信号中断,并不被视为错误
  22. continue;
  23. return -1;//视为错误
  24. } else if (nread == 0){
  25. return count - nleft;//这里传输的是整个函数已经接收了多少个。
  26. } else {
  27. bufp += nread;//更新
  28. nleft -= nread;
  29. }
  30. }
  31. return count;
  32. }
  33. ssize_t writen(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。
  34. size_t nleft = count;//剩余字节数
  35. ssize_t nwritten;//已发送的字节数
  36. char *bufp = (char *)buf;
  37. while(nleft > 0){
  38. if(nwritten = write(fd, bufp, nleft) < 0){
  39. if(errno == EINTR)
  40. continue;//这里有两种情况,可能是信号中断,并不被视为错误
  41. return -1;//视为错误
  42. } else if (nread == 0){
  43. continue;//如果等于零,对于write操作等于无事发生
  44. } else {
  45. bufp += nwritten;//更新
  46. nleft -= nwritten;
  47. }
  48. }
  49. return count;
  50. }
  51. void do_service(int conn){
  52. struct packet recvbuf;
  53. while(1){
  54. memset(&recvbuf, 0, sizeof(recvbuf));
  55. int ret = readn(conn, &recvbuf.len, 4);//这里使用readn,先接收四个字节的包头
  56. if{ret == -1 }{
  57. ERR_EXIT("read");
  58. } else if(ret < 4){
  59. printf("client_close\n");
  60. break;
  61. }
  62. n = ntohl(recvbuf.len);//此处的是数据包的长度,后面接收包体。
  63. ret = readn(conn, recvbuf.buf, n);
  64. if{ret == -1 }{
  65. ERR_EXIT("read");
  66. } else if(ret < n){
  67. printf("client_close\n");
  68. close(conn);
  69. break;
  70. }
  71. fputs(recvbuf.buf, stdout);
  72. writen(conn, &recvbuf, 4+n);//这里使用writen,如果客户端不使用同样的接口会导致无法接收
  73. }
  74. }
  75. }
  76. int main(){
  77. int listenfd;
  78. if(listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) < 0)//其实前两个已经表示为是tcp协议了,第三个参数可以填0
  79. {
  80. ERR_EXIT("socket");//报错误的宏
  81. }
  82. struct sockaddr_in servaddr;
  83. memset(&servaddr, 0, sizeof(servaddr));//全部填充0
  84. servaddr.sin_family = AF_INET;
  85. servaddr.sin_prot = htons(5188);
  86. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//参数表示本机的任意地址,以下两种也是正确的写法
  87. //servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  88. //inet_aton("127.0.0.1", &servaddr.sin_addr);
  89. if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){//一旦绑定完成,这个套接字就会被认为是被动套接字,否则是主动套接字。
  90. ERR_EXIT("bind");
  91. }
  92. if(listen(listenfd, SOMAXCONN) < 0)//后面的宏,代表队列的最大值
  93. {
  94. ERR_EXIT("listen");
  95. }
  96. struct sockaddr_in peeraddr;
  97. socklen_t peerlen = sizeof(peeraddr);
  98. int conn;
  99. //添加一个子进程完成多客户连接
  100. pid_t pid;
  101. while(1){
  102. if((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0){//这里返回值是文件描述符,在没有客户端连接时accept会阻塞
  103. ERR_EXIT("accept");
  104. }
  105. pid = fork();//创建一个子进程
  106. if(pid == -1){
  107. ERR_EXIT("fork");
  108. }
  109. if(pid == 0){
  110. close(listenfd);
  111. do_service(conn);
  112. exit(EXIT_SUCCESS);//记得推出子进程。
  113. } else {
  114. close(conn);
  115. }
  116. }
  117. return 0;
  118. }

客户端

  1. #include <unistd.h>
  2. #include <sys/types.h>
  3. #include <sys/socket.h>
  4. #include <stdlib.h>
  5. #include <stdio.h>
  6. #include <errno.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #include <string.h>
  10. struct packet{
  11. int len;
  12. char buf[1024];
  13. }
  14. ssize_t readn(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。
  15. size_t nleft = count;//剩余字节数
  16. ssize_t nread;//已接受的字节数
  17. char *bufp = (char *)buf;
  18. while(nleft > 0){
  19. if(nread = read(fd, bufp, nleft) < 0){
  20. if(errno == EINTR)
  21. //这里有两种情况,可能是信号中断,并不被视为错误
  22. continue;
  23. return -1;//视为错误
  24. } else if (nread == 0){
  25. return count - nleft;//这里传输的是整个函数已经接收了多少个。
  26. } else {
  27. bufp += nread;//更新
  28. nleft -= nread;
  29. }
  30. }
  31. return count;
  32. }
  33. ssize_t writen(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。
  34. size_t nleft = count;//剩余字节数
  35. ssize_t nwritten;//已发送的字节数
  36. char *bufp = (char *)buf;
  37. while(nleft > 0){
  38. if(nwritten = write(fd, bufp, nleft) < 0){
  39. if(errno == EINTR)
  40. continue;//这里有两种情况,可能是信号中断,并不被视为错误
  41. return -1;//视为错误
  42. } else if (nread == 0){
  43. continue;//如果等于零,对于write操作等于无事发生
  44. } else {
  45. bufp += nwritten;//更新
  46. nleft -= nwritten;
  47. }
  48. }
  49. return count;
  50. }
  51. int main(){
  52. int sock;
  53. if(sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) < 0)//其实前两个已经表示为是tcp协议了,第三个参数可以填0
  54. {
  55. ERR_EXIT("socket");//报错误的宏
  56. }
  57. struct sockaddr_in servaddr;
  58. memset(&servaddr, 0, sizeof(servaddr));//全部填充0
  59. servaddr.sin_family = AF_INET;
  60. servaddr.sin_prot = htons(5188);
  61. servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  62. if(connect(sock, (struct sockaddr*)servaddr, sizeof(servaddr)) < 0){
  63. ERR_EXIT("connect");
  64. }
  65. //此时使用自定义的包结构
  66. struct packet sendbuf;
  67. struct packet recvbuf;
  68. memset(&sendbuf, 0, sizeof(sendbuf));
  69. memset(&recvbuf, 0, sizeof(recvbuf));
  70. int n;
  71. char recvbuf[1024] = {0};
  72. char sendbuf[1024] = {0};
  73. while(fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin)!= NULL){//此处使用结构体里面的成员
  74. n = strlen(sendbuf.buf);
  75. sendbuf.len = htonl(n);//此处多此一举是为了统一字节序转换为网络字节序,此处是包体的长度
  76. //writen(sock, sendbuf, strlen(sendbuf));//改成writen,这里改成sizeof定长包就能与服务器通信了
  77. writen(sock, sendbuf, 4+n);//包头有四个字节
  78. int ret = readn(sock, &recvbuf.len, 4);//这里使用readn,先接收四个字节的包头
  79. if{ret == -1 }{
  80. ERR_EXIT("read");
  81. } else if(ret < 4){
  82. printf("client_close\n");
  83. break;
  84. }
  85. n = ntohl(recvbuf.len);//此处的是数据包的长度,后面接收包体。
  86. ret = readn(sock, recvbuf.buf, n);
  87. if{ret == -1 }{
  88. ERR_EXIT("read");
  89. } else if(ret < n){
  90. printf("client_close\n");
  91. break;
  92. }
  93. fputs(recvbuf.buf, stdout);
  94. memset(&sendbuf, 0, sizeof(sendbuf));
  95. memset(&recvbuf, 0, sizeof(recvbuf));
  96. }
  97. close(sock);
  98. return 0;
  99. }