nodejs做的韩国演唱会抢票软件项目报错如下:
0 1 2 3 4 5 6 7 8 9 10 11 12 |
Error: socket hang up at connResetException (internal/errors.js:570:14) at Socket.socketOnEnd (_http_client.js:440:23) at Socket.emit (events.js:215:7) at endReadableNT (_stream_readable.js:1183:12) at processTicksAndRejections (internal/process/task_queues.js:80:21) at process.runNextTicks [as _tickCallback] (internal/process/task_queues.js:62:3) at Function.module.exports.loopWhile (C:\nodejs\concert\node_modules\deasync\index.js:70:11) at Req.requestSync (C:\concert\src\common\lib\think\lib\request-pack.js:45:11) at module.exports.openDetailPage (C:\concert\src\concert\service\api_ticket.js:1301:34) at module.exports.processCheck (C:\concert\src\concert\service\api_ticket.js:3183:34) { code: 'ECONNRESET' } |
{ err: { code: ‘ECONNRESET’ } }
一、说明一下nodejs项目的基础环境:
- 使用thinkjs开发框架
- 项目中使用request模块进行HTTP请求
- 项目中需要模拟客户端进行对某网站的连续的不同地址的起求
- request请求头中使用Connection: Keep-Alive,持久连接,即长连接
二、TCP抓包分析
因为没有多次大量抓包,所以只发现了以下几咱情况
服务器 – 服务端主机,即目标主机
客户机 – 客户端,即程序所在主机
1、测试一的TCP抓包数据分析
- No-834 socket(A)是接收到服务器发来的全部数据,
- No-835 socket(A)使用原socket发起新的请求(应用层发来发起新的HTTP请求【假定为URL-1】的命令)
- No-836 socket(A)收到服务器发来的[FIN, ACK],即要求断开连接(四次挥手之第一手)
- No-837 socket(A)客户端应答[ACK](四次挥手之第二手)
- No-838,839,840 socket(A)接835发送新数据请求
- No-841 socket(A)客户端向服务器发送[FIN,ACK](四次挥手之第三手)
- No-842 socket(B)客户端创建新的socket向服务器发起请求[SYN](应用层继续发来发起新的HTTP请求【URL-1】的命令)(三次握手之第一手)
- No-843 socket(B)服务器收到握手请求的应答[SYN, ACK](三次握手之第二手)
- No-844 socket(B)客户端对服务器应答的应答[ACK](三次握手之第三手)
- No-845 socket(B)客户端发送数据包
- 正常的话,No-835应该收到一个服务器发来的[FIN, ACK],即要求断开连接
- No-846,No-847 socket(A)服务器向客户端发送重建连接的要求[RST](据说是在紧急情况才会连着发送两个[RST],表示强调的意思,我只是好像在哪里听到过这种说法,所以不确定是不是这个意思,只是有这个一点点的印象)
- No-848 socket(B)服务器应答
- No-849 socket(B)客户端应答
- No-850 socket(B)请求数据完成
- No-851 socket(B)服务器返回数据
- No-852 socket(B)服务器返回数据
- No-864 socket(B)服务器返回数据
- No-865 socket(B)服务器发起断开请求[FIN, ACK](四次挥手之第一手)Seq=715 Ack=3059 Win=10240 Len=0
- No-866 socket(B)客户端应答[ ACK](四次挥手之第二手)
- No-867 socket(B)客户端同意断开的应答[FIN, ACK](四次挥手之第三手)
- No-872 socket(B)客户端在RTO时间内没有收到服务器应答,决定重新发送,即超时重传
- No-875 socket(B)同上
- No-876 socket(B)虚假超时重传[TCP Spurious Retransmission],seq=3058, ack=716 … [Reassembly error, protocol TCP: New fragment overlaps old data (retransmission?)] 提示:重新组装错误
- No-879 socket(B)服务器发来[RST],要求重建连接
No-876的发包应该是从3059开始发送,但是组装TCP数据后从3058位置开始发送了,这里就是错误(Error: socket hang up)的元凶,至于说它为什么组装错了,我就查不出根源了,只能分析到这里。
错误的开始,是在No-834,834之后应该收到服务器的一个[FIN, ACK]。据之前的包分析,此服务器接收到我发的模拟请求都是不允许长连接的。也可以说,之前的与服务器的交互都是在服务器回复完一条数据(一个HTTP请求的响应)后,都会紧跟一个断开请求。
但是,因为客户端一直是认为对方支持长连接的(因为请求头中使用了Connection: Keep-Alive,又因为没有收到断开请求),又因为这次客户端是出奇的快,在服务器的断开请求包之前给服务器做了应答(No-835)
以下是用人话说的TCP通信:
No-836 【我的手机号A】服务器说:断开吧,
No-837 【我的手机号A】我说:收到,等一下我看看有没有什么事
No-838 【我的手机号A】我说:给你来一个包
No-839 【我的手机号A】我说:再给你来一个包
No-840 【我的手机号A】我说:再再给你一个包,最后一个了
No-841 【我的手机号A】我说:行了哥们,可以断开了
No-842 【我的手机号B】我说:哥们,我换了个号,你在不(握手第一次)
No-843 【我的手机号B】服务器说:我在呀,兄弟你在不?(握手第二次)
No-844 【我的手机号B】我说:收到,你我都在就好(握手第三次)
No-845 【我的手机号B】我说:说正事儿了,给你来个正经的包(带应用层数据的)
No-846 【我的手机号A】服务器说:喂喂,兄弟,我感觉你掉线了呢,换个号吧
No-847 【我的手机号A】服务器说:喂喂,兄弟,我感觉你掉线了,换个手机号
其实我TM早换完了,你还在那跟我喊个六呀!
以下都是使用【我的手机号B】与服务器通信
No-848 服务器说:收到
No-849 我说:哥们,给你个分片的包,第一个
No-850 我说:哥们,分片第二个包,没了
No-851 服务器说:回复849的,收到
No-852 服务器说:回复850的,收到
No-864 服务器说:兄弟,我知道你要什么了,给你(返回HTML信息)
No-865 服务器说:兄弟,不唠了,再见,行不?(挥手一)
No-866 我说:收到了,稍等,我看看还有啥事没。(挥手二)
No-867 我说:哥们,没事了,那就这样吧(挥手三)
…现在的时间,我正等服务器向我发起挥手四
但是超时了,通过计算得出的RTO超时时间
No-872 我说:哥们,收到没,我说没事了。
No-875 我说:哥们,收到没,我说没事了。
No-876 我说:这会我就着急了,嗓子都哑了,说话都变声了(数据计算错了,应该是seq=3059,结果也不知道怎么的,seq=3058了)
No-879 服务器说:你说啥呢,听不懂,你换电话吧。
结果:应用层的socket报错:Error: socket hang up
测试二的TCP抓包数据分析
Time直到166秒时,结束生命。
看这组抓包,换成人话大致的意思就是我一个劲的请求握手,服务器就一个劲的拒绝握手,要求我重说,只要我重说5次,他不给我正经的应答,我就换电话(换端口)
以下30秒都在这种状态下通信,我实在是受不了了,30秒了你都没给我来个个正经的,不跟你(服务器)扯了,应用层就报错了。
从时间上来看,这个测试的流程可能是因为代理使用时长过了。项目中使用代理IP,买得是1分钟的,但是粗略看到,时长大约都在90秒左右,可能还会有更长的。
结果:应用层的socket报错:Error: socket hang up
三、从应用层的角度来说造成这个报错的原因
在使用request模块发起请求时,请求头信息中使用了Connection: keep-alive,持久连接。双方都可以发起持久连接的请求,但是双方也都可以拒绝持久连接。通俗说就是我可以请求你在我闲着的时候保持通话,但是你也可以在我闲着没声儿的时候直接挂我电话的意思,反之亦然。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
request({ url: '请求URL', method: 'GET', timeout: 15000, gzip: true, jar: { _jar: [Object] }, followRedirect: true, headers: { 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Version/12.51)', Connection: 'keep-alive', 'Proxy-Authorization': 'Basic ......', Host: '......', Origin: '......', Referer: '......' } }, function(err, res, bosy){}); |
另一种可能在request参数中增加了forever: true,如下:
0 |
request({url: '请求的URL', method: 'POST', headers: {...}, forever: true}) |
在接收方不允许长连接的情况下,也同样是会报错的,Error: socket hang up
四、我的解决办法
- 检查request模块headers中是否存在Connection: Keep-Alive,如果存在,则删除此项;
- 检查request模块参数项中是否存在forever: true,如果存在,则删除此项;
- 如果与我前面说的环境大有不同,而且按照上面1、2两条查找修改后还是不能解决,那就联系我【织梦先生】,一起研究。
扩展: