細說Java Socket中的setSoLinger方法

在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狀態


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章