找工作小项目:day16-重构核心库、使用智能指针(1)

day16-重构核心库、使用智能指针

今天是该项目开源在gthub的最后一天,我这里只是将我自己对于这个项目的理解进行总结,如有错误敬请包含指正,今天会整体理一遍代码,并使用智能指针管理整个项目。

1、common

头文件

定义宏用于禁用类的拷贝构造函数、拷贝赋值运算符、移动构造函数和移动赋值运算符。这里注意对右值引用、左值引用、万能引用的理解。
之后这里定义了一些状态,用以防止在发生错误时直接崩溃(这样的程序不够健壮):
RC_UNDEFINED:未定义的状态。
RC_SUCCESS:成功状态。
RC_SOCKET_ERROR:套接字错误。
RC_POLLER_ERROR:轮询器错误。
RC_CONNECTION_ERROR:连接错误。
RC_ACCEPTOR_ERROR:接收器错误。
RC_UNIMPLEMENTED:未实现的功能。

#define DISALLOW_COPY(cname)     \cname(const cname &) = delete; \cname &operator=(const cname &) = delete;
#define DISALLOW_MOVE(cname) \cname(cname &&) = delete;  \cname &operator=(cname &&) = delete;
#define DISALLOW_COPY_AND_MOVE(cname) \DISALLOW_COPY(cname);               \DISALLOW_MOVE(cname);
enum RC {RC_UNDEFINED,RC_SUCCESS,RC_SOCKET_ERROR,RC_POLLER_ERROR,RC_CONNECTION_ERROR,RC_ACCEPTOR_ERROR,RC_UNIMPLEMENTED
};

2、Socket

在这里插入图片描述
在这里我们仅完成服务端的socket、bind、listen以及accept,客户端的socket以及connect。

头文件

首先在头文件中禁用了类的拷贝构造函数、拷贝赋值运算符、移动构造函数和移动赋值运算符。之后依此就是构造析构函数,设置套接字文件描述符 fd_ 的值,返回当前套接字文件描述符 fd_ 的值,获取与当前连接的对端地址,获取 socket 接收缓冲区中的数据大小,创建 socket,绑定 socket 到指定的 IP 和端口,将 socket 设置为监听状态,接受客户端连接请求,发起与服务器的连接请求,设置 socket 为非阻塞模式,检查 socket 是否为非阻塞模式。

class Socket {public:DISALLOW_COPY_AND_MOVE(Socket);Socket();~Socket();void set_fd(int fd);int fd() const;std::string get_addr() const;RC Create();RC Bind(const char *ip, uint16_t port) const;RC Listen() const;RC Accept(int &clnt_fd) const;RC Connect(const char *ip, uint16_t port) const;RC SetNonBlocking() const;bool IsNonBlocking() const;size_t RecvBufSize() const;private:int fd_;
};

实现

实现上我们一步一步来看如何完成的。
构造析构函数,没啥好说的对属性初始化和释放资源:

Socket::Socket() : fd_(-1) {}Socket::~Socket() {if (fd_ != -1) {close(fd_);fd_ = -1;}
}

设置获取fd_属性:

void Socket::set_fd(int fd) { fd_ = fd; }int Socket::fd() const { return fd_; }

获取与当前连接的对端地址,getpeername用来获取与某个套接字关联的外地协议地址。

std::string Socket::get_addr() const {struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));socklen_t len = sizeof(addr);if (getpeername(fd_, (struct sockaddr *)&addr, &len) == -1) {return "";}std::string ret(inet_ntoa(addr.sin_addr));ret += ":";ret += std::to_string(htons(addr.sin_port));return ret;
}

接下来是将socket设置为无阻塞模式以及判断是否为无阻塞状态,首先通过fcntl(fd_, F_GETFL)获取socket的属性并设置为O_NONBLOCK,之后将socket通过fcntl(fd_, F_SETFL, …)写入到属性中。

RC Socket::SetNonBlocking() const {if (fcntl(fd_, F_SETFL, fcntl(fd_, F_GETFL) | O_NONBLOCK) == -1) {perror("Socket set non-blocking failed");return RC_SOCKET_ERROR;}return RC_SUCCESS;
}
bool Socket::IsNonBlocking() const { return (fcntl(fd_, F_GETFL) & O_NONBLOCK) != 0; }

获取接收缓冲区的大小,通过ioctl获取文件描述符socket接收缓冲区中的待读取数据大小。

size_t Socket::RecvBufSize() const {size_t size = -1;if (ioctl(fd_, FIONREAD, &size) == -1) {perror("Socket get recv buf size failed");}return size;
}

创建一个套接字socket。

RC Socket::Create() {assert(fd_ == -1);fd_ = socket(AF_INET, SOCK_STREAM, 0);if (fd_ == -1) {perror("Failed to create socket");return RC_SOCKET_ERROR;}return RC_SUCCESS;
}

用于在指定的 IP 地址和端口上绑定 socket 。创建地址sockaddr_in并将套接字socket通过bind绑定到对应地址上。

RC Socket::Bind(const char *ip, uint16_t port) const {assert(fd_ != -1);struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr(ip);addr.sin_port = htons(port);if (::bind(fd_, (struct sockaddr *)&addr, sizeof(addr)) == -1) {perror("Failed to bind socket");return RC_SOCKET_ERROR;}return RC_SUCCESS;
}

开始将套接字转变为被动连接监听的套接字。

RC Socket::Listen() const {assert(fd_ != -1);if (::listen(fd_, SOMAXCONN) == -1) {perror("Failed to listen socket");return RC_SOCKET_ERROR;}return RC_SUCCESS;
}

从处于 established 状态的连接队列头部取出一个与服务器进行连接。

RC Socket::Accept(int &clnt_fd) const {// TODO: non-blockingassert(fd_ != -1);clnt_fd = ::accept(fd_, NULL, NULL);if (clnt_fd == -1) {perror("Failed to accept socket");return RC_SOCKET_ERROR;}return RC_SUCCESS;
}

将服务器的IP以及端口作为参数传入,建立同服务器的连接。

RC Socket::Connect(const char *ip, uint16_t port) const {// TODO: non-blockingstruct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr(ip);addr.sin_port = htons(port);if (::connect(fd_, (struct sockaddr *)&addr, sizeof(addr)) == -1) {perror("Failed to connect socket");return RC_SOCKET_ERROR;}return RC_SUCCESS;
}

3、Poller

不要忘记Poller在整个项目上的作用是Epoll,一个多路复用 I/O 事件通知接口。其是由一颗红黑树和一个双向链表构成的,处理流程如下:
1、通过 epoll_ctl 函数向 epoll 实例注册一个文件描述符及其感兴趣的事件,文件描述符和事件类型会被存储在 epoll 的红黑树上。
2、当内核检测到某个注册的文件描述符上发生了感兴趣的事件(如可读、可写等),这个文件描述符会被添加到一个内部的双向链表中,这个链表专门存储那些已经就绪的文件描述符。
3、当应用程序调用 epoll_wait 时,epoll 会检查这个双向链表,将其中的就绪事件返回给应用程序。

头文件

可以看到仍旧将类设置为禁止类复制和类移动,通过对树上事件的注册、更新以及删除方法看到我们的操作是针对Channel的,Channel是一个包含事件套接字和事件类型的类。Poll是调用 epoll_wait获得事件的方法。这里实现了Linux和macOS两种方法。

class Poller {public:DISALLOW_COPY_AND_MOVE(Poller);Poller();~Poller();RC UpdateChannel(Channel *ch) const;RC DeleteChannel(Channel *ch) const;std::vector<Channel *> Poll(long timeout = -1) const;private:int fd_;#ifdef OS_LINUXstruct epoll_event *events_{nullptr};
#endif#ifdef OS_MACOSstruct kevent *events_;
#endif
};

实现

首先是构造/析构函数,注意万物皆是文件这句话,所以epoll也有属于自己的socket,在构造和析构的过程中不要忘了释放,创建epoll中还要初始化分配关于events_ 的空间,他就是那个双向链表。

Poller::Poller() {fd_ = epoll_create1(0);ErrorIf(fd_ == -1, "epoll create error");events_ = new epoll_event[MAX_EVENTS];memset(events_, 0, sizeof(*events_) * MAX_EVENTS);
}Poller::~Poller() {if (fd_ != -1) {close(fd_);}delete[] events_;
}

通过epoll_wait将树上的事件读入到就绪事件链表中,之后根据发生的事件类型将就绪事件的类型进行记录。注意,只有发生与预期事件相关的事件时才会将事件加入就绪队列中。

std::vector<Channel *> Poller::Poll(int timeout) {std::vector<Channel *> active_channels;int nfds = epoll_wait(fd_, events_, MAX_EVENTS, timeout);ErrorIf(nfds == -1, "epoll wait error");for (int i = 0; i < nfds; ++i) {Channel *ch = (Channel *)events_[i].data.ptr;int events = events_[i].events;if (events & EPOLLIN) {ch->SetReadyEvents(Channel::READ_EVENT);}if (events & EPOLLOUT) {ch->SetReadyEvents(Channel::WRITE_EVENT);}if (events & EPOLLET) {ch->SetReadyEvents(Channel::ET);}active_channels.push_back(ch);}return active_channels;
}

获取事件的socket,并将预期的事件类型进行记录,当然如果该事件不存在在红黑树上,我们需要将事件记录到红黑树上。注意是epoll_event中data部分的ptr指向Channel,所以需要Channel中的期待类型来更新epoll_event中的事件类型,之后pool根据这个。
获取事件的socket,并将通道从红黑树上删除,将通道是否在树上的标志位置false。

void Poller::UpdateChannel(Channel *ch) {int sockfd = ch->GetSocket()->fd();struct epoll_event ev {};ev.data.ptr = ch;if (ch->GetListenEvents() & Channel::READ_EVENT) {ev.events |= EPOLLIN | EPOLLPRI;}if (ch->GetListenEvents() & Channel::WRITE_EVENT) {ev.events |= EPOLLOUT;}if (ch->GetListenEvents() & Channel::ET) {ev.events |= EPOLLET;}if (!ch->GetExist()) {ErrorIf(epoll_ctl(fd_, EPOLL_CTL_ADD, sockfd, &ev) == -1, "epoll add error");ch->SetExist();} else {ErrorIf(epoll_ctl(fd_, EPOLL_CTL_MOD, sockfd, &ev) == -1, "epoll modify error");}
}void Poller::DeleteChannel(Channel *ch) {int sockfd = ch->GetSocket()->fd();ErrorIf(epoll_ctl(fd_, EPOLL_CTL_DEL, sockfd, nullptr) == -1, "epoll delete error");ch->SetExist(false);
}

之后是在macOS上的代码,逻辑相似就是库函数的调用有差别。

#ifdef OS_MACOSPoller::Poller() {fd_ = kqueue();assert(fd_ != -1);events_ = new struct kevent[MAX_EVENTS];memset(events_, 0, sizeof(*events_) * MAX_EVENTS);
}
Poller::~Poller() {if (fd_ != -1) {close(fd_);fd_ = -1;}
}
std::vector<Channel *> Poller::Poll(long timeout) const {std::vector<Channel *> active_channels;struct timespec ts;memset(&ts, 0, sizeof(ts));if (timeout != -1) {ts.tv_sec = timeout / 1000;ts.tv_nsec = (timeout % 1000) * 1000 * 1000;}int nfds = 0;if (timeout == -1) {nfds = kevent(fd_, NULL, 0, events_, MAX_EVENTS, NULL);} else {nfds = kevent(fd_, NULL, 0, events_, MAX_EVENTS, &ts);}for (int i = 0; i < nfds; ++i) {Channel *ch = (Channel *)events_[i].udata;int events = events_[i].filter;if (events == EVFILT_READ) {ch->set_ready_event(ch->READ_EVENT | ch->ET);}if (events == EVFILT_WRITE) {ch->set_ready_event(ch->WRITE_EVENT | ch->ET);}active_channels.push_back(ch);}return active_channels;
}
RC Poller::UpdateChannel(Channel *ch) const {struct kevent ev[2];memset(ev, 0, sizeof(*ev) * 2);int n = 0;int fd = ch->fd();int op = EV_ADD;if (ch->listen_events() & ch->ET) {op |= EV_CLEAR;}if (ch->listen_events() & ch->READ_EVENT) {EV_SET(&ev[n++], fd, EVFILT_READ, op, 0, 0, ch);}if (ch->listen_events() & ch->WRITE_EVENT) {EV_SET(&ev[n++], fd, EVFILT_WRITE, op, 0, 0, ch);}int r = kevent(fd_, ev, n, NULL, 0, NULL);if (r == -1) {perror("kqueue add event error");return RC_POLLER_ERROR;}return RC_SUCCESS;
}
RC Poller::DeleteChannel(Channel *ch) const {struct kevent ev[2];int n = 0;int fd = ch->fd();if (ch->listen_events() & ch->READ_EVENT) {EV_SET(&ev[n++], fd, EVFILT_READ, EV_DELETE, 0, 0, ch);}if (ch->listen_events() & ch->WRITE_EVENT) {EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_DELETE, 0, 0, ch);}int r = kevent(fd_, ev, n, NULL, 0, NULL);if (r == -1) {perror("kqueue delete event error");return RC_POLLER_ERROR;}return RC_SUCCESS;
}
#endif

4、Channel

Channel主要是将事件的socket和事件类型进行联系,并加入相应的回调函数,即Channel中包含了事件的socket、状态、处理、轮询等信息。他就是一个集大成socket。

头文件

同样禁止了复制移动类构造函数。其中包含了需要Channel注册的事件循环EventLoop,事件socket,期待事件类型,就绪事件类型以及对应的读写回调函数。

class Channel {public:DISALLOW_COPY_AND_MOVE(Channel);Channel(int fd, EventLoop *loop);~Channel();void HandleEvent() const;void EnableRead();void EnableWrite();int fd() const;short listen_events() const;short ready_events() const;bool exist() const;void set_exist(bool in = true);void EnableET();void set_ready_event(short ev);void set_read_callback(std::function<void()> const &callback);void set_write_callback(std::function<void()> const &callback);static const short READ_EVENT;static const short WRITE_EVENT;static const short ET;private:int fd_;EventLoop *loop_;short listen_events_;short ready_events_;bool exist_;std::function<void()> read_callback_;std::function<void()> write_callback_;
};

实现

简单的构造和析构函数,析构函数中调用的EventLoop中的DeleteChannel应该是Poller中的DeleteChannel,将事件从树上删除。

Channel::Channel(int fd, EventLoop *loop) : fd_(fd), loop_(loop), listen_events_(0), ready_events_(0), exist_(false) {}Channel::~Channel() { loop_->DeleteChannel(this); }

根据不同的就绪事件类型调用不同的事件处理函数,注意根据Poller中,如果事件类型不为EPOLLIN、EPOLLOUT、EPOLLET中任何一种,那么事件不会设置就绪事件类型,在这里也就不会调用任何处理函数。

void Channel::HandleEvent() const {if (ready_events_ & READ_EVENT) {read_callback_();}if (ready_events_ & WRITE_EVENT) {write_callback_();}
}

将事件期待的类型进行设置,并通过EventLoop调用Poller中的UpdateChannel。

void Channel::EnableRead() {listen_events_ |= READ_EVENT;loop_->UpdateChannel(this);
}void Channel::EnableWrite() {listen_events_ |= WRITE_EVENT;loop_->UpdateChannel(this);
}

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

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

相关文章

/usr/bin/ld: 当搜索用于 /lib/i386-linux-gnu/libcuda.so 时跳过不兼容的 -lcuda

/usr/bin/ld: 当搜索用于 /lib/i386-linux-gnu/libcuda.so 时跳过不兼容的 -lcuda ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/023dbdeb215b4b4580f7f54706e32af9.pn当使用unsloth做微调时&#xff0c;发现找不到libcuda&#xff0c;很自然想到需要软链接到最新…

IDEA2023中使用run Dashboard面板?实现批量运行微服务

1、直接点击Add service--->Run Configuration Type---->Spring Boot 2、这样就出现了run Dashboard面板&#xff0c;可同时运行多个工程模块&#xff0c;shift选中所有启动类组命名&#xff08;Group Configurations&#xff09; 3、启动所有的项目

自行车在线租赁管理系统

摘 要 新时代是一个快速发展的时代&#xff0c;信息革命正在各个行业蔓延。互联网拉近了 人们的距离&#xff0c;物质生活水平的提高平静地改变了人类消费的观念。人们对自行车 租赁行业的要求越来越高&#xff0c;这对传统的自行车租赁行业来说既是挑战也是机遇。 有必要提高…

安卓多媒体(音频录播、传统摄制、增强摄制)

本章介绍App开发常用的一些多媒体处理技术&#xff0c;主要包括&#xff1a;如何录制和播放音频&#xff0c;如何使用传统相机拍照和录像&#xff0c;如何截取视频画面&#xff0c;如何使用增强相机拍照和录像。 音频录播 本节介绍Android对音频的录播操作&#xff0c;内容包…

【Linux】线程(二:线程控制)

本篇文章主要围绕线程控制来进行展开。 主题思路是以create与join两个接口展开。 目录 pthread_create 与 pthread_join:pthread_create:pthread_join: 代码&#xff1a;问题一&#xff1a;主线程与新线程谁先退出&#xff1f;问题二&#xff1a;哪个线程应该最后退出&#xf…

OpenCV读取和显示和保存图像

# 导入 OpenCV import cv2 as cv # 读取图像 image cv.imread(F:\\mytupian\\xihuduanqiao.jpg) # 创建窗口 #显示图像后&#xff0c;允许用户随意调整窗口大小 cv.namedWindow(image, cv.WINDOW_NORMAL) # 显示图像 cv.imshow(image, image)# 将图像保存到文件 success cv…

Centos部署openGauss6.0创新版本,丝滑的体验

作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、MySQL、PG、高斯及Greenplum备份恢复&#xff0c; 安装迁移&#xff0c;性能优化、故障…

同三维T80004EHL-W-4K30 4K HDMI编码器,支持WEBRTC协议

输入&#xff1a;1路HDMI1路3.5音频&#xff0c;1路HDMI环出1路3.5音频解嵌输出 4K30超高清,支持U盘/移动硬盘/TF卡录制&#xff0c;支持WEBRTC协议&#xff0c;超低延时&#xff0c;支持3个点外网访问 1个主流1个副流输出&#xff0c;可定制选配POE供电模块&#xff0c;WEBR…

理解CA-IS3050G高速CAN收发器的CANH和CANL的电压

CA-IS3050G高速CAN收发器符合ISO 11898-2物理层标准。 1、CANH和CANL的电压之和为5V&#xff0c;下图是CA-IS3050G的高速CAN收发器参数&#xff0c;分析如下&#xff1a; 1&#xff09;、总线输出显性电压 2.75V < VCANH <4.5V&#xff0c;负载为60Ω&#xff0c;CANH…

Proxy和definedProperty

1. Proxy 代理 定义: 用于定义基本操作的自定义行为 Proxy修改的是程序默认形为&#xff0c;就形同于在编程语言层面上做修改&#xff0c;属于元编程 元编程 是指某类计算机程序的编写&#xff0c;这类计算机程序编写或者操纵其它程序&#xff08;或者自身&#xff09;作为它…

leetcode 1355 活动参与者(postgresql)

需求 表: Friends ---------------------- | Column Name | Type | ---------------------- | id | int | | name | varchar | | activity | varchar | ---------------------- id 是朋友的 id 和该表的主键 name 是朋友的名字 activity 是朋友参加的活动的名字 表: Activit…

QT实现多摄像头监控

工具使用方法&#xff1a; 1、在add camera后面输入对应摄像头的IP后&#xff0c;点击add会自动布局显示。 2、在del camera后选择一个对应IP后&#xff0c;点击del会自动删除对应摄像头的显示&#xff0c;且整体布局会自动调整。 工具使用场景&#xff1a; 测试摄像头的好坏。…

探索CSS clip-path: polygon():塑造元素的无限可能

在CSS的世界里&#xff0c;clip-path 属性赋予了开发者前所未有的能力&#xff0c;让他们能够以非传统的方式裁剪页面元素&#xff0c;创造出独特的视觉效果。其中&#xff0c;polygon() 函数尤其强大&#xff0c;它允许你使用多边形来定义裁剪区域的形状&#xff0c;从而实现各…

Redis常见数据类型及其常用命令详解

文章目录 一、Redis概述二、Redis常用命令1.通用命令1.1 KEYS&#xff1a;查看符合模板的所有 key1.2 DEL&#xff1a;删除一个指定的 key1.3 EXISTS&#xff1a;判断 key 是否存在1.4 EXPIRE&#xff1a;给一个 key 设置有效期&#xff0c;有效期到期时该 key 会被自动删除1.5…

【读博日记】拓扑结构(待修正)

Topology 拓扑学 内容来源于互联网&#xff0c;还在甄别中——20240617 拓扑结构指把实体抽象成与其形状大小无关的点&#xff0c;把连接实体的线路抽象成线&#xff0c;再研究这些电线之间的关系。 所谓相似的拓扑结构&#xff1a; 例如一个圆环变成正方形、长方形、三角形…

.Net OpenCVSharp生成灰度图和二值图

文章目录 前言一、灰度图二、二值图 前言 使用OpenCVSharp生成图片的灰度图和二值图 .Net 8.0版本&#xff0c;依赖OpenCvSharp4和OpenCvSharp4.runtime.win组件。 原图&#xff1a; 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、灰度图 /// &…

efficientsam-pytorch基于point、box和segment everthing推理模型

EfficientSAM 论文 EfficientSAM: Leveraged Masked Image Pretraining for Efficient Segment Anything https://arxiv.org/abs/2312.00863 模型结构 EfficientSAM模型利用掩码图像预训练&#xff08;SAMI&#xff09;&#xff0c;该预训练学习从SAM图像编码器重构特征&a…

项目(一)--高并发内存池项目简介

什么是高并发内存池 它是一个全球性大厂google(谷歌)的 开源项目,项目名字叫tcmalloc,全称是Thread-Caching Malloc,即线程缓存的malloc 作用&#xff1a; 我们知道C语言在堆上开辟空间和 释放使用的是malloc和free函数 并且C的动态内存管理new和delete 的底层实际上也调用了…

【Linux】模拟实现一个简单的日志系统

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

SFNC —— 标准特征命名约定(一)

系列文章目录 SFNC —— 标准特征命名约定&#xff08;一&#xff09; 文章目录 系列文章目录1、介绍1.1 约定&#xff08;Conventions&#xff09;功能名称和接口&#xff08;Feature Name and Interface&#xff09;功能类别&#xff08;Feature Category&#xff09;功能级别…