socket通信句柄泄漏与read阻塞

本文通过分析一个简单的Java Socket客户端和服务端程序,展示了当流中无数据时,`read()`操作会阻塞,导致客户端和服务端堵塞在特定位置。服务端在第二次尝试`read()`时因无数据而阻塞,客户端则因服务端未响应而堵塞。解决句柄泄漏的关键在于正确关闭Socket、输入输出流。添加对关闭操作的注释可以避免句柄泄漏,确保资源释放。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

客户端程序
public class SocketClient {
 public static void main(String[] args) throws Exception {
  while(true){
   Thread.sleep(5000);
   new ClientThread().start();
  }
 }
}

class ClientThread extends Thread {
 public void run() {
  Socket socket = null;
  InputStream in = null;
  OutputStream out = null;
  try {
   socket = new Socket("10.1.24.147", 8888);
   in = socket.getInputStream();
   out = socket.getOutputStream();
   System.out.println("Established a connection...");
   String text = "B7A30350BE4F9F58482576B600239D54?Open";
   byte[] block = new byte[500];
   System.out.println("begin to write");
   out.write(text.getBytes());
   out.flush();
   ByteArrayOutputStream bOut = new ByteArrayOutputStream();
   System.out.println("begin to read");
   int num = in.read(block);  //A
   while (num > 0) {
    System.out.println("in while");
    bOut.write(block, 0, num);
    num = in.read(block);
   }
   System.out.println("read finished");
   System.out.println(bOut.toString());
  } catch (Exception e) {
   e.printStackTrace();
  }finally {
   try {
    //out.close();   //B
   } catch (Exception e) {
   }
   try {
    //in.close();    //B
   } catch (Exception e) {
   }
   try {
    //socket.close(); //B
   } catch (Exception e) {
   }
  }
 }
}

服务端程序
public class SocketServer {
 public SocketServer() {
  try {
   ServerSocket server = null;
   try {
    server = new ServerSocket(8888);
    System.out.println("Server starts...port = "+server.getLocalPort());
   } catch (Exception e) {
    System.out.println("Can not listen to. " + e);
   }
   while (true) {
    new ServerThread(server.accept()).start();
    System.out.println("server.accept() starts...");
   }
  } catch (Exception e) {
   System.out.println("Error. " + e);
  }
 }

 public static void main(String[] args) {
  new SocketServer();
 }
}

class ServerThread extends Thread {
 Socket socket = null;

 public ServerThread(Socket socket) {
  this.socket = socket;
 }

 public void run() {
  InputStream in = null;
  OutputStream out = null;
  try {
   in = socket.getInputStream();
   out = socket.getOutputStream();
   byte[] block = new byte[500];
   ByteArrayOutputStream bOut = new ByteArrayOutputStream();
   System.out.println("begin to read");
   int num = in.read(block);
   while (num > 0) {
    System.out.println("in while");
    bOut.write(block, 0, num);
    System.out.println("in while,begin to read");
    num = in.read(block);    //H
   }
   System.out.println("read finished");
   System.out.println(bOut.toString());
   System.out.println("begin to write");   
   out.write(bOut.toString().getBytes());
   out.flush();
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   try {
    //out.close();    //I
   } catch (Exception e) {
   }
   try {
    //in.close();     //I
   } catch (Exception e) {
   }
   try {
    //socket.close(); //I
   } catch (Exception e) {
   }
  }
 }
}
观察到的现象
服务端读取不到客户端发送的数据;客户端也读取不到服务端应答的数据。
客户端堵在A处,服务端堵在H处。
服务端阻塞在H处,服务端已经read了一次,并取到数据,准备read第二次的时候,由于流中已经没有数据,而read总是尝试读取更多的数据,所以堵在H处。
客户端堵在A处,是因为服务端没有写数据,因此流中没有数据可读。

也就是说,当socket流中没有数据的时候,read会阻塞,并等待超时,如果没有设置超时,就永远等待(这和读取文件不同)。

Client-->Server
如果server连不上,Client只会占用一个句柄,不管Client发起了多少个连接。
java    12827  oms    6u  sock    0,0          168883381 can't identify protocol

如果server连上了,Client每次连接只要不关闭就会一直占用一个句柄,lsof结果如下:
java    11544  oms  113u  IPv4 168861112                TCP TEST3050:35686->10.1.24.147:8888 (ESTABLISHED)
java    11544  oms  114u  IPv4 168862353                TCP TEST3050:35687->10.1.24.147:8888 (ESTABLISHED)

如果此时Server端进程杀掉,Client端lsof结果如下:
java    11544  oms  180u  sock       0,0          168845122 can't identify protocol
java    11544  oms  181u  sock       0,0          168845124 can't identify protocol
由于client没有关闭socket,导致连接泄漏。

如果去掉B处的注释,即使不去掉I处的注释,客户端的句柄也都释放了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值