1. SO_LINGER / SO_REUSEADDR
TCP正常的关闭过程如下(四次握手过程):
(FIN_WAIT_1) A ---FIN---> B(CLOSE_WAIT)
(FIN_WAIT_2) A <--ACK-- B(CLOSE_WAIT)
(TIME_WAIT)A <--FIN---- B(LAST_ACK)
(TIME_WAIT)A ---ACK-> B(CLOSED)
Ø A端首先发送一个FIN请求给B端,要求关闭,发送后A段的TCP状态变更为FIN_WAIT_1,接收到FIN请求后B端的TCP状态变更为CLOSE_WAIT
Ø B接收到ACK请求后,B回一个ACK给A端,确认接收到的FIN请求,接收到ACK请求后,A端的TCP状态变更为为FIN_WAIT_2。
Ø B端再发送一个FIN请求给A端,与连接过程的3次握手过程不一样,这个FIN请求之所以并不是与上一个请求一起发送,之所以如此处理,是因为TCP是双 通道的,允许在发送ACK请求后,并不马上发FIN请求,即只关闭A到B端的数据流,仍然允许B端到A端的数据流。这个ACK请求发送之后,B端的TCP 状态变更为LAST_ACK,A端的状态变更为TIME_WAIT。
Ø A端接收到B端的FIN请求后,再回B端一个ACK信息,对上一个FIN请求进行确认,到此时B端状态变更为CLOSED,Socket可以关闭。
除了如上正常的关闭(优雅关闭)之外,TCP还提供了另外一种非优雅的关闭方式RST(Reset)
(CLOSED) A ---RST--> B (CLOSED)
Ø A端发送RST状态之后,TCP进入CLOSED状态,B端接收到RST后,也即可进入CLOSED状态。
在第一种关闭方式上(优雅关闭),非常遗憾,A端在最后发送一个ACK请求后,并不能马上将该Socket回收,因为A并不能确定B一定能够接收到这个 ACK请求,因此A端必须对这个Socket维持TIME_WAIT状态2MSL(MSL=Max Segment Lifetime,取决于操作系统和TCP实现,该值为30秒、60秒或2分钟)。如果A端是客户端,这并不会成为问题,但如果A端是服务端,那就很危险 了,如果连接的Socket非常多,而又维持如此多的TIME_WAIT状态的话,那么有可能会将Socket耗尽(报Too Many Open File)。
服务端为了解决这个问题,可选择的方式有三种:
Ø 保证由客户端主动发起关闭(即做为B端)
Ø 关闭的时候使用RST的方式
Ø 对处于TIME_WAIT状态的TCP允许重用
一般我们当然最好是选择第一种方式,实在没有办法的时候,我们可以使用SO_LINGER选择第二种方式,使用SO_REUSEADDR选择第三种方式
- public void setSoLinger( boolean on, int linger) throws SocketException
- public void setReuseAddress( boolean on) throws SocketException
第一个on表示是否使用SO_LINGER选项,linger(以秒为单位)表示在发RST之前会等待多久,因为一旦发送RST,还在缓冲区中还没有发送出去的数据就会直接丢弃
2.TCP_NODELAY
对于交互型的应用(譬如telnet),经常存在的情况是客户端和服务端之间需要频繁地进行一些小数据交换,譬如telnet可能每敲一个键盘都需要将数 据发送到服务端。为了避免这种情况会产生大量小数据包,提出了Nagle算法。Nagle算法要求每次在发送端最后只有一个未被确认的包,因此上一个包发 送出去还没有接收到响应之前,要求发送的包回先放在缓冲区,接收到响应之后,会将缓冲区中的包合并成一个包发送出去(可以看到,响应回地越快,发送出去的 数据也会越快)。
需要注意的是,由Nagle算法要求只能有一个未被确认的包,因此窗口参数会失效,在大数据量传送的情况下会使网络吞吐量下降,因此对于大数据量的交互, 应该关闭Nagle算法,Nagle算法比较适合小数据量频繁交换的情景。我们可以使用TCP_NODELAY关闭Nagle算法。
- public void setTcpNoDelay( boolean on) throws SocketException
3.SO_KEEPALIVE
在一个TCP连接建立之后,我们会很奇怪地发现,默认情况下,如果一端异常退出(譬如网络中断后一端退出,使地关闭请求另一端无法接收到),TCP的另一 端并不能获得这种情况,仍然会保持一个半关闭的连接,对于服务端,大量半关闭的连接将会是非常致命的。SO_KEEPALIVE提供了一种手段让TCP的 一端(通常服务提供者端)可以检测到这种情况。如果我们设置了SO_KEEPALIVE,TCP在距离上一次TCP包交互2个小时(取决于操作系统和 TCP实现,规范建议不低于2小时)后,会发送一个探测包给另一端,如果接收不到响应,则在75秒后重新发送,连续10次仍然没有响应,则认为对方已经关 闭,系统会将该连接关闭。一般情况下,如果对方已经关闭,则对方的TCP层会回RST响应回来,这种情况下,同样会将连接关闭。
public void setKeepAlive(
boolean on)
throws SocketException