二、用于获得和设置Socket选项的getter和setter方法
Socket选择可以指定Socket类发送和接受数据的方式。在JDK1.4中共有8个Socket选择可以设置。这8个选项都定义在java.net.SocketOptions接口中。定义如下:
public
final
static
int
TCP_NODELAY
=
0x0001
;
public
final
static
int
SO_REUSEADDR
=
0x04
;
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
;
有趣的是,这8个选项除了第一个没在SO前缀外,其他7个选项都以SO作为前缀。其实这个SO就是Socket Option的缩写;因此,在Java中约定所有以SO为前缀的常量都表示Socket选项;当然,也有例外,如TCP_NODELAY.在Socket类中为每一个选项提供了一对get和set方法,分别用来获得和设置这些选项。
1. TCP_NODELAY
public
boolean
getTcpNoDelay()
throws
SocketException
public
void
setTcpNoDelay(
boolean
on)
throws
SocketException
在默认情况下,客户端向服务器发送数据时,会根据数据包的大小决定是否立即发送。当数据包中的数据很少时,如只有1个字节,而数据包的头却有几十个字节(IP头+TCP头)时,系统会在发送之前先将较小的包合并到软大的包后,一起将数据发送出去。在发送下一个数据包时,系统会等待服务器对前一个数据包的响应,当收到服务器的响应后,再发送下一个数据包,这就是所谓的Nagle算法;在默认情况下,Nagle算法是开启的。
这种算法虽然可以有效地改善网络传输的效率,但对于网络速度比较慢,而且对实现性的要求比较高的情况下(如游戏、Telnet等),使用这种方式传输数据会使得客户端有明显的停顿现象。因此,最好的解决方案就是需要Nagle算法时就使用它,不需要时就关闭它。而使用setTcpToDelay正好可以满足这个需求。当使用setTcpNoDelay(true)将Nagle算法关闭后,客户端每发送一次数据,无论数据包的大小都会将这些数据发送出去。
2. SO_REUSEADDR
public
boolean
getReuseAddress()
throws
SocketException
public
void
setReuseAddress(
boolean
on)
throws
SocketException
通过这个选项,可以使多个Socket对象绑定在同一个端口上。其实这样做并没有多大意义,但当使用close方法关闭Socket连接后,Socket对象所绑定的端口并不一定马上释放;系统有时在Socket连接关闭才会再确认一下是否有因为延迟面未到达的数据包,这完全是在底层处理的,也就是说对用户是透明的;因此,在使用Socket类时完全不会感觉到。
这种处理机制对于随机绑定端口的Socket对象没有什么影响,但对于绑定在固定端口的Socket对象就可能会抛出“Address already in use: JVM_Bind”例外。因此,使用这个选项可以避免个例外的发生。
package
mynet;
import
java.net.
*
;
import
java.io.
*
;
public
class
Test
{
public
static
void
main(String[] args)
{
Socket socket1
=
new
Socket();
Socket socket2
=
new
Socket();
try
{
socket1.setReuseAddress(
true
);
socket1.bind(
new
InetSocketAddress(
"
127.0.0.1
"
,
88
));
System.out.println(
"
socket1.getReuseAddress():
"
+
socket1.getReuseAddress());
socket2.bind(
new
InetSocketAddress(
"
127.0.0.1
"
,
88
));
}
catch
(Exception e)
{
System.out.println(
"
error:
"
+
e.getMessage());
try
{
socket2.setReuseAddress(
true
);
socket2.bind(
new
InetSocketAddress(
"
127.0.0.1
"
,
88
));
System.out.println(
"
socket2.getReuseAddress():
"
+
socket2.getReuseAddress());
System.out.println(
"
端口88第二次绑定成功!
"
);
}
catch
(Exception e1)
{
System.out.println(e.getMessage());
}
}
}
}
上面的代码的运行结果如下:
socket1.getReuseAddress():true
error:Address already in use: JVM_Bind
socket2.getReuseAddress():true
端口88第二次绑定成功!
使用SO_REUSEADDR选项时有两点需要注意:
1. 必须在调用bind方法之前使用setReuseAddress方法来打开SO_REUSEADDR选项。因此,要想使用SO_REUSEADDR选项,就不能通过Socket类的构造方法来绑定端口。
2. 必须将绑定同一个端口的所有的Socket对象的SO_REUSEADDR选项都打开才能起作用。如在例程4-12中,socket1和socket2都使用了setReuseAddress方法打开了各自的SO_REUSEADDR选项。