当前位置: 首页 > news >正文

【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;elsestd::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;elsestd::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  ewouldblockif (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;// 创建epollepoll_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_waitcontinue;}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;elsestd::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;elsestd::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 连接,使得网络编程更加高效和易于维护。

相关文章:

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

TcpClient 类的设计与实现&#xff1a;一个基于 C 的 TCP 客户端框架 在现代网络编程中&#xff0c;TCP&#xff08;传输控制协议&#xff09;客户端是实现网络通信的基础组件之一。本文将详细介绍一个基于 C 的 TcpClient 类的设计与实现&#xff0c;该类提供了创建 TCP 连接…...

PG备份恢复--pg_dump

pg_dump pg_dump 是一个逻辑备份工具。使用 pg_dump 可以在数据库处于使用状态下进行一致 性的备份,它不会阻塞其他用户对数据库的访问 。 一致性备份是 pg_dump 开始运行时&#xff0c;给数据库打了一个快照&#xff0c;且在 pg_dump 运行过程 中发生的更新将不会被备份。 …...

pikachu靶场搭建详细步骤

一、靶场下载 点我去下载 二、靶场安装 需要的环境&#xff1a; mysqlApaches&#xff08;直接使用小皮面板Phpstudy&#xff1a;https://www.xp.cn/&#xff09;&#xff0c;启动他们 设置网站&#xff0c;把靶场的路径对应过来 对应数据库的信息 由于没有核对数据库的信…...

HarmonyOS NEXT开发进阶(五):装饰器讲解

一、Provide Consume 父组件与子组件的子组件(官方叫法&#xff1a;后代组件)双向同步数据&#xff08;即&#xff0c;父组件与后代组件可以相互操作 Provide 修饰的数据&#xff09; 注意&#xff1a;Provide 与 Consume声明的变量名必须一致。 import {TestChild } from .…...

【编译原理】往年题汇总(山东大学软件学院用)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;编译原理_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2. …...

【漏洞复现】F5 BIG-IP Next Central Manager SQL注入漏洞(CVE-2024-26026)

免责声明 请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,作者不为此承担任何责任。工具来自网络,安全性自测,如有侵权请联系删除。本次测试仅供学习使用,如若非法他用,与平台和本文作…...

设计模式-创建型-单例模式

1. 单例模式简介 单例模式&#xff08;Singleton Pattern&#xff09;是一种常见的创建型设计模式&#xff0c;它确保一个类只有一个实例&#xff0c;并提供全局访问点。在很多情况下&#xff0c;我们只希望某个类在整个应用程序中有一个唯一的实例&#xff0c;且该实例需要在…...

VBA技术资料MF243:利用第三方软件复制PDF数据到EXCEL

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…...

【2024最新】基于Python+Mysql+django的水果销售系统Lw+PPT

作者&#xff1a;计算机搬砖家 开发技术&#xff1a;SpringBoot、php、Python、小程序、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;Java精选实战项…...

一种寻路的应用

应用背景 利用长途车进行货物转运的寻路计算。例如从深圳到大连。可以走有很多条长途车的路线。需要根据需求计算出最合适路线。不同的路线的总里程数、总价、需要的时间不一样。客户根据需求进行选择。主要有一些细节&#xff1a; 全国的长途车车站的数据的更新&#xff1a; …...

编译openssl遇到错误Parse errors: No plan found in TAP output的解决方法

在编译openssl时 tar -zxvf openssl-1.1.1p.tar.gz cd openssl-1.1.1p ./config --prefix/usr --openssldir/etc/ssl --shared zlib make make test 遇到错误 Parse errors: No plan found in TAP output 解决方法&#xff1a; yum install perl-Test-Simple...

一文大白话讲清楚防抖和节流,设计封装防抖和节流,以及防抖和节流的应用场景

文章目录 一文大白话讲清楚防抖和节流&#xff0c;设计封装防抖和节流&#xff0c;以及防抖和节流的应用场景1. 防抖和节流的背景2. 节流3. 节流的应用场景4. 防抖5. 防抖应用场景 一文大白话讲清楚防抖和节流&#xff0c;设计封装防抖和节流&#xff0c;以及防抖和节流的应用场…...

Windows开启IIS后依然出现http error 503.the service is unavailable

问题背景 已启用IIS服务&#xff0c;配置步骤可以参考Windows10 IIS Web服务器安装配置 问题描述 在这一步浏览网站时&#xff0c;并没有出现默认首页&#xff0c;而是 http error 503 the service is unavailable 问题解决 参考 成功解决http error 503.the service is un…...

C++的封装(十四):《设计模式》这本书

很多C学习者学到对C语言有一定自信后&#xff0c;会去读一下《设计模式》这本书。希望能够提升自己的设计水平。 据我所知&#xff0c;围绕C语言出了很多书。因为正好赶上泡沫经济时代。大家一拥而上&#xff0c;自己半懂不懂就出书&#xff0c;抢着出书收割读者&#xff0c;出…...

牛客周赛73B:JAVA

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 题目描述 \hspace{15pt}小红拿到了正整数 xxx &#xff0c;她希望你找到一个长度为 kkk 的区间&#xff0c;满足区间内恰好有 nnn 个数是 xxx 的倍数。你能帮帮她吗&#xff1f; 输入描述: …...

【Ubuntu 20.4安装截图软件 flameshot 】

步骤一&#xff1a; 安装命令&#xff1a; sudo apt-get install flameshot 步骤二&#xff1a; 设置快捷方式&#xff1a; Ubuntu20.4 设置菜单&#xff0c;点击 号 步骤三&#xff1a; 输入软件名称&#xff0c; 软件快捷命令&#xff08;flameshot gui&#xff09;&am…...

剑指Offer|LCR 014. 字符串的排列

LCR 014. 字符串的排列 给定两个字符串 s1 和 s2&#xff0c;写一个函数来判断 s2 是否包含 s1 的某个变位词。 换句话说&#xff0c;第一个字符串的排列之一是第二个字符串的 子串 。 示例 1&#xff1a; 输入: s1 "ab" s2 "eidbaooo" 输出: True 解…...

【Agent】Chatbot、Copilot与Agent如何帮助我们的提升效率?

人工智能&#xff08;AI&#xff09;技术的迅猛发展正在深刻改变我们的生活和工作方式。你是否曾想过&#xff0c;未来的工作场景会是什么样子&#xff1f;AI的崛起不仅仅是科技的进步&#xff0c;更是我们生活方式的革命。今天&#xff0c;我们将深入探讨三种主要的AI能力&…...

QT笔记- QTreeView + QFileSystemModel 当前位置的保存与恢复 #选中 #保存当前索引

保存当前位置 QString currentPath model->filePath(view->currentIndex()); // 获得当前位置路径 恢复位置 view->setCurrentIndex(model->index(currentPath)); // 设置此路径所在位置为当前位置...

OpenResty开发环境搭建

简介 OpenResty 是一个基于 Nginx的高性能 Web 平台&#xff0c;用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。官方地址&#xff1a;http://openresty.org/cn/ 具备下列特点&#xff1a; 具备Nginx的完整功能基于Lua语言进行扩展&#…...

国防科技大学计算机基础课程笔记02信息编码

1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制&#xff0c;因此这个了16进制的数据既可以翻译成为这个机器码&#xff0c;也可以翻译成为这个国标码&#xff0c;所以这个时候很容易会出现这个歧义的情况&#xff1b; 因此&#xff0c;我们的这个国…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

K8S认证|CKS题库+答案| 11. AppArmor

目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作&#xff1a; 1&#xff09;、切换集群 2&#xff09;、切换节点 3&#xff09;、切换到 apparmor 的目录 4&#xff09;、执行 apparmor 策略模块 5&#xff09;、修改 pod 文件 6&#xff09;、…...

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

vscode(仍待补充)

写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh&#xff1f; debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢

随着互联网技术的飞速发展&#xff0c;消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁&#xff0c;不仅优化了客户体验&#xff0c;还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用&#xff0c;并…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

Caliper 配置文件解析:config.yaml

Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中&#xff0c;新增了一个本地验证码接口 /code&#xff0c;使用函数式路由&#xff08;RouterFunction&#xff09;和 Hutool 的 Circle…...