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;
    }
  • 相关阅读:
    121.买卖股票 求最大收益1 Best Time to Buy and Sell Stock
    409.求最长回文串的长度 LongestPalindrome
    202.快乐数 Happy Number
    459.(KMP)求字符串是否由模式重复构成 Repeated Substring Pattern
    326.是否为3的平方根 IsPowerOfThree
    231.是否为2的平方根 IsPowerOfTwo
    461.求两个数字转成二进制后的“汉明距离” Hamming Distance
    206.反转单链表 Reverse Linked List
    448. 数组中缺少的元素 Find All Numbers Disappeared in an Array
    常见表单元素处理
  • 原文地址:https://www.cnblogs.com/zhanggaofeng/p/6134757.html
Copyright © 2011-2022 走看看