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