TCP连接状态的多种判断方法
前言
在TCP网络编程模型中,无论是客户端还是服务端,在网络编程的过程中都需要判断连接的对方网络状态是否正常。在linux系统中,有很多种方式可以判断连接的对方网络是否已经断开。
- 通过错误码和信号判断
- 通过select系统函数判断
- 通过TCP_INFO套接字选项判断
- 通过SO_KEEPALIVE套接字选项判断
- 通过SO_RCVTIMEO/SO_SNDTIMEO判断
(一)通过错误码和信号判断
(1)写数据信号和错误码判断
在写TCP连接数据的时候,如果对方连接已经正常断开,那么写数据端将会收到一个SIGPIPE信号,可以通过这个信号知道对方连接已经断开。该信号信号会终止当前进程,如果不在对方连接断开不退出进程,那么就应该注册信号函数。
同时,如果对方连接已经正常断开,那么write写数据端将会返回写错误。返回的写长度为-1,此时的错误码为:32,对应错误值为EPIPE;因此可以写数据时write的返回值和错误码来判断对方连接是否已经断开了。
(2)读数据判断返回值
如果当前是默认的阻塞模式读取,那么此时read读取返回的长度为0,错误码也是为0,其实表示读取成功。这里需要注意read 和recv接口的默认返回值是不一样的,使用recv接口也会返回EPIPE错误码。
client_tcp.c
/************************************************************
*Copyright (C),lcb0281at163.com lcb0281atgmail.com
*FileName: 01_client_tcp.c
*BlogAddr: caibiao-lee.blog.csdn.net
*Description: TCP 客户端收发数据
*Date: 2020-01-04
*Author: Caibiao Lee
*Version: V1.0
*Others:
通过read write 函数的返回值和错误码判断对方连接是否已经断开
*History:
***********************************************************/
#include <sys/uio.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <signal.h>
#include <errno.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#define SERVER_IP_ADDR "192.168.1.111"
#define PORT 8888 /* 侦听端口地址 */
void sig_proccess(int signo)
{
printf("Catch a exit signal\n");
exit(0);
}
void sig_pipe(int sign)
{
printf("Catch a SIGPIPE signal\n");
/* 释放资源 */
}
void process_conn_client(int s32SocketFd)
{
int size = 0;
char buffer[1024] = {0};
char *sendData = "I am client";
for(;;)
{
size = write(s32SocketFd, sendData, strlen(sendData)+1);
if(size!=strlen(sendData)+1)
{
printf("write data error size=%d errno=%d \n",size,errno);
//return ;
}
size = read(s32SocketFd, buffer, 1024);
if(size<=0)
{
printf("read data error size=%d errno=%d \n",size,errno);
//return ;
}else
{
printf("recv Data: %s\n",buffer);
}
sleep(1);
}
}
int main(int argc, char *argv[])
{
struct sockaddr_in server_addr;
int l_s32SocketFd = 0;
signal(SIGINT, sig_proccess);
signal(SIGPIPE, sig_pipe);
/* 建立一个流式套接字 */
l_s32SocketFd = socket(AF_INET, SOCK_STREAM, 0);
if(l_s32SocketFd < 0)
{/* 出错 */
printf("socket error\n");
return -1;
}
/* 设置服务器地址 */
bzero(&server_addr, sizeof(server_addr)); /* 清0 */
server_addr.sin_family = AF_INET; /* 协议族 */
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP_ADDR);/*服务器IP地址*//* 本地地址 */
server_addr.sin_port = htons(PORT); /* 服务器端口 */
/* 连接服务器 */
connect(l_s32SocketFd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr));
process_conn_client(l_s32SocketFd); /* 客户端处理过程 */
close(l_s32SocketFd); /* 关闭连接 */
return 0;
}
server_tcp.c
/************************************************************
*Copyright (C),lcb0281at163.com lcb0281atgmail.com
*FileName: 01_server_tcp.c
*BlogAddr: caibiao-lee.blog.csdn.net
*Description: TCP 客户端收发数据
*Date: 2020-01-04
*Author: Caibiao Lee
*Version: V1.0
*Others:
通过read write 函数的返回值和错误码判断对方连接是否已经断开
*History:
***********************************************************/
#include <sys/uio.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <signal.h>
#include <errno.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#define SERVER_IP_ADDR "192.168.1.111"
#define PORT 8888 /* 侦听端口地址 */
#define BACKLOG 2 /* 侦听队列长度 */
void sig_proccess(int signo)
{
printf("Catch a exit signal\n");
exit(0);
}
void sig_pipe(int sign)
{
printf("Catch a SIGPIPE signal\n");
/* 释放资源 */
}
/* 服务器对客户端的处理 */
void process_conn_server(int s32SocketFd)
{
int size = 0;
char buffer[1024]; /* 数据的缓冲区 */
for(;;)
{
/* 从套接字中读取数据放到缓冲区buffer中 */
size = read(s32SocketFd, buffer, 1024);
if(size==0)
{/* 没有数据 */
printf("read size = %d, error %d \n",size,errno);
//return;
}else if(size<0)
{
printf("read size = %d, error %d \n",size,errno);
//return ;
}else
{
printf("recv data:%s \n",buffer);
}
memset(buffer,0,sizeof(buffer));
/* 构建响应字符,为接收到客户端字节的数量 */
strcpy(buffer,"I am server");
size = write(s32SocketFd, buffer, strlen(buffer)+1);/* 发给客户端 */
if((strlen(buffer)+1)==size)
{
}else
{
printf("write data error size = %d, errno=%d\n",size,errno);
//return ;
}
sleep(1);
}
}
int main(int argc, char *argv[])
{
int l_s32ServerFd = -1;
int l_s32ClientrFd = -1;
struct sockaddr_in server_addr; /* 服务器地址结构 */
struct sockaddr_in client_addr; /* 客户端地址结构 */
int l_s32Ret = 0; /* 返回值 */
pid_t pid; /* 分叉的进行id */
signal(SIGINT, sig_proccess);
signal(SIGPIPE, sig_pipe);
/* 建立一个流式套接字 */
l_s32ServerFd = socket(AF_INET, SOCK_STREAM, 0);
if(l_s32ServerFd < 0)
{/* 出错 */
printf("socket error\n");
return -1;
}
/* 设置服务器地址 */
bzero(&server_addr, sizeof(server_addr)); /* 清0 */
server_addr.sin_family = AF_INET; /* 协议族 */
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP_ADDR);/*服务器IP地址*/
server_addr.sin_port = htons(PORT); /* 服务器端口 */
/*设置IP地址可以重复绑定*/
int l_s32UseAddr = 1;
if(setsockopt(l_s32ServerFd, SOL_SOCKET, SO_REUSEADDR, &l_s32UseAddr, sizeof(int)) < 0)
{
printf("%s %d\tsetsockopt error! Error code: %d,Error message: %s\n",
__FUNCTION__, __LINE__, errno, strerror(errno));
return -2;
}
/* 绑定地址结构到套接字描述符 */
l_s32Ret = bind(l_s32ServerFd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if(l_s32Ret < 0)
{/* 出错 */
printf("bind error\n");
return -1;
}
/* 设置侦听 */
l_s32Ret = listen(l_s32ServerFd, BACKLOG);
if(l_s32Ret < 0)
{/* 出错 */
printf("listen error\n");
return -1;
}
/* 主循环过程 */
for(;;)
{
int addrlen = sizeof(struct sockaddr);
/* 接收客户端连接 */
l_s32ClientrFd = accept(l_s32ServerFd, (struct sockaddr*)&client_addr, &addrlen);
if(l_s32ClientrFd < 0)
{ /* 出错 */
continue; /* 结束本次循环 */
}
/* 建立一个新的进程处理到来的连接 */
pid = fork(); /* 分叉进程 */
if( pid == 0 )
{ /* 子进程中 */
close(l_s32ServerFd); /* 在子进程中关闭服务器的侦听 */
process_conn_server(l_s32ClientrFd);/* 处理连接 */
}else
{
close(l_s32ClientrFd); /* 在父进程中关闭客户端的连接 */
}
}
}
(二)通过select系统函数判断
select实际是IO复用的一个接口,它可以同时检测多个连接是否有数据可读写操作,并且可以设置检测的超时时间。
在点对点的连接中如果select超时,它返回值为0;
- 当出现异常的时候,返回-1,如果对方断开可能收到104的错误码,也就是ECONNRESET,表示连接被重置
- 当select返回1,表示正常,如果read此时返回的值为0,表示对方连接已经断开。
/********************************************************
Function: process_conn_server
Description: 服务器对客户端的处理
Input: s32SocketFd :服务端接收到客户端连接的ID;
OutPut: none
Return: 0: success,none 0:error
Others: 通过select判断客户端的连接状态
Author: Caibiao Lee
Date: 2020-01-04
*********************************************************/
void process_conn_server(int s32SocketFd)
{
int size = 0;
int l_s32Ret = 0;
char buffer[1024]; /* 数据的缓冲区 */
fd_set l_stReadfd;
struct timeval l_stTimeout={0};
for(;;)
{
l_stTimeout.tv_sec=0;
l_stTimeout.tv_usec=10000;
FD_ZERO(&l_stReadfd);
FD_SET(s32SocketFd ,&l_stReadfd);
l_s32Ret = select(s32SocketFd+1, &l_stReadfd,NULL,NULL, &l_stTimeout);
if (l_s32Ret<=0)
{
printf("select error l_s32Ret=%d errno=%d\n",l_s32Ret,errno);
usleep(100000);
}
else if(FD_ISSET(s32SocketFd,&l_stReadfd))
{
printf("l_s32Ret = %d \n",l_s32Ret);
/* 从套接字中读取数据放到缓冲区buffer中 */
size = read(s32SocketFd, buffer, 1024);
if(size==0)
{/* 没有数据 */
printf("read size = %d, error %d \n",size,errno);
//return;
}else if(size<0)
{
printf("read size = %d, error %d \n",size,errno);
//return ;
}else
{
printf("recv data:%s \n",buffer);
}
}
memset(buffer,0,sizeof(buffer));
/* 构建响应字符,为接收到客户端字节的数量 */
strcpy(buffer,"I am server");
size = write(s32SocketFd, buffer, strlen(buffer)+1);/* 发给客户端 */
if((strlen(buffer)+1)==size)
{
}else
{
printf("write data error size = %d, errno=%d\n",size,errno);
//return ;
}
sleep(1);
}
}
(三)通过TCP_INFO套接字选项判断
通过getsockopt函数可以获取TCP连接的连接状态,当状态为ESTABLISHED的时候表示该连接正常。TCP的其它状态还有:
- CLOSED:表示初始状态。对服务端和C客户端双方都一样。
- LISTEN:表示监听状态。服务端调用了listen函数,可以开始accept连接了。
- SYN_SENT:表示客户端已经发送了SYN报文。当客户端调用connect函数发起连接时,首先发SYN给服务端,然后自己进入SYN_SENT状态,并等待服务端发送ACK+SYN。
- SYN_RCVD:表示服务端收到客户端发送SYN报文。服务端收到这个报文后,进入SYN_RCVD状态,然后发送ACK+SYN给客户端。
- ESTABLISHED:表示连接已经建立成功了。服务端发送完ACK+SYN后进入该状态,客户端收到ACK后也进入该状态。
- FIN_WAIT_1:表示主动关闭连接。无论哪方调用close函数发送FIN报文都会进入这个这个状态。
- FIN_WAIT_2:表示被动关闭方同意关闭连接。主动关闭连接方收到被动关闭方返回的ACK后,会进入该状态。
- TIME_WAIT:表示收到对方的FIN报文并发送了ACK报文,就等2MSL后即可回到CLOSED状态了。如果FIN_WAIT_1状态下,收到对方同时带FIN标志和ACK标志的报文时,可以直接进入TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
- CLOSING:表示双方同时关闭连接。如果双方几乎同时调用close函数,那么会出现双方同时发送FIN报文的情况,此时就会出现CLOSING状态,表示双方都在关闭连接。
- CLOSE_WAIT:表示被动关闭方等待关闭。当收到对方调用close函数发送的FIN报文时,回应对方ACK报文,此时进入CLOSE_WAIT状态。
- LAST_ACK:表示被动关闭方发送FIN报文后,等待对方的ACK报文状态,当收到ACK后进入CLOSED状态。
功能代码如下:
/********************************************************
Function: check_tcp_alive
Description: 通过TCP_INFO查询网络状态
Input: s32SocketFd :服务端接收到客户端连接的ID;
OutPut: none
Return: 0: success,none 0:error
Others:
Author: Caibiao Lee
Date: 2020-01-04
*********************************************************/
int check_tcp_alive(int s32SocketFd)
{
while(1)
{
printf("alive s32SocketFd = %d \n",s32SocketFd);
if(s32SocketFd>0)
{
struct tcp_info info;
int len = sizeof(info);
getsockopt(s32SocketFd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);
printf("info.tcpi_state = %d\n",info.tcpi_state);
if(info.tcpi_state == TCP_ESTABLISHED)
{
printf("connect ok \r\n");
//return 0;
}
else
{
printf("connect error\r\n");
//return -1;
}
}
sleep(1);
printf("\n\n");
}
}
(四)通过SO_KEEPALIVE套接字选项判断
选项SO_KEEPALIVE用于设置TCP连接的保持,当设置此项后,连接会测试连接的状态。这个选项用于可能长时间没有数据交流的连接,通常在服务器端进行设置。
当设置SO_KEEPALIVE选项后,如果在两个小时内没有数据通信时,TCP会自动发送一个活动探测数据报文,对方必须对此进行响应,通常有如下3种情况。
- TCP的连接正常,发送一个ACK响应,这个过程应用层是不知道的。再过两个小时,又会再发送一个。
- 对方发送RST响应,对方在2个小时内进行了重启或者崩溃。之前的连接己经失效,套接字收到一个ECONNRESET错误,之前的套接字关闭。
- 如果对方没有任何响应,则本机会发送另外8个活动探测报文,时间的间隔为75s,当第一个活动报文发送11分15秒后仍然没有收到对方的任何响应,则放弃探测,套接字错误类型设置为ETIMEOUT,并关闭套接字连接。如果收到一个ICMP控制报文响应,此时套接字也关闭,这种情况通常收到的是一个主机不可达的ICMP报文,此时套接字错误类型设置为EHOSTUNREACH,并关闭套接字连接。
SO_KEEPALIVE
的使用场景主要是在可能发送长时间无数据响应的TCP连接,例如Telnet会话,经常会出现打开一个telnet客户端后,长时间不用的情况,这需要服务器或 者客户端有一个探测机制知道对方是否仍然活动。根据探测结果服务器会释放己经失效的客户端,保证服务器资源的有效性,例如有的telnet客户端没有按照正常步骤进行关闭。
网上有不少资料介绍不推荐使用SO_KEEPALIVE来判断网络连接是否断开,具体原因没有去追踪,这里不再介绍它的使用。
(五)通过SO_RCVTIMEO/SO_SNDTIMEO判断
这个是通过套接字的SO_RCVTIMEO
、SO_SNDTIMEO
来设置收发数据超时。对于前面的前面的几种判断方式,都是基于对方正常网络断开后,主机才能够正常的判断到网络状态。如果连接的某一方突然断电,主机并不能知道对方设备突然断电,通过TCP_INFO查询到的也是网络正常,但实际情况是这是网络连接已经断开了。
这时,可以使用收发数据超时来判断:
如果设置的时间没有收到数据,read时会返回-1,同时有错误码EAGAIN产生,这时是可以判断出对连接已经断开了。
这种方式的确定就是,如果设定的一段时间没有收发数据,就会被判断为超时断开连接。
/********************************************************
Function: process_conn_server
Description: 通过设置收发操作判断对方连接已经断开了
Input: s32SocketFd :服务端接收到客户端连接的ID;
OutPut: none
Return: 0: success,none 0:error
Others:
Author: Caibiao Lee
Date: 2020-01-04
*********************************************************/
void process_conn_server(int s32SocketFd)
{
int size = 0;
char buffer[1024]; /* 数据的缓冲区 */
int optlen = -1; /* 整型的选项类型值 */
int l_s32Ret = 0;
/* 设置发送和接收超时时间 */
struct timeval tv;
tv.tv_sec = 10; /* 1秒 */
tv.tv_usec = 200000;/* 200ms */
optlen = sizeof(tv);
l_s32Ret = setsockopt(s32SocketFd, SOL_SOCKET, SO_RCVTIMEO, &tv, optlen); /* 设置接收超时时间 */
if(l_s32Ret == -1){/* 设置接收超时时间失败 */
printf("设置接收超时时间失败\n");
}
l_s32Ret = setsockopt(s32SocketFd, SOL_SOCKET, SO_SNDTIMEO, &tv, optlen);/* 设置发送超时时间 */
if(l_s32Ret == -1){
printf("设置发送超时时间失败\n");
}
for(;;)
{
/* 从套接字中读取数据放到缓冲区buffer中 */
size = read(s32SocketFd, buffer, 1024);
if(size==0)
{/* 没有数据 */
printf("read size = %d, error %d \n",size,errno);
//return;
}else if(size<0)
{
printf("read size = %d, error %d \n",size,errno);
//return ;
}else
{
printf("recv data:%s \n",buffer);
}
memset(buffer,0,sizeof(buffer));
/* 构建响应字符,为接收到客户端字节的数量 */
strcpy(buffer,"I am server");
size = write(s32SocketFd, buffer, strlen(buffer)+1);/* 发给客户端 */
if((strlen(buffer)+1)==size)
{
}else
{
printf("write data error size = %d, errno=%d\n",size,errno);
//return ;
}
sleep(1);
}
}
(六)自定义通信心跳判断
在一些比较重要的命令收发链接中,一般是客户端和服务端会建立心跳机制,心跳时间间隔根据不同的业务需求而不同。当约定的时间段内没有收到心跳数据包,就可以判断对方是否已经断开了连接。
这种方式非常简单,对于嵌入式设备而言,主要的缺点是心跳会耗费流量,同时会增加一点点系统负载,并且不适合并发连接的情况。
以上就是现在比较常用的判断网络连接的方法。 如有错误,欢迎指出!
---------------------------End---------------------------
长按识别二维码
关注 liwen01 公众号

TCP连接状态的多种判断方法的更多相关文章
- 查看 Apache并发请求数及其TCP连接状态
查看 Apache并发请求数及其TCP连接状态 (2011-06-27 15:08:36) 服务器上的一些统计数据: 1)统计80端口连接数 netstat -nat|grep -i "80 ...
- 查看 并发请求数及其TCP连接状态【转】
服务器上的一些统计数据: 1)统计80端口连接数netstat -nat|grep -i "80"|wc -l 2)统计httpd协议连接数ps -ef|grep httpd|wc ...
- 查看 并发请求数及其TCP连接状态
服务器上的一些统计数据: 1)统计80端口连接数netstat -nat|grep -i "80"|wc -l 2)统计httpd协议连接数ps -ef|grep httpd|wc ...
- 查看 Apache并发请求数及其TCP连接状态【转】
查看 Apache并发请求数及其TCP连接状态 (2011-06-27 15:08:36) 服务器上的一些统计数据: 1)统计80端口连接数netstat -nat|grep -i "80& ...
- 【转】TCP连接突然断开的处理方法
TCP是因特网中的传输层协议,使用三次握手协议建立连接,下面是TCP建立连接的全过程. TCP断开连接的过程:TCP四次挥手. TCP/IP 协议簇分层结构 数据链路层主要负责处理传输媒介等众多的物理 ...
- Linux下查看Web服务器当前的并发连接数和TCP连接状态
对于web服务器(Nginx.Apache等)来说,并发连接数是一个比较重要的参数,下面就通过netstat命令和awk来查看web服务器的并发连接数以及TCP连接状态. $ netstat -n | ...
- TCP连接状态
TCP 连接状态按 TCP 协议的标准表示法, TCP 可具有如下几种状态,为讨论方便,如下讨论中区分服务端和客户端,实际软件处理上对二者一视同仁. CLOSED关闭状态.在两个通信端使用“三路握手” ...
- TCP三次握手及TCP连接状态 TCP报文首部格式
建立TCP连接时的TCP三次握手和断开TCP连接时的4次挥手整体过程如下图: 开个玩笑 ACK: TCP协议规定,只有ACK=1时有效,连接建立后所有发送的报文ACK必须为1 SYN(SYNchron ...
- zabbix监控 linux/windows 主机tcp连接状态
更新内容:1).增加了对windows主机的tcp连接状态的监控2).修改linux主机的监控配置,使linux与windwos主机能够使用相同的模板tcp的连接状态对于监控服务器,尤其是Web服务器 ...
- Zabbix设置自定义监控项之——监控tcp连接状态
目录 一.用户自定义参数 二.配置 监控 TCP 连接状态 在实际监控中,除了官方自带的一些监控项,我们很多时候有一些定制化监控,比如特定的服务.TCP 连接状态等等,这时候就需要自定义监控项.自定义 ...
随机推荐
- 为什么要重写equals()?
为什么要重写equals()? Equals和 == 的区别: ==:是个运算符, 判断是否相等,基本数据类型进行判断 也可判断两个对象相等,比较两个对象的哈希码值 Equals:是个Object类的 ...
- BUUCTF-Crypto详细Writeup
每一天都要努力啊 ----2024-01-01 18:11:36 1.一眼就解密 原题:下面的字符串解密后便能获得flag:ZmxhZ3tUSEVfRkxBR19PRl9USElTX1NUUkl ...
- Python中的@abstractmethod
@abstractmethod 是 Python 中 abc 模块(Abstract Base Classes)提供的一个装饰器,用于声明抽象方法.抽象方法是指在抽象类中声明但没有提供具体实现的方 ...
- 互联网公司五八同城(58.com)研发效能团队建设之路
这是「二三线中型互联网公司研发效能团队规模.职能划分和优劣势分析」的一个铺垫,一个背景.因为如果不写此篇,大家可能仅得到一些经验总结,恐怕难以获取当时为啥做出那个决定.做决定要有上下文环境,要有场景才 ...
- EDS从小白到专家丨生态产业链高效协同的一计良策
本文分享自华为云社区<[EDS从小白到专家]第3期:生态产业链高效协同的一计良策>,作者:开天aPaaS小助手 . 号外! 华为将于2023年9月20-22日,在上海世博展览馆和上海世博中 ...
- 华为云IoT智简联接,开启物联世界新纪元
摘要:华为云IoT将聚焦物联网技术和商业基础能力建设,联接万物.联接生态.联接行业,帮助各行各业做好数字化转型. 近日,华为云通过线上专题演讲发布了IoT最新战略.华为云IoT将聚焦物联网基础能力(包 ...
- 技术驱动,数据赋能,华为云GaussDB给世界一个更优选择
摘要:5月16日,"数智深耕 让美好发生 2023华为云城市峰会广州站"成功举行. 5月16日,"数智深耕 让美好发生 2023华为云城市峰会广州站"成功举行. ...
- 当自动驾驶遇到5G,会擦出怎样的火花?这篇文章说明白了
作者:华为云EI专家厉天一 摘要:无人驾驶是通过自动驾驶系统,部分或完全的代替人类驾驶员,安全地驾驶汽车.汽车自动驾驶系统是一个涵盖了多个功能模块和多种技术的复杂软硬件结合的系统.本文将基于5G技术来 ...
- 华为API战略:规范、组织和流程驱动企业大循环
摘要:构建一套完善的API规范流程体系变得至关重要,用方法论驱动整个API变革,用API变革驱动共享经济模式,以共享模式反推数字化转型. 本文分享自华为云社区<API战略--华为在数字化浪潮下的 ...
- 云图说|一张图看懂一站式DevOps利器——华为云DevCloud
阅识风云是华为云信息大咖,擅长将复杂信息多元化呈现,其出品的一张图(云图说).深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手华为云.更多精彩内容请单击此处. 摘要: 华为云DevCl ...