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 条评论
登录 后参与评论

相关文章

来自专栏乐享123

Vim 命令行操作小技巧

19940
来自专栏Django中文社区

统计文章阅读量

如何精确地记录一篇文章的阅读量是一个比较复杂的问题,不过对于我们的博客来说,没有必要记录的那么精确。因此我们使用一种简单但有效的方式来记录博客文章的阅读量:文章...

42450
来自专栏cs

计算机网络--子网划分+思科CiscroPacket使用

<h1>1.0概念</h1> <p> <p><b>子网划分</b>是通过借用IP地址的若干位主机位来充当子网地址从而将原网络划分为若干子网而实现的...

44690
来自专栏linux驱动个人学习

Linux内存描述之高端内存--Linux内存管理(五)

过去,CPU的地址总线只有32位, 32的地址总线无论是从逻辑上还是从物理上都只能描述4G的地址空间(232=4Gbit),在物理上理论上最多拥有4G内存(除了...

50710
来自专栏H2Cloud

linux epoll 开发指南-【ffrpc源码解析】

摘要 关于epoll的问题很早就像写文章讲讲自己的看法,但是由于ffrpc一直没有完工,所以也就拖下来了。Epoll主要在服务器编程中使用,本文主要探讨服务器程...

43150
来自专栏进击的程序猿

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

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

19930
来自专栏乐沙弥的世界

Nginx内置状态信息(http_stub_status)

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

10820
来自专栏程序员互动联盟

【答疑解惑】如何安装eclipse

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

27240
来自专栏Danny的专栏

MySQL安装图解

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

33830
来自专栏嵌入式程序猿

树莓派交叉编译环境的建立

因为树莓派本身就相当于一台电脑,所以我们可以在树莓派上编译内核或者应用程序,但是树莓派相较于台式机或者笔记本电脑,资源和速度还是有区别的,所以就需要建立交叉编译...

67390

扫码关注云+社区

领取腾讯云代金券