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