JavaSocket编程—细说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包关闭连接


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

客户端代码如下:


   
   
  1. package com.test.client;
  2. import java.io.OutputStream;
  3. import java.net.InetSocketAddress;
  4. import java.net.Socket;
  5. import java.net.SocketAddress;
  6. import java.text.SimpleDateFormat;
  7. import java.util.Date;
  8. public class Client {
  9. private static int port = 9999;
  10. private static String host = “192.168.52.131”;
  11. // private static String host = “127.0.0.1”;
  12. public static SimpleDateFormat sdf = new SimpleDateFormat(
  13. “yy-MM-dd HH:mm:ss.SSS”);
  14. public static void main(String[] args) throws Exception {
  15. Socket socket = new Socket();
  16. // CASE 1 : default setting
  17. socket.setSoLinger( false, 0);
  18. // CASE 2 :
  19. // socket.setSoLinger(true, 0);
  20. // CASE 3 :
  21. // socket.setSoLinger(true, 1);
  22. SocketAddress address = new InetSocketAddress(
  23. Client.host, Client.port);
  24. socket.connect(address);
  25. OutputStream output = socket.getOutputStream();
  26. StringBuilder strB = new StringBuilder();
  27. for( int i = 0 ; i < 10000000 ; i++ ){
  28. strB.append( “a”);
  29. }
  30. byte[] request = strB.toString().getBytes( “utf-8”);
  31. System.out.println( “Client before write : “ + sdf.format( new Date()));
  32. output.write(request);
  33. System.out.println( “Client after write : “ + sdf.format( new Date()));
  34. socket.close();
  35. System.out.println( “Client after close : “ + sdf.format( new Date()));
  36. }
  37. }

服务端代码如下:


   
   
  1. package com.test.server;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.InputStream;
  4. import java.net.InetSocketAddress;
  5. import java.net.ServerSocket;
  6. import java.net.Socket;
  7. import java.text.SimpleDateFormat;
  8. import java.util.Date;
  9. public class Server {
  10. private static int queueSize = 10;
  11. private static int port = 9999;
  12. public static SimpleDateFormat sdf = new SimpleDateFormat(
  13. "yy-MM-dd HH:mm:ss.SSS");
  14. public static void main(String[] args) throws Exception {
  15. ServerSocket serverSocket = new ServerSocket();
  16. serverSocket.setReuseAddress( true);
  17. serverSocket.setReceiveBufferSize( 128* 1024);
  18. serverSocket.bind( new InetSocketAddress(Server.port),
  19. Server.queueSize);
  20. Socket socket = null;
  21. while( true){
  22. socket = serverSocket.accept();
  23. InputStream input = socket.getInputStream();
  24. ByteArrayOutputStream output = new ByteArrayOutputStream();
  25. byte[] buffer = new byte[ 1024];
  26. int length = - 1;
  27. Thread.sleep( 10* 1000);
  28. System.out.println( "Server before read : " + sdf.format( new Date()));
  29. while((length = input.read(buffer)) != - 1){
  30. output.write(buffer, 0, length);
  31. }
  32. System.out.println( "Server after read : " + sdf.format( new Date()));
  33. String req = new String(output.toByteArray(), "utf-8");
  34. System.out.println(req.length());
  35. socket.close();
  36. }
  37. }
  38. }


CASE 1

客户端运行结果:


   
   
  1. Client before write : 16 -09-01 16 :49 :45.396
  2. Client after write : 16 -09-01 16 :49 :55.307
  3. Client after close : 16 -09-01 16 :49 :55.307

服务端运行结果:


   
   
  1. Server before read : 16 -09-01 16 :49 :55.558
  2. Server after read : 16 -09-01 16 :49 :55.680
  3. 10000000

抓包结果如下:

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

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


CASE 2

客户端运行结果:


   
   
  1. Client before write : 16 -09-01 17 :34 :37.019
  2. Client after write : 16 -09-01 17 :34 :46.876
  3. Client after close : 16 -09-01 17 :34 :46.877

服务端运行结果:


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

抓包结果如下:

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

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

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

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

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

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


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

然后再次抓包观察

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

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

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


CASE 3

客户端运行结果:


   
   
  1. Client before write : 16 -09-01 18 :37 :30.523
  2. Client after write : 16 -09-01 18 :37 :40.419
  3. Client after close : 16 -09-01 18 :37 :40.424

服务端运行结果:


   
   
  1. Server before read : 16 -09-01 18 :37 :40.655
  2. Server after read : 16 -09-01 18 :37 :40.786
  3. 10000000

抓包结果如下:

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

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

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


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



猜你喜欢

转载自blog.csdn.net/liujian8654562/article/details/82699989