Socket选择可以指定Socket类发送和接受数据的方式。在JDK1.4中共有8个Socket选择可以设置。这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类中为每一个选项提供了一对get和set方法,分别用来获得和设置这些选项。
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对象绑定在同一个端口上。
正确的说明是:
如果端口忙,但TCP状态位于 TIME_WAIT ,可以重用 端口。如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息, 抛出“Address already in use: JVM_Bind”。如果你的服务程序停止后想立即重启,不等60秒,而新套接字依旧 使用同一端口,此时 SO_REUSEADDR 选项非常有用。必须意识到,此时任何非期 望数据到达,都可能导致服务程序反应混乱,不过这只是一种可能,事实上很不可能。
这个参数在Windows平台与Linux平台表现的特点不一样。在Windows平台表现的特点是不正确的, 在Linux平台表现的特点是正确的。
在Windows平台,多个Socket新建立对象可以绑定在同一个端口上,这些新连接是非TIME_WAIT状态的。这样做并没有多大意义。
在Linux平台,只有TCP状态位于 TIME_WAIT ,才可以重用 端口。这才是正确的行为。
publicclass Test { public static void main(String[] args) { try { ServerSocket socket1 = new ServerSocket(); ServerSocket socket2 = new ServerSocket(); socket1.setReuseAddress(true); socket1.bind(new InetSocketAddress("127.0.0.1", 8899)); System.out.println("socket1.getReuseAddress():" + socket1.getReuseAddress()); socket2.setReuseAddress(true); socket2.bind(new InetSocketAddress("127.0.0.1", 8899)); System.out.println("socket2.getReuseAddress():" + socket1.getReuseAddress()); } catch (Exception e) { e.printStackTrace(); } } } |
使用SO_REUSEADDR选项时有两点需要注意:
1. 必须在调用bind方法之前使用setReuseAddress方法来打开SO_REUSEADDR选项。因此,要想使用SO_REUSEADDR选项,就不能通过Socket类的构造方法来绑定端口。
2. 必须将绑定同一个端口的所有的Socket对象的SO_REUSEADDR选项都打开才能起作用。如在例程4-12中,socket1和socket2都使用了setReuseAddress方法打开了各自的SO_REUSEADDR选项。
在Windows操作系统上运行上面的代码的运行结果如下:
这种结果是不正确的。
socket1.getReuseAddress():true socket2.getReuseAddress():true |
在Linux操作系统上运行上面的代码的运行结果如下:
这种结果是正确的。因为第一个连接不是TIME_WAIT状态的,第二个连接就不能使用8899端口;
只有第一个连接是TIME_WAIT状态的,第二个连接就才能使用8899端口;
socket1.getReuseAddress():true java.net.BindException: Address already in use at java.net.PlainSocketImpl.socketBind(Native Method) at java.net.PlainSocketImpl.bind(PlainSocketImpl.java:383) at java.net.ServerSocket.bind(ServerSocket.java:328) at java.net.ServerSocket.bind(ServerSocket.java:286) at com.Test.main(Test.java:15)
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方法被阻塞时,如果设置timeout(timeout的单位是毫秒),那么系统在等待了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的参数data是int类型,但只有这个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,因Server和Client类必须在同一台机器上运行。
运行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分布在了两个字节上,但它的低字节仍然是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 CP/IP Socket编程 #### 第1章:简介 计算机网络的发展使得个人电脑不仅可以作为办公工具,还能成为娱乐中心和沟通平台。随着越来越多的人通过互联网进行互动,掌握网络编程技术变得尤为重要。本章旨在为...
《1.1TCP/IP详解卷1:协议 原书第2版》是网络通信领域的一本经典著作,深入解析了TCP/IP协议族的核心概念和技术。这本书对于理解互联网的工作原理至关重要,尤其对于Java开发者来说,熟悉TCP/IP协议有助于构建更高效...
**Socket** 类提供了创建客户端Socket的功能,它代表了一个TCP/IP连接的客户端部分。主要分为以下几部分: 1. **构造方法**: - 构造一个连接到指定主机和端口的Socket。 - 构造一个未连接的Socket。 2. **方法...
Socket TCP网络调试助手是一款强大的工具,它主要用于帮助开发者在TCP/IP网络环境中进行通信协议的调试和测试。在软件开发过程中,尤其是涉及到网络编程时,理解并掌握TCP/IP协议至关重要。这款工具提供了直观的界面...
在这个"Socket编程客户端"中,我们将会探讨以下几个关键知识点: 1. **Socket基础概念**:Socket在计算机网络中是一种接口,它允许应用程序通过网络进行通信。在TCP/IP协议栈中,Socket提供了应用层与传输层的交互...
根据提供的文件信息,我们可以深入探讨有关“IP数据包监控”的几个关键知识点,这些知识点主要集中在对IP数据包的理解、监控工具和技术的选择上。 ### 一、IP数据包的基础概念 在计算机网络中,IP(Internet ...
在Java网络编程中,我们主要涉及以下几个关键知识点: 1. **套接字(Sockets)编程**:Java的`java.net.Socket`和`ServerSocket`类是进行客户端-服务器通信的基础。源码中可能会包含如何创建和使用套接字,以及处理...
它是一个基于请求与响应模型的、无状态的、应用层协议,通常运行在TCP/IP协议之上。Java语言在开发过程中,HTTP协议的应用非常普遍,无论是客户端还是服务端,都能看到它的身影。 HTTP协议的核心概念包括以下几个...
Java的Socket编程接口提供了基础的TCP/IP通信功能,开发者可以使用这些接口实现玩家之间的实时交互。此外,NIO(非阻塞I/O)和Netty框架可以进一步优化网络性能,减少延迟,提高游戏体验。 4. **用户界面(UI)**:...
通信过程主要包括以下几个步骤: 1. **客户端**: - 创建Socket对象,指定服务器IP地址和端口号。 - 获取输出流,向服务器发送数据。 - 获取输入流,接收服务器返回的数据。 2. **服务器端**: - 创建...
FTP下载和解析涉及到的主要知识点包括以下几个方面: 1. FTP基本概念: FTP是Internet上的标准服务之一,它通过TCP/IP协议进行通信。FTP客户端和服务器之间建立连接,进行文件操作。主要有两种工作模式:主动模式...
在使用HttpClient时,我们通常会遇到以下几个关键步骤: 1. 创建HttpClient实例:通过`HttpClientBuilder`或`HttpAsyncClientBuilder`来配置HttpClient的行为,如设置连接管理器、请求处理器等。 2. 配置请求:使用`...
Java网络编程主要涵盖以下几个关键知识点: 1. **套接字(Socket)编程**:Java中的`java.net.Socket`和`ServerSocket`类是实现客户端-服务器模型的基础。Socket类代表网络上的一个端点,而ServerSocket则用于监听...
1. 数据传输:TCP/IP协议栈是基础,通过建立稳定的Socket连接,实现数据的安全传输。 2. 实时性:心跳机制确保连接有效性,定时推送或轮询策略获取最新数据,确保监控的实时性。 3. 数据解析:JSON或XML等格式用于...
在Java中实现局域网通信,我们需要以下几个步骤: 1. **服务器端**:创建一个ServerSocket实例,指定一个端口号,开始监听。例如: ```java ServerSocket serverSocket = new ServerSocket(12345); ``` 2. **...
这通常涉及以下几个方面: - **权限管理**:合理设置文件和目录的权限,避免敏感信息泄露。 - **资源限制**:对每个进程或线程的资源使用进行限制,防止服务器资源被恶意消耗。 - **异常处理**:良好的异常处理机制...
根据提供的文件信息,本文将详细解析“JAVA源码JAVA3D的网络三维技术的设计与实现”这一主题,包括JAVA3D的基本概念、网络三维技术的关键技术点、设计思路及其实现方法等内容。 ### JAVA3D简介 JAVA3D是基于Java...
D) Java语言中的多态的含义可以表达为:对外一个接口,内部多种实现。Java语言支持两种多态:运行时多态和编译时多态。 题目8:a 程序如下: public class Demo extends Base { private int count; public Demo()...
该聊天软件的核心技术主要包括以下几个方面: 1. **UDP套接字编程**:在Java中,我们使用`java.net.DatagramSocket`类来创建UDP套接字,用于发送和接收数据报。`DatagramPacket`类则用于封装要发送的数据和指定目标...