ServerSocket
类有以下三个选项:
1. SO_TIMEOUT
:
设置accept
方法的超时时间。
2. SO_REUSEADDR
:
设置服务端同一个端口是否可以多次绑定。
3. SO_RECBUF
:
设置接收缓冲区的大小。
一、SO_TIMEOUT
选项
可以通过SeverSocket
类的两个方法(setSoTimeout
和getSoTimeout)
来设置和获得SO_TIMEOUT
选项的值,这两个方法的定义如下:
public
synchronized
void
setSoTimeout(
int
timeout)
throws
SocketException
public synchronized int getSoTimeout() throws IOException
public synchronized int getSoTimeout() throws IOException
setSoTimeout
方法的timeout
参数表示accept
方法的超时时间,单位是毫秒。在通常情况下,ServerSocket
类的accept
方法在等待客户端请求时处于无限等待状态。如HTTP
服务器在没有用户访问网页时会一直等待用户的请求。一般不需要对服务端设置等待客户端请求超时,但在某些特殊情况下,服务端规定客户端必须在一定时间内向服务端发出请求,这时就要设置等待客户端请求超时,也就是accept
方法的超时时间。当设置客户端请求超时后,accept
方法在等待超时时间后抛出一个SocketTimeoutException
异常。下面的代码
演示了如何设置和获得SO_TIMEOUT
选项的值,超时时间通过命令行参数方式传入AcceptTimeout
。
package
server;
import java.net. * ;
public class AcceptTimeout
{
public static void main(String[] args) throws Exception
{
if (args.length == 0 )
return ;
ServerSocket serverSocket = new ServerSocket( 1234 );
int timeout = Integer.parseInt(args[ 0 ]);
serverSocket.setSoTimeout(Integer.parseInt(args[ 0 ]));
System.out.println((timeout > 0 ) ? " accept方法将在 "
+ serverSocket.getSoTimeout() + " 毫秒后抛出异常! " : " accept方法永远阻塞! " );;
serverSocket.accept();
}
}
import java.net. * ;
public class AcceptTimeout
{
public static void main(String[] args) throws Exception
{
if (args.length == 0 )
return ;
ServerSocket serverSocket = new ServerSocket( 1234 );
int timeout = Integer.parseInt(args[ 0 ]);
serverSocket.setSoTimeout(Integer.parseInt(args[ 0 ]));
System.out.println((timeout > 0 ) ? " accept方法将在 "
+ serverSocket.getSoTimeout() + " 毫秒后抛出异常! " : " accept方法永远阻塞! " );;
serverSocket.accept();
}
}
执行下面的命令:
java server.AcceptTimeout
3000
运行结果:
accept方法将在3000毫秒后抛出异常!
Exception in thread " main " java.net.SocketTimeoutException: Accept timed out
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java: 384 )
at java.net.ServerSocket.implAccept(ServerSocket.java: 450 )
at java.net.ServerSocket.accept(ServerSocket.java: 421 )
at chapter5.AcceptTimeout.main(AcceptTimeout.java: 16 )
Exception in thread " main " java.net.SocketTimeoutException: Accept timed out
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java: 384 )
at java.net.ServerSocket.implAccept(ServerSocket.java: 450 )
at java.net.ServerSocket.accept(ServerSocket.java: 421 )
at chapter5.AcceptTimeout.main(AcceptTimeout.java: 16 )
setSoTimeout
方法可以在ServerSocket
对象绑定端口之前调用,也以在绑定端口之后调用。如下面的代码也是正确的:
ServerSocket serverSocket
=
new
ServerSocket();
serverSocket.setSoTimeout( 3000 );
serverSocket.bind( new InetSocketAddress( 1234 ));
serverSocket.setSoTimeout( 3000 );
serverSocket.bind( new InetSocketAddress( 1234 ));
st1":*{behavior:url(#ieooui) }
二、
SO_REUSEADDR
选项
SO_REUSEADDR
选项决定了一个端口是否可以被绑定多次。可以通过SeverSocket
类的两个方法(setReuseAddres
和getReuseAddress)
来设置和获得SO_TIMEOUT
选项的值,这两个方法的定义如下:
public
void
setReuseAddress(
boolean
on)
throws
SocketException
public boolean getReuseAddress() throws SocketException
public boolean getReuseAddress() throws SocketException
在大多数操作系统中都不允许一个端口被多次绑定。如果一个ServerSocket
对象绑定了已经被占用的端口,那么ServerSocket
的构造方法或bind
方法就会抛出一个BindException
异常。
Java
提供这个选项的主要目的是为了防止由于频繁绑定释放一个固定端口而使系统无法正常工作。当ServerSocket
对象关闭后,如果ServerSocket
对象中仍然有未处理的数据,那么它所绑定的端口可能在一段时间内不会被释放。这就会造成其他的ServerSocket
对象无法绑定这个端口。在设置这个选项时,如果某个端口是第一次被绑定,无需调用setReuseAddress
方法,而再次绑定这个端口时,必须使用setReuseAddress
方法将这个选项设为true
。而且这个方法必须在调用bind
方法之前调用。下面的代码
演示了如何设置和获得这个选项的值:
package
server;
import java.net. * ;
public class TestReuseAddr1
{
public static void main(String[] args) throws Exception
{
ServerSocket serverSocket1 = new ServerSocket( 1234 );
System.out.println(serverSocket1.getReuseAddress());
ServerSocket serverSocket2 = new ServerSocket();
serverSocket2.setReuseAddress( true );
serverSocket2.bind( new InetSocketAddress( 1234 ));
ServerSocket serverSocket3 = new ServerSocket();
serverSocket3.setReuseAddress( true );
serverSocket3.bind( new InetSocketAddress( 1234 ));
}
}
import java.net. * ;
public class TestReuseAddr1
{
public static void main(String[] args) throws Exception
{
ServerSocket serverSocket1 = new ServerSocket( 1234 );
System.out.println(serverSocket1.getReuseAddress());
ServerSocket serverSocket2 = new ServerSocket();
serverSocket2.setReuseAddress( true );
serverSocket2.bind( new InetSocketAddress( 1234 ));
ServerSocket serverSocket3 = new ServerSocket();
serverSocket3.setReuseAddress( true );
serverSocket3.bind( new InetSocketAddress( 1234 ));
}
}
运行结果:false
在上面代码中
第一次绑定端口1234
,因此,serverSocket1
对象无需设置SO_REUSEADDR
选项(这个选项在大多数操作系统上的默认值是false
)。而serverSocket2
和serverSocket3
并不是第一次绑定端口1234
,因此,必须设置这两个对象的SO_REUSEADDR
值为true
。在设置SO_REUSEADDR
选项时要注意,必须在ServerSocket
对象绑定端口之前设置这个选项。
也许有的读者可能有这样的疑问。如果多个ServerSocket
对象同时绑定到一个端口上,那么当客户端向这个端口发出请求时,该由哪个ServerSocket
对象来接收客户端请求呢?在给出答案之前,让我们先看看下面的代码
的输出结果是什么。
package
server;
import java.net. * ;
public class TestReuseAddr2 extends Thread
{
String s;
public void run()
{
try
{
ServerSocket serverSocket = new ServerSocket();
serverSocket.setReuseAddress( true );
serverSocket.bind( new InetSocketAddress( 1234 ));
Socket socket = serverSocket.accept();
System.out.println(s + " : " + socket);
socket.close();
serverSocket.close();
}
catch (Exception e)
{
}
}
public TestReuseAddr2(String s)
{
this .s = s;
}
public static void main(String[] args)
{
for ( int i = 1 ; i <= 5 ; i ++ )
new TestReuseAddr2( " ServerSocket " + i).start();
}
}
import java.net. * ;
public class TestReuseAddr2 extends Thread
{
String s;
public void run()
{
try
{
ServerSocket serverSocket = new ServerSocket();
serverSocket.setReuseAddress( true );
serverSocket.bind( new InetSocketAddress( 1234 ));
Socket socket = serverSocket.accept();
System.out.println(s + " : " + socket);
socket.close();
serverSocket.close();
}
catch (Exception e)
{
}
}
public TestReuseAddr2(String s)
{
this .s = s;
}
public static void main(String[] args)
{
for ( int i = 1 ; i <= 5 ; i ++ )
new TestReuseAddr2( " ServerSocket " + i).start();
}
}
执行下面的命令:
java server.TestReuseAddr2
连续执行5次下面的命令:
telnet localhost
1234
执行结果:
ServerSocket1:Socket[addr
=/
127.0
.
0.1
,port
=
11724
,localport
=
1234
]
ServerSocket3:Socket[addr =/ 127.0 . 0.1 ,port = 11725 ,localport = 1234 ]
ServerSocket5:Socket[addr =/ 127.0 . 0.1 ,port = 11726 ,localport = 1234 ]
ServerSocket2:Socket[addr =/ 127.0 . 0.1 ,port = 11727 ,localport = 1234 ]
ServerSocket4:Socket[addr =/ 127.0 . 0.1 ,port = 11728 ,localport = 1234 ]
ServerSocket3:Socket[addr =/ 127.0 . 0.1 ,port = 11725 ,localport = 1234 ]
ServerSocket5:Socket[addr =/ 127.0 . 0.1 ,port = 11726 ,localport = 1234 ]
ServerSocket2:Socket[addr =/ 127.0 . 0.1 ,port = 11727 ,localport = 1234 ]
ServerSocket4:Socket[addr =/ 127.0 . 0.1 ,port = 11728 ,localport = 1234 ]
上面的运行结果只是一种可能,如果多次按着上面的步骤操作,可能得到不同的运行结果。由此可以断定,当多个ServerSocket
对象同时绑定一个端口时,系统会随机选择一个ServerSocket
对象来接收客户端请求。但要注意,这个接收客户端请求的ServerSocket
对象必须关闭(如019
行如示),才能轮到其他的ServerSocket
对象接收客户端请求。如果不关闭这个ServerSocket
对象,那么其他的ServerSocket
对象将永远无法接收客户端请求。读者可以将
serverSocket.close()
去掉,再执行上面操作步骤,看看会有什么结果。
st1":*{behavior:url(#ieooui) } 三、 SO_RCVBUF 选项
st1":*{behavior:url(#ieooui) } 三、 SO_RCVBUF 选项
可以通过SeverSocket
类的两个方法(setReceiveBufferSize
和getReceiveBufferSize)
来设置和获得SO_RCVBUF
选项的值,这两个方法的定义如下:
public
synchronized
void
setReceiveBufferSize (
int
size)
throws
SocketException
public synchronized int getReceiveBufferSize() throws SocketException
public synchronized int getReceiveBufferSize() throws SocketException
其中size参数表示接收缓冲区的大小,单位是字节。设置了ServerSocket类的SO_RCVBUF选项,就相当于设置了Socket对象的接收缓冲区大小。这个Socket对象是由accept返回的。下面积代码演示了如何使用这两个方法来设置和获得接收缓冲区的大小:
package
server;
import java.net. * ;
public class TestReceiveBufferSize
{
public static void main(String[] args) throws Exception
{
ServerSocket serverSocket = new ServerSocket( 1234 );
serverSocket.setReceiveBufferSize( 2048 ); // 将接收缓冲区设为2K
while ( true )
{
Socket socket = serverSocket.accept();
// 如果客户端请求使用的是本地IP地址,重新将Socket对象的接
// 收缓冲区设为1K
if (socket.getInetAddress().isLoopbackAddress())
socket.setReceiveBufferSize( 1024 );
System.out.println( " serverSocket: "
+ serverSocket.getReceiveBufferSize());
System.out.println( " socket: " + socket.getReceiveBufferSize());
socket.close();
}
}
}
import java.net. * ;
public class TestReceiveBufferSize
{
public static void main(String[] args) throws Exception
{
ServerSocket serverSocket = new ServerSocket( 1234 );
serverSocket.setReceiveBufferSize( 2048 ); // 将接收缓冲区设为2K
while ( true )
{
Socket socket = serverSocket.accept();
// 如果客户端请求使用的是本地IP地址,重新将Socket对象的接
// 收缓冲区设为1K
if (socket.getInetAddress().isLoopbackAddress())
socket.setReceiveBufferSize( 1024 );
System.out.println( " serverSocket: "
+ serverSocket.getReceiveBufferSize());
System.out.println( " socket: " + socket.getReceiveBufferSize());
socket.close();
}
}
}
执行如下命令:
java server.TestReceiveBufferSize
执行如下三个命令 (192.168.18.100为本机IP地址):
telnet
192.168
.
18.100
1234
telnet localhost 1234
telnet 192.168 . 18.100 1234
telnet localhost 1234
telnet 192.168 . 18.100 1234
运行结果:
serverSocket:
2048
socket: 2048
serverSocket: 2048
socket: 1024
serverSocket: 2048
socket: 2048
socket: 2048
serverSocket: 2048
socket: 1024
serverSocket: 2048
socket: 2048
从上面的运行结果可以看出,在执行telnet localhost 1234
命令后,由于localhost
是本地地址,因此程序通过
将Socket
对象的接收缓冲区设为1024
,而在执行其他两条命令后,由于192.168.18.100
不是本机地址,所以Socket
对象的接收缓冲区仍然保留着serverSocket
的值:2048
。因此,我们可以得出一个结论,设置ServerSocket
对象的接收缓冲区就相当于设置了所有从accept
返回的Socket
对象的接收缓冲区,只要不单独对某个Socket
对象重新设置,这些Socket
对象的接收缓冲区就会都保留这个值。
无论在ServerSocket
对象绑定到端口之前还是之后设置SO_RCVBUF
选项都有效,但如果要设置大于64K
的接收缓冲区时,就必须在ServerSocket
对象绑定端口之前设置SO_RCVBUF
选项。如下面的代码将接收缓冲区的大小设为100K
。
ServerSocket serverSocket
=
new
ServereSocket();
serverSocket. setReceiveBufferSize( 100 * 1024 ); // 将接收缓冲区的大小设为100K。
serverSocket.bind( new InetSocketAddress( 1234 ));
serverSocket. setReceiveBufferSize( 100 * 1024 ); // 将接收缓冲区的大小设为100K。
serverSocket.bind( new InetSocketAddress( 1234 ));
st1":*{behavior:url(#ieooui) }
一般情况下,并不需要设置这个选项,它的默认值(一般为8K
)足可以满足大多数情况。但有时为了适应特殊的需要,必须更改接收缓冲区的值。如在一些网络游戏中,需要实时地向服务器传送各种动作、指令信息。这就需要将接收缓冲区设小一点。这样可以在一定程度上增加游戏客户端的灵敏度。如果需要传送大量的数据,如HTTP
、FTP
等协议。这就需要较大的接收缓冲区。
四、设置 ServerSocket 的性能偏好
四、设置 ServerSocket 的性能偏好
在Java SE5.0及以上版本
中为ServerSocket
类增加了一个setPerformancePreferences
方法。这个和方法和Socket
类中的setPerformancePreferences
的作用一样,用来设置连接时间、延迟和带宽的相对重要性。setPerformancePerferences
方法的定义如下:
public
void
setPerformancePreferences(
int
connectionTime,
int
latency,
int
bandwidth)