基于TCP传输的粘包问题

1我们都知道TCP传输,是基于字节流传输的,所以流与流直接传输就会产生边界问题,我个人对粘包的理解就是,TCP传输无法获悉不同包与包之间的“界限”。

如果对等接受方彼此直接没有约定好传输数据大小的话,就会出现解析数据不准确问题,而且传输数据小于约定大小空间的话,也会出现浪费空间问题,为了解决这种问题,通常才有包头+包体传输,这样对等方就可以分辨出不同的包,所对应的数据。(该办法解决的是发送不定长包)

2.UDP是基于数据包协议,所以也就不存在粘包问题,虽然UDP是不可靠的传输协议,可能存在丢包,失序,错序,重复等问题,但是他的效率是比UDP高的多,我们知道TCP可靠性方面有一个超时重连机制,要让UDP实现可靠传输的话,就需要模拟一套TCP传输机制。

粘包问题解决方法代码如下:回射服务器代码,数据包格式:包头+包体

服务端代码:

/*************************************************************************
	> File Name: ser.cpp
	> Created Time: Sun 29 Oct 2017 09:00:58 PM CST
 ************************************************************************/
#include 
using namespace std;

#include           /* See NOTES */
#include 
#include 
#include 
#include 

#include 
#include 
#include 

//处理粘包问题
//封装read函数

struct packet
{
    int  len;        //包头
    char buf[1024];  //包体,实际长度
};

ssize_t readn(int fd, void* buf, size_t count)
{
    size_t nleft = count;//剩余的字节数
    ssize_t nread;       //已经接受的字节数
    char* bufp = (char*)buf;

    while(nleft > 0)
    {
        if((nread = read(fd, bufp, nleft))<0)
        {
            cout << " < 0" << endl;
            if(errno == EINTR) //信号打断
                continue;
            return -1;
        }
        else if(nread == 0)
        {
            return count-nleft;
        }
        bufp += nread;//偏移到屁股
        nleft -= nread;
    }
    return count;
}

//封装write方法
ssize_t writen(int fd, const void* buf, size_t count)
{
    size_t nleft = count;
    ssize_t nwritten;
    char* bufp = (char*)buf;

    while(nleft > 0)
    {
        if((nwritten = write(fd, bufp, nleft)) < 0)
        {
            if(errno == EINTR) //信号中断
                continue;
            return -1;
        }
        else if(nwritten == 0)
        {
            continue;
           //return count-nleft;
        }
        bufp += nwritten;
        nleft -= nwritten;
    }
    return count;
}
/////////////////////////////////////
void do_server(int conn)
{
    struct packet recvbuf;
    while(1)
    {
        memset(recvbuf.buf, 0, sizeof(recvbuf.buf));
        int ret = readn(conn, &recvbuf.len, 4);
        if(ret == -1)
        {
            perror("read");
            exit(1);
        }
        else if(ret < 4)
        {
            cout << "clien close\n";
            break;
        }
        int m = recvbuf.len;
      cout << "n:" << m << endl; 
        int n = ntohl(recvbuf.len);
      cout << "n:" << n << endl; 
        ret = readn(conn, &recvbuf.buf, n);
        if(ret == -1)
        {
            perror("read");
            exit(1);
        }
        if(ret < n)
        {
            cout << "clien close\n";
            break;
        }
        fputs(recvbuf.buf, stdout);
        writen(conn, &recvbuf, 4+n);

    }
    close(conn);
}


int main()
{
    int listenfd;
    //初始化监听套接字(默认是主动套接字,发起连接)
    if((listenfd = socket(AF_INET, SOCK_STREAM, 0))<0)
    {
        perror("socket created failed");
        exit(1);
    }
    //bind函数,bind一个本地地址到套接字
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family =  AF_INET;
    servaddr.sin_port = htons(8881);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    /*servaddr.sin_addr.s_addr = inet_addr("127.0.0.1")*/

////////////////////////////getsockopt///////////    
    int on = 1;//开启地址重复利用
    if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
    {
        perror("setsockopt");
    }
//////////////////////////////////////////////////
    if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
    {
        perror("bind a local addr to listenfd failed...");
        exit(1);
    }

    //listen 讲套接字用于监听进入的连接,讲套接字从close状态转换成监听状态
    //也就是说调用listen函数就将套接字由主动变为被动套接字(接受连接)
    if(listen(listenfd, SOMAXCONN) < 0)//SOMAXCONN 默认最大队列
    {
        perror("listenfd to listen client connect failed..");
        exit(1);
    }
    
    //accept 从已经连接的队列中返回是第一个连接,队列为空就阻塞
    //成功返回一个新的套接字,也就是连接套接字,并且这个新套接字是主动套接字
    struct sockaddr_in peeraddr;
    socklen_t peerlen = sizeof(peeraddr);//必须初始值
    int conn;

///多进程处理多客户端连接
    pid_t pid;
    while(1)
    {
        if((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
        {
            perror("accept client connect failed...");
            exit(1);
        }
        cout << "Ip:" << inet_ntoa(peeraddr.sin_addr) << " port:" << ntohs(peeraddr.sin_port) << endl;
        
        pid = fork();
        if(pid < 0)
        {
            perror("fork");
            exit(1);
        }
        if(pid == 0) // 子进程处理客户端连接
        {
            close(listenfd);
            do_server(conn);
        }
    }
    return 0;
}
回射服务器, 客户端代码如下

/*************************************************************************
	> File Name: ser.cpp
	> Created Time: Sun 29 Oct 2017 09:00:58 PM CST
 ************************************************************************/
#include 
using namespace std;

#include           /* See NOTES */
#include 
#include 
#include 

#include 
#include 
#include 
#include 

struct packet
{
    int  len;        //包头
    char buf[1024];  //包体,实际长度
};

ssize_t readn(int fd, void* buf, size_t count)
{
    size_t nleft = count;
    ssize_t nread;
    char* bufp = (char*)buf;

    while(nleft > 0)
    {
        if((nread = read(fd, bufp, nleft))<0)
        {
            if(errno == EINTR)
                continue;
            return -1;
        }
        else if(nread == 0)
        {
            return count-nleft;
        }
        bufp += nread;
        nleft -= nread;
    }
    return count;
}
ssize_t writen(int fd, const void* buf, size_t count)
{
    size_t nleft = count;
    ssize_t nwritten;
    char* bufp = (char*)buf;

    while(nleft > 0)
    {
        if((nwritten = write(fd, bufp, nleft)) < 0)
        {
            if(errno == EINTR)
                continue;
            return -1;
        }
        else if(nwritten == 0)
        {
            continue;
            //return count-nleft;
        }
        bufp += nwritten;
        nleft -= nwritten;
    }
    return count;
}
/////////////////////////////////////

int main()
{
    int sock;
    //初始化监听套接字(默认是主动套接字,发起连接)
    if((sock = socket(AF_INET, SOCK_STREAM, 0))<0)
    {
        perror("socket created failed");
        exit(1);
    }
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family =  AF_INET;
    servaddr.sin_port = htons(8881);
    //servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //connect ,发起连接
    if(connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) 
    {
        perror("connect ser is failed\n");
        exit(1);
    }

    struct packet sendbuf;
    struct packet recvbuf;
    memset(&sendbuf, 0, sizeof(sendbuf));
    memset(&recvbuf, 0, sizeof(recvbuf));
    int n;
    while(fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin) != NULL)
    {
        n = strlen(sendbuf.buf);

        sendbuf.len = htonl(n); //网络字节序
        
        writen(sock, &sendbuf, 4+n);//sizeof()发送定长包
        
        int ret = readn(sock, &recvbuf.len, 4);
        if(ret == -1)
        {
            perror("read");
            exit(1);
        }
        else if(ret < 4)
        {
            cout << "clien close\n";
            break;
        }
       
        n = ntohl(recvbuf.len);
        ret = readn(sock, &recvbuf.buf, n);
        if(ret == -1)
        {
            perror("read");
            exit(1);
        }
        if(ret < n)
        {
            cout << "clien close\n";
            break;
        }
        
        fputs(recvbuf.buf,stdout);
        memset(recvbuf.buf, 0, sizeof(recvbuf.buf));
        memset(sendbuf.buf, 0 ,sizeof(sendbuf.buf));
    }
    
    close(sock);
    
    return 0;
}






你可能感兴趣的:(Linux网络编程)