【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;
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 连接,使得网络编程更加高效和易于维护。