zoukankan      html  css  js  c++  java
  • TCP粘包问题的解决方案01——自定义包体

     
    粘包问题:应用层要发送数据,需要调用write函数将数据发送到套接口发送缓冲区。如果应用层数据大小大于SO_SNDBUF,
    那么,可能产生这样一种情况,应用层的数据一部分已经被发送了,还有一部分还在套接口缓冲区待发送。此时,对方延迟接收,就容易产生粘包。
    另一方面,TCP传输有MSS限制,也会对数据进行分割。第三个原因,由于MTU存在,也可能分割数据。都会产生粘包问题



       粘包问题解决方案:本质上是要在应用层维护消息与消息的边界。

    1、定长包

    2、包尾加 (FTP协议)

    3、包头加上包体长度

    4、更加复杂的应用层协议

      利用发送定常包解决粘包问题时,对于定长包的接收,是一个主要问题,在程序中,封装了readn(接收确切数目的读操作)与writen(发送。。。)函数来解决这个问题。

       

    定长包发送程序:
      1 /*
      2 客户端程序中发送定长包解决粘包问题:
      3 */
      4 #include<unistd.h>
      5 #include<sys/types.h>
      6 #include<sys/socket.h>
      7 #include<string.h>
      8 #include<stdlib.h>
      9 #include<stdio.h>
     10 #include<errno.h>
     11 #include<netinet/in.h>
     12 #include<arpa/inet.h>
     13 #include<signal.h>
     14 #define ERR_EXIT(m)
     15     do
     16     {
     17         perror(m);
     18         exit(EXIT_FAILURE);
     19     }while(0)
     20 struct packet
     21 {
     22     int len;//包头
     23     char buf[1024];//包体
     24 };
     25 //接收确切数目的读操作
     26 ssize_t readn(int fd,void *buf,size_t count)
     27 {
     28     size_t nleft=count;
     29     ssize_t nread;
     30     char *bufp=(char*)buf;
     31 //剩余字节数大于0就循环
     32     while(nleft>0)
     33     {
     34         if((nread=read(fd,bufp,nleft))<0)
     35         {
     36             if(errno==EINTR)
     37                 continue; //被信号中断
     38             else
     39                 return -1;//失败
     40         }
     41         //对等方关闭了
     42         else if(nread==0)
     43             return (count-nleft);//已经读取的字节数
     44         bufp+=nread;
     45         nleft-=nread;
     46     }
     47     return count;
     48 }
     49 //发送确切数目的写操作
     50 ssize_t writen(int fd, const void *buf, size_t count)
     51 {
     52     size_t nleft=count;
     53     ssize_t nwritten;
     54     char *bufp=(char*)buf;
     55     while(nleft>0)
     56     {
     57         if((nwritten=write(fd,bufp,nleft))<=0)
     58         {
     59             if(errno==EINTR)
     60                 continue;//信号中断
     61             return -1;
     62         }else if(nwritten==0)//write返回0,此时write()什么也不做,好像什么都没发生
     63             continue;
     64         bufp+=nwritten;
     65         nleft-=nwritten;
     66     }
     67     return count;
     68 
     69 }
     70 int main(void)
     71 {
     72     int sock;//客户端创建套接字
     73     if((sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
     74         ERR_EXIT("socket error");
     75     
     76     struct sockaddr_in servaddr;//本地协议地址赋给一个套接字
     77     memset(&servaddr,0,sizeof(servaddr));
     78     servaddr.sin_family=AF_INET;
     79     servaddr.sin_port=htons(5188);
     80     
     81     servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器段地址
     82     //inet_aton("127.0.0.1",&servaddr.sin_addr);
     83     
     84     if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
     85         ERR_EXIT("connect");
     86     struct packet sendbuf;//从标准输入接收发送包
     87     struct packet recvbuf;//获得服务器端的回射包
     88     memset(&sendbuf,0,sizeof(sendbuf));
     89     memset(&recvbuf,0,sizeof(recvbuf));
     90     int n;//fgets() 函数中的 size 如果小于字符串的长度,那么字符串将会被截取;如果 size 大于字符串的长度则多余的部分系统会自动用 '' 填充。所以假如你定义的字符数组长度为 n,那么 fgets() 中的 size 就指定为 n–1,留一个给 '' 就行了。
     91     while(fgets(sendbuf.buf,sizeof(sendbuf.buf),stdin)!=NULL)//默认有换行符
     92     {
     93         n=strlen(sendbuf.buf);
     94         //包体长度要转成网络字节序。
     95         sendbuf.len=htonl(n);
     96         writen(sock,&sendbuf,4+n);
     97         //客户端接收回射数据包头内容。
     98         int ret=readn(sock,&recvbuf.len,4);
     99         if(ret==-1)
    100             ERR_EXIT("readn");
    101         else if(ret<4)    //可能中途中断了
    102         {
    103             printf("clinet close
    ");
    104             break;
    105         }
    106         n=ntohl(recvbuf.len);
    107         //接收包体内容    
    108         ret=readn(sock,recvbuf.buf,n);
    109         if(ret==-1)
    110             ERR_EXIT("readn");
    111         else if(ret<n)
    112         {
    113             printf("clinet close
    ");
    114             break;
    115         }        
    116         fputs(recvbuf.buf,stdout);
    117         memset(&sendbuf,0,sizeof(sendbuf));
    118         memset(&recvbuf,0,sizeof(recvbuf));
    119     }
    120     close(sock);
    121     
    122     return 0;
    123 }

      定长包的接收服务器程序:

      1 /*
      2 流协议与粘包
      3 粘包产生的原因:若SO_SNDBUF的大小没有应用层一条消息大,可能产生粘包问题,因为因为应用层消息被分割,一部分发送了,一部分还在应用层缓冲区。详见UNP48页
      4 粘包处理方案:本质上是要在应用层维护消息与消息的边界
      5 1、定长包 2、包尾加 
     (ftp) 3、包头加上包体长度(例如包头定长4字节,收取时先读取包头算出包体长度) 4、更复杂的应用层协议
      6 
      7 封装readn  writen程序
      8 ssize_t read(int fd, void *buf, size_t count);
      9 ssize_t write(int fd, const void *buf, size_t count);
     10 */
     11 #include<unistd.h>
     12 #include<sys/types.h>
     13 #include<sys/socket.h>
     14 #include<string.h>
     15 #include<stdlib.h>
     16 #include<stdio.h>
     17 #include<errno.h>
     18 #include<netinet/in.h>
     19 #include<arpa/inet.h>
     20 #define ERR_EXIT(m)
     21     do
     22     {
     23         perror(m);
     24         exit(EXIT_FAILURE);
     25     }while(0)
     26 struct packet
     27 {
     28     int len;//包头
     29     char buf[1024];//包体
     30 };
     31 //接收确切数目的读操作
     32 ssize_t readn(int fd,void *buf,size_t count)
     33 {
     34     size_t nleft=count;
     35     ssize_t nread;
     36     char *bufp=(char*)buf;
     37 //剩余字节数大于0就循环
     38     while(nleft>0)
     39     {
     40         if((nread=read(fd,bufp,nleft))<0)
     41         {
     42             if(errno==EINTR)
     43                 continue;
     44             else
     45                 return -1;
     46         }
     47         //对等方关闭了
     48         else if(nread==0)
     49             return (count-nleft);//已经读取的字节数
     50         bufp+=nread;
     51         nleft-=nread;
     52     }
     53     return count;
     54 }
     55 //发送确切数目的写操作
     56 ssize_t writen(int fd, const void *buf, size_t count)
     57 {
     58     size_t nleft=count;
     59     ssize_t nwritten;
     60     char *bufp=(char*)buf;
     61     while(nleft>0)
     62     {
     63         if((nwritten=write(fd,bufp,nleft))<=0)
     64         {
     65             if(errno==EINTR)
     66                 continue;
     67             return -1;
     68         }else if(nwritten==0)//好像什么都没发生
     69             continue;
     70         bufp+=nwritten;
     71         nleft-=nwritten;
     72     }
     73     return count;
     74 
     75 }
     76 //服务器回射。
     77 void do_service(int conn)
     78 {
     79         struct packet recvbuf;
     80         int n;
     81         while(1)
     82         {
     83             memset(&recvbuf,0,sizeof(recvbuf));
     84             //使用readn之后客户端发送的数据不足n会阻塞
     85             //在客户端程序中确定消息的边界,发送定长包
     86             int ret=readn(conn,&recvbuf.len,4);                                                               
     87             //客户端关闭
     88             if(ret==-1)
     89                 ERR_EXIT("read error");            
     90             else if(ret<4)//中途中断了。
     91             {
     92                 printf("client close
    ");
     93                 break;//不用继续循环等待客户端数据
     94             }
     95             //接收包体
     96             n=ntohl(recvbuf.len);//包体长度
     97             ret=readn(conn,recvbuf.buf,n);
     98             if(ret==-1)
     99                 ERR_EXIT("read error");            
    100             else if(ret<n)//接收到的字节数不足,对端中途关闭
    101             {
    102                 printf("client close
    ");
    103                 break;//不用继续循环等待客户端数据
    104             }
    105             fputs(recvbuf.buf,stdout);
    106             writen(conn,&recvbuf,4+n);
    107         }
    108 }
    109 int main(void)
    110 {
    111     int listenfd;
    112     if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
    113         ERR_EXIT("socket error");
    114     //if((listenfd=socket(PF_INET,SOCK_STREAM,0))<0)
    115     
    116 
    117     //本地协议地址赋给一个套接字
    118     struct sockaddr_in servaddr;
    119     memset(&servaddr,0,sizeof(servaddr));
    120     servaddr.sin_family=AF_INET;
    121     servaddr.sin_port=htons(5188);
    122     servaddr.sin_addr.s_addr=htonl(INADDR_ANY);//表示本机地址
    123     //servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");
    124     //inet_aton("127.0.0.1",&servaddr.sin_addr);
    125     
    126     //开启地址重复使用,关闭服务器再打开不用等待TIME_WAIT
    127     int on=1;
    128     if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
    129         ERR_EXIT("setsockopt error");
    130     //绑定本地套接字
    131     if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
    132         ERR_EXIT("bind error");
    133     if(listen(listenfd,SOMAXCONN)<0)//设置监听套接字(被动套接字)
    134         ERR_EXIT("listen error");
    135     
    136     struct sockaddr_in peeraddr;//对方套接字地址
    137     socklen_t peerlen=sizeof(peeraddr);
    138     int conn;//已连接套接字(主动套接字)
    139     pid_t pid;
    140     while(1){
    141         if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
    142             ERR_EXIT("accept error");
    143         //连接好之后就构成连接,端口是客户端的。peeraddr是对端
    144         printf("ip=%s port=%d
    ",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
    145         pid=fork();
    146         if(pid==-1)
    147             ERR_EXIT("fork");
    148         if(pid==0){    
    149                 close(listenfd);
    150                 do_service(conn);
    151                 //某个客户端关闭,结束该子进程,否则子进程也去接受连接
    152                 exit(EXIT_SUCCESS);
    153         }else     close(conn);
    154     }
    155     return 0;
    156 }
  • 相关阅读:
    指针与数组实例练习(指针运算)
    C语言数组和函数实例练习(二)
    C语言数组和函数实例练习(一)
    近一个月的学习总结(4.8—5.12)
    Java网页小程序——Java Applet
    数据结构(二)线性表——链表
    JDBC的使用(MySQL数据库)
    Python变量类型及变量
    Shell编程语法
    文本处理三剑客
  • 原文地址:https://www.cnblogs.com/wsw-seu/p/8214422.html
Copyright © 2011-2022 走看看