细说Java Socket中的setSoLinger方法

原创 2016年09月05日 15:01:31

在Java Socket中,当我们调用Socket的close方法时,默认的行为是当底层网卡所有数据都发送完毕后,关闭连接

通过setSoLinger方法,我们可以修改close方法的行为

1,setSoLinger(true, 0)

当网卡收到关闭连接请求后,无论数据是否发送完毕,立即发送RST包关闭连接

2,setSoLinger(true, delay_time)

当网卡收到关闭连接请求后,等待delay_time

如果在delay_time过程中数据发送完毕,正常四次挥手关闭连接

如果在delay_time过程中数据没有发送完毕,发送RST包关闭连接


通过测试程序以及抓包文件详细的观察了一下这个方法的行为

客户端代码如下:

package com.test.client;

import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Client {
	private static int port = 9999;
	private static String host = "192.168.52.131";
//	private static String host = "127.0.0.1";
	
	public static SimpleDateFormat sdf = new SimpleDateFormat(
			"yy-MM-dd HH:mm:ss.SSS");
	
	public static void main(String[] args) throws Exception {
		Socket socket = new Socket();
//		CASE 1 : default setting
		socket.setSoLinger(false, 0);
//		CASE 2 :
//		socket.setSoLinger(true, 0);
//		CASE 3 :
//		socket.setSoLinger(true, 1);
		SocketAddress address = new InetSocketAddress(
				Client.host, Client.port);
		socket.connect(address);
		
		OutputStream output = socket.getOutputStream();
		StringBuilder strB = new StringBuilder();
		for(int i = 0 ; i < 10000000 ; i++ ){
			strB.append("a");
		}
		byte[] request = strB.toString().getBytes("utf-8");
		System.out.println("Client before write : " + sdf.format(new Date()));
		output.write(request);
		System.out.println("Client after write : " + sdf.format(new Date()));
		socket.close();
		System.out.println("Client after close : " + sdf.format(new Date()));
	}
}

服务端代码如下:

package com.test.server;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Server {
	private static int queueSize = 10;
	private static int port = 9999;
	
	public static SimpleDateFormat sdf = new SimpleDateFormat(
			"yy-MM-dd HH:mm:ss.SSS");
	
	public static void main(String[] args) throws Exception {
		ServerSocket serverSocket = new ServerSocket();
		serverSocket.setReuseAddress(true);
		serverSocket.setReceiveBufferSize(128*1024);
		serverSocket.bind(new InetSocketAddress(Server.port), 
				Server.queueSize);
		
		Socket socket = null;
		while(true){
			socket = serverSocket.accept();
			InputStream input = socket.getInputStream();
			ByteArrayOutputStream output = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int length = -1;
			Thread.sleep(10*1000);
			System.out.println("Server before read : " + sdf.format(new Date()));
			while((length = input.read(buffer)) != -1){
				output.write(buffer, 0, length);
			}
			System.out.println("Server after read : " + sdf.format(new Date()));
			String req = new String(output.toByteArray(), "utf-8");
			System.out.println(req.length());
			
			socket.close();
		}
	}
}


CASE 1

客户端运行结果:

Client before write : 16-09-01 16:49:45.396
Client after write : 16-09-01 16:49:55.307
Client after close : 16-09-01 16:49:55.307

服务端运行结果:

Server before read : 16-09-01 16:49:55.558
Server after read : 16-09-01 16:49:55.680
10000000

抓包结果如下:

虽然客户端在16:49:55.307(客户端运行结果)就已经关闭了连接,但是直到16:49:55.680107(抓包文件718行)客户端才发出FIN包开始关闭连接

即底层会等待数据发送完毕才关闭连接


CASE 2

客户端运行结果:

Client before write : 16-09-01 17:34:37.019
Client after write : 16-09-01 17:34:46.876
Client after close : 16-09-01 17:34:46.877

服务端运行结果:

Server before read : 16-09-01 17:34:47.107
Exception in thread "main" java.net.SocketException: Connection reset
	at java.net.SocketInputStream.read(SocketInputStream.java:196)
	at java.net.SocketInputStream.read(SocketInputStream.java:122)
	at java.net.SocketInputStream.read(SocketInputStream.java:108)
	at com.test.server.Server.main(Server.java:34)

抓包结果如下:

虽然客户端在17:34:46.877(客户端运行结果)就已经关闭了连接,但是从抓包文件中可以看到,在17:34:47.238493(抓包文件906行),依然有数据从客户端发往服务端

当然代码中的关闭连接请求发送至内核、内核通过驱动控制网卡关闭连接,这一系列操作也需要不少时间,因此关闭连接请求有延迟也是正常的

可以看到服务端至接收了9442408字节连接就关闭了

当网卡接收到关闭连接请求后,即便数据并没有完全发送完毕,网卡也会立即关闭连接

此时连接通过客户端发送RST包的方式强行关闭连接,而不是通过TCP的四次挥手方式正常关闭

如果数据能够发送完毕呢?修改一下客户端代码

		for(int i = 0 ; i < 100 ; i++ ){
			strB.append("a");
		}

然后再次抓包观察

可以看到这次所有数据都发送完成

但是客户端依然会发送RST包关闭连接

关键问题是,数据能不能全部发送完,这并不是我们能控制的


CASE 3

客户端运行结果:

Client before write : 16-09-01 18:37:30.523
Client after write : 16-09-01 18:37:40.419
Client after close : 16-09-01 18:37:40.424

服务端运行结果:

Server before read : 16-09-01 18:37:40.655
Server after read : 16-09-01 18:37:40.786
10000000

抓包结果如下:

从抓包结果可以看到这里是正常关闭

即当网卡收到关闭连接请求时,等待一段时间,然后关闭连接

如果在等待的过程中,数据发送完毕,则通过四次挥手的方式正常关闭连接,否则和CASE 2一样,通过发送RST包的方式强行关闭连接


有很多地方介绍可以通过setSoLinger方法避免主动关闭方处于time wait状态,其实只有通过直接发送RST包关闭连接,而不是通过正常四次挥手的方式才能避免主动关闭方处于time wait状态,但是从上面的CASE中可以看到,发送RST包的时候,数据有时是不完整的,所以不应该使用这种方式来避免主动关闭方处于time wait状态


版权声明:本文为博主原创文章,未经博主允许不得转载。

java网络编程Socket中SO_LINGER选项的用法解读

1:设置该选项: public void setSoLinger(boolean on, int seconds) throws SocketException;     读取该选项:public i...
  • woshisap
  • woshisap
  • 2011年06月30日 11:22
  • 17856

java socket参数详解:SoLinger

启用/禁用具有指定逗留时间(以秒为单位)的 SO_LINGER。最大超时值是特定于平台的。 该设置仅影响套接字关闭。默认值为-1,表示禁用。 这个Socket选项可以影响close方法的行为。在默认...

java网络编程Socket中SO_LINGER选项的用法解读

1:设置该选项: public void setSoLinger(boolean on, int seconds) throws SocketException;     读取该选项:public i...
  • woshisap
  • woshisap
  • 2011年06月30日 11:22
  • 17856

java socket参数详解:TcpNoDelay

TcpNoDelay=false,为启用nagle算法,也是默认值。 Nagle算法的立意是良好的,避免网络中充塞小封包,提高网络的利用率。但是当Nagle算法遇到delayed ACK悲剧就发生了。...
  • huang_xw
  • huang_xw
  • 2012年03月10日 18:10
  • 21326

JAVA 网络编程(5) SOCKET UDP 单播和组播,以及组播其他机器收不到报文的解决方法

UDP既可以单播也可以组播 一,单播 单播的过程为 发送方: InetAddress destAddr = InetAddress.getByName("192.168.4.199");// 发送的目...
  • kkgbn
  • kkgbn
  • 2016年08月19日 12:13
  • 1594

java基于TCP的socket数据包拆分方法

最近在做socket传送,遇到丢包的问题,困扰了好久, 看到了这篇文章,原文地址:http://suwish.com/html/java-tcp-socket-stream-packet-spl...

Java中Socket通信的基本方法

从Socket中读取数据 创建Socket连接到Server Socket chatSocket = new Socket("127.0.0.1", 5000);创建Socket的时候需要用到两个重要...
  • jyDave
  • jyDave
  • 2016年01月30日 23:03
  • 816

Java Socket通信技术收发线程互斥的解决方法

Java Socket通信技术收发线程互斥的解决方法 Java Socket通信技术在很长的时间里都在使用,在不少的程序员眼中都有很多高的评价。那么下面我们就看看如何才能掌握这门复杂的编...

Java基于socket服务实现UDP协议的方法

这篇文章主要介绍了Java基于socket服务实现UDP协议的方法,通过两个简单实例分析了java通过socket实现UDP发送与接收的技巧,需要的朋友可以参考下 示例1: 接收类: packa...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:细说Java Socket中的setSoLinger方法
举报原因:
原因补充:

(最多只允许输入30个字)