• TCP是个流协议,它存在粘包问题

    •   产生粘包的原因是:
      •   TCP所传输的报文段有MSS的限制,如果套接字缓冲区的大小大于MSS,也会导致消息的分割发送。
      •   由于链路层最大发送单元MTU,在IP层会进行数据的分片。
      •   应用层调用write方法,将应用层的缓冲区中的数据拷贝到套接字的发送缓冲区。而发送缓冲区有一个SO_SNDBUF的限制,如果应用层的缓冲区数据大小大于套接字发送缓冲区的大小,则数据需要进行多次的发送。
  • 粘包问题的解决
    •   ①:发送定长包
    • 这里需要封装两个函数:
      • ssize_t readn(int fd, void *buf, size_t count)
        ssize_t writen(int fd, void *buf, size_t count)
    •   这两个函数的参数列表和返回值与readwrite一致。它们的作用的读取/写入count个字节后再返回。其实现如下:
      • ssize_t readn(int fd, void *buf, size_t count)
        {
        int left = count ; //剩下的字节
        char * ptr = (char*)buf ;
        while(left>)
        {
        int readBytes = read(fd,ptr,left);
        if(readBytes< )//read函数小于0有两种情况:1中断 2出错
        {
        if(errno == EINTR)//读被中断
        {
        continue;
        }
        return -;
        }
        if(readBytes == )//读到了EOF
        {
        //对方关闭呀
        printf("peer close\n");
        return count - left;
        }
        left -= readBytes;
        ptr += readBytes ;
        }
        return count ;
        } /*
        writen 函数
        写入count字节的数据
        */
        ssize_t writen(int fd, void *buf, size_t count)
        {
        int left = count ;
        char * ptr = (char *)buf;
        while(left >)
        {
        int writeBytes = write(fd,ptr,left);
        if(writeBytes<)
        {
        if(errno == EINTR)
        continue;
        return -;
        }
        else if(writeBytes == )
        continue;
        left -= writeBytes;
        ptr += writeBytes;
        }
        return count;
        }

        有了这两个函数之后,我们就可以使用定长包来发送数据了,我抽取其关键代码来讲诉:

        char readbuf[];
        readn(conn,readbuf,sizeof(readbuf)); //每次读取512个字节 同理的,写入的时候也写入512个字节
        char writebuf[512];
        fgets(writebuf,sizeof(writebuf),stdin);
        writen(conn,writebuf,sizeof(writebuf);
      • 每个消息都以固定的512字节(或其他数字,看你的应用层的缓冲区大小)来发送,以此区分每一个信息,这便是以固定长度解决粘包问题的思路。定长包解决方案的缺点在于会导致增加网络的负担,无论每次发送的有效数据是多大,都得按照定长的数据长度进行发送。
    •   ②:粘包解决方案二:使用结构体,显式说明数据部分的长度
      • 在这个方案中,我们需要定义一个‘struct packet’包结构,结构中指明数据部分的长度,用四个字节来表示。发送端的对等方接收报文时,先读取前四个字节,获取数据的长度,由长度来进行数据的读取。定义一个结构体

        struct packet
        {
        unsigned int msgLen ; //4个字节字段,说明数据部分的大小
        char data[] ; //数据部分
        }
      • 读写过程如下所示,这里抽取关键代码进行说明:
        //发送数据过程
        struct packet writebuf;
        memset(&writebuf,,sizeof(writebuf));
        while(fgets(writebuf.data,sizeof(writebuf.data),stdin)!=NULL)
        {
        int n = strlen(writebuf.data); //计算要发送的数据的字节数
        writebuf.msgLen =htonl(n); //将该字节数保存在msgLen字段,注意字节序的转换
        writen(conn,&writebuf,+n); //发送数据,数据长度为4个字节的msgLen 加上data长度
        memset(&writebuf,,sizeof(writebuf));
        }
      • 下面是读取数据的过程,先读取msgLen字段,该字段指示了有效数据data的长度。依据该字段再读出data。
        memset(&readbuf,,sizeof(readbuf));
        int ret = readn(conn,&readbuf.msgLen,); //先读取四个字节,确定后续数据的长度
        if(ret == -)
        {
        err_exit("readn");
        }
        else if(ret == )
        {
        printf("peer close\n");
        break;
        }
        int dataBytes = ntohl(readbuf.msgLen); //字节序的转换
        int readBytes = readn(conn,readbuf.data,dataBytes); //读取出后续的数据
        if(readBytes == )
        {
        printf("peer close\n");
        break;
        }
        if(readBytes<)
        {
        err_exit("read");
        }
    •   ③:粘包解决方案三:按行读取
      •   ftp协议采用/r/n来识别一个消息的边界,我们在这里实现一个按行读取的功能,该功能能够按/n来识别消息的边界。这里介绍一个函数:

        ssize_t recv(int sockfd, void *buf, size_t len, int flags);
      • 与read函数相比,recv函数的区别在于两点:

        1. recv函数只能够用于套接口IO。
        2. recv函数含有flags参数,可以指定一些选项。

        recv函数的flags参数常用的选项是:

        1. MSG_OOB 接收带外数据,即通过紧急指针发送的数据
        2. MSG_PEEK 从缓冲区中读取数据,但并不从缓冲区中清除所读数据

        为了实现按行读取,我们需要使用recv函数的MSG_PEEK选项。PEEK的意思是"偷看",我们可以理解为窥视,看看socket的缓冲区内是否有某种内容,而清除缓冲区。

        /*
        * 封装了recv函数
        返回值说明:-1 读取出错
        */
        ssize_t read_peek(int sockfd,void *buf ,size_t len)
        {
        while()
        {
        //从缓冲区中读取,但不清除缓冲区
        int ret = recv(sockfd,buf,len,MSG_PEEK);
        if(ret == - && errno == EINTR)//文件读取中断
        continue;
        return ret;
        }
        } 下面是按行读取的代码: /*
        *读取一行内容
        * 返回值说明:
        == 0 :对端关闭
        == -1 : 读取错误
        其他:一行的字节数,包含\n
        *
        **/
        ssize_t readLine(int sockfd ,void * buf ,size_t maxline)
        {
        int ret ;
        int nRead = ;
        int left = maxline ;
        char * pbuf = (char *) buf;
        int count = ;
        while(true)
        {
        //从socket缓冲区中读取指定长度的内容,但并不删除
        ret = read_peek(sockfd,pbuf,left);
        // ret = recv(sockfd , pbuf , left , MSG_PEEK);
        if(ret<= )
        return ret;
        nRead = ret ;
        for(int i = ;i< nRead ; ++i)
        {
        if(pbuf[i]=='\n') //探测到有\n
        {
        ret = readn (sockfd , pbuf, i+);
        if(ret != i+)
        exit(EXIT_FAILURE);
        return ret + returnCount;
        }
        }
        //如果嗅探到没有\n
        //那么先将这一段没有\n的读取出来
        ret = readn(sockfd , pbuf , nRead);
        if(ret != nRead)
        exit(EXIT_FAILURE);
        pbuf += nRead ;
        left -= nRead ;
        count += nRead;
        }
        return -;
        }

Socket编程--TCP粘包问题的更多相关文章

  1. socket编程 TCP 粘包和半包 的问题及解决办法

    一般在socket处理大数据量传输的时候会产生粘包和半包问题,有的时候tcp为了提高效率会缓冲N个包后再一起发出去,这个与缓存和网络有关系. 粘包 为x.5个包 半包 为0.5个包 由于网络原因 一次 ...

  2. python/socket编程之粘包

    python/socket编程之粘包 粘包 只有TCP有粘包现象,UDP永远不会粘包. 首先需要掌握一个socket收发消息的原理 发送端可以是1k,1k的发送数据而接受端的应用程序可以2k,2k的提 ...

  3. Python全栈-网络编程-TCP粘包

    一.什么是TCP粘包 C/S架构下,接收方不知道每个消息的发送间隙.也不知道每次应该提取多少个字节的数据,与此同时,TCP是面向连接的,面向流的,收发两端都要有,因此发送端为了将多个发往接收端的数据包 ...

  4. socket编程解决粘包和丢包问题

    ##socket 丢包粘包解决方式 采用固定头部长度(一般为4个字节),包头保存的是包体的长度 header+body 包头+包体 下面的例子不是按照上图中规定的格式编写的,但是思路都是一样的,先读出 ...

  5. Socket编程 Tcp和粘包

    大多数程序员都要接触网络编程,Web开发天天和http打交道.稍微底层一点的程序员,就是TCP/UDP . 对程序员来说,Tcp/udp的核心是Socket编程. 我的浅薄的观点---------理解 ...

  6. Socket编程(4)TCP粘包问题及解决方案

    ① TCP是个流协议,它存在粘包问题 TCP是一个基于字节流的传输服务,"流"意味着TCP所传输的数据是没有边界的.这不同于UDP提供基于消息的传输服务,其传输的数据是有边界的.T ...

  7. Socket编程实践(5) --TCP粘包问题与解决

    TCP粘包问题 由于TCP协议是基于字节流且无边界的传输协议, 因此很有可能产生粘包问题, 问题描述如下 对于Host A 发送的M1与M2两个各10K的数据块, Host B 接收数据的方式不确定, ...

  8. 查漏补缺:socket编程:TCP粘包问题和常用解决方案(上)

    1.TCP粘包问题的产生(发送端) 由于TCP协议是基于字节流并且无边界的传输协议,因此很容易产生粘包问题.TCP的粘包可能发生在发送端,也可能发生在接收端.发送端的粘包是TCP协议本身引起的,TCP ...

  9. 【游戏开发】网络编程之浅谈TCP粘包、拆包问题及其解决方案

    引子 现如今手游开发中网络编程是必不可少的重要一环,如果使用的是TCP协议的话,那么不可避免的就会遇见TCP粘包和拆包的问题,马三觉得haifeiWu博主的 TCP 粘包问题浅析及其解决方案 这篇博客 ...

随机推荐

  1. PHP Smarty template for website

    /****************************************************************************** * PHP Smarty templat ...

  2. 七、python沉淀之路--集合

    一. 1.字符串转集合 s = 'hello' se = set(s) print(se) {'e', 'o', 'h', 'l'} 2.列表转集合 l1 = ['hello','python','n ...

  3. JavaScript6 新语法 let 有什么优势

    最近看国外的前端代码时,发现ES6的新特性已经相当普及,尤其是 let,应用非常普遍 虽然 let 的用法与 var 相同,但不管是语法语义上,还是性能上,都提升了很多,下面就从这两方面对比一下 语法 ...

  4. [Unity3D]关于U3D贴图格式压缩

    http://blog.sina.com.cn/s/blog_5b6cb9500102vi6i.html 因为有不少人都问过我压缩格式的问题,今天飞哥又重新提醒了一次.整理一下发个贴,以供大家查阅和讨 ...

  5. 在C#中实现截获shell程序的输出

    在Windows环境下的所谓shell程序就是dos命令行程序,比如VC的CL.exe命令行编译器,JDK的javac编译器,启动java程序用的java.exe都是标准的shell程序.截获一个sh ...

  6. Java基础--单例类创建和测试

    单例模式的主要作用是保证在Java程序中,某个类只有一个实例存在.单例模式有很多好处,它能够避免实例对象的重复创建,不仅可以减少每次创建对象的时间开销,还可以节约内存空间:能够避免由于操作多个实例导致 ...

  7. L2-002. 链表去重(map结构体,精彩的代码)

    链表去重 时间限制 300 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 给定一个带整数键值的单链表L,本题要求你编写程序,删除那些键值的绝对值 ...

  8. cassandra安装配置

    准备运行环境1.1 cassandra可以安装在windows和linux下,本例子安装在centos6.7的环境下.1.2 关闭防火墙.或者开放9042(默认的CQL本地服务端口).9160(默认的 ...

  9. 微信小程序基础语法总结

    本文介绍微信小程序语法 配置文件 app.json的配置(全局) { // 用来配置页面的路径 "pages":[ "pages/index/index", / ...

  10. Http服务端

    第一,使用node提供的http模块 var http=require('http'); 第二,创建一个服务器实例 通过http的createServer()方法. var server=http.c ...