形单影只的 Socket

栏目: 服务器 · 发布时间: 6年前

内容简介:最近工作上遇到过几次因 http client 没有配置相关超时参数,导致线程数占满或应用卡住的情况,出问题时线程的堆栈大致是这样的:程序卡在了上面设置了三个超时时间,含义分别是

最近工作上遇到过几次因 http client 没有配置相关超时参数,导致线程数占满或应用卡住的情况,出问题时线程的堆栈大致是这样的:

"qtp325266363-35729" #35729 prio=5 os_prio=0 tid=0x00007f5154033000 nid=0x1cf8f runnable [0x00007f4f7f511000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.security.ssl.InputRecord.readFully(InputRecord.java:465)
        at sun.security.ssl.InputRecord.read(InputRecord.java:503)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:983)
        - locked <0x00000006e1494d68> (a java.lang.Object)
        at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:940)
        at sun.security.ssl.AppInputStream.read(AppInputStream.java:105)
        - locked <0x00000006e1496d88> (a sun.security.ssl.AppInputStream)
        at org.apache.http.impl.conn.LoggingInputStream.read(LoggingInputStream.java:84)
        at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:137)
        at org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:153)
        at org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:282)
        at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:138)
        at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:56)
        at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:259)
        at org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:163)

程序卡在了 socketRead0 上,我们线上版本用的是 httpclient 4.4.5 ,配置下面参数即可:

int timeout = 5;
RequestConfig config = RequestConfig.custom()
  .setConnectTimeout(timeout * 1000)
  .setConnectionRequestTimeout(timeout * 1000)
  .setSocketTimeout(timeout * 1000).build();
CloseableHttpClient client = 
  HttpClientBuilder.create().setDefaultRequestConfig(config).build();

上面设置了三个超时时间,含义分别是

  • Connection Timeout,表示与远端服务期建立连接的超时
  • Socket Timeout,表示连接上两个 packet 之间的超时,当空闲时间超过这个后该连接就会自动断开
  • Connection Manager Timeout,表达从连接池申请连接时的超时

好了,其实这不是这篇文章的重点,重点时在 debug 这个问题时,发现的一个有趣现象,为了弄清问题缘由,需要先回顾下socket 编程的基本知识。

Socket 定义

计算机领域里的 socket ,表示可以进行通讯的两个程序,一般称为 endpoint,如果是同一台机器上,则对应 Unix domain socket ,如果是不同机器,则为 network socket ,一方称为 client,另一方称为 server。

对于 TCP socket 来说,使用流程如下:

形单影只的 Socket

连接建立后,可以通过 ss 命令 查看到

# 3000 端口为一  Java  写的 HTTP Server,35050 为 curl 访问时随机选择的本地端口
$ ss -np | grep 3000
Netid  State      Recv-Q Send-Q     Local Address:Port       Peer Address:Port
tcp    ESTAB      0      0              127.0.0.1:35050         127.0.0.1:3000   users:(("curl",12436,3))
tcp    ESTAB      0      0              127.0.0.1:3000          127.0.0.1:35050  users:(("java",12279,82))

由于 socket 涉及两端,每一端用 ip + port 去标示,再加上两端通讯的协议,所以需要用五个字段来区分,在一般表述为

# protocol 一般为 tcp/udp
(protocol, src_ip:src_port, dst_ip:dst_port)

形单影只的 socket

经过上面的介绍,往往会以为 TCP socket 都是成对出现的,毕竟有两方参与,这也符合 99.99% 的场景,但是 TCP 协议在定义时,并没有严格要去通讯双方必须为不同的程序,也就是说只要符合 TCP 状态机的模型,就可以做到自己与自己通讯。通过下面一示例可以证明:

$ while true; do nc -v localhost 11111; done
...
...
nc: connect to localhost port 11111 (tcp) failed: Connection refused
nc: connect to localhost port 11111 (tcp) failed: Connection refused
nc: connect to localhost port 11111 (tcp) failed: Connection refused
nc: connect to localhost port 11111 (tcp) failed: Connection refused
Connection to localhost 11111 port [tcp/*] succeeded!

上面示例在开始运行时一直失败,说明 11111 端口没有被 LISTEN,所以连接一直失败,但是在某一时刻突然就连上了!还是请出我们的老朋友 ss 看看怎么回事

$ ss -np  | grep 11111
tcp    ESTAB      0      0              127.0.0.1:11111         127.0.0.1:11111  users:(("nc",8419,3))

额,竟然只有一个 socket!这时的 nc 命令同时兼具了 server 与 client 两个角色,这时其实是个 echo server,输会什么,就好输会什么。

这看上去有些不可思议,为了弄清问题,可以通过 tcpdump 来分析连接是怎么建立的。

$ sudo tcpdump -i any port 11111 -Snw /tmp/debug.pcap -vvv

# 新开另一个窗口,输入
$ nc -vp 11111 localhost 11111
Connection to localhost 11111 port [tcp/*] succeeded!
# 这里通过 -p 选项制定了 client 的端口号,方便快速浮现问题

然后通过 Wireshark 打开得到的 pcap 文件,发现了著名的「三次握手」

形单影只的 Socket

虽然第二个 packet 显示为 out of order,但是并没有影响该 socket TCP 状态的转移!

这里需要再介绍下 TCP 状态转移过程:

形单影只的 Socket

当出于 close 状态的 socket 发出 SYN 包后,会处于 SYN_SENT 状态,这时如果收到 SYN,ACK 并回复 ACK 包后,就会处于 ESTABLISHED

可以看到,一个 socket 竟然就可以完成「三次握手」!

那么能不能复现「四次挥手」呢?直接 Ctrl+C 结束上面的 nc 进程,然后再通过 tcpdump+wireshark 可以得到下面的结果:

形单影只的 Socket

通过 ss 命令也没找到处于 TIME-WAIT 状态的 11111,说明进行的是「被动关闭」(状态转移图右下脚)流程。

结论

看完本文的一点“实用”干货可能是解释为什么不要去 LISTEN 比较高的端口,但是更希望大家能多去动手,发现隐藏在表象下的根源,这其实和脱单是一个道理 -:)

参考


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

深度学习

深度学习

[美]特伦斯·谢诺夫斯基(Terrence Sejnowski) / 姜悦兵 / 中信出版集团 / 2019-2 / 88

全球科技巨头纷纷拥抱深度学习,自动驾驶、AI医疗、语音识别、图像识别、智能翻译以及震惊世界的AlphaGo,背后都是深度学习在发挥神奇的作用。深度学习是人工智能从概念到繁荣得以实现的主流技术。经过深度学习训练的计算机,不再被动按照指令运转,而是像自然进化的生命那样,开始自主地从经验中学习。 本书作者特伦斯·谢诺夫斯基是全球人工智能十大科学家之一、深度学习先驱及奠基者,亲历了深度学习在20世纪......一起来看看 《深度学习》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具