Java网络编程从入门到精通(29):服务端Socket的选项

简介: 本文为原创,如需转载,请注明作者和出处,谢谢! 上一篇:Java网络编程从入门到精通(28):获取ServerSocket信息的方法及FTP原理 ServerSocket类有以下三个选项: 1.       SO_TIMEOUT: 设置accept方法的超时时间。

本文为原创,如需转载,请注明作者和出处,谢谢!


ServerSocket类有以下三个选项:

1.      SO_TIMEOUT 设置accept方法的超时时间。

2.      SO_REUSEADDR设置服务端同一个端口是否可以多次绑定。

3.      SO_RECBUF设置接收缓冲区的大小。

一、SO_TIMEOUT选项

可以通过SeverSocket类的两个方法(setSoTimeoutgetSoTimeout)来设置和获得SO_TIMEOUT选项的值,这两个方法的定义如下:

public synchronized void setSoTimeout(int timeout) throws SocketException
public synchronized int getSoTimeout() throws IOException

<!-- /* Font Definitions */ &#64;font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1;} &#64;font-face {font-family:""&#64;宋体"; panose-1:2 1 6 0 3 1 1 1 1 1;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; font-size:10.5pt; font-family:"Times New Roman";} /* Page Definitions */ &#64;page {} &#64;page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt;} div.Section1 {page:Section1;} -->

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();
   }
}

执行下面的命令:

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)

<!-- /* Font Definitions */ &#64;font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1;} &#64;font-face {font-family:""&#64;宋体"; panose-1:2 1 6 0 3 1 1 1 1 1;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; font-size:10.5pt; font-family:"Times New Roman";} /* Page Definitions */ &#64;page {} &#64;page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt;} div.Section1 {page:Section1;} -->

setSoTimeout方法可以在ServerSocket对象绑定端口之前调用,也以在绑定端口之后调用。如下面的代码也是正确的:

ServerSocket serverSocket = new ServerSocket();
serverSocket.setSoTimeout(
3000);
serverSocket.bind(
new InetSocketAddress(1234));

<!-- /* Font Definitions */ &#64;font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1;} &#64;font-face {font-family:""&#64;宋体"; panose-1:2 1 6 0 3 1 1 1 1 1;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; font-size:10.5pt; font-family:"Times New Roman";} /* Page Definitions */ &#64;page {} &#64;page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt;} div.Section1 {page:Section1;} -->

二、SO_REUSEADDR选项

SO_REUSEADDR选项决定了一个端口是否可以被绑定多次。可以通过SeverSocket类的两个方法(setReuseAddresgetReuseAddress)来设置和获得SO_TIMEOUT选项的值,这两个方法的定义如下:

public void setReuseAddress(boolean on) throws SocketException
public boolean getReuseAddress() throws SocketException

<!-- /* Font Definitions */ &#64;font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1;} &#64;font-face {font-family:""&#64;宋体"; panose-1:2 1 6 0 3 1 1 1 1 1;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; font-size:10.5pt; font-family:"Times New Roman";} /* Page Definitions */ &#64;page {} &#64;page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt;} div.Section1 {page:Section1;} -->

在大多数操作系统中都不允许一个端口被多次绑定。如果一个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));
   }
}

运行结果:false
<!-- /* Font Definitions */ &#64;font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1;} &#64;font-face {font-family:""&#64;宋体"; panose-1:2 1 6 0 3 1 1 1 1 1;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; font-size:10.5pt; font-family:"Times New Roman";} /* Page Definitions */ &#64;page {} &#64;page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt;} div.Section1 {page:Section1;} -->

在上面代码中第一次绑定端口1234,因此,serverSocket1对象无需设置SO_REUSEADDR选项(这个选项在大多数操作系统上的默认值是false)。而serverSocket2serverSocket3并不是第一次绑定端口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();
   }
}

执行下面的命令:

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]

   上面的运行结果只是一种可能,如果多次按着上面的步骤操作,可能得到不同的运行结果。由此可以断定,当多个ServerSocket对象同时绑定一个端口时,系统会随机选择一个ServerSocket对象来接收客户端请求。但要注意,这个接收客户端请求的ServerSocket对象必须关闭(如019行如示),才能轮到其他的ServerSocket对象接收客户端请求。如果不关闭这个ServerSocket对象,那么其他的ServerSocket对象将永远无法接收客户端请求。读者可以将 <!-- /* Font Definitions */ &#64;font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1;} &#64;font-face {font-family:""&#64;宋体"; panose-1:2 1 6 0 3 1 1 1 1 1;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; font-size:10.5pt; font-family:"Times New Roman";} /* Page Definitions */ &#64;page {} &#64;page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt;} div.Section1 {page:Section1;} --> serverSocket.close()去掉,再执行上面操作步骤,看看会有什么结果。

<!-- /* Font Definitions */ &#64;font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1;} &#64;font-face {font-family:""&#64;宋体"; panose-1:2 1 6 0 3 1 1 1 1 1;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; font-size:10.5pt; font-family:"Times New Roman";} /* Page Definitions */ &#64;page {} &#64;page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt;} div.Section1 {page:Section1;} --> 三、SO_RCVBUF选项

可以通过SeverSocket类的两个方法(setReceiveBufferSizegetReceiveBufferSize)来设置和获得SO_RCVBUF选项的值,这两个方法的定义如下:

public synchronized void setReceiveBufferSize (int size) 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();
       }
   }
}


执行如下命令:

java server.TestReceiveBufferSize

执行如下三个命令 (192.168.18.100为本机IP地址):

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

<!-- /* Font Definitions */ &#64;font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1;} &#64;font-face {font-family:""&#64;宋体"; panose-1:2 1 6 0 3 1 1 1 1 1;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; font-size:10.5pt; font-family:"Times New Roman";} /* Page Definitions */ &#64;page {} &#64;page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt;} div.Section1 {page:Section1;} -->

从上面的运行结果可以看出,在执行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));

<!-- /* Font Definitions */ &#64;font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1;} &#64;font-face {font-family:""&#64;宋体"; panose-1:2 1 6 0 3 1 1 1 1 1;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; font-size:10.5pt; font-family:"Times New Roman";} /* Page Definitions */ &#64;page {} &#64;page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt;} div.Section1 {page:Section1;} -->

  一般情况下,并不需要设置这个选项,它的默认值(一般为8K)足可以满足大多数情况。但有时为了适应特殊的需要,必须更改接收缓冲区的值。如在一些网络游戏中,需要实时地向服务器传送各种动作、指令信息。这就需要将接收缓冲区设小一点。这样可以在一定程度上增加游戏客户端的灵敏度。如果需要传送大量的数据,如HTTPFTP等协议。这就需要较大的接收缓冲区。

四、设置ServerSocket的性能偏好 

Java SE5.0及以上版本中为ServerSocket类增加了一个setPerformancePreferences方法。这个和方法和Socket类中的setPerformancePreferences的作用一样,用来设置连接时间、延迟和带宽的相对重要性。setPerformancePerferences方法的定义如下:

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




目录
相关文章
|
9月前
|
存储 Python
Python网络编程基础(Socket编程)UDP客户端编程
【4月更文挑战第9天】在UDP通信中,客户端负责发送数据到服务器,并接收来自服务器的响应。与服务器不同,客户端通常不需要绑定到特定的地址和端口,因为它可以临时使用任何可用的端口来发送数据。下面,我们将详细讲解UDP客户端编程的基本步骤。
|
9月前
|
网络协议
Socket实现服务器和客户端(手把手教会)
Socket实现服务器和客户端(手把手教会)
|
网络协议 Java
netty编程实战01-创建一个tcp服务端程序
netty编程实战01-创建一个tcp服务端程序
275 0
|
域名解析 网络协议 安全
迈入JavaWeb第一步,Java网络编程基础,TCP网络编程URL网络编程等
迈入JavaWeb第一步,Java网络编程基础,TCP网络编程URL网络编程等
142 0
迈入JavaWeb第一步,Java网络编程基础,TCP网络编程URL网络编程等
|
XML JSON 网络协议
这篇文章带你读懂Socket,让你知道什么是Socket?
这篇文章带你读懂Socket,让你知道什么是Socket?
这篇文章带你读懂Socket,让你知道什么是Socket?
|
Python
Python编程:socket实现简单的网站服务器
Python编程:socket实现简单的网站服务器
351 0
Python编程:socket实现简单的网站服务器
|
Java 大数据 编译器
网络编程:socket--基于 UDP 客户端编程|学习笔记
快速学习 网络编程:socket--基于 UDP 客户端编程
160 0
|
存储 Web App开发 监控
C#Socket编程笔记,Socket 详解,入门简单
目录 一,网络基础 二,Socket 对象 三,Bind() 绑定与 Connect() 连接 四,Listen() 监听请求连接 和 Accept() 接收连接请求 五,Receive() 与 Send() 六,释放资源 七,IPAddress 和 IPEndPoint
591 0
|
网络协议 Python
Python 技术篇-socket套接字实现服务器客户端消息传递实例演示,UDP实现
Python 技术篇-socket套接字实现服务器客户端消息传递实例演示,UDP实现
277 0
Python 技术篇-socket套接字实现服务器客户端消息传递实例演示,UDP实现
|
网络协议 Python
Python 技术篇-socket套接字实现两个窗口间消息传递实例演示,TCP实现
Python 技术篇-socket套接字实现两个窗口间消息传递实例演示,TCP实现
297 0
Python 技术篇-socket套接字实现两个窗口间消息传递实例演示,TCP实现