您的位置:首页 > 理论基础 > 计算机网络

TCP/IP选项解析(Java Socket)

2010-07-15 17:55 465 查看
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,还在缓冲区中还没有发送出去的数据就会直接丢弃

值得注意的是socket.setResuseAddress(true)方法必须在Socket还没有绑定到一个本地端口之前调用,否则执行socket.setResuseAddress(true)方法无效。因此必须按照以下方式创建Socket对象,然后再连接远程服务器:

Socket socket = new Socket(); //此时Socket对象未绑定到本地端口,并且未连接远程服务器
socket.setResuseAddress(true);
SocketAddress remoteAddr = new InetSocketAddress("remotehost",8000);
socket.connect(remoteAddr); //连接远程服务器,并且绑定匿名的本地端口
或者:
Socket socket = new Socket(); //此时Socket对象未绑定到本地端口,并且未连接远程服务器
socket.setResuseAddress(true);
SocketAddress localAddr = new InetSocketAddress("localhost",9000);
SocketAddress remoteAddr = new InetSocketAddress("remotehost",8000);
socket.bind(localAddr); //与本地端口绑定
socket.connect(remoteAddr); //连接远程服务器,并且绑定匿名的本地端口


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


4. SO_TIMEOUT选项

1) 设置该选项:public void setSoTimeout(int milliseconds) throws SocketException

2) 读取该选项:public int getSoTimeOut() throws SocketException

3) 当通过Socket的输入流读数据时,如果还没有数据,就会等待。Socket类的SO_TIMEOUT选项用于设定接收数据的等待超时时间,单位为毫秒,它的默认值为0,表示会无限等待,永远不会超时。

4) Socket的setSoTimeout()方法必须在接收数据之前执行才有效。此外,当输入流的read()方法抛出SocketTimeoutException后,Socket仍然是连接的,可以尝试再次读取数据。

5. SO_RCVBUF选项

1) 设置该选项:public void setReceiveBufferSize(int size) throws SocketException

2) 读取该选项:public int getReceiveBufferSize() throws SocketException

3) SO_RCVBUF表示Socket的用于输入数据的缓冲区的大小。

4) 如果底层Socket不支持SO_RCVBUF选项,那么setReceiveBufferSize()方法会抛出SocketException。

6. SO_SNDBUF选项

1) 设置该选项:public void setSendBufferSize(int size) throws SocketException

2) 读取该选项:public int getSendBufferSize() throws SocketException

3) SO_SNDBUF表示Socket的用于输出数据的缓冲区的大小。

4) 如果底层Socket不支持SO_SNDBUF选项,setSendBufferSize()方法会抛出SocketException。

7. 服务类型选项

1) IP规定了四种服务类型,用来定性的描述服务的质量:

l 低成本:发送成本低。

l 高可靠性:保证把数据可靠的送达目的地。

l 最高吞吐量:一次可以接收或发送大批量的数据。

l 最小延迟:传输数据的速度快,把数据快速送达目的地。

2) 这四种服务类型还可以进行组合,例如,可以同时要求获得高可靠性和最小延迟。Socket类中提供了设置和读取服务类型的方法:

l 设置服务类型:public void setTrafficClass(int trafficClass) throws SocketException

l 读取服务类型:public int getTrafficClass() throws SocketException

3) Socket类用四个整数表示服务类型:

l 低成本:0x02 (二进制的倒数第二位为1)

l 高可靠性:0x04(二进制的倒数第三位为1)

l 最高吞吐量:0x08(二进制的倒数第四位为1)

l 最小延迟:0x10(二进制的倒数第五位为1)

10. 设定连接时间、延迟和带宽的相对重要性

public void setPerformancePreferences(int connectionTime,int latency,int bandwidth)

以上方法的三个参数表示网络传输数据的三项指标:

n 参数connectionTime:表示用最少时间建立连接。

n 参数latency:表示最小延迟。

n 参数bandwidth:表示最高带宽。

setPerformancePreferences()方法用来设定这三项指标之间的相对重要性。可以为这些参数赋予任意的整数,这些整数之间的相对大小就决定了相应参数的相对重要性。例如,如果参数connectionTime为2,参数latency为1,而参数bandwidth为3,就表示最高带宽最重要,其次是最少连接时间,最后是最小延迟。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: