4.Socket类、InetAddr类、Epoll类实现模块化

目录

1. InetAddr类

类定义

代码说明 

类实现

2.Socket类

类定义

类实现

3. Epoll类

类定义

构造与析构函数

方法实现

类实现

4. 使用模块化设计

示例使用(main.cpp)

5. 运行程序


随着程序复杂度的增加,单一的面向过程的代码会变得难以理解和维护。为了提高代码的可读性和可维护性,我们可以通过模块化的方式,将程序分解为多个类,每个类负责特定的功能。这种设计不仅提高了代码的复用率,还能帮助开发者集中精力在每个单独的功能模块上,便于维护和扩展。

在本文中,我们将介绍一种简单的模块化设计方法,使用C++面向对象的特性,来设计一个网络编程框架。我们将创建三个关键类:InetAddrSocketEpoll。这些类的设计目的在于简化网络编程的过程,提高代码的可读性和可维护性。

1. InetAddr类

InetAddr类主要负责处理与IP地址相关的操作。它封装了sockaddr_in结构体,并提供了一系列方法来获取IP地址和端口。

类定义

#pragma once // 确保该头文件只被包含一次  #include <string>     // 引入字符串类  
#include <arpa/inet.h> // 提供网络地址结构定义及函数  
#include <stdio.h>    // 标准输入输出库  class InetAddr {
public:// 默认构造函数  InetAddr();// 带参数的构造函数,接受端口和可选的IP地址  InetAddr(unsigned short port, const char* ip = nullptr);// 获取sockaddr_in结构的指针,供外部使用  const sockaddr_in* getAddr() const { return &addr_; }// 设置sockaddr_in结构的地址,供外部使用  void setAddr(const struct sockaddr_in& addr) { addr_ = addr; }// 将地址转换为IP字符串格式  std::string toIp() const;// 将地址转换为IP:端口格式的字符串  std::string toIpPort() const;// 获取端口号  unsigned short toPort() const;private:struct sockaddr_in addr_; // 存储网络地址信息的结构体  
};

代码说明 

  • InetAddr 类用于处理网络地址信息,主要封装了 sockaddr_in 结构体,提供了获取和设置网络地址、IP转换等操作。
  • 构造函数
    • 默认构造函数:初始化为0的地址,设置地址族为IPv4。
    • 带参数的构造函数:允许用户指定端口和IP地址,并适当地初始化 sockaddr_in 结构体。
  • 公共方法
    • getAddr():返回存储的地址信息的常量引用,便于外部访问。
    • setAddr():允许设置或修改当前存储的 sockaddr_in 地址。
    • toIp():转换当前地址为点分十进制格式的字符串。
    • toIpPort():将IP和端口组合为字符串,便于显示。
    • toPort():获取当前对象的端口号,返回值为无符号短整型。

Socket类负责封装与套接字相关的操作,包括创建、绑定、监听和接受连接等。

类实现

#include "InetAddr.h"         // 包含InetAddr类的定义  
#include <string.h>           // 包含字符串操作函数  
#include <arpa/inet.h>       // 包含网络地址转换函数  // 默认构造函数  
InetAddr::InetAddr() {// 将地址结构清零,确保没有未定义的值  memset(&addr_, 0, sizeof(addr_));
}// 带参数的构造函数,用于初始化port和ip  
InetAddr::InetAddr(unsigned short port, const char* ip) {// 将地址结构清零  memset(&addr_, 0, sizeof(addr_));addr_.sin_family = AF_INET; // 设置地址族为IPv4  addr_.sin_port = htons(port); // 将主机字节序的端口转换为网络字节序  // 如果未提供IP,则使用INADDR_ANY,允许接受任何IP地址的连接  if (ip == nullptr) {addr_.sin_addr.s_addr = htonl(INADDR_ANY);}else {// 将字符串形式的IP地址转换为网络字节序的二进制格式  inet_pton(AF_INET, ip, &addr_.sin_addr.s_addr);}
}// 将存储的IP地址转换为字符串格式  
std::string InetAddr::toIp() const {char ip[64] = { 0 }; // 存储转换后的IP地址  // 将网络字节序的IP地址转换为字符串形式  inet_ntop(AF_INET, &addr_.sin_addr.s_addr, ip, sizeof(ip));return ip; // 返回IP字符串  
}// 将IP地址和端口组合为 "IP:port" 的格式  
std::string InetAddr::toIpPort() const {char buf[128] = { 0 }; // 存储组合后的字符串  // 使用sprintf将IP和端口格式化为字符串  sprintf(buf, "%s:%d", toIp().c_str(), toPort());return buf; // 返回组合后的字符串  
}// 获取存储的端口号  
unsigned short InetAddr::toPort() const {// 将网络字节序的端口转换为主机字节序并返回  return ntohs(addr_.sin_port);
}

2.Socket类

类定义

#pragma once // 确保该头文件只被包含一次  class InetAddr; // 前向声明InetAddr类  class Socket
{
public:// 默认构造函数,初始化Socket对象  Socket();// 带参数的构造函数,使用给定的文件描述符初始化Socket  Socket(int fd);// 析构函数,关闭Socket以释放资源  ~Socket();// 将Socket绑定到指定的InetAddr地址  void bind(InetAddr* serv_addr);// 接受来自客户端的连接,并返回新的套接字文件描述符  int accept(InetAddr* addr);// 将Socket设置为监听状态,准备接受连接  void listen();// 设置Socket为非阻塞模式  void setNonblock();// 获取套接字文件描述符  int fd() const { return sockfd_; }private:int sockfd_; // 存储套接字的文件描述符  
};
  • 构造函数

    • Socket(): 默认构造函数,用于创建一个新的TCP套接字。
    • Socket(int fd): 通过传入的文件描述符初始化套接字对象,允许外部使用现有套接字。
  • 成员函数

    • void bind(InetAddr* serv_addr): 绑定给定的地址信息到套接字。
    • void listen(): 设置套接字为监听状态,准备接收连接请求。
    • int accept(InetAddr* peerAddr): 接受客户端连接,并返回新连接的套接字文件描述符,此外可以获取客户端地址。
    • int fd() const: 返回当前套接字的文件描述符,供外部访问。
  • 私有成员变量

    • const int sockfd_: 存储套接字的文件描述符,使用 const 限制其在对象生命周期内不可更改。

类实现

#include "Socket.h"                // 引入Socket类的定义  
#include "util.h"          
#include "InetAddr.h"              // 引入InetAddr类的定义  
#include <fcntl.h>                 // 提供fcntl函数的定义  
#include <unistd.h>                // 提供close函数的定义  // 默认构造函数,创建一个TCP套接字  
Socket::Socket(): sockfd_(socket(AF_INET, SOCK_STREAM, 0)) // 创建一个IPv4 TCP套接字  
{// 检查套接字创建是否成功,如果失败、打印错误信息  perror_if(sockfd_ == -1, "socket");
}// 带参数的构造函数,通过给定的文件描述符初始化套接字  
Socket::Socket(int fd): sockfd_(fd) // 使用提供的文件描述符进行初始化  
{// 检查文件描述符是否有效  perror_if(sockfd_ == -1, "socket(int fd)");
}// 析构函数,关闭套接字以释放资源  
Socket::~Socket()
{if (sockfd_ != -1) { // 确保套接字有效  close(sockfd_); // 关闭套接字文件描述符  sockfd_ = -1;   // 将文件描述符标记为无效,防止重复关闭  }
}// 将套接字绑定到指定的InetAddr地址  
void Socket::bind(InetAddr* serv_addr)
{// 调用系统级绑定函数  int ret = ::bind(sockfd_, (sockaddr*)serv_addr->getAddr(), sizeof(sockaddr_in));// 检查绑定是否成功,如果失败,打印错误信息  perror_if(ret == -1, "bind");
}// 接受客户端连接,并返回新的套接字文件描述符  
int Socket::accept(InetAddr* addr)
{struct sockaddr_in cliaddr; // 存储客户端地址信息  socklen_t len = sizeof(cliaddr); // 存储地址长度  // 调用系统级接受函数  int cfd = ::accept(sockfd_, (struct sockaddr*)&cliaddr, &len);// 检查接受客户端连接是否成功,如果失败,打印错误信息  perror_if(cfd == -1, "accept");// 设置已连接客户端的地址信息  addr->setAddr(cliaddr);// 输出新连接的客户端信息  printf("new client fd %d ip: %s, port: %d connected..\n", cfd, addr->toIp().c_str(), addr->toPort());return cfd; // 返回新连接的套接字文件描述符  
}// 将套接字设置为监听状态,准备接受连接  
void Socket::listen()
{// 调用系统级监听函数,最多同时处理128个连接请求  int ret = ::listen(sockfd_, 128);// 检查监听是否成功,如果失败,打印错误信息  perror_if(ret == -1, "listen");
}// 将套接字设置为非阻塞模式  
void Socket::setNonblock()
{// 获取当前文件描述符的标志  int flag = fcntl(sockfd_, F_GETFL);flag |= O_NONBLOCK; // 将非阻塞标志添加到当前标志中  // 将新的标志设置回文件描述符  fcntl(sockfd_, F_SETFL, flag);
}

3. Epoll类

Epoll类用于处理epoll事件,包括创建epoll实例、管理文件描述符添加/删除以及等待事件的发生。

类定义

#pragma once // 确保该头文件只被包含一次  #include <sys/epoll.h> // 包含epoll相关的系统调用  
#include <vector>      // 引入vector标准库  
using std::vector;    // 使用std命名空间中的vector类  class Epoll
{
public:// 构造函数,初始化epoll实例  Epoll();// 析构函数,清理epoll资源  ~Epoll();// 更新给定文件描述符的事件  void update(int sockfd, int events, int op);// 从epoll中删除指定的文件描述符  void epoll_delete(int fd);// 等待事件发生,返回活跃的文件描述符事件  void Epoll_wait(vector<epoll_event>& active, int timeout = 10);private:int epfd_;                       // epoll实例的文件描述符  struct epoll_event* events_;      // 存储返回的事件  
};

构造与析构函数

  • 创建epoll实例并初始化事件数组。
  • 在析构函数中释放资源。

方法实现

  • update(): 添加、修改或删除文件描述符的事件。
  • wait(): 使用epoll_wait()等待活动事件并填充事件数组。

类实现

#include "Epoll.h"               // 引入Epoll类的定义  
#include "util.h"                // 引入自定义工具函数头文件  
#include <string.h>              // 引入cstring库以使用memset和相关函数  
#include <unistd.h>             // 引入unistd.h以使用close函数const int SIZE = 1024;         // 定义epoll事件数组的大小  // 构造函数,创建一个新的epoll实例并初始化事件数组  
Epoll::Epoll(): epfd_(epoll_create(1)), // 创建epoll实例,参数为1,表示初始的事件数  events_(new epoll_event[SIZE]) // 动态分配事件数组  
{// 检查epoll_create是否成功,失败则调用perror_if输出错误信息  perror_if(epfd_ == -1, "epoll_create");// 初始化事件数组,清空内存  memset(events_, 0, sizeof(epoll_event) * SIZE);
}// 析构函数,清理epoll资源  
Epoll::~Epoll()
{// 删除事件数组  delete[] events_; // 释放动态分配的事件数组  // 将epfd_设为-1以避免重复关闭  if (epfd_ != -1) {close(epfd_); // 关闭epoll实例的文件描述符  epfd_ = -1; // 将文件描述符设置为-1表示无效  }
}// 更新给定的文件描述符,设置其事件类型  
void Epoll::update(int sockfd, int events, int op)
{struct epoll_event ev; // 创建epoll_event结构体以存储事件信息  memset(&ev, 0, sizeof(ev)); // 清空结构体  ev.data.fd = sockfd; // 将文件描述符存储在event结构体中  ev.events = events; // 设置感兴趣的事件  // 调用epoll_ctl更新epoll实例  int ret = epoll_ctl(epfd_, op, sockfd, &ev);// 检查epoll_ctl是否成功  perror_if(ret == -1, "epoll_ctl");
}// 从epoll中删除指定的文件描述符  
void Epoll::epoll_delete(int fd)
{// 调用epoll_ctl删除指定的文件描述符  int ret = epoll_ctl(epfd_, EPOLL_CTL_DEL, fd, nullptr);// 检查epoll_ctl是否成功  perror_if(ret == -1, "epoll_ctl del");
}// 等待事件发生,返回活跃的事件列表  
void Epoll::Epoll_wait(vector<epoll_event>& active, int timeout)
{// 调用epoll_wait等待事件的发生  int nums = epoll_wait(epfd_, events_, SIZE, timeout);// 检查epoll_wait是否成功  perror_if(nums == -1, "epoll_wait");// 将活跃的事件添加到active vector中  for (int i = 0; i < nums; ++i) {active.emplace_back(events_[i]); // 将每个活跃事件存入active数组  }
}

4. 使用模块化设计

通过将网络相关的功能分割成几个类,可以简化主要的服务器逻辑。使用这些类,我们可以方便地进行网络编程而不必关注底层的细节。

示例使用(main.cpp)

下面是如何使用这些类来构建一个简单的服务器:

#include "Epoll.h"        // 引入Epoll类的定义  
#include "Socket.h"      // 引入Socket类的定义  
#include "util.h"          // 引入工具函数的头文件  
#include "InetAddr.h"       // 引入InetAddr类的定义  
#include <stdio.h>              // 引入标准输入输出库  
#include <string.h>             // 引入cstring库以使用memset和相关函数  
#include <unistd.h>             // 引入unistd库以使用read和close函数  const int READ_BUFFER = 1024;  // 定义读取缓冲区的大小  
const int MAXSIZE = 1024;      // 定义最大连接数(未使用)  // 前向声明函数,用于处理事件  
void handleEvent(int sockfd, Epoll& poll);int main()
{Socket serv_socket;             // 创建一个服务器套接字  InetAddr saddr(10000);         // 创建一个InetAddr对象,用于绑定到端口10000  serv_socket.bind(&saddr);       // 绑定服务器套接字到指定地址  serv_socket.listen();           // 开始监听客户端连接  serv_socket.setNonblock();      // 设置服务器套接字为非阻塞模式  Epoll poll;                     // 创建epoll实例  poll.update(serv_socket.fd(), EPOLLIN, EPOLL_CTL_ADD); // 将服务器套接字添加到epoll实例中,监控可读事件  // 主循环,持续处理事件  while (1){vector<epoll_event> active; // 存储活动事件的向量  poll.Epoll_wait(active);     // 等待事件的发生  int nums = active.size();    // 当前活动事件的数量  for (int i = 0; i < nums; ++i) {int curfd = active[i].data.fd; // 获取当前事件对应的文件描述符  // 检查是否是可读事件  if (active[i].events & EPOLLIN) {if (curfd == serv_socket.fd()) { // 如果当前文件描述符是服务器套接字  InetAddr caddr;              // 创建一个InetAddr对象,存储客户端地址  Socket* cli_socket = new Socket(serv_socket.accept(&caddr)); // 接受客户端连接并创建新的套接字  // 注意:需要处理内存泄漏(后续版本将修复内存管理)  cli_socket->setNonblock();   // 设置客户端套接字为非阻塞模式  poll.update(cli_socket->fd(), EPOLLIN, EPOLL_CTL_ADD); // 将客户端套接字添加到epoll实例  }else {handleEvent(curfd, poll);   // 处理其他可读事件  }}else if (active[i].events & EPOLLOUT) {// 其他事件以后的版本会实现  }}}return 0; // 主程序结束  
}// 处理可读事件的函数  
void handleEvent(int sockfd, Epoll& poll) {char buf[READ_BUFFER];         // 声明读取缓冲区  memset(buf, 0, sizeof(buf));   // 清空缓冲区  // 从套接字读取数据  ssize_t bytes_read = read(sockfd, buf, sizeof(buf));// 判断读取结果  if (bytes_read > 0) {// 成功读取数据,输出客户端发来的消息  printf("client fd %d says: %s\n", sockfd, buf);// 将接收到的数据写回给客户端(回显)  write(sockfd, buf, bytes_read);}else if (bytes_read == -1) { // 读取出错  perror_if(1, "read");    // 调用错误处理函数  }else if (bytes_read == 0) {  // 客户端断开连接  printf("client fd %d disconnected\n", sockfd);poll.epoll_delete(sockfd); // 从epoll实例中删除该套接字  close(sockfd);             // 关闭套接字  }
}

5. 运行程序

g++ -o Server main.cpp Epoll.cpp util.cpp Socket.cpp InetAddr.cpp

编译完成后,可以通过以下命令运行服务器:

./Server        # 启动服务器

客户端可以使用之前的客户端程序作为连接方式,确保与服务器在同一网络下运行。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

视频生成的测试时Scaling时刻!清华开源Video-T1,无需重新训练让性能飙升

来源 | 机器之心 视频作为包含大量时空信息和语义的媒介&#xff0c;对于 AI 理解、模拟现实世界至关重要。视频生成作为生成式 AI 的一个重要方向&#xff0c;其性能目前主要通过增大基础模型的参数量和预训练数据实现提升&#xff0c;更大的模型是更好表现的基础&#xff0c…

Go 语言标准库中time模块详细功能介绍与示例

以下是 Go 语言 time 模块的详细说明及示例&#xff0c;涵盖时间操作、定时器、时区处理等核心功能&#xff1a; 一、时间基础操作 1. 获取时间 // 当前本地时间 now : time.Now() fmt.Println(now) // 2023-08-04 15:30:45.123456 0800 CST// 构造指定时间 t : time.Date(20…

【强化学习】基于深度强化学习的微能源网能量管理与优化策略研究【Python】

目录 主要内容 程序要点 2.1 微能源网系统组成 2.2 强化学习及Q学习算法 部分代码 运行结果 下载链接 主要内容 该程序借助深度 Q 网络&#xff08;DQN&#xff09;&#xff0c;学习预测负荷、风 / 光可再生能源功率输出及分时电价等环境信息&#xff0c;运用…

dom0-kernel: /thermal-zones/soc_max/cooling-maps/map0: could not find phandle 2

问题描述&#xff1a; 由于soc_max下某个节点找不到&#xff0c;到时dom0-kernel后面有很多有关thermal热管理之类报错 问题解决及其原因分析&#xff1a; 这是因为在Xen解析相关节点时&#xff0c;soc_max下的某个节点被跳过了&#xff0c;注释掉相关的cpu节点处理dom0就可以找…

关于计算机视觉中的插值小记

计算机视觉中的插值&#xff08;Interpolation&#xff09;讲解 插值&#xff08;Interpolation&#xff09;在计算机视觉中是一项基础操作&#xff0c;常用于图像缩放、旋转、去噪、图像重建等任务。其核心思想是在已知数据点之间进行推测&#xff0c;估计未知的像素值或特征…

计算机网络--传输层(1)

第五章 传输层 一、传输层基本功能 进程到进程的逻辑通信 套接字&#xff08;Socket&#xff09;&#xff1a;IP地址:端口号 IP地址&#xff1a;标识主机&#xff08;网络层功能&#xff09;端口号&#xff1a;16位整数&#xff08;0-65535&#xff09;&#xff0c;标识进程 熟…

指定 Python 3.12.6-slim 作为基础镜像

指定 Python 3.12.6-slim 作为基础镜像&#xff0c;意思就是&#xff1a; &#x1f449; 用官方的 Python 3.12.6&#xff08;精简版&#xff09;作为容器的起点&#xff0c;里面已经有 Python 3.12.6 预装好了&#xff0c;你不用自己装。 &#x1f539; 为什么用 -slim&…

【蓝桥杯】算法笔记1

1.暴力枚举 给定一个正整数n,请找出所有满足a + b = n的整数对(a, b),其中a和b都是正整数,且a ≤ b。 输入格式:一个正整数n (1 ≤ n ≤ 10⁶) 输出格式:所有符合条件的(a, b)对,每行一对,按a的升序排列。如果没有符合条件的对,输出"No solution"。 问题分…

专注自习室:番茄工作法实践

专注自习室&#xff1a;番茄工作法实践 我需要一个任务管理工具&#xff0c;但在网上找了很多都找不到合适的工具。市面上的大多数产品过于强调任务完成性&#xff0c;给我带来了很强的心理压力&#xff0c;这种压力最终反而降低了我的工作效率。于是我决定自己动手&#xff0…

VUE3项目VITE打包优化

VUE3项目VITE打包优化 代码加密依赖配置效果对比图 自动导入依赖配置 代码压缩依赖配置效果对比图 图片压缩依赖配置效果对比图 字体压缩总结与实践运用效果 代码加密 依赖 npm install -D vite-plugin-bundle-obfuscator配置 import vitePluginBundleObfuscator from "…

文章记单词 | 第14篇(六级)

一&#xff0c;单词释义 affection&#xff1a;n. 喜爱&#xff0c;钟爱&#xff1b;爱慕之情&#xff1b;感情stream&#xff1a;n. 小河&#xff0c;溪流&#xff1b;一连串&#xff0c;源源不断&#xff1b;水流&#xff0c;气流&#xff1b;vi. 流&#xff0c;流动&#x…

欧几里得距离(Euclidean Distance)公式

欧几里得距离公式 欧几里得距离&#xff08;Euclidean Distance&#xff09;是计算两点之间直线距离的一种方法。它是最常见的距离度量方式之一&#xff0c;广泛应用于数学、物理、机器学习、计算机视觉等领域。 公式定义 1. 二维空间 在二维平面上&#xff0c;假设有两个点…

机器学习——LightGBM

LightGBM(light gradient boosting machine&#xff0c;轻量梯度提升机)是对XGBoost进行改进的模型版本&#xff0c;其三者之间的演变关系为&#xff1a;GBDT-》XGBoost-》LightGBM&#xff0c;依次对性能进行优化&#xff0c;尽管XGBoost已经很高效了&#xff0c;但是仍然有缺…

内网服务器无法通过公网地址访问映射到公网的内网服务

内网服务器无法通过公网地址访问映射到公网的内网服务 问题现象问题原因解决方法总结 前几天遇到一个网络问题&#xff0c;在这里做下记录&#xff0c;希望能帮助到有相同问题的朋友。 问题现象 网络拓扑如上所示&#xff0c;服务器1和服务器2在同一内网&#xff0c;网段均为1…

python每日十题(13)

一般把计算机完成一条指令所花费的时间称为一个指令周期。指令周期越短&#xff0c;指令执行就越快。本题答案为D选项。 顺序程序具有顺序性、封闭性和可再现性的特点&#xff0c;使得程序设计者能够控制程序执行的过程(包括执行顺序、执行时间&#xff09;&#xff0c;对程序执…

Python 装饰器(Decorators)

什么是装饰器&#xff1f; 装饰器&#xff08;Decorator&#xff09;本质上是一个 修改其他函数功能的函数。它的核心思想是&#xff1a;不修改原函数代码&#xff0c;动态添加新功能。比如&#xff1a; 记录函数执行时间 检查用户权限 缓存计算结果 自动重试失败操作 理解…

uWebSockets开发入门

一、常用C++ WebSocket开源库 一些常用的 C++ WebSocket 开源库,它们支持 WebSocket 协议的实现,适用于客户端或服务器端开发。 1. Boost.Beast (推荐) 特点:基于 Boost.Asio 的高性能库,支持 HTTP/WebSocket,属于 Boost 官方库的一部分,稳定且跨平台。 适用场景:需要高…

多智能体功能分化的核心优势是什么:提升效率,查漏补缺

多智能体功能分化的核心优势是什么:提升效率,查漏补缺 在于通过分工协作提升整体效率、灵活性和鲁棒性。 1. 提升效率与专业性 原理:单一智能体无需处理全流程,通过专业化分工减少冗余计算和决策延迟。 示例: 自动驾驶系统: 感知智能体:专门处理摄像头、激光雷达等传…

项目复盘:websocket不受跨域限制的原理

主要还是因为&#xff1a; 1、WebSocket 是独立于 HTTP 的应用层协议&#xff0c;通过 HTTP 建立连接后&#xff0c;完全脱离 HTTP 语义约束。这意味着 不受 HTTP 同源策略限制 不需要预检请求 不依赖 CORS 头机制 2、建立连接时的握手请求仍使用 HTTP 格式&#xff0c;但…

COMPASS:通过残差强化学习和技能合成实现跨具身移动策略

25年2月来自 Nvidia、UC Berkeley 和 UT Austin 的论文“COMPASS: Cross-embOdiment Mobility Policy via ResiduAl RL and Skill Synthesis”。 随着机器人越来越多地部署在不同的应用领域&#xff0c;可泛化的跨具身移动策略变得越来越重要。虽然经典的移动栈已被证明在特定…