【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_addr
和remote_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 类的设计与实现:一个基于 C 的 TCP 客户端框架 在现代网络编程中,TCP(传输控制协议)客户端是实现网络通信的基础组件之一。本文将详细介绍一个基于 C 的 TcpClient 类的设计与实现,该类提供了创建 TCP 连接…...
PG备份恢复--pg_dump
pg_dump pg_dump 是一个逻辑备份工具。使用 pg_dump 可以在数据库处于使用状态下进行一致 性的备份,它不会阻塞其他用户对数据库的访问 。 一致性备份是 pg_dump 开始运行时,给数据库打了一个快照,且在 pg_dump 运行过程 中发生的更新将不会被备份。 …...

pikachu靶场搭建详细步骤
一、靶场下载 点我去下载 二、靶场安装 需要的环境: mysqlApaches(直接使用小皮面板Phpstudy:https://www.xp.cn/),启动他们 设置网站,把靶场的路径对应过来 对应数据库的信息 由于没有核对数据库的信…...
HarmonyOS NEXT开发进阶(五):装饰器讲解
一、Provide Consume 父组件与子组件的子组件(官方叫法:后代组件)双向同步数据(即,父组件与后代组件可以相互操作 Provide 修饰的数据) 注意:Provide 与 Consume声明的变量名必须一致。 import {TestChild } from .…...

【编译原理】往年题汇总(山东大学软件学院用)
🌈 个人主页:十二月的猫-CSDN博客 🔥 系列专栏: 🏀编译原理_十二月的猫的博客-CSDN博客 💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2. …...
【漏洞复现】F5 BIG-IP Next Central Manager SQL注入漏洞(CVE-2024-26026)
免责声明 请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,作者不为此承担任何责任。工具来自网络,安全性自测,如有侵权请联系删除。本次测试仅供学习使用,如若非法他用,与平台和本文作…...
设计模式-创建型-单例模式
1. 单例模式简介 单例模式(Singleton Pattern)是一种常见的创建型设计模式,它确保一个类只有一个实例,并提供全局访问点。在很多情况下,我们只希望某个类在整个应用程序中有一个唯一的实例,且该实例需要在…...

VBA技术资料MF243:利用第三方软件复制PDF数据到EXCEL
我给VBA的定义:VBA是个人小型自动化处理的有效工具。利用好了,可以大大提高自己的工作效率,而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套,分为初级、中级、高级三大部分,教程是对VBA的系统讲解&#…...

【2024最新】基于Python+Mysql+django的水果销售系统Lw+PPT
作者:计算机搬砖家 开发技术:SpringBoot、php、Python、小程序、SSM、Vue、MySQL、JSP、ElementUI等,“文末源码”。 专栏推荐:SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏:Java精选实战项…...

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

编译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 解决方法: yum install perl-Test-Simple...
一文大白话讲清楚防抖和节流,设计封装防抖和节流,以及防抖和节流的应用场景
文章目录 一文大白话讲清楚防抖和节流,设计封装防抖和节流,以及防抖和节流的应用场景1. 防抖和节流的背景2. 节流3. 节流的应用场景4. 防抖5. 防抖应用场景 一文大白话讲清楚防抖和节流,设计封装防抖和节流,以及防抖和节流的应用场…...

Windows开启IIS后依然出现http error 503.the service is unavailable
问题背景 已启用IIS服务,配置步骤可以参考Windows10 IIS Web服务器安装配置 问题描述 在这一步浏览网站时,并没有出现默认首页,而是 http error 503 the service is unavailable 问题解决 参考 成功解决http error 503.the service is un…...
C++的封装(十四):《设计模式》这本书
很多C学习者学到对C语言有一定自信后,会去读一下《设计模式》这本书。希望能够提升自己的设计水平。 据我所知,围绕C语言出了很多书。因为正好赶上泡沫经济时代。大家一拥而上,自己半懂不懂就出书,抢着出书收割读者,出…...

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

【Ubuntu 20.4安装截图软件 flameshot 】
步骤一: 安装命令: sudo apt-get install flameshot 步骤二: 设置快捷方式: Ubuntu20.4 设置菜单,点击 号 步骤三: 输入软件名称, 软件快捷命令(flameshot gui)&am…...

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

【Agent】Chatbot、Copilot与Agent如何帮助我们的提升效率?
人工智能(AI)技术的迅猛发展正在深刻改变我们的生活和工作方式。你是否曾想过,未来的工作场景会是什么样子?AI的崛起不仅仅是科技的进步,更是我们生活方式的革命。今天,我们将深入探讨三种主要的AI能力&…...
QT笔记- QTreeView + QFileSystemModel 当前位置的保存与恢复 #选中 #保存当前索引
保存当前位置 QString currentPath model->filePath(view->currentIndex()); // 获得当前位置路径 恢复位置 view->setCurrentIndex(model->index(currentPath)); // 设置此路径所在位置为当前位置...
OpenResty开发环境搭建
简介 OpenResty 是一个基于 Nginx的高性能 Web 平台,用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。官方地址:http://openresty.org/cn/ 具备下列特点: 具备Nginx的完整功能基于Lua语言进行扩展&#…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...

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

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...

九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 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,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...

C++实现分布式网络通信框架RPC(2)——rpc发布端
有了上篇文章的项目的基本知识的了解,现在我们就开始构建项目。 目录 一、构建工程目录 二、本地服务发布成RPC服务 2.1理解RPC发布 2.2实现 三、Mprpc框架的基础类设计 3.1框架的初始化类 MprpcApplication 代码实现 3.2读取配置文件类 MprpcConfig 代码实现…...

Axure 下拉框联动
实现选省、选完省之后选对应省份下的市区...
es6+和css3新增的特性有哪些
一:ECMAScript 新特性(ES6) ES6 (2015) - 革命性更新 1,记住的方法,从一个方法里面用到了哪些技术 1,let /const块级作用域声明2,**默认参数**:函数参数可以设置默认值。3&#x…...