专栏首页架构说Socket基本-TCP粘包问题

Socket基本-TCP粘包问题

Thrift是Facebook的一个开源项目,主要是一个跨语言的服务开发框架 提供完整的解决方案 优点很多也就不说了, 但是有个缺点必须要求客户端调用采用thrift框架 于是开始使用基本socket 来传输数据 一看到涉及底层,有的同学就表示

现在分析其中一个问题:

问题1 如何读出socket所有数据 (这里阻塞方式 )

方法1 读取数据到固定大小冲区(读取一次)

var buffer []byte = make([]byte, 1024)

outBytes, err := con.Read(buffer)

缺点:对方发送数据过大 造成解析失败

日志:

2017/12/21 15:12:33 read pack failed runtime error: slice bounds out of range

没有接受到完整的包

什么是半包 接受方没有接受到完整的包,只接受了一部分。 由于发送方看到内容太大切分数据包进行发送,

涉及问题:如何确定接收数据的大小

方法2:读取数据到动态缓冲区(读取多次)

缺点:涉及问题: TCP粘包

概念:TCP TCP 协议本身把这些数据块仅仅看成一连串无结构的字节流 就是没有界限的一串数据.就像河里的流水,绵延不断,没有分界 针对结构化数据如何确定边界呢

思考下面几个场景

1: 类似 http的请求就不用考虑粘包的问题,因为服务端收到报文后, 就将缓冲区数据接收, 然后关闭连接,这样粘包问题不用考虑到,因为大家都知道是发送一段字符。

2:如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包

3: UDP

UDP是有边界的,应用层要整包地收,一次只能收一包,每次接收的要么是一个独立的完整的数据包,要么什么也接收不到。

TCP是无边界的,是字节流,需要应用层自己判断包边界,一次不一定能收几包,也不一定是完整的包

粘包情况

每个包数据长度大小不一 ,每次接受和发送 数据大小不一致 可能导致 包含数据,也可能多,也可能少于实际数据

第一种情况,接收端正常收到两个数据包 都是完整的包,即没有发生拆包和粘包的现象,此种情况不在本文的讨论范围内。

第二种情况(多),接收端只收到一个数据包,由于TCP是不会出现丢包的,所以这一个数据包中包含了发送端发送的两个数据包的信息,这种现象即为粘包。

这种情况由于接收端不知道这两个数据包的界限,所以对于接收端来说很难处理。

第三种情况(少),这种情况有两种表现形式,如下图。接收端收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来一块,

这种情况即发生了拆包和粘包。这两种情况如果不加特殊处理,对于接收端同样是不好处理的。

方法3 粘包处理原理:读取数据到缓冲区,然后根据协议来解析

包头+数据的格式 确定边界

解包步骤: 1 建立一个缓冲去 2 读取固定数据到缓冲区 3 如果符合一个完整的pack,移走 4 如果不符合 ,继续读取 说明:数据不能错位

方法4 粘包处理原理:通过请求头中数据包大小,将客户端N次发送的数据缓冲到一个数据包中

从数据流中读取数据的时候,只要根据包头和数据长度就能取到需要的数据。这个其实就是平时说的协议(protocol)

里面处理这个包的方式之一如下:

1: 一直阻塞读取第一个第二个字节,获取版本号(如果错误就做错误处理);

2: 然后读取第三、四个字节,获取数据的大小;

3: 然后根据第二步中的数据大小,后面下面的数据;

4: 重复上面的过程;

NSQ就是采取这种方式。

参考例子

1 https://feixiao.github.io/2016/05/08/bufio/

2 http://blog.csdn.net/scythe666/article/details/51996268

思考:

这只是其中一个问题 阻塞下read,

非阻塞,同步呢 异步

正确读写方式是什么

不同语言实现区别:

read函数说明

  • c语言:

对于阻塞socket,read/write返回-1代表网络出错了。

但对于非阻塞socket,read/write返回-1不一定网络真的出错了。

可能是Resource temporarily unavailable。

这时你应该再试,直到Resource available

综上,对于non-blocking的socket,正确的读写操作为:

读:忽略掉errno = EAGAIN的错误,下次继续读

写:忽略掉errno = EAGAIN的错误,下次继续写

对于select和epoll的LT模式,这种读写方式是没有问题的。但对于epoll的ET模式,这种方式还有漏洞。

所以,在epoll的ET模式下,正确的读写方式为:

读:只要可读,就一直读,直到返回0,或者 errno = EAGAIN 写:只要可写,就一直写,直到数据发送完,或者 errno = EAGAIN。

  • java Netty框架 把数据读取到通道内 通道可以知道数据大小和长度来进行获取
  • goroutine底层用的非阻塞+epoll 异步多路复用的机制来

本文分享自微信公众号 - 架构说(JiaGouS),作者:王传义

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2017-12-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Effective C++ 35:考虑虚函数的其他替代设计

    Item 35: Consider alternatives to virtual functions.

    程序员小王
  • CPU核数和线程 (池)数量的关系(概念理解)

    目前手机配置: 支持HUAWEI Mate 8非凡表现的, 是拥有强大性能的华为麒麟950芯片。 此芯片为八核4*Cortex A72 ...

    程序员小王
  • 二叉树的递归遍历

    百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节...

    程序员小王
  • 硬件笔记(7)----USB学习笔记4

    从时间角度来看,USB 通信由一系列帧构成。每一帧都有一个帧开始(SOF),随后是一个或多个数据操作。每一个数据操作都由一系列数据包构成。一个数据包由一个同步信...

    小火柴棒
  • linux下如何查看软件的漏洞修复情况

    工作中经常会遇到客户咨询更新软件包是否就修复了xx漏洞,本文就是针对此场景而出。下面以bash为例进行讲解:

    Ernst
  • 网络测量之EverFlow(SIGCOMM-2015)

    SIGCOMM 2015年中,由微软研究院发表了题为《Packet-Level Telemetry in Large Datacenter Networks》的...

    我是东东东
  • 【每天一道编程系列-2018.2.9】(Ans)

    Implementation of a given order of 4 integers from large to small

    yesr
  • 为什么时间戳对网络流量数据包捕获很重要?

    网络上发生的所有事件都是时间敏感的,这就是为什么在讨论数据包捕获和分析时,给数据包加上时间戳非常重要。 此功能不仅可以防止和分析网络攻击,而且还能...

    虹科网络可视化与安全
  • 如何解决web系统session劫持

    往期精选 session劫持是一种比较复杂的攻击方法。大部分互联网上的电脑多存在被攻击的危险。这是一种劫持tcp协议的方法,所以几乎所有的局域网,都存在被劫持 ...

    企鹅号小编
  • OSI第3层:网络层

    4) 解封。(网络层解封该数据包,然后将数据包中包含的第 4 层 PDU 向上传 送到传输层的相应服务。)

    py3study

扫码关注云+社区

领取腾讯云代金券