linux C语言 socket如何判断socket_fd对应的socket是否断开?(是否连通、是否正常连接)recv()、tcp_info TCP_ESTABLISHED、keepalive

判断socket连接断开的方法

法一:判断recv()返回值

当recv()返回值小于等于0时,socket连接断开。但是还需要判断 errno是否等于 EINTR,如果errno == EINTR 则说明recv函数是由于程序接收到信号后返回的,socket连接还是正常的,不应close掉socket连接。

但是参考这篇文章的代码,程序居然被阻塞了,不知道怎么回事

参考文章:如何在C语言中判断socket是否已经断开

#include <errno.h> 
 
bool IsSocketClosed(int clientSocket) 
{ 
 char buff[32]; 
 int recvBytes = recv(clientSocket, buff, sizeof(buff), MSG_PEEK); 
 
 int sockErr = errno; 
 
 //cout << "In close function, recv " << recvBytes << " bytes, err " << sockErr << endl; 
 
 if( recvBytes > 0) //Get data 
 return false; 
 
 if( (recvBytes == -1) && (sockErr == EWOULDBLOCK) ) //No receive data 
 return false; 
 
 return true; 
} 

法二:创建tcp_info结构体,判断info.tcpi_state是否为TCP_ESTABLISHED(注意:需包含tcp.h)

  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等系统函数,若远端断开,则select返回1,recv返回0则断开。其他注意事项同法一。

法四:用keepalive属性

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错误

说明

socket心跳机制so_keepalive的三个参数详解

SO_KEEPALIVE 保持连接检测对方主机是否崩溃,避免(服务器)永远阻塞于TCP连接的输入。

设置该选项后,如果2小时内在此套接口的任一方向都没有数据交换,TCP就自动给对方 发一个保持存活探测分节(keepalive probe)。这是一个对方必须响应的TCP分节.它会导致以下三种情况:

1、对方接收一切正常:以期望的ACK响应,2小时后,TCP将发出另一个探测分节。

2、对方已崩溃且已重新启动:以RST响应。套接口的待处理错误被置为ECONNRESET,套接 口本身则被关闭。

3、对方无任何响应:源自berkeley的TCP发送另外8个探测分节,相隔75秒一个,试图得到一个响应。在发出第一个探测分节11分钟15秒后若仍无响应就放弃。套接口的待处理错误被置为ETIMEOUT,套接口本身则被关闭。如ICMP错误是“host unreachable(主机不可达)”,说明对方主机并没有崩溃,但是不可达,这种情况下待处理错误被置为 EHOSTUNREACH。

有关SO_KEEPALIVE的三个参数详细解释如下

(16)tcp_keepalive_intvl,保活探测消息的发送频率。默认值为75s。

发送频率tcp_keepalive_intvl乘以发送次数tcp_keepalive_probes,就得到了从开始探测直到放弃探测确定连接断开的时间,大约为11min。

(17)tcp_keepalive_probes,TCP发送保活探测消息以确定连接是否已断开的次数。默认值为9(次)。

注意:只有设置了SO_KEEPALIVE套接口选项后才会发送保活探测消息。

(18)tcp_keepalive_time,在TCP保活打开的情况下,最后一次数据交换到TCP发送第一个保活探测消息的时间,即允许的持续空闲时间。默认值为7200s(2h)。

法五:自己实现心跳检测

自己实现一个心跳检测,一定时间内未收到自定义的心跳包则标记为已断开。

TCP中已有SO_KEEPALIVE选项,为什么还要在应用层加入心跳包机制?
首先,我想说的是,SO_Keeplive是实现在服务器侧,客户端被动响应,缺省超时时间为120分钟,这是RFC协议标准规范。

SO_Keeplive是实现在TCP协议栈(四层),应用层的心跳实现在第七层,本质没有任何区别,但应用层需要自己来定义心跳包格式。

之所以实现在服务器侧,是因为与客户端相比,服务器侧的寿命更长,因为服务器侧需要不间断地提供服务,而客户端可能由于用户下班而合上电脑(TCP没有来得及发送FIN关闭连接),这样的话,服务器侧就会有很多不可用的TCP连接(established),这样的连接依然会占用服务器内存资源,于是就设计这个keepalive 来检测客户端是否可用,如果几次重传keepalive ,客户端没有相应,删除连接,释放资源。

需要指出的是,超时时间是指TCP连接没有任何数据、控制字传输的时间,如果有任何数据传输,会刷新定时器,重新走表。

TCP心跳是一个备受争议的实现,只是一个option,不是强制标准。

之所以应用层需要独立实现自己的心跳,是因为超时时间较长,无法给应用层提供快速的反馈。

所以类似BGP协议就独立实现了自己的keepalive,最小可以设置一秒钟,三次没有应答即可以Reset连接,最快三秒可以检测到失效。

而三秒依然太慢,可以用另外一个协议BFD来提供更快发现链路失效,最快可以配置成10ms
,三次超时(30ms)就可以完成失效检测。

区别
tcp keepalive检查连接是否存活。
应用keppalive检测应用是否正常可响应。

举个例子。服务端死锁,无法处理任何业务请求。但是操作系统仍然可以响应网络层keepalive包。

参考文章:判断socket连接断开的方法

  • 3
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在 Windows 平台下开发 TCP 服务器,可以使用 `select()` 函数来判断客户端是否断开连接。`select()` 函数是一个多路复用 I/O 函数,可以监视一组文件描述符,判断它们是否处于可读、可写或异常状态。当 `select()` 函数返回时,通过检查文件描述符的状态,可以得知哪些客户端断开连接。 下面是一个使用 `select()` 函数来判断客户端是否断开连接的示例代码: ```c++ #include <winsock2.h> #include <ws2tcpip.h> #include <stdio.h> void handle_client(SOCKET client_socket) { // 处理客户端连接 } int main() { // 初始化 Winsock WSADATA wsa_data; if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) { printf("WSAStartup failed\n"); return 1; } // 创建服务器套接字 SOCKET server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (server_socket == INVALID_SOCKET) { printf("socket failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } // 绑定服务器套接字到本地地址和端口 sockaddr_in server_addr = { 0 }; server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(12345); if (bind(server_socket, (sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) { printf("bind failed with error: %ld\n", WSAGetLastError()); closesocket(server_socket); WSACleanup(); return 1; } // 监听连接请求 if (listen(server_socket, SOMAXCONN) == SOCKET_ERROR) { printf("listen failed with error: %ld\n", WSAGetLastError()); closesocket(server_socket); WSACleanup(); return 1; } // 创建文件描述符集合 fd_set read_fds; FD_ZERO(&read_fds); FD_SET(server_socket, &read_fds); while (true) { // 调用 select() 函数检查文件描述符状态 fd_set tmp_fds = read_fds; if (select(0, &tmp_fds, NULL, NULL, NULL) == SOCKET_ERROR) { printf("select failed with error: %ld\n", WSAGetLastError()); break; } // 依次处理可读文件描述符 for (int i = 0; i < tmp_fds.fd_count; i++) { if (tmp_fds.fd_array[i] == server_socket) { // 有新的连接请求 sockaddr_in client_addr = { 0 }; int client_addr_len = sizeof(client_addr); SOCKET client_socket = accept(server_socket, (sockaddr*)&client_addr, &client_addr_len); if (client_socket == INVALID_SOCKET) { printf("accept failed with error: %ld\n", WSAGetLastError()); continue; } printf("client connected: %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); FD_SET(client_socket, &read_fds); } else { // 有客户端发送数据 char buf[1024]; int recv_len = recv(tmp_fds.fd_array[i], buf, sizeof(buf), 0); if (recv_len == SOCKET_ERROR) { printf("recv failed with error: %ld\n", WSAGetLastError()); closesocket(tmp_fds.fd_array[i]); FD_CLR(tmp_fds.fd_array[i], &read_fds); } else if (recv_len == 0) { // 客户端已经断开连接 printf("client disconnected\n"); closesocket(tmp_fds.fd_array[i]); FD_CLR(tmp_fds.fd_array[i], &read_fds); } else { // 处理客户端发送的数据 handle_client(tmp_fds.fd_array[i]); } } } } // 关闭服务器套接字 closesocket(server_socket); // 清理 Winsock WSACleanup(); return 0; } ``` 在上面的代码中,我们先创建了一个文件描述符集合 `read_fds`,将服务器套接字 `server_socket` 添加到集合中。然后在循环中,调用 `select()` 函数检查文件描述符状态。如果 `select()` 函数返回,就遍历可读文件描述符,如果是服务器套接字,表示有新的连接请求,我们就调用 `accept()` 函数创建一个新的客户端套接字,并将其添加到文件描述符集合中。如果是客户端套接字,表示有客户端发送数据,我们就调用 `recv()` 函数接收数据,并根据返回值判断客户端是否已经断开连接。如果客户端已经断开连接,我们就关闭客户端套接字,并从文件描述符集合中删除它。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值