Java Socket 几个重要的TCP/IP选项解析(一)
2014-10-09 10:48
363 查看
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; |
1. TCP_NODELAY
public boolean getTcpNoDelay() throws SocketException public void setTcpNoDelay(boolean on) throws SocketException |
这种算法虽然可以有效地改善网络传输的效率,但对于网络速度比较慢,而且对实现性的要求比较高的情况下(如游戏、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 |
这种结果是正确的。因为第一个连接不是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实现不支持SO_LINGER都会抛出SocketException例外。当给linger参数传递负数值时,setSoLinger还会抛出一个IllegalArgumentException例外。可以通过getSoLinger方法得到延迟关闭的时间,如果返回-1,则表明SO_LINGER是关闭的。例如,下面的代码将延迟关闭的时间设为1分钟:
if(socket.getSoLinger() == -1) socket.setSoLinger(true, 60); |
public int getSoTimeout() throws SocketException public void setSoTimeout(int timeout) throws SocketException |
如果将timeout设为0,就意味着read将会无限等待下去,直到服务端程序关闭这个Socket.这也是timeout的默认值。如下面的语句将读取数据超时设为30秒:
socket1.setSoTimeout(30 * 1000); |
5. SO_SNDBUF
public int getSendBufferSize() throws SocketException public void setSendBufferSize(int size) throws SocketException |
如果底层的Socket实现不支持SO_SENDBUF选项,这两个方法将会抛出SocketException例外。必须将size设为正整数,否则setSendBufferedSize方法将抛出IllegalArgumentException例外。
6. SO_RCVBUF
public int getReceiveBufferSize() throws SocketException public void setReceiveBufferSize(int size) throws SocketException |
如果底层的Socket实现不支持SO_RCVBUF选项,这两个方法将会抛出SocketException例外。必须将size设为正整数,否则setReceiveBufferSize方法将抛出IllegalArgumentException例外。
7. SO_KEEPALIVE
public boolean getKeepAlive() throws SocketException public void setKeepAlive(boolean on) throws SocketException |
socket1.setKeepAlive(true); |
public boolean getOOBInline() throws SocketException public void setOOBInline(boolean on) throws SocketException |
public void sendUrgentData(int data) throws IOException |
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(); } } |
测试
由于本例使用了127.0.0.1,因Server和Client类必须在同一台机器上运行。
运行Server
java mynet.Server |
java mynet.Client |
服务器已经启动,端口号:1234 ABChello world 中国 |
![](http://java.chinaitlab.com/UploadFiles_8734/200906/20090609120912119.jpg)
图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 Socket 几个重要的TCP/IP选项解析(二)
- 几个重要的TCP/IP选项解析(Java Socket)
- Java Socket 几个重要的TCP/IP选项解析(一)
- 几个重要的TCP/IP选项解析(Java Socket)
- Java Socket 几个重要的TCP/IP选项解析
- 几个重要的TCP/IP选项解析(Java Socket)
- Java Socket 几个重要的TCP/IP选项解析
- 几个重要的TCP/IP选项解析(Java Socket)
- Java Socket 几个重要的TCP/IP选项解析(二)
- TCP/IP选项解析(Java Socket)
- Linux下高性能网络编程中的几个TCP/IP选项_SO_REUSEADDR、SO_RECVBUF、SO_SNDBUF、SO_KEEPALIVE、SO_LINGER、TCP_CORK、TCP_NODE
- Linux下高性能网络编程中的几个TCP/IP选项
- TCP/IP - 几个重要数据结构
- Linux下高性能网络编程中的几个TCP/IP选项
- Linux下高性能网络编程中的几个TCP/IP选项_SO_REUSEADDR、SO_RECVBUF、SO_SNDBUF、SO_KEEPALIVE、SO_LINGER、TCP_CORK、TCP_NODE
- TCP/IP - 2.1 本章几个重要数据结构
- Linux下高性能网络编程中的几个TCP/IP选项
- Linux下高性能网络编程中的几个TCP/IP选项
- Linux下高性能网络编程中的几个TCP/IP选项
- Linux下高性能网络编程中的几个TCP/IP选项_SO_REUSEADDR、SO_RECVBUF、SO_SNDBUF、SO_KEEPALIVE、SO_LINGER、TCP_CORK、TCP_NODE