Java网络编程从入门到精通(18):Socket类的ge

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


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

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

Socket 选择可以指定 Socket 类发送和接受数据的方式。在 JDK1.4 中共有 8 Socket 选择可以设置。这 8 个选项都定义在 java.net.SocketOptions 接口中。定义如下:


<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> 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

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> 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

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> 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” 例外。因此,使用这个选项可以避免个例外的发生。

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> package mynet;

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

public class Test
{
public static void main(String[]args)
{
Socketsocket1
= new Socket();
Socketsocket2
= 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 (Exceptione)
{
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 (Exceptione1)
{
System.out.println(e.getMessage());
}
}
}
}

上面的代码的运行结果如下:

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> socket1.getReuseAddress():true
error:Addressalreadyinuse: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

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> 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 分钟:

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> if (socket.getSoLinger() == - 1 )socket.setSoLinger( true , 60 );

4. SO_TIMEOUT

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> public int getSoTimeout() throws SocketException
public void setSoTimeout( int timeout) throws SocketException

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

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

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> socket1.setSoTimeout( 30 * 1000 );

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

5. SO_SNDBUF

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> 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


<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> 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

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> public boolean getKeepAlive() throws SocketException
public void setKeepAlive( boolean on) throws SocketException

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

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> socket1.setKeepAlive( true );

8. SO_OOBINLINE

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> public boolean getOOBInline() throws SocketException
public void setOOBInline( boolean on) throws SocketException

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

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> public void sendUrgentData( int data) throws IOException

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

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> package mynet;

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

class Server
{
public static void main(String[]args) throws Exception
{
ServerSocketserverSocket
= new ServerSocket( 1234 );
System.out.println(
" 服务器已经启动,端口号:1234 " );
while ( true )
{
Socketsocket
= serverSocket.accept();
socket.setOOBInline(
true );
InputStreamin
= socket.getInputStream();
InputStreamReaderinReader
= new InputStreamReader(in);
BufferedReaderbReader
= 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
{
Socketsocket
= new Socket( " 127.0.0.1 " , 1234 );
socket.setOOBInline(
true );
OutputStreamout
= socket.getOutputStream();
OutputStreamWriteroutWriter
= new OutputStreamWriter(out);
outWriter.write(
67 ); // 向服务器发送字符"C"
outWriter.write( " helloworld\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 ,因Server Client类 必须在同一台机器上运行。

运行 Server

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> javamynet.Server

运行 Client

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> javamynet.Client

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

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> 服务器已经启动,端口号: 1234
ABChelloworld
中国

在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类中 向服务器发送 " 中国 " 字符串。由于 " " 是由 214 208 两个字节组成的;而 " " 是由 185 250 两个字节组成的;因此,可分别发送这四个字节来传送 " 中国 " 字符串。

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

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



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

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

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

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


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论