【Linux编程】一个基于 C++ 的 TCP 客户端异步(epoll)框架(一))

TcpClient 类的设计与实现:一个基于 C++ 的 TCP 客户端框架

在现代网络编程中,TCP(传输控制协议)客户端是实现网络通信的基础组件之一。本文将详细介绍一个基于 C++ 的 TcpClient 类的设计与实现,该类提供了创建 TCP 连接、数据传输和接收等功能。通过这个类,我们可以更容易地理解和实现 TCP 通信的细节。

1. TcpClient 类概述

TcpClient 类是一个用于创建和管理 TCP 连接的客户端类。它封装了套接字创建、连接、数据发送和接收、断开连接等操作,使得网络通信更加简洁和易于管理。

2. 类构造与析构
  • 构造函数 TcpClient::TcpClient() 初始化了客户端文件描述符 client_fd 和 epoll 文件描述符 epoll_fd,并清零本地和远程地址结构体 local_addrremote_addr
TcpClient::TcpClient() : client_fd(0), epoll_fd(0) {
    memset(&local_addr, 0, sizeof(local_addr));
    memset(&remote_addr, 0, sizeof(remote_addr));
}
  • 析构函数 TcpClient::~TcpClient() 虚析构函数,确保派生类的析构函数被正确调用。
TcpClient::~TcpClient() {
}
3. 绑定与连接
  • 绑定套接字 TcpClient::Bind() 设置套接字选项,允许重用本地地址,避免 “地址已在使用” 的错误,并绑定本地端口。
void TcpClient::Bind(const std::string _host, const int _port) {
    int yes = 1;
    if (setsockopt(client_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
        std::cerr << "Setsockopt(SO_REUSEADDR) failed: " << strerror(errno) << std::endl;
        return;
    }
    // 绑定本地地址和端口
    local_addr.sin_family = AF_INET;
    local_addr.sin_port = ntohs(_port);
    if (inet_pton(AF_INET, _host.c_str(), &local_addr.sin_addr) <= 0) {
        std::cout << "Bind Invalid address:" << strerror(errno) << std::endl;
        return;
    }
    int ret = bind(client_fd, (sockaddr *)&local_addr, sizeof(local_addr));
    if (ret < 0)
        std::cout << "Bind error:" << strerror(errno) << std::endl;
}
  • 连接服务器 TcpClient::Connect() 创建套接字,设置非阻塞模式,并尝试连接到远程服务器。
int TcpClient::Connect(const std::string _host, const int _port) {
    if (running)
        return -1;

    create_socket();
    if (send_data_size != 0)
        set_data_cache_size(send_data_size, SO_SNDBUF);
    if (recv_data_size != 0)
        set_data_cache_size(recv_data_size, SO_RCVBUF);
    set_epoll_mode(client_fd, O_NONBLOCK);

    remote_addr.sin_family = AF_INET;
    remote_addr.sin_port = htons(_port);
    if (inet_pton(AF_INET, _host.c_str(), &remote_addr.sin_addr) <= 0) {
        std::cout << "Invalid address:" << strerror(errno) << std::endl;
        close(client_fd);
        return -1;
    }

    int ret = connect(client_fd, (sockaddr *)&remote_addr, sizeof(remote_addr));
    if (ret == -1 && errno != EINPROGRESS) {
        std::cout << "Connection failed:" << errno << " : " << strerror(errno) << std::endl;
        close(client_fd);
        return -1;
    }
    create_epoll();
    add_epoll_event(client_fd, epoll_fd, EPOLLIN | EPOLLOUT | EPOLLET);
    start_receive();
    return ret;
}
4. 数据传输
  • 写入数据 TcpClient::Write() 向服务器发送数据。
int TcpClient::Write(char *_data, int _offset, int _count) {
    if (!running || !connected) {
        std::cout << "write failed: running = false or connected = false:" << strerror(errno) << std::endl;
        return -1;
    }

    int ret = send(client_fd, _data + _offset, _count, 0);
    if (ret < 0)
        std::cout << "write failed:" << strerror(errno) << std::endl;
    return ret;
}
5. 资源管理
  • 关闭连接 TcpClient::Close() 关闭套接字和 epoll 文件描述符,释放资源。
void TcpClient::Close() {
    if (!running)
        return;
    int ret = -2;
    running = false;
    connected = false;
    if (epoll_fd != 0) {
        client_event.data.fd = client_fd;
        ret = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, &client_event);
        std::cout << "epoll_fd已关闭:" << ret << " : " << strerror(errno) << std::endl;
    }
    ret = close(client_fd);
    std::cout << "client_fd已关闭:" << ret << " : " << strerror(errno) << std::endl;
    if (epoll_fd != 0) {
        ret = close(epoll_fd);
        std::cout << "epoll_fd已关闭:" << ret << " : " << std::endl;
    }

    if (recv_data != nullptr)
        delete[] recv_data;
    recv_data = nullptr;
}
6. 缓冲区管理
  • 设置发送缓冲区大小 TcpClient::SetSendBuffSize()TcpClient::GetSendBuffSize() 管理套接字发送缓冲区大小。
void TcpClient::SetSendBuffSize(const int &_size) {
    send_data_size = _size;
}

int TcpClient::GetSendBuffSize() {
    return send_data_size;
}
  • 设置接收缓冲区大小 TcpClient::SetRecvBuffSize()TcpClient::GetRecvBuffSize() 管理套接字接收缓冲区大小。
void TcpClient::SetRecvBuffSize(const int &_size) {
    recv_data_size = _size;
}

int TcpClient::GetRecvBuffSize() {
    return recv_data_size;
}
7. 套接字操作
  • 创建套接字 TcpClient::create_socket() 创建 TCP 套接字。
int TcpClient::create_socket() {
    client_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (client_fd == -1) {
        std::cout << "Socket creation failed:" << strerror(errno) << std::endl;
        return -1;
    }
    return client_fd;
}
  • 设置非阻塞模式 TcpClient::set_epoll_mode() 设置套接字为非阻塞模式。
int TcpClient::set_epoll_mode(int sock_fd, int mode) {
    int flags = fcntl(sock_fd, F_GETFL, 0);
    if (flags == -1) {
        std::cout << "epoll_mode failed:" << sock_fd << std::endl;
        return -1;
    }
    int ret = fcntl(sock_fd, F_SETFL, flags | mode);
    std::cout << "设置epoll模式为: " << (mode == O_NONBLOCK ? "非阻塞模式" : "阻塞模式") << " : " << ret << " : " << strerror(errno) << std::endl;
    return ret;
}
8. 数据接收与处理
  • 启动接收线程 TcpClient::start_receive() 启动数据接收线程。
void TcpClient::start_receive() {
    running = true;
    recv_data = new char[recv_data_length];
    std::thread th = std::thread(&TcpClient::data_received_thread, this);
    th.detach();
}
  • 数据接收线程 TcpClient::data_received_thread() epoll 等待数据事件,处理接收和发送。
void TcpClient::data_received_thread() {
    struct epoll_event events[10];
    while (running) {
        int ret = epoll_wait(epoll_fd, events, 10, -1);
        if (ret < 0) {
            if (errno == EINTR || errno == EWOULDBLOCK) {
                continue;
            } else {
                std::cout << "epoll wait failed:" << strerror(errno) << std::endl;
                break;
            }
        }
        for (int i = 0; i < ret; i++) {
            if (events[i].data.fd == client_fd) {
                if (events[i].events & EPOLLIN) {
                    data_receive(this);
                }
                if (events[i].events & EPOLLOUT) {
                    connected = get_connect_state();
                }
            }
        }
    }
    Close();
    isDispose = true;
}
  • 获取连接状态 TcpClient::get_connect_state() 使用 getsockopt 检查连接是否成功。
bool TcpClient::get_connect_state() {
    int error = 0;
    socklen_t len = sizeof(error);
    if (getsockopt(client_fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
        std::cout << "get_connetct_state failed:" << error << " : " << strerror(error) << " : " << strerror(errno) << std::endl;
    } else if (error == 0) {
        if (connected)
            return connected;
        connected = true;
        get_local_addr();
        get_remote_addr();
        GetRecvBuffSize();
        GetSendBuffSize();
        std::cout << "成功连接到服务端: " << sockaddr_to_string(remote_addr) << std::endl;
    } else {
        connected = false;
        std::cout << "连接失败,错误码:" << error << " : " << strerror(error) << " : " << strerror(errno) << std::endl;
    }
    return connected;
}
  • 数据接收处理 TcpClient::data_receive() 接收数据并触发事件。
int TcpClient::data_receive(TcpClient *_client) {
    int recv_length = -1;
    while (true) {
        recv_length = recv(_client->client_fd, recv_data, recv_data_length, 0);
        if (recv_length > 0) {
            DataReceiveEventArgs e(_client->recv_data, recv_length, _client);
            DataReceived.Invoke(this, &e);
        } else {
            if (recv_length == 0) {
                std::cout << "客户端正常断开连接: " << recv_length << " : " << errno << " : " << strerror(errno) << std::endl;
                _client->Close();
                std::cout << "关闭客户端:" << _client->GetLocalEndpointTostring() << std::endl;
            } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
            } else {
                _client->Close();
                std::cout << "客户端异常断开连接:" << recv_length << " : " << errno << " : " << strerror(errno) << std::endl;
            }
            break;
        }
    }
    return recv_length;
}
9. 辅助函数
  • 设置缓冲区大小 TcpClient::set_data_cache_size() 设置套接字缓冲区大小。
int TcpClient::set_data_cache_size(const int &_size, const int &_cache) {
    std::string cache_name = (SO_SNDBUF == _cache) ? "发送缓冲区大小" : ((SO_RCVBUF == _cache) ? "接收缓冲区大小" : "未知缓冲区大小");
    int ret = setsockopt(client_fd, SOL_SOCKET, _cache, &_size, sizeof(_size));
    if (ret < 0)
        std::cout << "设置" << cache_name << "失败:" << strerror(errno) << std::endl;
    socklen_t len;
    if (_cache == SO_SNDBUF) {
        len = sizeof(send_data_size);
        ret = getsockopt(client_fd, SOL_SOCKET, _cache, &send_data_size, &len);
        if (ret < 0)
            std::cout << "获取" << cache_name << "失败" << strerror(errno) << std::endl;
        else
            std::cout << "获取" << cache_name << ":" << send_data_size << std::endl;
    } else if (_cache == SO_RCVBUF) {
        len = sizeof(recv_data_size);
        ret = getsockopt(client_fd, SOL_SOCKET, _cache, &recv_data_size, &len);
        if (ret < 0)
            std::cout << "获取" << cache_name << "失败" << strerror(errno) << std::endl;
        else
            std::cout << "获取" << cache_name << ":" << recv_data_size << std::endl;
    }
    return ret;
}
  • 获取本地地址 TcpClient::get_local_addr() 获取套接字本地地址。
sockaddr_in TcpClient::get_local_addr() {
    socklen_t len = sizeof(local_addr);
    int ret = getsockname(client_fd, (sockaddr *)&local_addr, &len);
    std::cout << "get_local_addr:" << ret << " : " << strerror(ret) << std::endl;
    return local_addr;
}
  • 获取远程地址 TcpClient::get_remote_addr() 获取套接字远程地址。
sockaddr_in TcpClient::get_remote_addr() {
    socklen_t len = sizeof(remote_addr);
    int ret = getpeername(client_fd, (sockaddr *)&remote_addr, &len);
    std::cout << "get_remote_addr:" << ret << " : " << strerror(ret) << std::endl;
    return remote_addr;
}
  • 地址转字符串 TcpClient::sockaddr_to_string() 将地址结构转换为字符串。
std::string TcpClient::sockaddr_to_string(sockaddr_in _sock_addr) {
    return std::string(inet_ntoa(_sock_addr.sin_addr)) + std::string(":") + std::to_string(htons(_sock_addr.sin_port));
}

完整的代码:
TcpClient.h 头文件

#pragma once

#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <sys/epoll.h>
#include <fcntl.h>
#include <thread>
#include "DataReceiveEventArgs.h"

class TcpServer;
class TcpClient
{
public:
    EventHandler<DataReceiveEventArgs> DataReceived;
    friend class TcpServer;

public:
    TcpClient();
    ~TcpClient();

public:
    void Bind(const std::string _host, const int _port);
    int Connect(const std::string _host, const int _port);
    bool Connected();
    int Write(char *_data, int _offset, int _count);
    void Close();
    bool IsDispose();
    std::string GetLocalEndpointTostring();
    std::string GetLocalEndpointTostring() const;
    void SetSendBuffSize(const int &_size);
    int GetSendBuffSize();
    void SetRecvBuffSize(const int &_size);
    int GetRecvBuffSize();

private:
    int create_socket();
    int set_epoll_mode(int sock_fd, int mode); // epoll模式--创建socket时,为非阻塞模式
    int create_epoll();
    int add_epoll_event(int _sock_fd, int _epoll_fd, uint32_t _events);
    void start_receive();

    void data_received_thread();
    bool get_connect_state();
    int data_receive(TcpClient *_client);
    int set_data_cache_size(const int &_size, const int &_cache /*_cache = SO_SNDBUF | SO_RCVBUF*/);

    sockaddr_in get_local_addr();
    sockaddr_in get_remote_addr();

    std::string sockaddr_to_string(sockaddr_in _sock_addr);
    std::string sockaddr_to_string(sockaddr_in _sock_addr) const;

private:
    int client_fd;
    int epoll_fd;
    bool running = false;
    bool connected = false;
    int recv_data_length = 1024 * 1024;
    int recv_data_size = 0;
    int send_data_size = 0;
    char *recv_data = nullptr;
    // 将客户端套接字添加到 epoll 中,监控 EPOLLIN 事件(表示有数据可读) EPOLLOUT(可写)  EPOLLET(边沿触发)
    struct epoll_event client_event;
    sockaddr_in remote_addr;
    sockaddr_in local_addr;
    bool isDispose = false;
};

TcpClient.cpp 实现

#include "TcpClient.h"

TcpClient::TcpClient() : client_fd(0), epoll_fd(0)
{
    memset(&local_addr, 0, sizeof(local_addr));
    memset(&remote_addr, 0, sizeof(remote_addr));
}

TcpClient::~TcpClient()
{
}

void TcpClient::Bind(const std::string _host, const int _port)
{
    /*这段代码的主要目的是设置一个套接字选项,使得该套接字可以重用本地地址。
    具体来说,它是通过 setsockopt 系统调用来设置 SO_REUSEADDR 选项。
    这是网络编程中非常常见的一个操作,尤其是当你需要在服务器端快速重启时,避免 "地址已在使用" 的错误。*/
    int yes = 1;
    if (setsockopt(client_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0)
    {
        std::cerr << "Setsockopt(SO_REUSEADDR) failed: " << strerror(errno) << std::endl;
        return;
    }

    // 设置本地端口
    std::memset(&local_addr, 0, sizeof(local_addr));
    local_addr.sin_family = AF_INET;
    local_addr.sin_port = ntohs(_port);
    if (inet_pton(AF_INET, _host.c_str(), &local_addr.sin_addr) <= 0)
    {
        std::cout << "Bind Invalid address:" << strerror(errno) << std::endl;
        return;
    }
    // local_addr.sin_addr.s_addr = INADDR_ANY; // 本地 IP 地址为任意地址
    int ret = bind(client_fd, (sockaddr *)&local_addr, sizeof(local_addr));
    if (ret < 0)
        std::cout << "Bind error:" << strerror(errno) << std::endl;
}

int TcpClient::Connect(const std::string _host, const int _port)
{
    if (running)
        return -1;

    // 创建套接字
    create_socket();
    if (send_data_size != 0)
        set_data_cache_size(send_data_size, SO_SNDBUF);
    if (recv_data_size != 0)
        set_data_cache_size(recv_data_size, SO_RCVBUF);
    // 设置socket为非阻塞模式
    set_epoll_mode(client_fd, O_NONBLOCK);

    // 设置服务器地址结构
    remote_addr.sin_family = AF_INET;
    remote_addr.sin_port = htons(_port);
    if (inet_pton(AF_INET, _host.c_str(), &remote_addr.sin_addr) <= 0)
    {
        std::cout << "Invalid address:" << strerror(errno) << std::endl;
        close(client_fd);
        return -1;
    }

    int ret = connect(client_fd, (sockaddr *)&remote_addr, sizeof(remote_addr));
    // 连接到服务器 eagain  ewouldblock
    if (ret == -1 && errno != EINPROGRESS)
    {
        std::cout << "Connection failed:" << errno << " : " << strerror(errno) << std::endl;
        close(client_fd);
        return -1;
    }
    create_epoll();
    add_epoll_event(client_fd, epoll_fd, EPOLLIN | EPOLLOUT | EPOLLET);
    start_receive();
    return ret;
}

bool TcpClient::Connected()
{
    return connected;
}

int TcpClient::Write(char *_data, int _offset, int _count)
{
    if (!running || !connected)
    {
        std::cout << "write failed: running = false or connected = false:" << strerror(errno) << std::endl;
        return -1;
    }

    int ret = send(client_fd, _data + _offset, _count, 0);
    if (ret < 0)
        std::cout << "write failed:" << strerror(errno) << std::endl;
    return ret;
}

void TcpClient::Close()
{
    if (!running)
        return;
    int ret = -2;
    running = false;
    connected = false;
    // 1. 删除client_fd的epoll事件
    if (epoll_fd != 0)
    {
        client_event.data.fd = client_fd;

        ret = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, &client_event);
        std::cout << "epoll_fd已关闭:" << ret << " : " << strerror(errno) << std::endl;
    }
    // 2. 关闭监听套接字
    ret = close(client_fd);
    std::cout << "client_fd已关闭:" << ret << " : " << strerror(errno) << std::endl;
    // 3. 关闭 epoll 文件描述符
    if (epoll_fd != 0)
    {
        ret = close(epoll_fd);
        std::cout << "epoll_fd已关闭:" << ret << " : " << std::endl;
    }

    if (recv_data != nullptr)
        delete[] recv_data;
    recv_data = nullptr;
}

bool TcpClient::IsDispose()
{
    return this->isDispose;
}

std::string TcpClient::GetLocalEndpointTostring()
{
    return sockaddr_to_string(local_addr);
}

std::string TcpClient::GetLocalEndpointTostring() const
{
    return sockaddr_to_string(local_addr);
}

void TcpClient::SetSendBuffSize(const int &_size)
{
    send_data_size = _size;
}

int TcpClient::GetSendBuffSize()
{
    // 打印当前设置的缓冲区大小
    return send_data_size;
}

void TcpClient::SetRecvBuffSize(const int &_size)
{
    // 设置接收缓冲区大小
    recv_data_size = _size;
}

int TcpClient::GetRecvBuffSize()
{
    // 获取接收缓冲区大小
    return recv_data_size;
}

int TcpClient::create_socket()
{
    client_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (client_fd == -1)
    {
        std::cout << "Socket creation failed:" << strerror(errno) << std::endl;
        return -1;
    }
    return client_fd;
}

int TcpClient::set_epoll_mode(int sock_fd, int mode)
{
    /*
    O_NONBLOCK(非阻塞模式):如果设置了这个标志,表示该套接字(或文件)是非阻塞的,执行读写操作时不会阻塞调用进程或线程。
    套接字在没有数据可读或可写时不会让程序等待,而是立即返回。
    O_RDWR、O_WRONLY、O_RDONLY(访问模式):表示套接字的打开方式。
    O_APPEND(追加模式):指示文件或套接字在写操作时会追加数据。
    */
    int flags = fcntl(sock_fd, F_GETFL, 0); // 获取当前套接字的文件状态标志
    if (flags == -1)
    {
        std::cout << "epoll_mode failed:" << sock_fd << std::endl;
        return -1;
    }

    std::string mode_str = (mode == O_NONBLOCK) ? "非阻塞模式" : "阻塞模式";
    // 设置套接字为非阻塞模式
    int ret = fcntl(sock_fd, F_SETFL, flags | mode);
    std::cout << "设置epoll模式为: " << mode_str << " : " << ret << " : " << strerror(errno) << std::endl;
    return ret;
}

int TcpClient::create_epoll()
{
    // // 获取本地IP和端口
    // sockaddr_in local_addr;
    // socklen_t addr_len = sizeof(local_addr);
    // getsockname(client_fd, (sockaddr *)&local_addr, &addr_len);
    // std::cout << "本地IP和端口:" << "(" << ret << ")" << inet_ntoa(local_addr.sin_addr) << ":" << htons(local_addr.sin_port) << std::endl;
    // 创建epoll
    epoll_fd = epoll_create1(0);
    if (epoll_fd < 0)
    {
        std::cout << "epoll create failed:" << strerror(errno) << std::endl;
        close(client_fd);
    }
    return epoll_fd;
}

int TcpClient::add_epoll_event(int _sock_fd, int _epoll_fd, uint32_t _events)
{
    epoll_event _epoll_event;
    _epoll_event.events = _events;
    _epoll_event.data.fd = _sock_fd;
    int ret = epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, _sock_fd, &_epoll_event);
    if (ret == -1)
    {
        std::cout << "epoll ctl add failed:" << strerror(errno) << std::endl;
        close(_sock_fd);
        close(_epoll_fd);
        return -1;
    }
    return ret;
}

void TcpClient::start_receive()
{
    running = true;
    recv_data = new char[recv_data_length];
    std::thread th = std::thread(&TcpClient::data_received_thread, this);
    th.detach();
}

void TcpClient::data_received_thread()
{
    struct epoll_event events[10];
    while (running)
    {
        // 处理客户端数据
        int ret = epoll_wait(epoll_fd, events, 10, -1);
        if (ret < 0)
        {
            if (errno == EINTR || errno == EWOULDBLOCK)
            {
                // 如果 epoll_wait 被信号中断,继续调用 epoll_wait
                continue;
            }
            else
            {
                std::cout << "epoll wait failed:" << strerror(errno) << std::endl;
                break;
            }
        }
        // 遍历所有发生的事件
        for (int i = 0; i < ret; i++)
        {
            if (events[i].data.fd == client_fd)
            {
                if (events[i].events & EPOLLIN)
                {
                    data_receive(this);
                }
                if (events[i].events & EPOLLOUT)
                {
                    connected = get_connect_state();
                    // 当send或write后,会触发此事件,可以做其他事情了
                    // std::cout << (clock() / 1000 % 60) << "一触发一次写操作" << std::endl;
                }
            }
        }
    }
    Close();
    isDispose = true;
}

bool TcpClient::get_connect_state()
{
    // 8. 使用 getsockopt 检查连接是否成功
    int error = 0;
    socklen_t len = sizeof(error);
    if (getsockopt(client_fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
    {
        std::cout << "get_connetct_state failed:" << error << " : " << strerror(error) << " : " << strerror(errno) << std::endl;
    }
    else if (error == 0)
    {
        if (connected)
            return connected;
        connected = true;
        get_local_addr();
        get_remote_addr();
        GetRecvBuffSize();
        GetSendBuffSize();
        std::cout << "成功连接到服务端: " << sockaddr_to_string(remote_addr) << std::endl;
    }
    else
    {
        connected = false;
        std::cout << "连接失败,错误码:" << error << " : " << strerror(error) << " : " << strerror(errno) << std::endl;
    }
    return connected;
}

int TcpClient::data_receive(TcpClient *_client)
{
    // 处理客户端数据
    // int length = 0;
    // if (ioctl(_client.client_fd, FIONREAD, &length) == -1)
    // {
    //     std::cout << "读取客户端(" << _client.GetLocalEndpointTostring() << ")缓冲区数据长度失败:" << strerror(errno) << std::endl;
    //     return;
    // }
    int recv_length = -1;
    while (true)
    {
        recv_length = recv(_client->client_fd, recv_data, recv_data_length, 0); // 从客户端读取数据
        if (recv_length > 0)
        {
            // 接收到客户端的数据,执行后续处理
            DataReceiveEventArgs e(_client->recv_data, recv_length, _client);
            // e.AutoRelease();
            DataReceived.Invoke(this, &e);
        }
        else
        {
            // 如果读取到 0 字节或者出错,表示客户端关闭连接或发生错误
            if (recv_length == 0)
            {
                std::cout << "客户端正常断开连接: " << recv_length << " : " << errno << " : " << strerror(errno) << std::endl; // 客户端正常断开连接

                _client->Close();

                std::cout << "关闭客户端:" << _client->GetLocalEndpointTostring() << std::endl;
            }
            else if (errno == EAGAIN || errno == EWOULDBLOCK)
            {
                // std::cout << "客户端无数据可接收了,请再试一次:" << bytes_count << " : " << errno << " : " << strerror(errno) << std::endl; // 客户端正常断开连接
            }
            else
            {
                _client->Close();
                std::cout << "客户端异常断开连接:" << recv_length << " : " << errno << " : " << strerror(errno) << std::endl; // 客户端正常断开连接
            }
            break;
        }
    }

    return recv_length;
    // std::this_thread::sleep_for(std::chrono::milliseconds(20));
}

int TcpClient::set_data_cache_size(const int &_size, const int &_cache /*_cache = SO_SNDBUF | SO_RCVBUF*/)
{
    // 设置发送缓冲区大小  _cache = SO_SNDBUF | SO_RCVBUF/
    std::string cache_name = (SO_SNDBUF == _cache) ? "发送缓冲区大小" : ((SO_RCVBUF == _cache) ? "接收缓冲区大小" : "未知缓冲区大小");

    int ret = setsockopt(client_fd, SOL_SOCKET, _cache, &_size, sizeof(_size));
    if (ret < 0)
        std::cout << "设置" << cache_name << "失败:" << strerror(errno) << std::endl;
    socklen_t len;
    if (_cache == SO_SNDBUF)
    {
        len = sizeof(send_data_size);
        ret = getsockopt(client_fd, SOL_SOCKET, _cache, &send_data_size, &len);
        if (ret < 0)
            std::cout << "获取" << cache_name << "失败" << strerror(errno) << std::endl;
        else
            std::cout << "获取" << cache_name << ":" << send_data_size << std::endl;
    }
    else if (_cache == SO_RCVBUF)
    {
        len = sizeof(recv_data_size);
        ret = getsockopt(client_fd, SOL_SOCKET, _cache, &recv_data_size, &len);
        if (ret < 0)
            std::cout << "获取" << cache_name << "失败" << strerror(errno) << std::endl;
        else
            std::cout << "获取" << cache_name << ":" << recv_data_size << std::endl;
    }
    return ret;
}

sockaddr_in TcpClient::get_local_addr()
{
    socklen_t len = sizeof(local_addr);
    int ret = getsockname(client_fd, (sockaddr *)&local_addr, &len);
    std::cout << "get_local_addr:" << ret << " : " << strerror(ret) << std::endl;
    return local_addr;
}

sockaddr_in TcpClient::get_remote_addr()
{
    socklen_t len = sizeof(remote_addr);
    int ret = getpeername(client_fd, (sockaddr *)&remote_addr, &len);
    std::cout << "get_remote_addr:" << ret << " : " << strerror(ret) << std::endl;
    return remote_addr;
}

std::string TcpClient::sockaddr_to_string(sockaddr_in _sock_addr)
{
    return std::string(inet_ntoa(_sock_addr.sin_addr)) + std::string(":") + std::to_string(htons(_sock_addr.sin_port));
}

std::string TcpClient::sockaddr_to_string(sockaddr_in _sock_addr) const
{
    return std::string(inet_ntoa(_sock_addr.sin_addr)) + std::string(":") + std::to_string(htons(_sock_addr.sin_port));
}

10. 总结

本文详细介绍了 TcpClient 类的设计与实现,包括构造与析构、绑定与连接、数据传输、资源管理、缓冲区管理、套接字操作、数据接收与处理以及辅助函数。通过这个类,我们可以更容易地理解和实现 TCP 通信的细节。这个类提供了一个简洁的接口来管理 TCP 连接,使得网络编程更加高效和易于维护。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/942981.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

20241225在ubuntu22.04.5下使用smartmontools命令查看ssd的寿命

20241225在ubuntu22.04.5下使用smartmontools命令查看ssd的寿命 2024/12/25 15:10 rootrootrootroot-ThinkBook-16-G5-IRH:~$ sudo apt install smartmontools rootrootrootroot-ThinkBook-16-G5-IRH:~$ sudo fdisk -l Disk /dev/nvme0n1: 3.73 TiB, 4096805658624 bytes, 800…

大数据学习之Redis 缓存数据库二,Scala分布式语言一

一.Redis 缓存数据库二 26.Redis数据安全_AOF持久化机制 27.Redis数据安全_企业中该如何选择持久化机制 28.Redis集群_主从复制概念 29.Redis集群_主从复制搭建 30.Redis集群_主从复制原理剖析 31.Redis集群_哨兵监控概述 32.Redis集群_配置哨兵监控 33.Redis集群_哨兵监控原理…

Datawhale AI 冬令营学习笔记-零编程基础制作井字棋小游戏

井字棋小游戏是通过豆包MarsCode实现的&#xff0c;没有改动任何的代码&#xff0c;全部是通过对话让AI进行优化和改进。 开始进入正题&#xff1a;进入豆包MarsCode在线IDE&#xff0c;直接点击上方蓝字&#xff0c;或复制链接打开: 豆包 MarsCode - 编程助手。 IDE界面&…

vscode+编程AI配置、使用说明

文章目录 [toc]1、概述2、github copilot2.1 配置2.2 使用文档2.3 使用说明 3、文心快码&#xff08;Baidu Comate&#xff09;3.1 配置3.2 使用文档3.3 使用说明 4、豆包&#xff08;MarsCode&#xff09;4.1 配置4.2 使用文档4.3 使用说明 5、通义灵码&#xff08;TONGYI Lin…

Redis数据结构和内部编码以及单线程架构

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 Redis数据结构和内部编码以及单线程架构 收录于专栏[redis] 本专栏旨在分享学习Redis的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 …

虚拟机Hyper-V,安装网络宝塔Docker

我下载的是centos-min大小1G&#xff0c;安装后没网络&#xff0c; 关闭防火墙&#xff0c;网络&#xff0c;修改onBootyes,这里需要看下network-Scripts下有什么文件。 然后就可以访问网络了 虚拟机的设置也是默认就好 网络需要设置允许共享-重要 urlhttps://download.bt.cn/i…

红魔电竞PadPro平板解BL+ROOT权限-KernelSU+LSPosed框架支持

红魔Padpro设备目前官方未开放解锁BL&#xff0c;也阉割了很多解锁BL指令&#xff0c;造成大家都不能自主玩机。此规则从红魔8开始&#xff0c;就一直延续下来&#xff0c;后续的机型大概率也是一样的情况。好在依旧有开发者进行适配研究&#xff0c;目前红魔PadPro平板&#x…

Linux-----进程处理(文件IO资源使用)

下面代码是通过父进程和子进程对同一个文件IO资源进行操作&#xff0c;父进程和子进程都对这个进程进行写入操作&#xff0c;我们都知道这两个进程实际上是并发的&#xff0c;所以需要一个同步机制来去操作同一个资源&#xff08;后面再深入去说明同步的api&#xff0c;这里使用…

EdgeX Core Service 核心服务之 Core Command 命令

EdgeX Core Service 核心服务之 Core Command 命令 一、概述 Core-command(通常称为命令和控制微服务)可以代表以下角色向设备和传感器发出命令或动作: EdgeX Foundry中的其他微服务(例如,本地边缘分析或规则引擎微服务)EdgeX Foundry与同一系统上可能存在的其他应用程序…

【LeetCode】94.二叉树的中序遍历

题目链接&#xff1a; 94.二叉树的中序遍历 题目描述&#xff1a; 题解&#xff1a;&#xff08;递归算法实现二叉树中序遍历&#xff09; 二叉树的中序遍历&#xff1a;按照访问左子树——根节点——右子树的方式遍历这棵树&#xff0c;而在访问左子树或者右子树的时候我们按…

LeetCode:404.左叶子之和

跟着carl学算法&#xff0c;本系列博客仅做个人记录&#xff0c;建议大家都去看carl本人的博客&#xff0c;写的真的很好的&#xff01; 代码随想录 LeetCode&#xff1a;404.左叶子之和 给定二叉树的根节点 root &#xff0c;返回所有左叶子之和。 示例 1&#xff1a; 输入: …

AI对话机器人简单实现--智谱BigModel+SpringBoot+Vue2+ElementUI

成品展示 一、首先去注册个账号然后申请个API keys 二、引入依赖 <dependency><groupId>cn.bigmodel.openapi</groupId><artifactId>oapi-java-sdk</artifactId><version>release-V4-2.3.0</version></dependency><depend…

每天40分玩转Django:Django静态文件

Django静态文件 一、今日学习内容概述 学习模块重要程度主要内容静态文件配置⭐⭐⭐⭐⭐基础设置、路径配置CDN集成⭐⭐⭐⭐⭐CDN配置、资源优化静态文件处理⭐⭐⭐⭐压缩、版本控制部署优化⭐⭐⭐⭐性能优化、缓存策略 二、基础配置 # settings.py import os# 静态文件配置…

改进爬山算法之一:随机化爬山法(Stochastic Hill Climbing,SHC)

随机化爬山法(Stochastic Hill Climbing),也被称为随机爬山法,是一种基于搜索算法的优化方法,是爬山算法的一个变种,它通过引入随机性来减少算法陷入局部最优解的风险,并增加搜索解空间的能力。这种方法特别适合于解决那些具有多个局部最优解的优化问题。 一、算法思想 …

农家乐系统|Java|SSM|VUE| 前后端分离

【技术栈】 1⃣️&#xff1a;架构: B/S、MVC 2⃣️&#xff1a;系统环境&#xff1a;Windowsh/Mac 3⃣️&#xff1a;开发环境&#xff1a;IDEA、JDK1.8、Maven、Mysql5.7 4⃣️&#xff1a;技术栈&#xff1a;Java、Mysql、SSM、Mybatis-Plus、VUE、jquery,html 5⃣️数据库可…

探究音频丢字位置和丢字时间对pesq分数的影响

丢字的本质 丢字的本质是在一段音频中一小段数据变为0 丢字对主观感受的影响 1. 丢字位置 丢字的位置对感知效果有很大影响。如果丢字发生在音频信号的静音部分或低能量部分&#xff0c;感知可能不明显&#xff1b;而如果丢字发生在高能量部分或关键音素上&#xff0c;感知…

《Java源力物语》-3.空值猎手

~犬&#x1f4f0;余~ “我欲贱而贵&#xff0c;愚而智&#xff0c;贫而富&#xff0c;可乎&#xff1f; 曰&#xff1a;其唯学乎” \quad 夜色渐深&#xff0c;在一处偏僻小径上&#xff0c;月光透过浓密的源力云层&#xff0c;在地面上投下斑驳的光影。String正独自练习着刚从…

产品初探Devops!以及AI如何赋能Devops?

DevOps源自Development&#xff08;开发&#xff09;和Operations&#xff08;运维&#xff09;的组合&#xff0c;是一种新的软件工程理念&#xff0c;旨在打破传统软件工程方法中“开发->测试->运维”的割裂模式&#xff0c;强调端到端高效一致的交付流程&#xff0c;实…

使用 OpenCV 在图像中添加文字

在图像处理任务中&#xff0c;我们经常需要将文本添加到图像中。OpenCV 提供了 cv2.putText() 函数&#xff0c;可以很方便地在图像上绘制文本&#xff0c;支持多种字体、颜色、大小和位置等参数。 本文将详细介绍如何使用 OpenCV 在图像中添加文字&#xff0c;介绍 cv2.putTe…

接口测试Day-02-安装postman项目推送Gitee仓库

postman安装 下载 Postman&#xff08;已提供安装包&#xff0c;此步可以跳过&#xff09; https://www.postman.com/downloads/安装 Postman 安装Postman插件newman 要想给 postman 安装 newman 插件&#xff0c;必须 先 安装 node.js。 这是前提&#xff01; 安装node.js 可能…