当前位置: 首页 > 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语言进行扩展&#…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术&#xff0c;说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号&#xff08;调制&#xff09; 把信息从信号中抽取出来&am…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

九天毕昇深度学习平台 | 如何安装库?

pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子&#xff1a; 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

JS设计模式(4):观察者模式

JS设计模式(4):观察者模式 一、引入 在开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;一个对象的状态变化需要自动通知其他对象&#xff0c;比如&#xff1a; 电商平台中&#xff0c;商品库存变化时需要通知所有订阅该商品的用户&#xff1b;新闻网站中&#xff0…...

C++实现分布式网络通信框架RPC(2)——rpc发布端

有了上篇文章的项目的基本知识的了解&#xff0c;现在我们就开始构建项目。 目录 一、构建工程目录 二、本地服务发布成RPC服务 2.1理解RPC发布 2.2实现 三、Mprpc框架的基础类设计 3.1框架的初始化类 MprpcApplication 代码实现 3.2读取配置文件类 MprpcConfig 代码实现…...

Axure 下拉框联动

实现选省、选完省之后选对应省份下的市区...

es6+和css3新增的特性有哪些

一&#xff1a;ECMAScript 新特性&#xff08;ES6&#xff09; ES6 (2015) - 革命性更新 1&#xff0c;记住的方法&#xff0c;从一个方法里面用到了哪些技术 1&#xff0c;let /const块级作用域声明2&#xff0c;**默认参数**&#xff1a;函数参数可以设置默认值。3&#x…...