前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Socket基本-TCP粘包问题

Socket基本-TCP粘包问题

作者头像
程序员小王
发布2018-04-13 09:59:24
2.3K0
发布2018-04-13 09:59:24
举报
文章被收录于专栏:架构说架构说

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 异步多路复用的机制来
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-12-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Offer多多 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档