Golang 解决TCP“粘包“问题

Golang 解决TCP“粘包“问题,第1张

TCP 协议是面向连接,可靠的流式协议,当 Server 去 Read 的时候,每次读到的数据都不一定是完整的,该方法会返回读到的字节数,因此,当我们写 Server 的时候,什么时候去回调用户设置的 callback ?也就是怎么样保证每次都能拿到一个完整的包数据,这个就是”粘包“问题的由来。

传统的,有两种方法解决。一是分隔符协议,即每条消息结尾设置固定分隔符,Server 读到分隔符就认为读到了完整的包数据;二是长度协议,即在每个陪敬消息头部设置固定长度的字段,表征消息长度,再往后读取该长度的消息即可。

目前长度协议用的较多,因为分隔符协议需要 Server 不停的检测,很耗费性能。长度协议实现中比较重要的点是头部的长度以及字节序, 2 个字节可表示 2^16-1 个字节的内容,如果不够,那就上4字节,字节序相关手乱御的只是可以参考: ”字节序“是什么鬼?

先来个没处理粘包的:

common/server.go:

测试一下:

因为每次只读5个字节,可以明显看到消息”乱了“,但就算把这个值增大,你只要是设置了,就会存在这个问题。

delimeter/server.go:

还是拿上个测试脚本跑,结果:

这次看到每个消息长度不一样,但是都是”完整的“。

length/server.go:

client 也得相应调整:

测试结果:

效果跟分隔符协议一样,都可以解决”粘包“问题。

只要是 TCP 协议,任何语言都需要处理 ”粘包“ 问题,我们用 PHP 写一个客户端测试一下:

完美!

只有在直接使用 TCP 协议才存在 "粘包" 问题,其上层应用层协议比如 HTTP ,已经帮我们处理好了,无毕岩需关注这些底层,但是我们自己实现一个自定义协议,就必须考虑这些细节了。

2021-03-08

TLDR 在使用 Golang 编写 TCP/UDP socket 的时候,第一步做的就是地址解析。

该函数返回的地址包含的信息如下:

TCPAddr 里, IP 既可以是 IPv4 地址,也可以是 IPv6 地址。 Port 就是端口了。 Zone 是 IPv6 本地地址所衡早在的区域。

从返回结果看该函数的参数, network 指 address 的网络类型; address 指要解析的地址,会从中解析出我们想要的 IP , Port 和 Zone 。

从源码中可以看出键拦巧稿键,参数 network 只能是如下四个值,否则会得到一个错误。

解析过程跟 ResolveTCPAddr 的一样,不过得到的是 *UDPAddr 。

UDPAddr 包含的信息如下:

首先,看一下TCP握手简单描绘过程:

其握手过程原理,就不必说了,有很多详细文章进行叙述,本文只关注研究重点。

在第三次握手过程中,如果服务器收到ACK,就会与客户端建立连接,此时内核会把连接神贺从半连接队列移除,然后创建新的连接,并将其添加到笑兆全连接队列,等待进程调用。

如果服务器繁忙,来不及调用连接导致全连接队列溢出,服务器就会放弃当前握手连接,发送RST给客户端,即碰瞎租connection reset by peer。

在linux平台上,客户端在进行高并发TCP连接处理时,最高并发数量都要受系统对用户单一进程同时打开文件数量的限制(这是因为系统每个TCP都是SOCKET句柄,每个soker句柄都是一个文件),当打开连接超过限制,就会出现too many open files。

使用下指令查看最大句柄数量:

增加句柄解决方案


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/12404847.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-25
下一篇 2023-05-25

发表评论

登录后才能评论

评论列表(0条)

保存