nodejs tcp socket丢包问题
发布于 3 年前 作者 cool-firer 2224 次浏览 来自 问答

是这样的: 一个游戏tcp服务,没有什么特殊配置: const server = require(‘net’).createServer(app.callback());

服务端处理流程是这样的:服务端发完最后一个数据包后,服务端调用destroy方法关闭socket。 线上发现,会有机率出现最后一个数据包丢失的情况(前端没收到最后一个数据包),通过前端日志发现,出现问题时,前端是先收到了close事件,后续就没有了。

猜想:当调用destroy, 不会把socket发送缓冲区flush到对端,运气好的时候,destroy之前,最后一个数据发送完了,就没问题;运气不好就丢弃了。翻开node文档,里面写了:

Use end() instead of destroy if data should flush before close, or wait for the ‘drain’ event before destroying the stream.

于是把destroy改为end,心想一定没问题了。然而,又出现了(但概率要小很多),前端还是有机率收不到最后一个数据包。 又一次猜想: 1、Use end() instead of destroy if data should flush before close, or wait for the ‘drain’ event before destroying the stream. 这句话的意思是调用end(data),把data flush进内核发送缓冲区,不是说从内核发送缓冲flush到对端,在close时,还是一个丢弃了发送缓冲; 2、在调用close后,触发服务端close事件,在close事件里将socket = null,socket失去引用,被gc了,而销毁socket对象时, 丢弃了发送缓冲; 3、write(最后个数据包) -> end() 此时发磅缓冲有数据包,有FIN包,FIN包的被优先处理,导致后续的数据被丢弃;

翻了一遍unix网络编程,影响socket close行为的只有LINGER选项,但Node也没设置这个选项的方法,带外数据(优先级)也没讲到这种情况,有点懵懂。

有大神知道为什么么? 你的解答,我将万分感谢~

13 回复

哦,对了,前端到服务端有个代理,前端用的WS,代理把WS转成TCP

可以用 nodejs 单独写个简单的tcp client,不走代理测试一下,排除代理的问题。

destroy会直接销毁数据队列,无论这个数据在内核还是Node.js中,end的底层是内核的shutdown。shutdown会保证数据先flush。fin包会排队到数据队列后面,理论上是没问题的。(或许关闭nagle算法试试?)

@theanarkh nodejs tcp server没设置setNoDelay,nagle默认是关闭的

@myy 线上实施起来挺困难,业务服务器都在内网。。我看看能不能协调下运维和前端

@theanarkh 有个疑惑,假设最后一个包的序号是7,之后的FIN包序号是10,如果最后一个包来的比FIN包晚,FIN包会被丢弃么

文档不是让你监听drain 吗,

@cool-firer 你是想说7会不会丢?具体的我也不确定,从逻辑上看,我觉得不会被丢弃的,因为tcp的包本来就会乱序收到,有空洞就等待空洞的包,对于fin这种场景,应该也是一样的。否则就意味着,我们要等待对端收到并返回ack后,我们才能调函数关闭写端。但是操作系统收到最后一个ack也不会通知我们,哪怕触发了epoll的可写事件,也只代表了内核有空闲内存可写了,不一定是说对端收到数据了。我们调用htthttp.request的时候,最后也会调用end函数,这么说,这里也会有问题。

@ganshiqingyuan drain只是说有空间可写了,不代表数据被对端收到了

@theanarkh 嗯,有道理,在stackflow上有人的回答意思跟你差不多。 找到不源头,头大

@ganshiqingyuan 我打印了write(最后一坨数据)的返回值,是true,应该是写进了内核发送缓冲

@cool-firer 我感觉代理问题的概率相当大,你分析来分析去,都是正常的两个tcp端点可能的行为。

当有代理在中间做二传手时,实际上有两段tcp连接,总共有4个端点,情况是不一样的。

client <----> proxy <-----> server

@myy 是的很有可能,代理是go写的,在WS和TCP当桥梁。看了几遍代码,暂时没看出啥问题,还不是必现的。。

回到顶部