【问题标题】:Ruby Net::HTTP not sending TCP Keep-Alive packets, resulting in "Errno::ECONNRESET - Connection reset by peer" exceptionsRuby Net::HTTP 不发送 TCP Keep-Alive 数据包,导致“Errno::ECONNRESET - Connection reset by peer”异常
【发布时间】:2017-05-08 16:10:51
【问题描述】:

我正在与一个通过 HTTPS 接受 POST 请求并以 XML 响应的 API 进行交互。远程服务器生成数据需要很长时间,这意味着我的本地客户端在发送其 POST 请求和接收响应之间等待几分钟。有时响应会按预期返回,但有时 Ruby (2.3.1p112) 会在长时间暂停后引发以下异常:

Exception occurred: Errno::ECONNRESET - Connection reset by peer

/usr/share/ruby/openssl/buffering.rb:178:in `sysread_nonblock'
/usr/share/ruby/openssl/buffering.rb:178:in `read_nonblock'
/usr/share/ruby/net/protocol.rb:154:in `rbuf_fill'
/usr/share/ruby/net/protocol.rb:136:in `readuntil'
/usr/share/ruby/net/protocol.rb:146:in `readline'
/usr/share/ruby/net/http/response.rb:40:in `read_status_line'
/usr/share/ruby/net/http/response.rb:29:in `read_new'
/usr/share/ruby/net/http.rb:1437:in `block in transport_request'
/usr/share/ruby/net/http.rb:1434:in `catch'
/usr/share/ruby/net/http.rb:1434:in `transport_request'
/usr/share/ruby/net/http.rb:1407:in `request'

出于好奇,我尝试使用 cURL 发出相同的请求,发现每次都收到响应,从未遇到远程主机发起的连接重置。 cURL 和 Ruby 请求在同一台机器上运行,所以我认为这不是低级系统或网络问题。为了找出一些不同,我运行了 Wireshark 并查看了请求期间来回发送的数据包。

首先使用 Ruby:

1302978 3988.123708  [local-ip] → [remote-ip]  SSL 286 Client Hello
1302981 3988.189299  [remote-ip] → [local-ip]  TLSv1.2 463 Server Hello, Certificate
1302982 3988.189388  [local-ip] → [remote-ip]  TCP 54 55265 → 443 [ACK] Seq=233 Ack=1361 Win=65535 Len=0
1302983 3988.189389  [local-ip] → [remote-ip]  TCP 54 55265 → 443 [ACK] Seq=233 Ack=2721 Win=65535 Len=0
1302984 3988.189389  [local-ip] → [remote-ip]  TCP 54 55265 → 443 [ACK] Seq=233 Ack=3130 Win=65535 Len=0
1302985 3988.194978  [remote-ip] → [local-ip]  TLSv1.2 396 Server Key Exchange, Server Hello Done
1302986 3988.195047  [local-ip] → [remote-ip]  TCP 54 55265 → 443 [ACK] Seq=233 Ack=3472 Win=65535 Len=0
1302987 3988.195812  [local-ip] → [remote-ip]  TLSv1.2 180 Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message
1302988 3988.257643  [remote-ip] → [local-ip]  TCP 60 443 → 55265 [ACK] Seq=3472 Ack=359 Win=35098 Len=0
1302989 3988.272131  [remote-ip] → [local-ip]  TLSv1.2 105 Change Cipher Spec, Encrypted Handshake Message
1302990 3988.272200  [local-ip] → [remote-ip]  TCP 54 55265 → 443 [ACK] Seq=359 Ack=3523 Win=65535 Len=0
1302994 3988.339450  [remote-ip] → [local-ip]  TCP 60 443 → 55265 [ACK] Seq=3523 Ack=945 Win=34512 Len=0
1302995 3988.339455  [remote-ip] → [local-ip]  TCP 60 443 → 55265 [ACK] Seq=3523 Ack=1206 Win=34251 Len=0
1406186 4376.828078  [remote-ip] → [local-ip]  TCP 54 443 → 55265 [RST, ACK] Seq=3523 Ack=1206 Win=9300 Len=0

然后用 cURL:

51468 268.062527  [local-ip] → [remote-ip]  SSL 292 Client Hello
51472 268.125416  [remote-ip] → [local-ip]  TLSv1.2 463 Server Hello, Certificate
51473 268.125530  [local-ip] → [remote-ip]  TCP 54 53819 → 443 [ACK] Seq=239 Ack=1361 Win=65535 Len=0
51474 268.125531  [local-ip] → [remote-ip]  TCP 54 53819 → 443 [ACK] Seq=239 Ack=2721 Win=65535 Len=0
51475 268.125531  [local-ip] → [remote-ip]  TCP 54 53819 → 443 [ACK] Seq=239 Ack=3130 Win=65535 Len=0
51476 268.132509  [remote-ip] → [local-ip]  TLSv1.2 396 Server Key Exchange, Server Hello Done
51477 268.132604  [local-ip] → [remote-ip]  TCP 54 53819 → 443 [ACK] Seq=239 Ack=3472 Win=65535 Len=0
51479 268.158620  [local-ip] → [remote-ip]  TLSv1.2 129 Client Key Exchange
51481 268.220146  [remote-ip] → [local-ip]  TCP 60 443 → 53819 [ACK] Seq=3472 Ack=314 Win=35137 Len=0
51482 268.220216  [local-ip] → [remote-ip]  TLSv1.2 105 Change Cipher Spec, Encrypted Handshake Message
51483 268.281636  [remote-ip] → [local-ip]  TCP 60 443 → 53819 [ACK] Seq=3472 Ack=365 Win=35086 Len=0
51484 268.281642  [remote-ip] → [local-ip]  TLSv1.2 105 Change Cipher Spec, Encrypted Handshake Message
51485 268.281718  [local-ip] → [remote-ip]  TCP 54 53819 → 443 [ACK] Seq=365 Ack=3523 Win=65535 Len=0
51487 268.344020  [remote-ip] → [local-ip]  TCP 60 443 → 53819 [ACK] Seq=3523 Ack=770 Win=34681 Len=0
62427 328.950531  [local-ip] → [remote-ip]  TCP 54 [TCP Keep-Alive] 53819 → 443 [ACK] Seq=769 Ack=3523 Win=65535 Len=0
62435 329.012004  [remote-ip] → [local-ip]  TCP 60 [TCP Window Update] 443 → 53819 [ACK] Seq=3523 Ack=770 Win=65535 Len=0
72644 389.584563  [local-ip] → [remote-ip]  TCP 54 [TCP Keep-Alive] 53819 → 443 [ACK] Seq=769 Ack=3523 Win=65535 Len=0
72647 389.647037  [remote-ip] → [local-ip]  TCP 60 [TCP Keep-Alive ACK] 443 → 53819 [ACK] Seq=3523 Ack=770 Win=65535 Len=0

我发现的相关区别是 cURL 在等待响应时每分钟发送一个 TCP Keep-Alive 数据包,让服务器知道它仍然处于活动状态并希望保持其连接打开。 Ruby Net::HTTP 没有发送这些数据包,并最终从远程主机获得 TCP RST(重置),这会触发 Errno::ECONNRESET 异常。

所以我的问题是:有没有办法配置 Net::HTTP 来发送那些 TCP Keep-Alive 数据包并保持我的连接打开?谢谢!


编辑:我可以通过切换到 Typhoeus(包含 cURL)并将选项 tcp_keepalive: true 传递给我的请求来解决这个问题,这是 tells cURL to send keepalive probes 的标志。

如果有人知道是否有可能从 Net::HTTP 获得相同的行为,我将保留这个问题。

【问题讨论】:

    标签: ruby ssl networking tcp


    【解决方案1】:

    您可以在请求的标头中指定保持活动状态,但这并不是特定于 nethttp 的! https://tools.ietf.org/id/draft-thomson-hybi-http-timeout-01.html

    【讨论】:

    • 我尝试设置该标头,但远程主机最终仍然发送了一个 RST 数据包,并且它没有提示 Net::HTTP 开始发送 Keep-Alive 探测。 AFAICT,该标头指定主机的期望,设置它关于 other 主机需要如何行为以保持连接处于活动状态。
    猜你喜欢
    • 2020-05-15
    • 2013-10-26
    • 1970-01-01
    • 2021-03-07
    • 2012-01-18
    • 1970-01-01
    • 2020-06-28
    • 2013-05-05
    • 1970-01-01
    相关资源
    最近更新 更多