百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

TCP连接状态的多种判断方法

bigegpt 2024-08-28 12:28 1 浏览

liwen01_2020.01.10

前言

在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);    /* 关闭连接 */
    
    return0;
}

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;    
        }elseif(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);
        }
        elseif(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;    
            }elseif(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种情况。

  1. TCP的连接正常,发送一个ACK响应,这个过程应用层是不知道的。再过两个小时,又会再发送一个。
  2. 对方发送RST响应,对方在2个小时内进行了重启或者崩溃。之前的连接己经失效,套接字收到一个ECONNRESET错误,之前的套接字关闭。
  3. 如果对方没有任何响应,则本机会发送另外8个活动探测报文,时间的间隔为75s,当第一个活动报文发送11分15秒后仍然没有收到对方的任何响应,则放弃探测,套接字错误类型设置为ETIMEOUT,并关闭套接字连接。如果收到一个ICMP控制报文响应,此时套接字也关闭,这种情况通常收到的是一个主机不可达的ICMP报文,此时套接字错误类型设置为EHOSTUNREACH,并关闭套接字连接。 SO_KEEPALIVE的使用场景主要是在可能发送长时间无数据响应的TCP连接,例如Telnet会话,经常会出现打开一个telnet客户端后,长时间不用的情况,这需要服务器或 者客户端有一个探测机制知道对方是否仍然活动。根据探测结果服务器会释放己经失效的客户端,保证服务器资源的有效性,例如有的telnet客户端没有按照正常步骤进行关闭。

网上有不少资料介绍不推荐使用SO_KEEPALIVE来判断网络连接是否断开,具体原因没有去追踪,这里不再介绍它的使用。

(五)通过SO_RCVTIMEO/SO_SNDTIMEO判断

这个是通过套接字的SO_RCVTIMEOSO_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;    
        }elseif(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

相关推荐

得物可观测平台架构升级:基于GreptimeDB的全新监控体系实践

一、摘要在前端可观测分析场景中,需要实时观测并处理多地、多环境的运行情况,以保障Web应用和移动端的可用性与性能。传统方案往往依赖代理Agent→消息队列→流计算引擎→OLAP存储...

warm-flow新春版:网关直连和流程图重构

本期主要解决了网关直连和流程图重构,可以自此之后可支持各种复杂的网关混合、多网关直连使用。-新增Ruoyi-Vue-Plus优秀开源集成案例更新日志[feat]导入、导出和保存等新增json格式支持...

扣子空间体验报告

在数字化时代,智能工具的应用正不断拓展到我们工作和生活的各个角落。从任务规划到项目执行,再到任务管理,作者深入探讨了这款工具在不同场景下的表现和潜力。通过具体的应用实例,文章展示了扣子空间如何帮助用户...

spider-flow:开源的可视化方式定义爬虫方案

spider-flow简介spider-flow是一个爬虫平台,以可视化推拽方式定义爬取流程,无需代码即可实现一个爬虫服务。spider-flow特性支持css选择器、正则提取支持JSON/XML格式...

solon-flow 你好世界!

solon-flow是一个基础级的流处理引擎(可用于业务规则、决策处理、计算编排、流程审批等......)。提供有“开放式”驱动定制支持,像jdbc有mysql或pgsql等驱动,可...

新一代开源爬虫平台:SpiderFlow

SpiderFlow:新一代爬虫平台,以图形化方式定义爬虫流程,不写代码即可完成爬虫。-精选真开源,释放新价值。概览Spider-Flow是一个开源的、面向所有用户的Web端爬虫构建平台,它使用Ja...

通过 SQL 训练机器学习模型的引擎

关注薪资待遇的同学应该知道,机器学习相关的岗位工资普遍偏高啊。同时随着各种通用机器学习框架的出现,机器学习的门槛也在逐渐降低,训练一个简单的机器学习模型变得不那么难。但是不得不承认对于一些数据相关的工...

鼠须管输入法rime for Mac

鼠须管输入法forMac是一款十分新颖的跨平台输入法软件,全名是中州韵输入法引擎,鼠须管输入法mac版不仅仅是一个输入法,而是一个输入法算法框架。Rime的基础架构十分精良,一套算法支持了拼音、...

Go语言 1.20 版本正式发布:新版详细介绍

Go1.20简介最新的Go版本1.20在Go1.19发布六个月后发布。它的大部分更改都在工具链、运行时和库的实现中。一如既往,该版本保持了Go1的兼容性承诺。我们期望几乎所...

iOS 10平台SpriteKit新特性之Tile Maps(上)

简介苹果公司在WWDC2016大会上向人们展示了一大批新的好东西。其中之一就是SpriteKitTileEditor。这款工具易于上手,而且看起来速度特别快。在本教程中,你将了解关于TileE...

程序员简历例句—范例Java、Python、C++模板

个人简介通用简介:有良好的代码风格,通过添加注释提高代码可读性,注重代码质量,研读过XXX,XXX等多个开源项目源码从而学习增强代码的健壮性与扩展性。具备良好的代码编程习惯及文档编写能力,参与多个高...

Telerik UI for iOS Q3 2015正式发布

近日,TelerikUIforiOS正式发布了Q32015。新版本新增对XCode7、Swift2.0和iOS9的支持,同时还新增了对数轴、不连续的日期时间轴等;改进TKDataPoin...

ios使用ijkplayer+nginx进行视频直播

上两节,我们讲到使用nginx和ngixn的rtmp模块搭建直播的服务器,接着我们讲解了在Android使用ijkplayer来作为我们的视频直播播放器,整个过程中,需要注意的就是ijlplayer编...

IOS技术分享|iOS快速生成开发文档(一)

前言对于开发人员而言,文档的作用不言而喻。文档不仅可以提高软件开发效率,还能便于以后的软件开发、使用和维护。本文主要讲述Objective-C快速生成开发文档工具appledoc。简介apple...

macOS下配置VS Code C++开发环境

本文介绍在苹果macOS操作系统下,配置VisualStudioCode的C/C++开发环境的过程,本环境使用Clang/LLVM编译器和调试器。一、前置条件本文默认前置条件是,您的开发设备已...