zoukankan      html  css  js  c++  java
  • Linux 网络编程详解四(流协议与粘包)

    TCP/IP协议是一种流协议,流协议是字节流,只有开始和结束,包与包之间没有边界,所以容易产生粘包,但是不会丢包。
    UDP/IP协议是数据报,有边界,不存在粘包,但是可能丢包
    产生粘包问题的原因
    1.SQ_SNDBUF套接字本身有缓冲区(发送缓冲区,接收缓冲区)
    2.tcp传送的网络数据最大值MSS大小限制
    3.链路层也有MTU(最大传输单元)大小限制,如果数据包大于>MTU要在IP层进行分片,导致消息分割。(可以简单的认为MTU是MSS加包头数据)
    4.tcp的流量控制和拥塞控制,也可能导致粘包
    5.tacp延迟发送机制等等
    结论:TCP/IP协议,在传输层没有处理粘包问题,必须由程序员处理

    粘包的解决方案--本质上是要在应用层维护消息与消息的边界
    1.定包长
    2.包尾加
    (比如ftp协议)
    3.包头加上包体长度
    4.更复杂的应用层协议
    粘包的几种状态

     

    //粘包解决方案--包头加上包体长度
    //服务器
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <errno.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <signal.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    typedef struct _packet
    {
        int len; //定义包体长度
        char buf[1024]; //定义包体
    } Packet;
    
    /*
     * fd:文件描述符
     * buf:数据缓存区
     * count:读取字符数
     * */
    ssize_t readn(int fd, const void *buf, ssize_t count)
    {
        //定义临时指针变量
        char *pbuf = (char *)buf;
        //定义每次已读数据
        ssize_t nread = 0;
        //定义剩余数据
        ssize_t lread = count;
        while (lread > 0)
        {
            nread = read(fd, pbuf, lread);
            /*
             * 情况分析:假设b缓冲区buf足够大
             * 如果nread==count,说明数据正好被读完
             * nread<count,说明数据没有被读完,这种情况就是由于粘包产生的
             * socket只接收了数据的一部分,TCP/IP协议不可能出现丢包情况
             * nread==0,说明对方关闭文件描述符
             * nread==-1,说明read函数报错
             * nread>count,这种情况不可能存在
             * */
            if (nread == -1)
            {
                //read()属于可中断睡眠函数,需要做信号处理
                if (errno == EINTR)
                    continue;
                perror("read() err");
                return -1;
            } else if (nread == 0)
            {
                printf("client is closed !
    ");
                //返回已经读取的字节数
                return count - lread;
            }
            //重新获取 剩余的 需要读取的 字节数
            lread = lread - nread;
            //指针后移
            pbuf = pbuf + nread;
        }
        return count;
    }
    
    /* fd:文件描述符
     * buf:数据缓存区
     * count:读取字符数
     * */
    ssize_t writen(int fd, const void *buf, ssize_t count)
    {
        //定义临时指针变量
        char *pbuf = (char *)buf;
        //每次写入字节数
        ssize_t nwrite = 0;
        //剩余未写字节数
        ssize_t lwrite = count;
        while (lwrite > 0)
        {
            nwrite = write(fd, pbuf, lwrite);
            if (nwrite == -1)
            {
                if (errno == EINTR)
                    continue;
                perror("write() err");
                return -1;
            } else if (nwrite == 0)
            {
                printf("client is closed !
    ");
                //对方关闭文件描述符,返回已经写完的字节数
                return count - lwrite;
            }
            lwrite -= nwrite;
            pbuf += nwrite;
        }
        return count;
    }
    
    int main(int arg, char *args[])
    {
        //create socket
        int listenfd = socket(AF_INET, SOCK_STREAM, 0);
        if (listenfd == -1)
        {
            perror("socket() err");
            return -1;
        }
        //reuseaddr
        int optval = 1;
        if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))
                == -1)
        {
            perror("setsockopt() err");
            return -1;
        }
        //bind
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(8080);
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        if (bind(listenfd, (struct sockaddr *) &addr, sizeof(addr)) == -1)
        {
            perror("bind() err");
            return -1;
        }
        //listen
        if(listen(listenfd,SOMAXCONN)==-1)
        {
            perror("listen() err");
            return -1;
        }
        //accept
        struct sockaddr_in peeraddr;
        socklen_t peerlen = sizeof(peeraddr);
        int conn = accept(listenfd, (struct sockaddr *)&peeraddr,&peerlen);
        if (conn == -1)
        {
            perror("accept() err");
            return -1;
        }
        Packet _packet;
        while (1)
        {
            memset(&_packet, 0, sizeof(_packet));
            //获取报文自定义包头
            int rc = readn(conn, &_packet.len, 4);
            if (rc == -1)
            {
                exit(0);
            } else if (rc < 4)
            {
                exit(0);
            }
            //把网络字节序转化成本地字节序
            int n = ntohl(_packet.len);
            //获取报文自定义包体
            rc = readn(conn, _packet.buf, n);
            if (rc == -1)
            {
                exit(0);
            } else if (rc < n)
            {
                exit(0);
            }
            //打印报文数据
            fputs(_packet.buf, stdout);
            //将原来的报文数据发送回去
            printf("发送报文的长度%d
    ", 4 + n);
            rc = writen(conn, &_packet, 4 + n);
            if (rc == -1)
            {
                exit(0);
            } else if (rc < 4 + n)
            {
                exit(0);
            }
        }
        return 0;
    }
    //粘包解决方案--包头加上包体长度
    //客户端
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <errno.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <signal.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    typedef struct _packet
    {
        int len; //定义包体长度
        char buf[1024]; //定义包体
    } Packet;
    
    /*
     * fd:文件描述符
     * buf:数据缓存区
     * count:读取字符数
     * */
    ssize_t readn(int fd, const void *buf, ssize_t count)
    {
        //定义临时指针变量
        char *pbuf = (char *)buf;
        //定义每次已读数据
        ssize_t nread = 0;
        //定义剩余数据
        ssize_t lread = count;
        while (lread > 0)
        {
            nread = read(fd, pbuf, lread);
            /*
             * 情况分析:假设b缓冲区buf足够大
             * 如果nread==count,说明数据正好被读完
             * nread<count,说明数据没有被读完,这种情况就是由于粘包产生的
             * socket只接收了数据的一部分,TCP/IP协议不可能出现丢包情况
             * nread==0,说明对方关闭文件描述符
             * nread==-1,说明read函数报错
             * nread>count,这种情况不可能存在
             * */
            if (nread == -1)
            {
                //read()属于可中断睡眠函数,需要做信号处理
                if (errno == EINTR)
                    continue;
                perror("read() err");
                return -1;
            } else if (nread == 0)
            {
                printf("client is closed !
    ");
                //返回已经读取的字节数
                return count - lread;
            }
            //重新获取 剩余的 需要读取的 字节数
            lread = lread - nread;
            //指针后移
            pbuf = pbuf + nread;
        }
        return count;
    }
    
    /* fd:文件描述符
     * buf:数据缓存区
     * count:读取字符数
     * */
    ssize_t writen(int fd, const void *buf, ssize_t count)
    {
        //定义临时指针变量
        char *pbuf = (char *)buf;
        //每次写入字节数
        ssize_t nwrite = 0;
        //剩余未写字节数
        ssize_t lwrite = count;
        while (lwrite > 0)
        {
            nwrite = write(fd, pbuf, lwrite);
            if (nwrite == -1)
            {
                if (errno == EINTR)
                    continue;
                perror("write() err");
                return -1;
            } else if (nwrite == 0)
            {
                printf("client is closed !
    ");
                //对方关闭文件描述符,返回已经写完的字节数
                return count - lwrite;
            }
            lwrite -= nwrite;
            pbuf += nwrite;
        }
        return count;
    }
    
    int main(int arg, char *args[])
    {
        //create socket
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd == -1)
        {
            perror("socket() err");
            return -1;
        }
        //connect
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(8080);
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        if (connect(sockfd, (struct sockaddr *) &addr, sizeof(addr)) == -1)
        {
            perror("connect() err");
            return -1;
        }
        int rc = 0, numx = 0;
        Packet _packet;
        memset(&_packet, 0, sizeof(_packet));
        while (fgets(_packet.buf, sizeof(_packet.buf), stdin) != NULL)
        {
            //发送数据
            numx = strlen(_packet.buf);
            //将本地字节转化成网络字节序
            _packet.len = htonl(numx);
            rc = writen(sockfd, &_packet, 4 + numx);
            if (rc == -1)
            {
                return -1;
            } else if (rc < 4 + numx)
            {
                return -1;
            }
            //接收数据
            memset(&_packet, 0, sizeof(_packet));
            //获取包头
            rc = readn(sockfd, &_packet.len, 4);
            if (rc == -1)
            {
                return -1;
            } else if (rc < 4)
            {
                return -1;
            }
            //将网络字节转化成本地字节
            numx = ntohl(_packet.len);
            //printf("接收数据的大小是%d
    ",numx);
            //获取包体
            rc = readn(sockfd, &_packet.buf, numx);
            if (rc == -1)
            {
                return -1;
            } else if (rc < numx)
            {
                return -1;
            }
            //打印包体
            fputs(_packet.buf,stdout);
            memset(&_packet, 0, sizeof(_packet));
        }
        return 0;
    }
  • 相关阅读:
    VUE报错: Duplicate keys detected: '0'. This may cause an update error.
    VUE中 resource如何引入和GET POST JSONP 如何使用
    VUE中 axios GET和POST 如何使用
    微信小程序封装组件,子父组件通信 详细解析
    CSS3文字超出块元素显示省略号
    微信小程序处理后端返回图片二进制流,渲染页面
    记录平台向用户发送消息功能界面(HTML+CSS)
    原生JavaScript写出日历功能 无引用Jq
    mongoTemplate Aggregation first
    封装返回前端的通用格式
  • 原文地址:https://www.cnblogs.com/zhanggaofeng/p/6134757.html
Copyright © 2011-2022 走看看