TCP网络连接断开检测机制详解——C++实现网络连通性判断与断线类型识别

前言

最近在做通信模块时,遇到一个棘手的问题:怎么判断网络到底是断了还是对方没发数据?更进一步,怎么区分对方是正常关闭连接还是网线被拔了?

这个问题看似简单,实际上涉及TCP协议的很多细节。折腾了几天,把这块彻底搞清楚了,记录一下。

一、问题背景

先明确我们要解决的几个问题:

  1. 对方静默(没发数据)vs 网络断开------怎么区分?
  2. 主动断开(正常close)vs 意外断开(拔网线、断电)------怎么区分?
  3. 如何设计一个可靠的断线检测机制?

要搞清楚这些,得先理解TCP连接的本质。

二、TCP连接的本质------没你想的那么"实时"

很多人以为TCP是"实时连接"的,其实不是。TCP连接本质上是两端维护的一组状态信息,中间的网络设备(路由器、交换机)根本不知道有这个连接存在。

这意味着什么?

复制代码
正常情况:
[主机A] <---> [路由器] <---> [主机B]
   │                            │
   └── 双方都记录连接状态 ───────┘

拔掉主机B的网线:
[主机A] <---> [路由器]    X    [主机B]
   │                            │
   └── A不知道B已经断了!────────┘

关键点:如果双方都不发数据,即使网线断了,A也感知不到!这就是问题的根源。

三、recv()返回值的真相

先看最基本的检测手段------recv()函数的返回值:

cpp 复制代码
ssize_t ret = recv(sockfd, buf, sizeof(buf), 0);

返回值含义:

返回值 含义 说明
> 0 收到数据 正常情况
= 0 对方关闭连接 对方调用了close()或shutdown()
-1 出错 需要看errno判断具体原因

注意recv()=0只能检测主动关闭,检测不了拔网线这种情况!

因为正常关闭时会发FIN包:

复制代码
主动关闭流程:
A                              B
│                              │
│  ←─── FIN ─────────────────  │  B调用close()
│  ──── ACK ─────────────────→ │
│                              │
此时A端recv()返回0

但如果是拔网线,FIN包根本发不出来,A端啥也收不到。

四、四种断线检测机制

4.1 机制一:TCP Keepalive(系统级心跳)

Linux内核自带的保活机制,不用自己写心跳代码。

原理:连接空闲一段时间后,内核自动发探测包,对方必须回应ACK。

cpp 复制代码
#include <sys/socket.h>
#include <netinet/tcp.h>

int enable_keepalive(int sockfd) 
{
    int keepalive = 1;      // 开启keepalive
    int keepidle = 10;      // 空闲10秒后开始探测
    int keepinterval = 3;   // 探测间隔3秒
    int keepcount = 3;      // 探测3次无响应则认为断开
    
    if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, 
                   &keepalive, sizeof(keepalive)) < 0) {
        perror("SO_KEEPALIVE");
        return -1;
    }
    
    if (setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, 
                   &keepidle, sizeof(keepidle)) < 0) {
        perror("TCP_KEEPIDLE");
        return -1;
    }
    
    if (setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, 
                   &keepinterval, sizeof(keepinterval)) < 0) {
        perror("TCP_KEEPINTVL");
        return -1;
    }
    
    if (setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, 
                   &keepcount, sizeof(keepcount)) < 0) {
        perror("TCP_KEEPCNT");
        return -1;
    }
    
    return 0;
}

探测流程

复制代码
空闲10秒后:
A ─── Keepalive探测包 ───→ B
A ←─── ACK ──────────────  B  (正常)

网线断开情况:
A ─── Keepalive探测包 ───→ X (无响应)
等待3秒...
A ─── Keepalive探测包 ───→ X (无响应)
等待3秒...
A ─── Keepalive探测包 ───→ X (无响应)
探测3次失败,内核标记连接断开,recv()返回-1,errno=ETIMEDOUT

优点:内核实现,开销小,不占应用层带宽

缺点

  1. 检测时间较长(上面配置要10+3×3=19秒)
  2. 某些NAT设备会拦截或修改keepalive包
  3. 无法区分"对方静默"和"网络断开"(还是得等探测超时)

4.2 机制二:应用层心跳(推荐)

自己实现心跳包,更灵活可控。

cpp 复制代码
#include <sys/time.h>
#include <thread>
#include <atomic>
#include <chrono>

// 心跳包定义
#pragma pack(push, 1)
struct HeartbeatPacket {
    uint8_t type;       // 0x01=请求, 0x02=响应
    uint32_t seq;       // 序列号
    int64_t timestamp;  // 时间戳(ms)
};
#pragma pack(pop)

class HeartbeatManager {
private:
    int m_sockfd;
    std::atomic<bool> m_running{false};
    std::atomic<int64_t> m_lastRecvTime{0};     // 最后收到数据的时间
    std::atomic<int64_t> m_lastHeartbeatAck{0}; // 最后收到心跳响应的时间
    std::atomic<uint32_t> m_seq{0};
    
    int m_heartbeatInterval = 3000;   // 心跳间隔(ms)
    int m_timeoutThreshold = 10000;   // 超时阈值(ms)
    
    std::thread m_sendThread;
    
public:
    HeartbeatManager(int sockfd) : m_sockfd(sockfd) {
        m_lastRecvTime = getCurrentTimeMs();
        m_lastHeartbeatAck = getCurrentTimeMs();
    }
    
    static int64_t getCurrentTimeMs() {
        auto now = std::chrono::system_clock::now();
        auto duration = now.time_since_epoch();
        return std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
    }
    
    // 发送心跳请求
    int sendHeartbeat() {
        HeartbeatPacket pkt;
        pkt.type = 0x01;
        pkt.seq = ++m_seq;
        pkt.timestamp = getCurrentTimeMs();
        
        ssize_t ret = send(m_sockfd, &pkt, sizeof(pkt), MSG_NOSIGNAL);
        if (ret != sizeof(pkt)) {
            return -1;
        }
        return 0;
    }
    
    // 处理收到的心跳包
    int handleHeartbeat(const HeartbeatPacket& pkt) {
        if (pkt.type == 0x01) {
            // 收到心跳请求,回复响应
            HeartbeatPacket resp;
            resp.type = 0x02;
            resp.seq = pkt.seq;
            resp.timestamp = getCurrentTimeMs();
            send(m_sockfd, &resp, sizeof(resp), MSG_NOSIGNAL);
        } else if (pkt.type == 0x02) {
            // 收到心跳响应
            m_lastHeartbeatAck = getCurrentTimeMs();
            int64_t rtt = getCurrentTimeMs() - pkt.timestamp;
            // 可以统计RTT用于网络质量评估
        }
        return 0;
    }
    
    // 更新最后收到数据的时间(收到任何数据都调用)
    void updateRecvTime() {
        m_lastRecvTime = getCurrentTimeMs();
    }
    
    // 检查连接状态
    enum ConnectionState {
        STATE_NORMAL,           // 正常
        STATE_IDLE,             // 对方静默(没发数据,但心跳正常)
        STATE_DISCONNECTED      // 网络断开
    };
    
    ConnectionState checkState() {
        int64_t now = getCurrentTimeMs();
        int64_t silentTime = now - m_lastRecvTime;
        int64_t heartbeatTime = now - m_lastHeartbeatAck;
        
        // 心跳超时,网络断开
        if (heartbeatTime > m_timeoutThreshold) {
            return STATE_DISCONNECTED;
        }
        
        // 心跳正常但没收到业务数据,对方静默
        if (silentTime > m_timeoutThreshold && heartbeatTime < m_timeoutThreshold) {
            return STATE_IDLE;
        }
        
        return STATE_NORMAL;
    }
    
    // 启动心跳线程
    void start() {
        m_running = true;
        m_sendThread = std::thread([this]() {
            while (m_running) {
                sendHeartbeat();
                std::this_thread::sleep_for(
                    std::chrono::milliseconds(m_heartbeatInterval));
            }
        });
    }
    
    void stop() {
        m_running = false;
        if (m_sendThread.joinable()) {
            m_sendThread.join();
        }
    }
};

这个方案能区分

  • 心跳正常 + 没收到业务数据 = 对方静默(STATE_IDLE)
  • 心跳超时 = 网络断开(STATE_DISCONNECTED)

4.3 机制三:send()探测

往socket写数据也能检测断线,但有坑。

cpp 复制代码
int probe_connection(int sockfd) 
{
    char probe = 0;
    
    // 发送0字节数据
    ssize_t ret = send(sockfd, &probe, 0, MSG_NOSIGNAL);
    
    if (ret < 0) {
        if (errno == EPIPE || errno == ECONNRESET) {
            printf("连接已断开\n");
            return -1;
        }
    }
    
    return 0;
}

坑在这里

  1. send()成功只表示数据进了发送缓冲区,不表示对方收到了
  2. TCP有重传机制,拔网线后第一次send可能还是成功的
  3. 要等到重传超时(可能几十秒到几分钟),send才会返回错误

所以send()探测检测不及时,只能作为辅助手段。

4.4 机制四:select/poll/epoll超时检测

用I/O多路复用设置超时,这是实际工程中最常用的方式。

cpp 复制代码
#include <sys/epoll.h>
#include <errno.h>

class ConnectionMonitor {
private:
    int m_epollfd;
    int m_sockfd;
    int m_timeout;  // 超时时间(ms)
    
public:
    ConnectionMonitor(int sockfd, int timeout_ms) 
        : m_sockfd(sockfd), m_timeout(timeout_ms) 
    {
        m_epollfd = epoll_create1(0);
        
        struct epoll_event ev;
        ev.events = EPOLLIN | EPOLLERR | EPOLLHUP | EPOLLRDHUP;
        ev.data.fd = sockfd;
        epoll_ctl(m_epollfd, EPOLL_CTL_ADD, sockfd, &ev);
    }
    
    ~ConnectionMonitor() {
        close(m_epollfd);
    }
    
    enum EventType {
        EVENT_DATA,         // 有数据可读
        EVENT_TIMEOUT,      // 超时(对方静默)
        EVENT_CLOSED,       // 对方主动关闭
        EVENT_ERROR         // 连接异常
    };
    
    EventType wait() {
        struct epoll_event events[1];
        
        int nfds = epoll_wait(m_epollfd, events, 1, m_timeout);
        
        if (nfds < 0) {
            return EVENT_ERROR;
        }
        
        if (nfds == 0) {
            // 超时,对方没发数据
            return EVENT_TIMEOUT;
        }
        
        uint32_t ev = events[0].events;
        
        // EPOLLRDHUP: 对方关闭连接(调用close或shutdown)
        if (ev & EPOLLRDHUP) {
            return EVENT_CLOSED;
        }
        
        // EPOLLERR或EPOLLHUP: 连接异常
        if (ev & (EPOLLERR | EPOLLHUP)) {
            return EVENT_ERROR;
        }
        
        // EPOLLIN: 有数据
        if (ev & EPOLLIN) {
            return EVENT_DATA;
        }
        
        return EVENT_ERROR;
    }
};

关键事件说明

事件 含义 触发场景
EPOLLIN 有数据可读 正常收到数据
EPOLLRDHUP 对方关闭写端 对方调用close()或shutdown(WR)
EPOLLHUP 连接挂起 本端或对端异常
EPOLLERR 错误 连接出错

注意EPOLLRDHUP需要在add时显式指定,否则不会触发。

五、区分主动断开和意外断开

这是最难的部分。先看两种断开的区别:

主动断开(正常关闭)

复制代码
对方执行close()或程序正常退出:

A                              B
│                              │
│  ←─── FIN ─────────────────  │  B关闭
│  ──── ACK ─────────────────→ │
│  ──── FIN ─────────────────→ │  A收到后也关闭
│  ←─── ACK ─────────────────  │
│                              │

检测方式:recv()返回0,或触发EPOLLRDHUP

意外断开(拔网线、断电、进程崩溃)

复制代码
对方没机会发FIN:

A                              B
│                              │
│  ──── 数据/心跳 ──────→ X     │  网络中断
│  等待ACK...超时重传           │
│  重传多次后放弃               │
│                              │

检测方式:
1. 发送超时(send失败,errno=ETIMEDOUT)
2. Keepalive探测超时
3. 应用层心跳超时

完整检测方案

cpp 复制代码
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/tcp.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <thread>
#include <atomic>
#include <mutex>
#include <functional>

class TcpConnectionDetector {
public:
    enum DisconnectType {
        DISCONNECT_NONE,            // 未断开
        DISCONNECT_GRACEFUL,        // 主动断开(对方正常关闭)
        DISCONNECT_TIMEOUT,         // 超时断开(可能是网络问题或对方静默)
        DISCONNECT_RESET,           // 连接重置(对方进程崩溃等)
        DISCONNECT_NETWORK_ERROR    // 网络错误(拔网线等)
    };
    
    using DisconnectCallback = std::function<void(DisconnectType, const std::string&)>;
    
private:
    int m_sockfd;
    int m_epollfd;
    std::atomic<bool> m_running{false};
    std::thread m_monitorThread;
    
    // 心跳相关
    std::atomic<int64_t> m_lastRecvTime{0};
    std::atomic<int64_t> m_lastSendTime{0};
    int m_heartbeatInterval = 3000;   // ms
    int m_heartbeatTimeout = 10000;   // ms
    
    // 回调
    DisconnectCallback m_callback;
    std::mutex m_mutex;
    
    // 获取当前时间(ms)
    static int64_t now() {
        struct timespec ts;
        clock_gettime(CLOCK_MONOTONIC, &ts);
        return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
    }
    
    // 获取socket错误
    int getSocketError() {
        int error = 0;
        socklen_t len = sizeof(error);
        getsockopt(m_sockfd, SOL_SOCKET, SO_ERROR, &error, &len);
        return error;
    }
    
    // 分析断开类型
    DisconnectType analyzeDisconnect(int recvRet, int err, uint32_t events) {
        // recv返回0: 对方主动关闭
        if (recvRet == 0) {
            return DISCONNECT_GRACEFUL;
        }
        
        // EPOLLRDHUP: 对方关闭写端
        if (events & EPOLLRDHUP) {
            return DISCONNECT_GRACEFUL;
        }
        
        // 连接重置
        if (err == ECONNRESET) {
            return DISCONNECT_RESET;
        }
        
        // 超时类错误:通常是网络问题
        if (err == ETIMEDOUT || err == EHOSTUNREACH || err == ENETUNREACH) {
            return DISCONNECT_NETWORK_ERROR;
        }
        
        // 其他错误
        if (events & (EPOLLERR | EPOLLHUP)) {
            int sockErr = getSocketError();
            if (sockErr == ECONNRESET) {
                return DISCONNECT_RESET;
            }
            if (sockErr == ETIMEDOUT) {
                return DISCONNECT_NETWORK_ERROR;
            }
            return DISCONNECT_NETWORK_ERROR;
        }
        
        return DISCONNECT_TIMEOUT;
    }
    
    // 监控线程主函数
    void monitorLoop() {
        struct epoll_event events[4];
        char buf[1024];
        
        while (m_running) {
            // 计算下次心跳的等待时间
            int64_t elapsed = now() - m_lastSendTime;
            int waitTime = std::max(100, m_heartbeatInterval - (int)elapsed);
            
            int nfds = epoll_wait(m_epollfd, events, 4, waitTime);
            
            if (!m_running) break;
            
            // 检查心跳超时
            if (now() - m_lastRecvTime > m_heartbeatTimeout) {
                notifyDisconnect(DISCONNECT_NETWORK_ERROR, "心跳超时");
                break;
            }
            
            // 超时,发送心跳
            if (nfds == 0) {
                sendHeartbeat();
                continue;
            }
            
            if (nfds < 0) {
                if (errno != EINTR) {
                    notifyDisconnect(DISCONNECT_NETWORK_ERROR, strerror(errno));
                    break;
                }
                continue;
            }
            
            // 处理事件
            for (int i = 0; i < nfds; i++) {
                uint32_t ev = events[i].events;
                
                // 先检查错误事件
                if (ev & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)) {
                    DisconnectType type = analyzeDisconnect(-1, 0, ev);
                    std::string reason;
                    
                    if (ev & EPOLLRDHUP) {
                        reason = "对方关闭连接";
                    } else if (ev & EPOLLHUP) {
                        reason = "连接挂起";
                    } else {
                        reason = "连接错误: " + std::to_string(getSocketError());
                    }
                    
                    notifyDisconnect(type, reason);
                    m_running = false;
                    break;
                }
                
                // 有数据可读
                if (ev & EPOLLIN) {
                    ssize_t ret = recv(m_sockfd, buf, sizeof(buf), MSG_DONTWAIT);
                    
                    if (ret > 0) {
                        m_lastRecvTime = now();
                        // 这里处理收到的数据,包括心跳响应
                        processData(buf, ret);
                    } else if (ret == 0) {
                        // 对方主动关闭
                        notifyDisconnect(DISCONNECT_GRACEFUL, "对方正常关闭连接");
                        m_running = false;
                        break;
                    } else {
                        if (errno != EAGAIN && errno != EWOULDBLOCK) {
                            DisconnectType type = analyzeDisconnect(-1, errno, 0);
                            notifyDisconnect(type, strerror(errno));
                            m_running = false;
                            break;
                        }
                    }
                }
            }
        }
    }
    
    void sendHeartbeat() {
        uint8_t heartbeat[] = {0xAA, 0x55, 0x01, 0x00};  // 简单心跳包
        ssize_t ret = send(m_sockfd, heartbeat, sizeof(heartbeat), MSG_NOSIGNAL);
        if (ret > 0) {
            m_lastSendTime = now();
        } else if (ret < 0 && errno != EAGAIN) {
            // 发送失败
            DisconnectType type = analyzeDisconnect(-1, errno, 0);
            notifyDisconnect(type, "发送失败: " + std::string(strerror(errno)));
            m_running = false;
        }
    }
    
    void processData(const char* data, size_t len) {
        // 处理业务数据,这里简化处理
        // 实际应该解析协议,识别心跳响应等
    }
    
    void notifyDisconnect(DisconnectType type, const std::string& reason) {
        std::lock_guard<std::mutex> lock(m_mutex);
        if (m_callback) {
            m_callback(type, reason);
        }
    }
    
public:
    TcpConnectionDetector(int sockfd) : m_sockfd(sockfd) {
        m_lastRecvTime = now();
        m_lastSendTime = now();
        
        // 创建epoll
        m_epollfd = epoll_create1(0);
        
        // 添加socket
        struct epoll_event ev;
        ev.events = EPOLLIN | EPOLLERR | EPOLLHUP | EPOLLRDHUP;
        ev.data.fd = sockfd;
        epoll_ctl(m_epollfd, EPOLL_CTL_ADD, sockfd, &ev);
        
        // 设置TCP Keepalive作为兜底
        int keepalive = 1;
        int keepidle = 30;
        int keepinterval = 5;
        int keepcount = 3;
        setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive));
        setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle));
        setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, &keepinterval, sizeof(keepinterval));
        setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, &keepcount, sizeof(keepcount));
    }
    
    ~TcpConnectionDetector() {
        stop();
        close(m_epollfd);
    }
    
    void setCallback(DisconnectCallback cb) {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_callback = std::move(cb);
    }
    
    void setHeartbeatInterval(int ms) { m_heartbeatInterval = ms; }
    void setHeartbeatTimeout(int ms) { m_heartbeatTimeout = ms; }
    
    void start() {
        m_running = true;
        m_monitorThread = std::thread(&TcpConnectionDetector::monitorLoop, this);
    }
    
    void stop() {
        m_running = false;
        if (m_monitorThread.joinable()) {
            m_monitorThread.join();
        }
    }
    
    // 获取断开类型的字符串描述
    static const char* getDisconnectTypeStr(DisconnectType type) {
        switch (type) {
            case DISCONNECT_NONE:          return "未断开";
            case DISCONNECT_GRACEFUL:      return "正常关闭";
            case DISCONNECT_TIMEOUT:       return "超时";
            case DISCONNECT_RESET:         return "连接重置";
            case DISCONNECT_NETWORK_ERROR: return "网络错误";
            default:                       return "未知";
        }
    }
};

六、使用示例

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main() 
{
    // 创建socket并连接(示例代码,实际使用需要错误处理)
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    inet_pton(AF_INET, "192.168.1.100", &addr.sin_addr);
    
    if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("connect");
        return -1;
    }
    
    printf("连接成功\n");
    
    // 创建连接检测器
    TcpConnectionDetector detector(sockfd);
    
    // 设置断开回调
    detector.setCallback([](TcpConnectionDetector::DisconnectType type, 
                           const std::string& reason) {
        printf("\n===== 连接断开 =====\n");
        printf("类型: %s\n", TcpConnectionDetector::getDisconnectTypeStr(type));
        printf("原因: %s\n", reason.c_str());
        
        switch (type) {
            case TcpConnectionDetector::DISCONNECT_GRACEFUL:
                printf("处理建议: 对方正常关闭,可以清理资源\n");
                break;
            case TcpConnectionDetector::DISCONNECT_RESET:
                printf("处理建议: 对方进程可能崩溃,考虑重连\n");
                break;
            case TcpConnectionDetector::DISCONNECT_NETWORK_ERROR:
                printf("处理建议: 网络异常,检查网线/路由,尝试重连\n");
                break;
            case TcpConnectionDetector::DISCONNECT_TIMEOUT:
                printf("处理建议: 超时,可能对方静默或网络拥塞\n");
                break;
            default:
                break;
        }
    });
    
    // 设置参数
    detector.setHeartbeatInterval(3000);  // 3秒发一次心跳
    detector.setHeartbeatTimeout(10000);  // 10秒无响应判定断开
    
    // 启动检测
    detector.start();
    
    // 主线程可以做其他事情
    printf("按Enter键退出...\n");
    getchar();
    
    detector.stop();
    close(sockfd);
    
    return 0;
}

七、各种断线场景的检测结果

实测不同断线场景的检测效果:

场景 检测方式 检测时间 返回类型
对方调用close() recv()=0 / EPOLLRDHUP 立即 GRACEFUL
对方进程被kill -9 recv()=0 / EPOLLRDHUP 立即 GRACEFUL
对方进程崩溃(段错误) ECONNRESET 立即 RESET
拔网线 心跳超时 心跳超时时间 NETWORK_ERROR
对方断电 心跳超时 心跳超时时间 NETWORK_ERROR
网络拥塞丢包 心跳超时(可能误判) 心跳超时时间 TIMEOUT

说明

  • kill -9虽然强制杀进程,但内核还是会发FIN包,所以和正常close()一样
  • 段错误等异常退出,内核会发RST包,触发ECONNRESET
  • 拔网线、断电这种物理断开,只能靠心跳超时检测

八、工程实践建议

8.1 参数调优

cpp 复制代码
// 对实时性要求高的场景(如遥控机器人)
detector.setHeartbeatInterval(500);   // 500ms心跳
detector.setHeartbeatTimeout(2000);   // 2秒超时

// 对带宽敏感的场景(如低带宽卫星链路)
detector.setHeartbeatInterval(10000); // 10秒心跳
detector.setHeartbeatTimeout(35000);  // 35秒超时

8.2 重连策略

cpp 复制代码
class ReconnectManager {
    int m_maxRetries = 5;
    int m_baseDelay = 1000;  // 基础延迟1秒
    
public:
    void reconnect(TcpConnectionDetector::DisconnectType type) {
        int retries = 0;
        
        while (retries < m_maxRetries) {
            // 指数退避
            int delay = m_baseDelay * (1 << retries);
            delay = std::min(delay, 30000);  // 最大30秒
            
            printf("等待%d毫秒后重连...\n", delay);
            std::this_thread::sleep_for(std::chrono::milliseconds(delay));
            
            if (tryConnect()) {
                printf("重连成功\n");
                return;
            }
            
            retries++;
        }
        
        printf("重连失败,放弃\n");
    }
    
    bool tryConnect() {
        // 实现连接逻辑
        return false;
    }
};

8.3 双向心跳

单向心跳有个问题:如果A→B的链路断了,但B→A正常,A会一直发心跳但收不到响应。

更好的方案是双向心跳:

cpp 复制代码
// A发心跳请求,B必须回复心跳响应
// B也发心跳请求,A必须回复心跳响应
// 这样双向都能检测到断线

九、总结

检测TCP断线其实就三板斧:

  1. recv()返回值:检测主动关闭
  2. epoll事件:EPOLLRDHUP检测关闭,EPOLLERR/EPOLLHUP检测异常
  3. 心跳机制:检测意外断开(拔网线等)

区分断开类型:

  • recv()=0 → 主动关闭
  • ECONNRESET → 进程崩溃
  • 心跳超时 → 网络断开

实际工程中,建议应用层心跳 + TCP Keepalive双保险,前者响应快,后者作为兜底。

以上代码在Ubuntu 20.04 + GCC 9.3测试通过,有问题欢迎评论区讨论。


参考资料:

  • TCP/IP详解 卷1:协议
  • Linux man pages: tcp(7), socket(7), epoll(7)
  • Stevens, UNIX网络编程 卷1
相关推荐
雾岛听蓝2 小时前
C/C++内存管理
c语言·c++
AuroraWanderll2 小时前
类和对象(三)-默认成员函数详解与运算符重载
c语言·开发语言·数据结构·c++·算法
Minecraft红客2 小时前
C++制作迷宫第一版
c++·游戏·电脑·娱乐
小螺软件宝2 小时前
使用DNGuard加密并打包C# .NET Core程序为单一EXE文件
网络·.netcore
雪域迷影2 小时前
Windows11中VS2026使用C++ 现代化json库nlohmann的3种方式
开发语言·c++·json
Bruce_Liuxiaowei2 小时前
Mac_Linux 查询网站IP地址:4个核心命令详解
linux·tcp/ip·macos
羑悻的小杀马特2 小时前
LRU Cache:高频访问数据的“智能保鲜舱”与经典淘汰艺术
c++·后端·lru cache·热点数据与冷数据
zephyr052 小时前
C++ STL string 用法详解与示例
开发语言·c++
爱吃番茄鼠骗2 小时前
Linux操作系统———UDP/IPC网络编程
linux·网络·udp