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.rhkb.cn/news/42580.html

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

相关文章

RocketMQ可视化工具使用 - Dashboard(保姆级教程)

1、github拉取代码&#xff0c;地址&#xff1a; https://github.com/apache/rocketmq-dashboard 2、指定Program arguments&#xff0c;本地启动工程 勾上这个Program arguments&#xff0c;会出现多一个对应的框 写入参数 --server.port1280 --rocketmq.config.namesrvAddr…

湖南(源点咨询)市场调研 商业综合体定位调研分享(上篇)

​ 该项目位于某新一线城市的城市副中心区域&#xff0c;系一个正在发展中的中央居住区&#xff0c;项目本身是一个涵盖社区综合服务中心、商业、文体活动中心、卫生服务、社区养老等多功能复合的公共配套项目&#xff0c;本次调研主要针对其商业&#xff08;及其他可商用的&a…

硬件测试工装设计不合理的补救措施

硬件测试工装设计不合理的补救措施主要包括重新评估设计需求、优化工装结构、强化工装校准与验证。其中&#xff0c;优化工装结构尤其重要&#xff0c;通过结构优化能够有效解决因设计不合理导致的测试准确性下降和可靠性不足的问题。根据工程实践数据&#xff0c;经过优化结构…

PyQt6实例_批量下载pdf工具_使用pyinstaller与installForge打包成exe文件

目录 前置&#xff1a; 步骤&#xff1a; step one 准备好已开发完毕的项目代码 step two 安装pyinstaller step three 执行pyinstaller pdfdownload.py&#xff0c;获取初始.spec文件 step four 修改.spec文件&#xff0c;将data文件夹加入到打包程序中 step five 增加…

open-cv的安装

python -m pip install numpy matplotlib opencv-python 【记得科学上网&#xff0c;不然太慢了】

AI写一个视频转图片帧工具(python)

现在的AI写python太方便了 说的话 我想用python实现一个能够将视频的所有帧数转化为图片的软件&#xff0c;可以自由配置转换的帧率或者全部&#xff0c;需要有界面&#xff0c;我需要增加一点功能&#xff0c;就是我需要读取出视频的分辨率&#xff0c;然后设置输出帧的分辨…

Axure RP9.0教程: 多级联动【设置选项改变时->情形->面板状态】(给动态面板元件设置相关交互事件的情形,来控制其他面板不同的状态。)

文章目录 引言I 多级联动(省、市、区)实现思路添加三省、市、区下拉列表给省下拉框添加数据源将市、区下拉框添加不同状态,分别以省、市命名给省下拉控件设置选项改变时的交互事件省下拉控件的交互事件情形市下拉交互事件的配置II 知识扩展: 展示省 → 地级市 → 区县的多级…

浙江大学|DeepSeek系列专题公开课|第一季|PDF+视频(全)

大家好&#xff0c;我是吾鳴。 之前吾鳴给大家分享过由浙大出品的DeepSeek系列专题公开课的教程&#xff0c;不过都比较零散&#xff0c;而且都只有PDF&#xff0c;有粉丝朋友直呼看得不过瘾。今天吾鳴把视频也给大家找来了&#xff0c;而且把第一季的DeepSeek系列公开课也稍做…

数据可视化(matplotlib)-------图表样式美化

目录 一、图表样式概述 &#xff08;一&#xff09;、默认图表样式 &#xff08;二&#xff09;、图表样式修改 1、局部修改 2、全局修改 二、使用颜色 &#xff08;一&#xff09;、使用基础颜色 1、单词缩写或单词表示的颜色 2、十六进制/HTML模式表示的颜色 3、RGB…

SpringBoot3解决跨域请求问题(同源策略、JSONP、CORS策略)(Access-Control-Allow-Origin)(2025详细教程)

目录 浏览器跨域请求问题。 浏览器同源策略。 第三方API调用。 前后端分离项目。 一、JSONP。&#xff08;dataType:jsonp&#xff09; &#xff08;1&#xff09;代码示例。 <1>前端ajax04.jsp页面。(发起Ajax请求) <2>后端springboot接口。(/hello)(返回JSONPObj…

rent8_wechat-新增提醒收租功能

本次更新中&#xff0c;rent8_wechat 小程序全新推出了“提醒收租”功能&#xff0c;为房东提供更加便捷的收租体验。房东只需在小程序内点击“提醒收租”按钮&#xff0c;系统便会在需要收租当天的上午9点准时推送通知&#xff0c;贴心提醒房东及时收取租金。 以下是该功能的…

诠视科技MR眼镜如何安装apk应用

诠视科技MR眼镜如何安装apk应用 1、使用adb工具安装1.1 adb工具下载1.2 解压adb文件1.3 使用adb安装apk 2、拷贝到文件夹安装 1、使用adb工具安装 1.1 adb工具下载 点击下面的链接开始下载adb工具&#xff0c;下载结束以后解压文件。 下载链接: https://download.xvisiotech…

JAVA学习*异常

什么是异常 在 Java 里&#xff0c;异常是指程序运行期间出现的不正常状况&#xff0c;它会中断程序的正常执行流程。 异常的分类 Java 中的异常是对象&#xff0c;这些对象都继承自 Throwable类。Throwable类有两个主要的子类&#xff1a;Error 和 Exception。 Error类表示…

DataGear 5.3.0 制作支持导出表格数据的数据可视化看板

DataGear 内置表格图表底层采用的是DataTable表格组件&#xff0c;默认并未引入导出数据的JS支持库&#xff0c;如果有导出表格数据需求&#xff0c;则可以在看板中引入导出相关JS支持库&#xff0c;制作具有导出CSV、Excel、PDF功能的表格数据看板。 在新发布的5.3.0版本中&a…

【电气设计】接地/浮地设计

在工作的过程中&#xff0c;遇到了需要测量接地阻抗的情况&#xff0c;组内讨论提到了保护接地和功能接地的相关需求。此文章用来记录这个过程的学习和感悟。 人体触电的原理&#xff1a; 可以看到我们形成了电流回路&#xff0c;导致触电。因此我们需要针对设备做一些保护设计…

【计算机操作系统】线程的概念和特点

1、什么是线程&#xff0c;为什么要引入线程&#xff1f; 还没引入进程之前&#xff0c;系统中各个程序只能串行执行。 比如&#xff1a;当我们在使用QQ与好友视频时&#xff0c;我们可以给其他好友发信息&#xff0c;发送文件...&#xff0c;我们知道进程是程序的一次执行&am…

【C++数据库】SQLite3数据库连接与操作

注意:本文代码均为C++20标准下实现 一、SQLite3库安装 1.1 安装库文件 【工具】跨平台C++包管理利器vcpkg完全指南 vcpkg install sqlite3# 集成至系统目录,之前执行过此命令的无需再次执行 vcpkg integrate install1.2 验证代码 在VS2022中新建控制台项目,测试代码如下…

CLion下载安装(Windows11)

目录 CLion工具下载安装其他 CLion CLion-2024.1.4.exe 工具 系统&#xff1a;Windows 11 下载 1.通过百度网盘分享的文件&#xff1a;CLion-2024.1.4.exe 链接&#xff1a;https://pan.baidu.com/s/1-zH0rZPCZtQ60IqdHA7Cew?pwdux5a 提取码&#xff1a;ux5a 安装 打开…

‘无法定位程序输入点kernel32.dll’详细的修复方法,一键快速修复kernel32.dll

在 Windows 系统运行过程中&#xff0c;若程序提示“无法定位程序输入点 kernel32.dll”&#xff0c;往往意味着程序调用了 kernel32.dll 中不存在或已变更的函数接口。作为系统的核心动态链接库&#xff0c;kernel32.dll 承担着内存管理、进程控制、文件操作等底层功能&#x…

二层综合实验

拓扑图 实验要求 1.内网IP地址使用172.16.6.0/16分配 2.sw1和sW2之间互为备份 3.VRRP/STP/VLAN/Eth-trunk均使用 4.所有Pc均通过DHCP获取IP地址 5.ISP只能配置IP地址 6.所有电脑可以正常访问IsP路由器环回 实验思路 这是一个二层综合实验每当拿到一个实验看清楚要求之后都有…