引子

  前段时间我们的服务由于一台交换机网络出现故障,导致数据库连接不上,但是在数据库的连接超时参数设置不合理,connect timeout设置的过长,导致接口耗时增加。DB连接超时后线程未正常结束,上游请求又持续进来,最终耗光了Java线程,JVM进入持续GC状态,无法恢复,直到手工重启才恢复服务。

  于是在服务的保护方面新增了两个措施,第一,调小服务端workThread的最大线程数。第二,在Server端设置Accept后Socket的readTimeout时间,当Socket调用read方法后在一定时间内读不到数据的时候会自动关闭socket。

  说了这么多背景,但是本篇描述的不是这两个问题,而是我们上线了保护措施后遇到的奇怪的问题。

  我们上线了这两个保护措施后,上游调用方向我们报异常,说请求总是返回错误,表现的情形为Socket的InputStream的read方法返回-1。

  对方一口咬定是我们的问题,因为发送已经成功了,发送时候没有报任何异常,然而读消息的时候就返回-1了。

  猜测应该是复用了已超时的连接所导致的,询问了一下,发现对方果然使用了连接池,而且这条业务线请求量比较小,所以极有可能是连接池中的socket其实已经在服务端被超时关闭了,所以调用服务的时候会发生异常,可是为什么发送的时候不会报错呢?而是在读结果的时候发现连接关闭了呢?

Flush?

  首先很容易想到的是有一个时间差。对方正在发送的时候,socket并还没有被关闭,但是这些发送的内容在网络传输过程中的时候,服务端这边把socket给关闭了,所以出现了上文所描述的问题。对于rpc调用来说,一旦涉及到网络的,都可以认为时间间隔是完全随机的值,这种情形也是能解释得通。

  但是,上游反馈这个异常非常的多,而且所有的都是同一个异常,读操作时候报错,连接已关闭。如果是上文中的情形才出现,那么首先这种错误应该比较少,而且绝大多数应该都是写出错,而不是读出错。

    socket.getOutputStream.flush()返回成功到底是一个什么样的行为?

  于是写的小程序进行测试,逻辑很简单,Server端accept后立即关闭socket,而client端则是连上1s后发送消息。

  在flush()成功返回后立即记录下System.currentTimeMillis(),同时使用ngrep进行抓包,分析TCP层面数据发送和接受的时间。

  当时显示的时间的11:46:52.154 flush返回成功,建议测试的时候使用两台间隔远一点的机器来测试,我当时在本机上测试的,抓到一条这个跨ms的记录还费了好大一番功夫。

  其实java的socket的outputstream在进行flush的时候,是不需要block到直到ACK到达才返回成功,或者说RST到达返回失败的。

  只是简单地把数据交给TCP/IP层后就直接返回success了,也有可能是TCP层返回PUSH成功后在返回success。

  但是可以肯定的是,success的时间在PUSH之后,在收到ACK之前。目前还不知道怎么把nanoseconds转化成DateTime的形式,不然就可以更加明确地确认,这个返回success是在TCP层面PUSH之前还是TCP层面PUSH之后。

  无论是将Socket的setTcpNoDelay设置成true或false,都是一样的表现,outputStream.flush()收到ACP/RST之前就已经成功返回success了。

  所以说,当一个Socket已经被remote方close后,执行write方法却返回success是一个正常的行为,而且从Stack Overflow的问答如何判断一个socket已经关闭。可以了解到其实在java层面,一个socket如何确认自己的连接状态是好的,还是已经不可用了,没有一个好的方法。唯一有用的方法就是执行read操作或者是一个write操作,在连接已经断开的情况下,read会返回-1或者是Connection Reset,write会抛出Broken PipeLine。

  但是对于一个尚且活着的socket调用read方法,会导致线程阻塞,这个验证方法基本不可取。

  做了一些测试,查看这些字段的值,isConnected、isClosed、isInputShutdown、isOutputShutdown,都是徒劳。

继续测试

  但是之前描述过,flush操作返回值是不靠谱的。所以在对一个已经被remote端close的socket,进行read or write操作返回结果的情形还不是像上文描述的这么简单。其实此socket处于CLOSE_WAIT状态。

  方便说明的话,假设W是写操作,R是读操作,统计一下依次做如下操作的结果。

  测试的过程,本机启动一个server,accept之后立即关闭。本机client连接server后,先sleep 1 s,之后做接下来的工作。

  不同操作顺序的结果如下


操作 结果
W success
R connection reset
W broken pipeline

操作 结果
R -1
W success
W broken pipeline

操作 结果
R * N -1
W success
W broken pipeline

  所以说,其实读其实也根本没有对write操作产生影响,只有W的操作,才会真正地触发这个socket去看看自身的状态到底是什么。

  而读这个操作,读一个关闭后的socket,会返回-1,但是不会对下一次的R或W操作的结果产生影响,也就是说,不会去更新本地是socket的状态。

  后来才发现,其实是因为这个Socket处于CLOSE_WAIT状态,进行了FLUSH操作后,会收到一个RST的包,Socket在收到RST后会立即关闭此Socket,不会进行所谓的LAST_ACK和TIME_WAIT状态。

  直到这里,还是没有复现之前说过的那个问题。

操作 结果
W success
R -1

  仔细想一想,既然Write操作是不等待ACK之后就立即返回的,所以说明这个更新Socket状态的动作是异步做的,异步的动作肯定是有时间差的。

  所以说,没有复现R返回-1的情形有可能是因为之前的操作都是在本机在测试,网络的处理很快,PUSH后立马就收到了RST,所以导致R的操作就抛出Connection Reset的异常。

  于是,将Server放在另一台远一点的机器,延迟1ms左右,于是就复现了刚才的问题。当然,有趣的是,上文图中的那些情况,都不成立了,出现了这种情况:

操作 结果
W success
R -1
W success

  是不是意味着之前的所有结论都是错的?当然没关系,因为第一次的情形都是出于网络基本无延迟的情况下。

  其实这些结果是程序严格按顺序执行时的结果,编程理想情况下的结果。其实这就是我们真实想要的,所有步骤都顺序执行的结果。

  所以说,真实确认这个socket关闭不可用的结论,其实是在收到RST之后异步处理的。

  现在我们回到图1去仔细看看,从网络抓包情况来看,即使是在收到RST后,还是有W返回success的情况,因为从TCP层面收到RST后,到应用层对socket的内存内容进行一些修改,还是需要一定时间的。

四次挥手?

  之所以会出现这么些问题,都是因为处于中间状态所导致,Server端的socket处于FIN_WAIT2状态,而Client端的Socket处于CLOST_WAIT状态,并没有走到一个完全关闭的终态。

  在上面的测试用例中,我把client端的等待时间改成了120S,用netstat命名查询,这两个Socket还是处于两个状态,更长的时间并没有进行测试。

  在TCP/IP Vol1 18章中描述到“许多伯克利的实现会在空闲10分钟75秒的情况下关闭处于FIN_WAIT2状态的Socket”,Client端会如何表现,目前我没测试,等哪天有心情了测试一下

  TCP/IP协议的四次挥手流程图在这就不赘述了,感觉被课本骗了,说好的四次挥手呢,为什么总是进行到一半就不动了呢?

  什么情况下会正常地走完这个流程?

1、Client的socket也主动调用close方法。
2、Client程序在CLOSE_WAIT状态下终止了,四次握手会立即就完成,操作系统层面做了这个FIN包的发送工作。
3、如果Client程序如果没有结束,也不主动发FIN包,会停留在此状态,直至被操作系统回收。

  这从另外一方面也验证了为什么一定得主动关闭连接,既是帮助别人也是帮助自己。

  即使是Server已经把你的连接关闭了,你这边也会一直停留在CLOSE_WAIT状态,Server端会停留在FIN_WAIT_2状态。

  这两个状态是否会进入完全关闭的状态,以及进入完全关闭的状态所需的时间,由操作系统来决定,也就是说,和操作系统的实现有关。

小现象

  1、在我的操作系统,OS X EI Capitan 10.11.5 (15F34)情况下,FIN_WAIT_2自动关闭的时间符合10分钟75秒这个描述,精确时间我也没统计到,差距1分钟内。已经30分钟过去了,Client端的Socket还傻傻停留在CLOST_WAIT状态,我也不知道啥时候它会完全关闭。

  2、OSX作为Server服务的时候,无论是TCP连接建立的时候,还是TCP关闭的时候,都会重发一个ACK,linux下不存在这样的问题。

参考文档

java-socket-api-how-to-tell-if-a-connection-has-been-closed

TCP/IP Illustrated Volume 1: The Protocols

后记

  后续看到有可以通过sendUrgentData()来实现发送urgent data,既实现写探活,同时数据还会被对端忽略,不进入业务层。但是也不能达到实时性的效果,也是会在第一次成功,收到RST后才失败。

所以还是觉得NIO的项目好,比如netty的channel,可以直接收到INACTIVE和UNREGISTERED事件。

如何判断Socket已经关闭的更多相关文章

  1. JAVA 判断Socket 远程端是否断开连接

    最近在做项目的时候,遇到这样一个问题,如何判断 Socket 远程端连接是否关闭,如果关闭的话,就要重建连接Socket的类提供了一些已经封装好的方法, 如  isClosed().isConnect ...

  2. 怎样实时判断socket连接状态?

    对端正常close socket,或者进程退出(正常退出或崩溃),对端系统正常关闭 这种情况下,协议栈会走正常的关闭状态转移,使用epoll的话,一般要判断如下几个情况 处理可读事件时,在循环read ...

  3. java socket 判断Socket连接失效

    要判断socket连接链路是否可用时,不能通过socket.isClosed() 和 socket.isConnected() 方法判断,要通过心跳包 socket.sendUrgentData(0x ...

  4. C#socket通信时,怎样判断socket双方是否断开连接

    我在Server端new了一个socket,然后bind,开了一个线程来accept前来连接的client,每接到一个client前来连接就新开一个线程和它进行通信.我把Server端得到的socke ...

  5. Java socket中关闭IO流后,发生什么事?(以关闭输出流为例)

    声明:该博文以socket中,关闭输出流为例进行说明. 为了方便讲解,我们把DataOutputstream dout = new DataOutputStream(new BufferedOutpu ...

  6. js判断获取浏览器关闭状态

    如题,js获取浏览器关闭状态,可实现判断选择是否关闭. <html> <head> <title> </title> </head> < ...

  7. Android—Socket中关闭IO流后导致Socket关闭不能再收发数据的解决办法

    以Socket发送数据为例: 发送数据时候要声明:DataOutputStream os = new DataOutputStream(socket.getOutputStream()); 最近开发遇 ...

  8. 如何判断Socket连接失效

    http://cuisuqiang.iteye.com/blog/1453632 ——————————————————————————————————————————————————————————— ...

  9. 如何判断SOCKET还是连接着的

    转自 http://blog.csdn.net/loadstar_kun/article/details/5790407 1. 用read函数来判断 读到长度0不能断定是已经断开.除非是-1,才代表输 ...

随机推荐

  1. Linq 更改主键值

    有一个班级表,主键是class_id,在管理班级时要进行逻辑删除,而只是单纯的is_del字段(记录每条数据是否有效)更改为true,主键class_id如果不变动,在再次增加一个班级时,其主键如果和 ...

  2. siverlight 后台动态设置图片路径的总结

    最近碰到了个问题,需要给一个用户控件中的image动态设置图片资源 1.图片资源属性为resource时,静态引用无任何问题,但是动态设置时,就什么也不显示 后来找到问题所在, 必须把此图片属性项中“ ...

  3. android应用程序fps meter[帧数显示]的分析 —— 浅谈root的风险 (1)

    fps meter是常用的检测帧率的软件,该软件需要root权限才能工作,一直比较好奇它一个apk是如何知道系统当前的帧率情况的,就针对此apk分析了一下其工作原理. Apk组成 首先看一下apk的组 ...

  4. dm3730和dm6437,dm6446,AM335x启动过程的不同

    dm3730的启动流程为RBL+X-loader+uboot+uImage分别在片内ROM(fireware),片内SRAM,片外的DDR,片外的DDR. 之所以建立这样一个复杂的启动过程,我个人的理 ...

  5. Head First设计模式——策略设计模式

    策略设计模式 说在前面的话 入软件一年啦,平心而论,总算不限于只会钻研些基础的语言语法了,数据结构和算法也恶补的差不多了.所以~趁着现在一边实习一边啃<Head First设计模式>的功夫 ...

  6. Maven之(四)Maven命令

    常用命令 从某种意义上来说,软件是帮助不懂程序的人来操作计算机的,图形化界面尤其如此.在上个世纪,比尔盖茨之所以成为世界首富,微软之所以IT界的巨鳄,就是因为Windows开图形化操作之先河,并抢先占 ...

  7. TCP/IP协议中backlog参数

    TCP建立连接是要进行三次握手,但是否完成三次握手后,服务器就处理(accept)呢? backlog其实是一个连接队列,在Linux内核2.2之前,backlog大小包括半连接状态和全连接状态两种队 ...

  8. python新手 实践操作 作业

    #有如下值集合 [11,22,33,44,55,66,77,88,99],将所有大于 66 的值保存至字典的第一个key中,将小于 66 的值保存至第二个key的值中.即: {'k1': 大于66的所 ...

  9. 实战ASP.NET访问共享文件夹(含详细操作步骤)

    博客园找找看(http://zzk.cnblogs.com)的索引文件占用空间太大,需要移至另外一台服务器,所以要解决"在ASP.NET中通过共享文件夹访问索引文件"的问题. 假设 ...

  10. CODE[VS]-寻找子串位置-字符串处理-天梯青铜

    题目描述 Description 给出字符串a和字符串b,保证b是a的一个子串,请你输出b在a中第一次出现的位置. 输入描述 Input Description 仅一行包含两个字符串a和b 输出描述 ...