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)

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

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员互动联盟

【答疑解惑】如何安装eclipse

随着android应用开发的火热,eclipse使用的人也越来越多。好多初学者不知道如何安装eclipse,这里做一个简单回答。 首先我们要确认系统的版本,是3...

2634
来自专栏乐沙弥的世界

Nginx内置状态信息(http_stub_status)

Nginx提供了一个内置的状态信息监控页面,可用于监控Nginx的整体访问情况。这个内置功能由模块ngx_http_stub_status_module实现。如...

882
来自专栏图形学与OpenGL

实验3 文件操作

    (3)     根据这个随机数,从所读取的记录中找到对应的记录,并输出显示;

1072
来自专栏进击的程序猿

6.824 Lab 3: Fault-tolerant Key/Value Service Part-AIntroduction实际设计中出现的问题

该实验是mit 6.824课程的第3个实验,基于raft协议完成一个key-value系统

1503
来自专栏沃趣科技

Oracle中的sysctl.conf内核参数

当我们对Oracle进行安装部署时,需要按照相关要求修改OS内核参数,下面对Oracle按照部署时需要修改的相关内核参数进行简单介绍。

1293
来自专栏移动端周边技术扩展

python pytesseract

1605
来自专栏小鹏的专栏

tf API 研读6:Running Graphs

会话管理 (Session management) 操作 描述 class tf.Session 运行TF操作的类, 一个Session对象将操作节...

2136
来自专栏网络

服务器模型——从单线程阻塞到多线程非阻塞(上)

前言的前言 服务器模型涉及到线程模式和IO模式,搞清楚这些就能针对各种场景有的放矢。该系列分成三部分: 单线程/多线程阻塞I/O模型 单线程非阻塞I/O模型 多...

2305
来自专栏debugeeker的专栏

《coredump问题原理探究》Linux x86版第二章coredump捕获的环境配置

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/detai...

652
来自专栏小狼的世界

[每天五分钟,备战架构师-3]操作系统基本原理之存储管理

存储器是计算机系统中最重要的资源之一,任何程序和数据及各种控制用的数据结构都必须占有一定的存储空间,因此,存储管理直接影响系统性能。

922

扫码关注云+社区