如何判断Socket的实时连接

2024年11月15日 10:28
有2个网友回答
网友(1):

  看到这个标题,估计很多人会说用socket.isConnected()或者socket.isClosed()等方法来判断就行了,但事实上这些方法都是访问socket在内存驻留的状态,当socket和服务器端建立链接后,即使socket链接断掉了,调用上面的方法返回的仍然是链接时的状态,而不是socket的实时链接状态,下面给出例子证明这一点。

  服务器端:

  package com.csc.server;
  import java.net.*;
  /**
  * @description 从这里启动一个服务端监听某个端口
  * @author csc
  */
  public class DstService {
  public static void main(String[] args) {
  try {
  // 启动监听端口 30000
  ServerSocket ss = new ServerSocket(30000);
  // 没有连接这个方法就一直堵塞
  Socket s = ss.accept();
  // 将请求指定一个线程去执行
  new Thread(new DstServiceImpl(s)).start();
  } catch (Exception e) {
  e.printStackTrace();
  }
  }
  }
  这里我设置了启动新线程来管理建立的每一个socket链接,此处我们设置收到链接后10秒端来链接,代码如下:
  package com.csc.server;
  import java.net.Socket;
  /**
  * @description 服务的启动的线程类
  * @author csc
  */
  public class DstServiceImpl implements Runnable {
  Socket socket = null;
  public DstServiceImpl(Socket s) {
  this.socket = s;
  }
  public void run() {
  try {
  int index = 1;
  while (true) {
  // 5秒后中断连接
  if (index > 10) {
  socket.close();
  System.out.println("服务端已经关闭链接!");
  break;
  }
  index++;
  Thread.sleep(1 * 1000);//程序睡眠1秒钟
  }
  } catch (Exception e) {
  e.printStackTrace();
  }
  }
  }
  以上是服务端代码,下面写一个客户端代码来测试:
  package com.csc.client;
  import java.net.*;
  /**
  * @description 客户端打印链接状态
  * @author csc
  */
  public class DstClient {
  public static void main(String[] args) {
  try {
  Socket socket = new Socket("127.0.0.1", 8001);
  socket.setKeepAlive(true);
  socket.setSoTimeout(10);
  while (true) {
  System.out.println(socket.isBound());
  System.out.println(socket.isClosed());
  System.out.println(socket.isConnected());
  System.out.println(socket.isInputShutdown());
  System.out.println(socket.isOutputShutdown());
  System.out.println("------------我是分割线------------");
  Thread.sleep(3 * 1000);
  }
  } catch (Exception e) {
  e.printStackTrace();
  }
  }
  }
  先运行服务端代码,再运行客户端代码,我们会在客户端代码的控制台看到如下信息:
  true
  false
  true
  false
  false
  ------------我是分割线------------
  从连接对象的属性信息来看,连接是没有中断,但实际链接已经在服务端建立链接10秒后断开了。这说明了上述几个方法是不能实时判断出socket的链接状态,只是socket驻留在内存的状态。其实,此时如果调用流去读取信息的话,就会出现异常。
  其实,想要判断socket是否仍是链接状态,只要发一个心跳包就行了,如下一句代码:

  socket.sendUrgentData(0xFF); // 发送心跳包
  关于心跳包的理论可以去google一下,我给出点参考:心跳包就是在客户端和服务器间定时通知对方自己状态的一个自己定义的命令字,按照一定的时间间隔发送,类似于心跳,所以叫做心跳包。 用来判断对方(设备,进程或其它网元)是否正常运行,采用定时发送简单的通讯包,如果在指定时间段内未收到对方响应,则判断对方已经离线。用于检测TCP的异常断开。基本原因是服务器端不能有效的判断客户端是否在线,也就是说,服务器无法区分客户端是长时间在空闲,还是已经掉线的情况。所谓的心跳包就是客户端定时发送简单的信息给服务器端告诉它我还在而已。代码就是每隔几分钟发送一个固定信息给服务端,服务端收到后回复一个固定信息如果服务端几分钟内没有收到客户端信息则视客户端断开。 比如有些通信软件长时间不使用,要想知道它的状态是在线还是离线就需要心跳包,定时发包收包。发包方:可以是客户也可以是服务端,看哪边实现方便合理,一般是客户端。服务器也可以定时发心跳下去。一般来说,出于效率的考虑,是由客户端主动向服务器端发包,而不是服务器向客户端发。客户端每隔一段时间发一个包,使用TCP的,用send发,使用UDP的,用sendto发,服务器收到后,就知道当前客户端还处于“活着”的状态,否则,如果隔一定时间未收到这样的包,则服务器认为客户端已经断开,进行相应的客户端断开逻辑处理!
  既然找到了方法,我们就在测试一下,服务端代码无需改动,客户端代码如下:

  package com.csc.client;
  import java.net.*;
  /**
  * @description 客户端打印链接状态
  * @author csc
  */
  public class DstClient {
  public static void main(String[] args) {
  try {
  Socket socket = new Socket("127.0.0.1", 30000);
  socket.setKeepAlive(true);
  socket.setSoTimeout(10);
  while (true) {
  socket.sendUrgentData(0xFF); // 发送心跳包
  System.out.println("目前是处于链接状态!");
  Thread.sleep(3 * 1000);//线程睡眠3秒
  }
  } catch (Exception e) {
  e.printStackTrace();
  }
  }
  }
  重新运行客户端程序,看到控制台打印如下信息:
  目前是处于链接状态!
  目前是处于链接状态!
  目前是处于链接状态!
  java.net.SocketException: Invalid argument: sendat java.net.PlainSocketImpl.socketSendUrgentData(Native Method)at java.net.PlainSocketImpl.sendUrgentData(PlainSocketImpl.java:550)at java.net.Socket.sendUrgentData(Socket.java:928)at com.client.DstClient.main(DstClient.java:14) 这说明当执行“socket.sendUrgentData(0xFF);”这个语句时,socket链接断开了,执行失败抛出了异常。
  另外注意,心跳包只是用来检测socket的链接状态,并不会作为socket链接的通信内容,这点应当注意。

网友(2):

法一:
当recv()返回值小于等于0时,socket连接断开。但是还需要判断 errno是否等于 EINTR,如果errno == EINTR 则说明recv函数是由于程序接收到信号后返回的,socket连接还是正常的,不应close掉socket连接。
法二:
struct tcp_info info;
int len=sizeof(info);
getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);
if((info.tcpi_state==TCP_ESTABLISHED)) 则说明未断开 else 断开
法三:
若使用了select等系统函数,若远端断开,则select返回1,recv返回0则断开。其他注意事项同法一。
法四:
int keepAlive = 1; // 开启keepalive属性
int keepIdle = 60; // 如该连接在60秒内没有任何数据往来,则进行探测
int keepInterval = 5; // 探测时发包的时间间隔为5 秒
int keepCount = 3; // 探测尝试的次数.如果第1次探测包就收到响应了,则后2次的不再发.
setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
setsockopt(rs, SOL_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));
setsockopt(rs, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
setsockopt(rs, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
设置后,若断开,则在使用该socket读写时立即失败,并返回ETIMEDOUT错误