Java Socket 的参数选项解读
Java Socket中有很多参数可以选择,这篇文章的目的是沉淀出这些参数的语义和用法,供自己以后查阅。
1、java socket参数选项总览
在JDK1.6中有如下参数选项:
public final static int TCP_NODELAY = 0x0001;
public final static int SO_BINDADDR = 0x000F;
public final static int SO_REUSEADDR = 0x04;
public final static int SO_BROADCAST = 0x0020;
public final static int IP_MULTICAST_IF = 0x10;
public final static int IP_MULTICAST_IF2 = 0x1f;
public final static int IP_MULTICAST_LOOP = 0x12;
public final static int IP_TOS = 0x3;
public final static int SO_LINGER = 0x0080;
public final static int SO_TIMEOUT = 0x1006;
public final static int SO_SNDBUF = 0x1001;
public final static int SO_RCVBUF = 0x1002;
public final static int SO_KEEPALIVE = 0x0008;
public final static int SO_OOBINLINE = 0x1003;
2、public final static int TCP_NODELAY = 0x0001;
要理解这个参数,首先要理解Nagle算法,下面先说说这个Nagle算法
2.1 Nagle算法产生的背景
当网络传输中存在大量小包传输时,会严重影响传输效率。比如一个包,包头40字节,而真正的内容只有一个字节或者几个字节(典型的有Telnet),这样的传输效率是十分低下的。Nagle算法要解决的就是这种低效率的传输问题。
2.2 Nagle算法的原理
用通俗的话来说就是,把小包要发送的字节先缓存,当到达一定的阀值的时候再一次性传输。具体算法(伪代码)如下:
if there is new data to send
if the window size >= MSS and available data is >= MSS
send complete MSS segment now
else
if there is unconfirmed data still in the pipe
enqueue data in the buffer until an acknowledge is received
else
send data immediately
end if
end if
end if
其中MSS为maximum segment size的缩写,是TCP头部的一个字段,表示一个TCP段最大的数据承载量。
2.3 Nagle算法的问题
在传输大文件的时候,如果使用这个算法,那么会出现明显的延迟现象,因此,在这种情况下,最好是关闭这个算法。
知道了Nagle算法,就知道了TCP_NODELAY这个参数的意义了,如果这个参数被设置为True,那么就是关闭Nagle算法,实现无延迟传输,如果设置为false,则是打开这个算法,会对发送的数据进行缓存。
3、public final static int SO_BINDADDR = 0x000F;
获取绑定套接字的本地地址(不能仅将此选项“设置”为“得到”,因为套接字是在创建时绑定的,所以本地绑定的地址不可更改)。
4、public final static int SO_REUSEADDR = 0x04;
这个参数表示套接字对端口是否可重用。
这个套接字选项通知内核,如果端口忙,但TCP状态位于 TIME_WAIT ,可以重用端口。如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息,指明"地址已经使用中"。如果你的服务程序停止后想立即重启,而新套接字依旧使用同一端口,此时 SO_REUSEADDR 选项非常有用。必须意识到,此时任何非期望数据到达,都可能导致服务程序反应混乱,不过这只是一种可能,事实上很不可能。
一个套接字由相关五元组构成,协议、本地地址、本地端口、远程地址、远程端口。SO_REUSEADDR 仅仅表示可以重用本地本地地址、本地端口,整个相关五元组还是唯一确定的。所以,重启后的服务程序有可能收到非期望数据。必须慎重使用 SO_REUSEADDR 选项。
5、public final static int SO_BROADCAST = 0x0020;
这个参数选项用来控制广播,目前只有在DatagramSocket里支持。
6、public final static int IP_MULTICAST_IF = 0x10;
用来控制多播的参数选项,目前只有在MulticastSocket里支持
在MulticastSocket的源代码里有设置多播的方法:
public void setInterface(InetAddress inf) throws SocketException {
if (isClosed()) {
throw new SocketException("Socket is closed");
}
checkAddress(inf, "setInterface");
synchronized (infLock) {
getImpl().setOption(SocketOptions.IP_MULTICAST_IF, inf);
infAddress = inf;
}
}
7、public final static int IP_MULTICAST_IF2 = 0x1f;
这个字段的效果和上面的是一样的,只是扩展支持IPV6
8、public final static int IP_MULTICAST_LOOP = 0x12;
用来设置本地回环接口的多播特性,在MulticastSocket源代码中有相关方法:
/**
* Disable/Enable local loopback of multicast datagrams
* The option is used by the platform's networking code as a hint
* for setting whether multicast data will be looped back to
* the local socket.
*
* <p>Because this option is a hint, applications that want to
* verify what loopback mode is set to should call
* {@link #getLoopbackMode()}
* @param disable <code>true</code> to disable the LoopbackMode
* @throws SocketException if an error occurs while setting the value
* @since 1.4
* @see #getLoopbackMode
*/
public void setLoopbackMode(boolean disable) throws SocketException {
getImpl().setOption(SocketOptions.IP_MULTICAST_LOOP, Boolean.valueOf(disable));
}
9、public final static int IP_TOS = 0x3;
这个参数是用来控制IP头中的TOS字段的,是用来控制和优化IP包的路径的,在Socket源代码里有一个设置的方法:
/**
* Sets traffic class or type-of-service octet in the IP
* header for packets sent from this Socket.
* As the underlying network implementation may ignore this
* value applications should consider it a hint.
*
* <P> The tc <B>must</B> be in the range <code> 0 <= tc <=
* 255</code> or an IllegalArgumentException will be thrown.
* <p>Notes:
* <p> For Internet Protocol v4 the value consists of an octet
* with precedence and TOS fields as detailed in RFC 1349. The
* TOS field is bitset created by bitwise-or'ing values such
* the following :-
* <p>
* <UL>
* <LI><CODE>IPTOS_LOWCOST (0x02)</CODE></LI>
* <LI><CODE>IPTOS_RELIABILITY (0x04)</CODE></LI>
* <LI><CODE>IPTOS_THROUGHPUT (0x08)</CODE></LI>
* <LI><CODE>IPTOS_LOWDELAY (0x10)</CODE></LI>
* </UL>
* The last low order bit is always ignored as this
* corresponds to the MBZ (must be zero) bit.
* <p>
* Setting bits in the precedence field may result in a
* SocketException indicating that the operation is not
* permitted.
* <p>
* As RFC 1122 section 4.2.4.2 indicates, a compliant TCP
* implementation should, but is not required to, let application
* change the TOS field during the lifetime of a connection.
* So whether the type-of-service field can be changed after the
* TCP connection has been established depends on the implementation
* in the underlying platform. Applications should not assume that
* they can change the TOS field after the connection.
* <p>
* For Internet Protocol v6 <code>tc</code> is the value that
* would be placed into the sin6_flowinfo field of the IP header.
*
* @param tc an <code>int</code> value for the bitset.
* @throws SocketException if there is an error setting the
* traffic class or type-of-service
* @since 1.4
* @see #getTrafficClass
*/
public void setTrafficClass(int tc) throws SocketException {
if (tc < 0 || tc > 255)
throw new IllegalArgumentException("tc is not in range 0 -- 255");
if (isClosed())
throw new SocketException("Socket is closed");
getImpl().setOption(SocketOptions.IP_TOS, new Integer(tc));
}
从源代码的注释看,TOS设置了是否生效,和底层的操作系统的实现有关。应用程序无法保证TOS的变更会对socket连接产生影响。个人认为,TOS在一般情况下用不到。
10、public final static int SO_LINGER = 0x0080;
先看Socket源代码:
/**
* Enable/disable SO_LINGER with the specified linger time in seconds.
* The maximum timeout value is platform specific.
*
* The setting only affects socket close.
*
* @param on whether or not to linger on.
* @param linger how long to linger for, if on is true.
* @exception SocketException if there is an error
* in the underlying protocol, such as a TCP error.
* @exception IllegalArgumentException if the linger value is negative.
* @since JDK1.1
* @see #getSoLinger()
*/
public void setSoLinger(boolean on, int linger) throws SocketException {
if (isClosed())
throw new SocketException("Socket is closed");
if (!on) {
getImpl().setOption(SocketOptions.SO_LINGER, new Boolean(on));
} else {
if (linger < 0) {
throw new IllegalArgumentException("invalid value for SO_LINGER");
}
if (linger > 65535)
linger = 65535;
getImpl().setOption(SocketOptions.SO_LINGER, new Integer(linger));
}
}
这个字段对Socket的close方法产生影响,当这个字段设置为false时,close会立即执行并返回,如果这时仍然有未被送出的数据包,那么这些数据包将被丢弃。如果设置为True时,有一个延迟时间可以设置。这个延迟时间就是close真正执行所有等待的时间,最大为65535。
11、public final static int SO_TIMEOUT = 0x1006;
/**
* Enable/disable SO_TIMEOUT with the specified timeout, in
* milliseconds. With this option set to a non-zero timeout,
* a read() call on the InputStream associated with this Socket
* will block for only this amount of time. If the timeout expires,
* a <B>java.net.SocketTimeoutException</B> is raised, though the
* Socket is still valid. The option <B>must</B> be enabled
* prior to entering the blocking operation to have effect. The
* timeout must be > 0.
* A timeout of zero is interpreted as an infinite timeout.
* @param timeout the specified timeout, in milliseconds.
* @exception SocketException if there is an error
* in the underlying protocol, such as a TCP error.
* @since JDK 1.1
* @see #getSoTimeout()
*/
public synchronized void setSoTimeout(int timeout) throws SocketException {
if (isClosed())
throw new SocketException("Socket is closed");
if (timeout < 0)
throw new IllegalArgumentException("timeout can't be negative");
getImpl().setOption(SocketOptions.SO_TIMEOUT, new Integer(timeout));
}
这个参数用来控制客户端读取socket数据的超时时间,如果timeout设置为0,那么就一直阻塞,否则阻塞直到超时后直接抛超时异常。
12、public final static int SO_SNDBUF = 0x1001;
在默认情况下,输出流的发送缓冲区是8096个字节(8K)。这个值是Java所建议的输出缓冲区的大小。如果这个默认值不能满足要求,可以用setSendBufferSize方法来重新设置缓冲区的大小。
13、public final static int SO_RCVBUF = 0x1002;
在默认情况下,输入流的接收缓冲区是8096个字节(8K)。这个值是Java所建议的输入缓冲区的大小。如果这个默认值不能满足要求,可以用setReceiveBufferSize方法来重新设置缓冲区的大小。
14、public final static int SO_KEEPALIVE = 0x0008;
如果将这个参数这是为True,客户端每隔一段时间(一般不少于2小时)就像服务器发送一个试探性的数据包,服务器一般会有三种回应:
1、服务器正常回一个ACK,这表明远程服务器一切OK,那么客户端不会关闭连接,而是再下一个2小时后再发个试探包。
2、服务器返回一个RST,这表明远程服务器挂了,这时候客户端会关闭连接。
3、如果服务器未响应这个数据包,在大约11分钟后,客户端Socket再发送一个数据包,如果在12分钟内,服务器还没响应,那么客户端Socket将关闭。
15、public final static int SO_OOBINLINE = 0x1003;
如果这个Socket选项打开,可以通过Socket类的sendUrgentData方法向服务器发送一个单字节的数据。这个单字节数据并不经过输出缓冲区,而是立即发出。虽然在客户端并不是使用OutputStream向服务器发送数据,但在服务端程序中这个单字节的数据是和其它的普通数据混在一起的。因此,在服务端程序中并不知道由客户端发过来的数据是由OutputStream还是由sendUrgentData发过来的。