Java网络编程从入门到精通(18):Socket类的getter和setter方法(2)

简介: 本文为原创,如需转载,请注明作者和出处,谢谢! 上一篇:Java网络编程从入门到精通(17):Socket类的getter和setter方法(1) 二、用于获得和设置Socket选项的getter和setter方法 Socket选择可以指定Socket类发送和接受数据的方式。
本文为原创,如需转载,请注明作者和出处,谢谢!


上一篇:Java网络编程从入门到精通(17):Socket类的getter和setter方法(1)

二、
用于获得和设置Socket选项的gettersetter方法

Socket选择可以指定Socket类发送和接受数据的方式。在JDK1.4中共有8Socket选择可以设置。这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类中为每一个选项提供了一对getset方法,分别用来获得和设置这些选项。

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选项。

3.  SO_LINGER

public   int  getSoLinger()  throws  SocketException
public   void  setSoLinger( boolean  on,  int  linger)  throws  SocketException

这个Socket选项可以影响close方法的行为。在默认情况下,当调用close方法后,将立即返回;如果这时仍然有未被送出的数据包,那么这些数据包将被丢弃。如果将linger参数设为一个正整数n(n的值最大是65,535),在调用close方法后,将最多被阻塞n秒。在这n秒内,系统将尽量将未送出的数据包发送出去;如果超过了n秒,如果还有未发送的数据包,这些数据包将全部被丢弃;而close方法会立即返回。如果将linger设为0,和关闭SO_LINGER选项的作用是一样的。

如果底层的Socket实现不支持SO_LINGER都会抛出SocketException例外。当给linger参数传递负数值时,setSoLinger还会抛出一个IllegalArgumentException例外。可以通过getSoLinger方法得到延迟关闭的时间,如果返回-1,则表明SO_LINGER是关闭的。例如,下面的代码将延迟关闭的时间设为1分钟:

if (socket.getSoLinger()  ==   - 1 ) socket.setSoLinger( true 60 );

4.  SO_TIMEOUT

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

这个Socket选项在前面已经讨论过。可以通过这个选项来设置读取数据超时。当输入流的read方法被阻塞时,如果设置timeouttimeout的单位是毫秒),那么系统在等待了timeout毫秒后会抛出一个InterruptedIOException例外。在抛出例外后,输入流并未关闭,你可以继续通过read方法读取数据。

如果将timeout设为0,就意味着read将会无限等待下去,直到服务端程序关闭这个Socket。这也是timeout的默认值。如下面的语句将读取数据超时设为30秒:

socket1.setSoTimeout( 30   *   1000 );

当底层的Socket实现不支持SO_TIMEOUT选项时,这两个方法将抛出SocketException例外。不能将timeout设为负数,否则setSoTimeout方法将抛出IllegalArgumentException例外。

5.  SO_SNDBUF

public   int  getSendBufferSize()  throws  SocketException
public   void  setSendBufferSize( int  size)  throws  SocketException

在默认情况下,输出流的发送缓冲区是8096个字节(8K)。这个值是Java所建议的输出缓冲区的大小。如果这个默认值不能满足要求,可以用setSendBufferSize方法来重新设置缓冲区的大小。但最好不要将输出缓冲区设得太小,否则会导致传输数据过于频繁,从而降低网络传输的效率。

如果底层的Socket实现不支持SO_SENDBUF选项,这两个方法将会抛出SocketException例外。必须将size设为正整数,否则setSendBufferedSize方法将抛出IllegalArgumentException例外。

6.  SO_RCVBUF


public   int  getReceiveBufferSize()  throws  SocketException
public   void  setReceiveBufferSize( int  size)  throws  SocketException

在默认情况下,输入流的接收缓冲区是8096个字节(8K)。这个值是Java所建议的输入缓冲区的大小。如果这个默认值不能满足要求,可以用setReceiveBufferSize方法来重新设置缓冲区的大小。但最好不要将输入缓冲区设得太小,否则会导致传输数据过于频繁,从而降低网络传输的效率。

如果底层的Socket实现不支持SO_RCVBUF选项,这两个方法将会抛出SocketException例外。必须将size设为正整数,否则setReceiveBufferSize方法将抛出IllegalArgumentException例外。

7.  SO_KEEPALIVE

public   boolean  getKeepAlive()  throws  SocketException
public   void  setKeepAlive( boolean  on)  throws  SocketException

如果将这个Socket选项打开,客户端Socket每隔段的时间(大约两个小时)就会利用空闲的连接向服务器发送一个数据包。这个数据包并没有其它的作用,只是为了检测一下服务器是否仍处于活动状态。如果服务器未响应这个数据包,在大约11分钟后,客户端Socket再发送一个数据包,如果在12分钟内,服务器还没响应,那么客户端Socket将关闭。如果将Socket选项关闭,客户端Socket在服务器无效的情况下可能会长时间不会关闭。SO_KEEPALIVE选项在默认情况下是关闭的,可以使用如下的语句将这个SO_KEEPALIVE选项打开:

socket1.setKeepAlive( true );

8.  SO_OOBINLINE

  public   boolean  getOOBInline()  throws  SocketException
 
public   void  setOOBInline( boolean  on)  throws  SocketException

如果这个Socket选项打开,可以通过Socket类的sendUrgentData方法向服务器发送一个单字节的数据。这个单字节数据并不经过输出缓冲区,而是立即发出。虽然在客户端并不是使用OutputStream向服务器发送数据,但在服务端程序中这个单字节的数据是和其它的普通数据混在一起的。因此,在服务端程序中并不知道由客户端发过来的数据是由OutputStream还是由sendUrgentData发过来的。下面是sendUrgentData方法的声明:

public   void  sendUrgentData( int  data)  throws  IOException

虽然sendUrgentData的参数dataint类型,但只有这个int类型的低字节被发送,其它的三个字节被忽略。下面的代码演示了如何使用SO_OOBINLINE选项来发送单字节数据。

package mynet;

import  java.net. * ;
import  java.io. * ;

class Server
{
    
public   static   void  main(String[] args)  throws  Exception
    {
        ServerSocket serverSocket 
=   new  ServerSocket( 1234 );
        System.out.println(
" 服务器已经启动,端口号:1234 " );
        
while  ( true )
        {
            Socket socket 
=  serverSocket.accept();
            socket.setOOBInline(
true );
            InputStream in 
=  socket.getInputStream();
            InputStreamReader inReader 
=   new  InputStreamReader(in);
            BufferedReader bReader 
=   new  BufferedReader(inReader);
            System.out.println(bReader.readLine());
            System.out.println(bReader.readLine());
            socket.close();
        }
    }
}
public   class Client
{
    
public   static   void  main(String[] args)  throws  Exception
    {
        Socket socket 
=   new  Socket( " 127.0.0.1 " 1234 );
        socket.setOOBInline(
true );
        OutputStream out 
=  socket.getOutputStream();
        OutputStreamWriter outWriter 
=   new  OutputStreamWriter(out);
        outWriter.write(
67 );               //  向服务器发送字符"C"
        outWriter.write( " hello world/r/n " );
        socket.sendUrgentData(
65 );         //  向服务器发送字符"A"
        socket.sendUrgentData( 322 );         //  向服务器发送字符"B"
        outWriter.flush();
        socket.sendUrgentData(
214 );        //  向服务器发送汉字”中”
        socket.sendUrgentData( 208 );
        socket.sendUrgentData(
185 );        //  向服务器发送汉字”国”
        socket.sendUrgentData( 250 );
        socket.close();
    }
}

由于运行上面的代码需要一个服务器类,因此,在加了一个类名为Server的服务器类,关于服务端套接字的使用方法将会在后面的文章中详细讨论。在类Server类中只使用了ServerSocket类的accept方法接收客户端的请求。并从客户端传来的数据中读取两行字符串,并显示在控制台上。

测试

由于本例使用了127.0.0.1,因ServerClient类必须在同一台机器上运行。

运行Server

java mynet.Server

运行Client

java mynet.Client

在服务端控制台的输出结果

服务器已经启动,端口号: 1234
ABChello world
中国

在ClienT类中使用了sendUrgentData方法向服务器发送了字符'A'(65)'B'(66)。但发送'B'时实际发送的是322,由于sendUrgentData只发送整型数的低字节。因此,实际发送的是66。十进制整型322的二进制形式如图1所示。

 

图1  十进制整型322的二进制形式

     从图1可以看出,虽然322分布在了两个字节上,但它的低字节仍然是66。

在Client类中使用flush将缓冲区中的数据发送到服务器。我们可以从输出结果发现一个问题,在Client类中先后向服务器发送了'C'"hello world"r"n"'A''B'。而在服务端程序的控制台上显示的却是ABChello world。这种现象说明使用sendUrgentData方法发送数据后,系统会立即将这些数据发送出去;而使用write发送数据,必须要使用flush方法才会真正发送数据。

在Client类中向服务器发送"中国"字符串。由于""是由214208两个字节组成的;而""是由185250两个字节组成的;因此,可分别发送这四个字节来传送"中国"字符串。

注意:在使用setOOBInline方法打开SO_OOBINLINE选项时要注意是必须在客户端和服务端程序同时使用setOOBInline方法打开这个选项,否则无法命名用sendUrgentData来发送数据。

下一篇:Java网络编程从入门到精通(19):套接字(Socket)的异常



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

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

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

目录
相关文章
|
12天前
|
安全 Java 数据处理
Python网络编程基础(Socket编程)多线程/多进程服务器编程
【4月更文挑战第11天】在网络编程中,随着客户端数量的增加,服务器的处理能力成为了一个重要的考量因素。为了处理多个客户端的并发请求,我们通常需要采用多线程或多进程的方式。在本章中,我们将探讨多线程/多进程服务器编程的概念,并通过一个多线程服务器的示例来演示其实现。
|
12天前
|
程序员 开发者 Python
Python网络编程基础(Socket编程) 错误处理和异常处理的最佳实践
【4月更文挑战第11天】在网络编程中,错误处理和异常管理不仅是为了程序的健壮性,也是为了提供清晰的用户反馈以及优雅的故障恢复。在前面的章节中,我们讨论了如何使用`try-except`语句来处理网络错误。现在,我们将深入探讨错误处理和异常处理的最佳实践。
|
1月前
|
网络协议 算法 Java
|
1月前
|
监控 网络协议 Java
Linux 网络编程从入门到进阶 学习指南
在上一篇文章中,我们探讨了 Linux 系统编程的诸多基础构件,包括文件操作、进程管理和线程同步等,接下来,我们将视野扩展到网络世界。在这个新篇章里,我们要让应用跳出单机限制,学会在网络上跨机器交流信息。
Linux 网络编程从入门到进阶 学习指南
|
12天前
|
存储 算法 Linux
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
32 6
|
4天前
|
监控 Java 开发者
深入理解 Java 网络编程和 NIO
【4月更文挑战第19天】Java网络编程基于Socket,但NIO(非阻塞I/O)提升了效率和性能。NIO特点是非阻塞模式、选择器机制和缓冲区,适合高并发场景。使用NIO涉及通道、选择器和事件处理,优点是高并发、资源利用率和可扩展性,但复杂度、错误处理和性能调优是挑战。开发者应根据需求选择是否使用NIO,并深入理解其原理。
|
6天前
|
网络协议 Java API
深度剖析:Java网络编程中的TCP/IP与HTTP协议实践
【4月更文挑战第17天】Java网络编程重在TCP/IP和HTTP协议的应用。TCP提供可靠数据传输,通过Socket和ServerSocket实现;HTTP用于Web服务,常借助HttpURLConnection或Apache HttpClient。两者结合,构成网络服务基础。Java有多种高级API和框架(如Netty、Spring Boot)简化开发,助力高效、高并发的网络通信。
|
11天前
|
网络协议 Java API
Python网络编程基础(Socket编程)Twisted框架简介
【4月更文挑战第12天】在网络编程的实践中,除了使用基本的Socket API之外,还有许多高级的网络编程库可以帮助我们更高效地构建复杂和健壮的网络应用。这些库通常提供了异步IO、事件驱动、协议实现等高级功能,使得开发者能够专注于业务逻辑的实现,而不用过多关注底层的网络细节。
|
13天前
|
JavaScript Java 测试技术
基于Java的网络类课程思政学习系统的设计与实现(源码+lw+部署文档+讲解等)
基于Java的网络类课程思政学习系统的设计与实现(源码+lw+部署文档+讲解等)
30 0
基于Java的网络类课程思政学习系统的设计与实现(源码+lw+部署文档+讲解等)
|
15天前
|
Python
Python网络编程基础(Socket编程)UDP服务器编程
【4月更文挑战第8天】Python UDP服务器编程使用socket库创建UDP套接字,绑定到特定地址(如localhost:8000),通过`recvfrom`接收客户端数据报,显示数据长度、地址和内容。无连接的UDP协议使得服务器无法主动发送数据,通常需应用层实现请求-响应机制。当完成时,用`close`关闭套接字。