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;
}
原文地址:https://www.cnblogs.com/zhanggaofeng/p/6134757.html