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 状态并退出程序

运行截图

重连成功

重连失败

相关推荐
Sinclair11 小时前
简单几步,安卓手机秒变服务器,安装 CMS 程序
android·服务器
Rockbean1 天前
用40行代码搭建自己的无服务器OCR
服务器·python·deepseek
蝎子莱莱爱打怪2 天前
Centos7中一键安装K8s集群以及Rancher安装记录
运维·后端·kubernetes
茶杯梦轩2 天前
CompletableFuture 在 项目实战 中 创建异步任务 的核心优势及使用场景
服务器·后端·面试
海天鹰2 天前
【免费】PHP主机=域名+解析+主机
服务器
DianSan_ERP2 天前
电商API接口全链路监控:构建坚不可摧的线上运维防线
大数据·运维·网络·人工智能·git·servlet
呉師傅2 天前
火狐浏览器报错配置文件缺失如何解决#操作技巧#
运维·网络·windows·电脑
不是二师兄的八戒2 天前
Linux服务器挂载OSS存储的完整实践指南
linux·运维·服务器
芝士雪豹只抽瑞克五2 天前
Nginx 高性能Web服务器笔记
服务器·nginx
失重外太空啦2 天前
Tomcat
java·服务器·tomcat