关闭

细说Java Socket中的setSoLinger方法

标签: SocketsetSoLinger
1465人阅读 评论(0) 收藏 举报
分类:

在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状态


0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:1725828次
    • 积分:12260
    • 等级:
    • 排名:第1161名
    • 原创:207篇
    • 转载:16篇
    • 译文:2篇
    • 评论:226条