connect 的断线重连

目录

TcpClient.cc

辅助函数

连接状态枚举

[ClientConnection 类](#ClientConnection 类)

成员变量

核心方法

构造函数:

Connect():

Disconnect():

SockFd():

Reconnect():

GetStatus():

Process():

析构函数:

[TcpClient 类](#TcpClient 类)

核心方法

功能与流程

[main 函数](#main 函数)

程序工作流程

运行截图

重连成功

重连失败


客户端会面临服务器崩溃的情况,我们可以试着写一个客户端重连的代码,模拟并理解一些客户端行为,比如游戏客户端等

TcpClient.cc

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

void Usage(const std::string &process)
{
    std::cout << "Usage: " << process << " server_ip server_port" << std::endl;
}

enum class Status // C++11强类型枚举
{
    NEW,           // 新建状态,就是单纯的连接
    CONNECTING,    // 正在连接,仅仅方便查询conn状态
    CONNECTED,     // 连接或者重连成功
    DISTCONNECTED, // 重连失败
    CLOSED         // 连接失败,经历重连,无法连接
};

class ClientConnection
{
public:
    ClientConnection(uint16_t serverport, const std::string serverip)
        : _sockfd(-1), _serverport(serverport), _serverip(serverip), _retry_interval(1), _max_retries(5), _status(Status::NEW)
    {
    }
    void Connect()
    {
        // 1.创建socket
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            std::cerr << "socket error" << std::endl;
            exit(1);
        }

        // 2.要不要bind?必须要有Ip和Port,需要bind,但是不需要用户显示的bind,client系统随机端口
        // 发起连接的时候,client会被OS自动进行本地绑定
        // 2.connect
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(_serverport);
        // p:process(进程),n(网络) -- 不太准确,但是好记忆
        inet_pton(AF_INET, _serverip.c_str(), &server.sin_addr);
        // 1.字符串ip->4字节IP 2.网络序列

        int n = connect(_sockfd, (struct sockaddr *)&server, sizeof(server)); // 自动进行bind
        if (n < 0)
        {
            Disconnect();                    // 恢复sockfd的默认值,是连接没有成功,不代表sockfd创建没有成功
            _status = Status::DISTCONNECTED; // 没有连接成功
            return;
        }
        _status = Status::CONNECTED; // 连接成功
    }
    int SockFd()
    {
        return _sockfd;
    }
    void Reconnect()
    {
        _status = Status::CONNECTING; // 正在重连
        int count = 0;
        while (count < _max_retries)
        {
            Connect(); // 重连
            if (_status == Status::CONNECTED)
            {
                return;
            }
            sleep(_retry_interval);
            count++;
            std::cout << "重连次数: " << count << ", 最大上限: " << _max_retries << std::endl;
        }
        _status = Status::CLOSED; // 重连失败,可以关闭了
    }
    void Disconnect()
    {
        if (_sockfd != -1)
        {
            close(_sockfd);
            _status = Status::CLOSED;
            _sockfd = -1;
        }
    }
    Status GetStatus()
    {
        return _status;
    }
    void Process()
    {
        // 简单的IO即可
        while (true)
        {
            std::string inbuffer;
            std::cout << "Please Enter#";
            getline(std::cin, inbuffer);
            if (inbuffer.empty())
                continue;

            ssize_t n = write(_sockfd, inbuffer.c_str(), inbuffer.size());
            if (n > 0)
            {
                char buffer[1024];
                ssize_t m = read(_sockfd, buffer, sizeof(buffer) - 1);
                if (m > 0)
                {
                    buffer[m] = 0;
                    std::cout << buffer << std::endl;
                }
                else if (m == 0) // 这里证明server端掉线了
                {
                    _status = Status::DISTCONNECTED;
                    break;
                }
                else
                {
                    std::cout << "read m : " << m << "errno: " << errno << "errno string: " << strerror(errno) << std::endl;
                    _status = Status::CLOSED;
                    break;
                }
            }
            else
            {
                std::cout << "write n : " << n << "errno: " << errno << "errno string: " << strerror(errno) << std::endl;
                _status = Status::CLOSED;
                break;
            }
        }
    }
    ~ClientConnection()
    {
        Disconnect();
    }

private:
    int _sockfd;
    uint16_t _serverport;  // server port端口号
    std::string _serverip; // server ip地址
    int _retry_interval;   // 重试时间间隔
    int _max_retries;      // 重试次数
    Status _status;        // 连接状态
};

class TcpClient
{
public:
    TcpClient(uint16_t serverport, const std::string serverip)
        : _conn(serverport, serverip)
    {
    }

    void Execute()
    {
        while (true)
        {
            switch (_conn.GetStatus())
            {
            case Status::NEW:
                _conn.Connect();
                break;
            case Status::CONNECTED:
                std::cout << "连接成功,开始进行通信." << std::endl;
                _conn.Process();
                break;
            case Status::DISTCONNECTED:
                std::cout << "连接失败或者对方掉线,开始重连." << std::endl;
                _conn.Reconnect();
                break;
            case Status::CLOSED:
                _conn.Disconnect();
                std::cout << "重连失败,退出." << std::endl;
                return;
            default:
                break;
            }
        }
    }

    ~TcpClient()
    {
    }

private:
    ClientConnection _conn; // 简单组合起来即可
};
// class Tcp

//./tcpclient 127.0.0.1 8888
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        return 1;
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);
    TcpClient client(serverport, serverip);
    client.Execute();
    return 0;
}

我们一块一块来讲

辅助函数

这个函数用于提示用户程序的正确使用方法,当命令行参数不正确时会被调用。

连接状态枚举

使用 C++11 的强类型枚举定义了客户端连接的各种状态,使状态管理更清晰。

ClientConnection 类

这个类封装了与服务器连接的所有操作:

成员变量

核心方法

构造函数:
  • 作用 :用于初始化ClientConnection类的成员变量。
  • 成员变量初始化
    • _sockfd初始化为 - 1,表示尚未创建 socket。
    • _serverport_serverip分别由构造函数参数初始化,对应服务器的端口和 IP 地址。
    • _retry_interval初始化为 1,即重连时间间隔为 1 秒。
    • _max_retries初始化为 5,即最大重连次数为 5 次。
    • _status初始化为Status::NEW,表示初始连接状态为新建。
Connect()

功能概述

该函数完成了 TCP 客户端与服务器建立连接的核心流程,包括创建 socket、设置服务器地址结构、发起连接并根据结果更新连接状态。

步骤解析

  1. 创建 socket
    • 调用socket函数创建基于 IPv4(AF_INET)的流式套接字(SOCK_STREAM,即 TCP 协议)。
    • 若创建失败,输出错误信息并退出程序。
  2. 设置服务器地址结构
    • 定义sockaddr_in类型的服务器地址变量server,并通过memset初始化。
    • 设置地址族为AF_INET,端口号(通过htons转换为网络字节序),以及服务器 IP 地址(通过inet_pton将字符串 IP 转换为网络字节序的 4 字节 IP)。
  3. 发起连接
    • 调用connect函数尝试与服务器建立连接。
    • 若连接失败,调用Disconnect函数恢复 socket 状态,并将连接状态设置为DISTCONNECTED(重连失败);若连接成功,将状态设置为CONNECTED
Disconnect()

功能与逻辑

  • 该函数用于安全地关闭客户端与服务器的 socket 连接。
  • 首先判断sockfd(socket 文件描述符)是否有效(不为 - 1),若有效则调用close函数关闭 socket。
  • 随后将连接状态_status设置为CLOSED,并将sockfd重置为 - 1,确保资源释放和状态一致性。
SockFd():

返回sockfd套接字

Reconnect()

功能与流程

  • 状态设置 :首先将连接状态置为CONNECTING,表示正在重连。
  • 重连循环 :通过while循环,在最大重试次数(_max_retries)内反复调用Connect函数尝试重连。
  • 重试控制 :每次重连失败后,休眠_retry_interval时间(秒),并输出重连次数和最大上限的提示信息。
  • 结果处理 :若重连成功(状态为CONNECTED)则直接返回;若达到最大重试次数仍失败,将状态置为CLOSED
GetStatus():

获取当前状态。

Process()

功能与流程

  • 输入处理 :通过getline读取用户输入,若输入为空则跳过本次循环。
  • 数据发送 :调用write函数将用户输入发送给服务器。
  • 数据接收 :调用read函数接收服务器返回的数据,若接收成功则输出;若接收长度为 0(服务器端掉线),则将状态置为DISTCONNECTED并退出循环;若接收失败,输出错误信息并将状态置为CLOSED
  • 发送失败处理 :若write失败,输出错误信息并将状态置为CLOSED
析构函数:

调用Disconnect使sockfd和status回到初始状态。

TcpClient 类

这个类是客户端的入口,通过组合 ClientConnection 对象来实现功能:

核心方法

Execute()

功能与流程
  • 循环控制 :通过while(true)循环持续监控连接状态。
  • 状态分支
    • 当状态为NEW时,调用Connect尝试建立连接。
    • 当状态为CONNECTED时,输出连接成功提示并调用Process进行通信交互。
    • 当状态为DISTCONNECTED时,输出重连提示并调用Reconnect尝试重连。
    • 当状态为CLOSED时,调用Disconnect并输出重连失败提示后退出函数。

main 函数

  • 检查命令行参数
  • 解析服务器 IP 和端口
  • 创建 TcpClient 对象并调用 Execute () 开始运行

程序工作流程

  1. 程序启动时检查命令行参数
  2. 创建客户端对象,初始状态为 NEW
  3. 尝试连接服务器,成功则进入 CONNECTED 状态
  4. 在 CONNECTED 状态下,用户可以输入消息发送给服务器,并接收服务器响应
  5. 如果连接断开(服务器关闭或网络问题),进入 DISTCONNECTED 状态并尝试重连
  6. 重连失败达到最大次数后,进入 CLOSED 状态并退出程序

运行截图

重连成功

重连失败

相关推荐
wydaicls4 小时前
Linux 内核伙伴系统在快速路径分配内存时,对一个内存区域(Zone)进行水位线检查和内存压力评估的关键逻辑
linux·服务器
黄昏晓x4 小时前
Linux----权限
linux·运维·服务器
小白不想白a4 小时前
【shell】每日shell练习(系统服务状态监控/系统性能瓶颈分析)
linux·运维·服务器
一匹电信狗5 小时前
【MySQL】数据库的相关操作
linux·运维·服务器·数据库·mysql·ubuntu·小程序
迦蓝叶5 小时前
JAiRouter v1.0.0 正式发布:企业级 AI 服务网关的开源解决方案
java·运维·人工智能·网关·spring·ai·开源
bugtraq20215 小时前
为什么.NET的System.IO.Compression无法解压zlib流
linux·运维·服务器
insight^tkk6 小时前
【Docker】记录一次使用docker部署dify网段冲突的问题
运维·人工智能·docker·ai·容器
K_i1347 小时前
Hadoop 集群自动化运维实战
运维·hadoop·自动化
TH_17 小时前
cmd_常用命令
服务器