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

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

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

上一篇:Java网络编程从入门到精通(28):获取ServerSocket信息的方法及FTP原理

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

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 )

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

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

二、SO_REUSEADDR选项

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

public   void  setReuseAddress( boolean  on)  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 ));
    }
}

运行结果:false

在上面代码中第一次绑定端口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对象将永远无法接收客户端请求。读者可以将 serverSocket.close()去掉,再执行上面操作步骤,看看会有什么结果。

三、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

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

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

四、设置ServerSocket的性能偏好 

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

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

下一篇: Java网络编程从入门到精通(30):定制accept方法



国内最棒的Google Android技术社区(eoeandroid),欢迎访问!

《银河系列原创教程》发布

《Java Web开发速学宝典》出版,欢迎定购

目录
相关文章
|
17天前
|
网络协议 算法 网络性能优化
C语言 网络编程(十五)套接字选项设置
`setsockopt()`函数用于设置套接字选项,如重复使用地址(`SO_REUSEADDR`)、端口(`SO_REUSEPORT`)及超时时间(`SO_RCVTIMEO`)。其参数包括套接字描述符、协议级别、选项名称、选项值及其长度。成功返回0,失败返回-1并设置`errno`。示例展示了如何创建TCP服务器并设置相关选项。配套的`getsockopt()`函数用于获取这些选项的值。
|
17天前
|
网络协议 C语言
C语言 网络编程(十三)并发的TCP服务端-以进程完成功能
这段代码实现了一个基于TCP协议的多进程并发服务端和客户端程序。服务端通过创建子进程来处理多个客户端连接,解决了粘包问题,并支持不定长数据传输。客户端则循环发送数据并接收服务端回传的信息,同样处理了粘包问题。程序通过自定义的数据长度前缀确保了数据的完整性和准确性。
|
17天前
|
网络协议 C语言
C语言 网络编程(十一)TCP通信创建流程---服务端
在服务器流程中,新增了绑定IP地址与端口号、建立监听队列及接受连接并创建新文件描述符等步骤。`bind`函数用于绑定IP地址与端口,`listen`函数建立监听队列并设置监听状态,`accept`函数则接受连接请求并创建新的文件描述符用于数据传输。套接字状态包括关闭(CLOSED)、同步发送(SYN-SENT)、同步接收(SYN-RECEIVE)和已建立连接(ESTABLISHED)。示例代码展示了TCP服务端程序如何初始化socket、绑定地址、监听连接请求以及接收和发送数据。
|
17天前
|
网络协议 C语言
C语言 网络编程(十四)并发的TCP服务端-以线程完成功能
这段代码实现了一个基于TCP协议的多线程服务器和客户端程序,服务器端通过为每个客户端创建独立的线程来处理并发请求,解决了粘包问题并支持不定长数据传输。服务器监听在IP地址`172.17.140.183`的`8080`端口上,接收客户端发来的数据,并将接收到的消息添加“-回传”后返回给客户端。客户端则可以循环输入并发送数据,同时接收服务器回传的信息。当输入“exit”时,客户端会结束与服务器的通信并关闭连接。
|
17天前
|
C语言
C语言 网络编程(八)并发的UDP服务端 以进程完成功能
这段代码展示了如何使用多进程处理 UDP 客户端和服务端通信。客户端通过发送登录请求与服务端建立连接,并与服务端新建的子进程进行数据交换。服务端则负责接收请求,验证登录信息,并创建子进程处理客户端的具体请求。子进程会创建一个新的套接字与客户端通信,实现数据收发功能。此方案有效利用了多进程的优势,提高了系统的并发处理能力。
|
17天前
|
C语言
C语言 网络编程(九)并发的UDP服务端 以线程完成功能
这是一个基于UDP协议的客户端和服务端程序,其中服务端采用多线程并发处理客户端请求。客户端通过UDP向服务端发送登录请求,并根据登录结果与服务端的新子线程进行后续交互。服务端在主线程中接收客户端请求并创建新线程处理登录验证及后续通信,子线程创建新的套接字并与客户端进行数据交换。该程序展示了如何利用线程和UDP实现简单的并发服务器架构。
|
20天前
|
算法 Java 开发者
Java 编程入门:从零到一的旅程
本文将带领读者开启Java编程之旅,从最基础的语法入手,逐步深入到面向对象的核心概念。通过实例代码演示,我们将一起探索如何定义类和对象、实现继承与多态,并解决常见的编程挑战。无论你是编程新手还是希望巩固基础的开发者,这篇文章都将为你提供有价值的指导和灵感。
|
9天前
|
Java 程序员
Java中的异常处理:从入门到精通
在Java编程的世界中,异常处理是保持程序稳定性和可靠性的关键。本文将通过一个独特的视角—把异常处理比作一场“捉迷藏”游戏—来探讨如何在Java中有效管理异常。我们将一起学习如何识别、捕捉以及处理可能出现的异常,确保你的程序即使在面对不可预见的错误时也能优雅地运行。准备好了吗?让我们开始这场寻找并解决Java异常的冒险吧!
|
20天前
|
Java 程序员 UED
Java 中的异常处理:从入门到精通
【8月更文挑战第31天】在Java编程的世界中,异常处理是保持应用稳定性的重要机制。本文将引导你理解异常的本质,学会如何使用try-catch语句来捕获和处理异常,并探索自定义异常类的魅力。我们将一起深入异常的世界,让你的代码更加健壮和用户友好。
|
20天前
|
Java 数据库连接 开发者
Java中的异常处理:从入门到精通
【8月更文挑战第31天】 在编程世界中,错误和异常就像是不请自来的客人,总是在不经意间打扰我们的程序运行。Java语言通过其异常处理机制,为开发者提供了一套优雅的“待客之道”。本文将带你走进Java异常处理的世界,从基础语法到高级技巧,再到最佳实践,让你的程序在面对意外时,也能从容不迫,优雅应对。