【Linux C++】实现Reactor服务器:封装Channel类
日期:2025.2.16(晚)2025.2.17(凌晨)
学习内容:
- 封装Channel类
个人总结:
注:本篇内容比较枯燥,主要是各个类的关系,不想看的可以直接看代码。
背景:
好的,目前我们已经有了Socket,InetAddress,Epoll三个类,目前为止思路还不是特别的凌乱,但是加上了Channel类之后,我的脑子已经乱掉了。这四个类的关系我很混乱,如果让我自己敲我不一定能够敲出来,所以特地整理了一下思绪。
注意:以下内容是各个类之间的关系的推理。
首先是Socket和InetAddress类:InetAddress类就是一个单纯的数据包装的类,没有别的特别的,一个更好使用的地址类。而Socket类是一个对fd描述符的高级包装,我们可以更好的操纵fd(在网络编程的层面上),简化了bind,accept操作,而其中accept函数可以让我们得到客户端已连接描述符的地址。
接着是我们现在多了一个Channel类,Channel类起到的是一个桥梁作用,Epoll类负责任务的分发,就像是一个boss,每一次Channel将客户的fd要做的事情告诉Epoll,然后Epoll将任务分发给Channel去做。(关于为什么要用Channel类,是为了方便更多可以执行的操作,之前的简单的版本没有Channel类,我们只能去执行一些简单的例如数据发送的操作)。
Channel类的HandlEvent函数就是处理事件的函数,根据revent变量(在下面有讲revent和event)来判断执行什么函数。(由于我们写的简单版本,目前只有读事件的处理,也就是EPOLLIN),当是EPOLLIN,就会执行read_callback函数,这是一个function<void()>类型,接口统一为了方便。
然后再说一点细节:
之前我们使用epoll_event,但是现在使用的是Channel类,所以两者之间还是有转化的。这里详情我们直接看代码:
void Epoll::UpdateChannel(Channel* ch) {
epoll_event one;
one.data.ptr = ch;
one.events = ch->GetEvent();
if (ch->IsInEpoll()) {
//...
}
else {
if (epoll_ctl(m_epollfd, EPOLL_CTL_ADD, ch->GetFD(), &one) < 0) {
perror("epoll_ctl");
exit(-1);
}
ch->SetInEpoll();
}
}
可以看到这里我们存了指针,将Channel类转化到了epoll_event类型,然后再用epoll_ctl函数存入epoll实例中。
那么相对应的,我们在使用epoll_wait函数后,得到的也是epoll_event类型的,也是类似的方式:
std::vector<Channel*> Epoll::NewEvents(int timeout) {
m_evs.resize(MAXSIZE);
int newthings = epoll_wait(m_epollfd, m_evs.data(), MAXSIZE, timeout);
//...
std::vector<Channel*> ch_vec;
for (int i = 0; i < newthings; i++) {
Channel* one = (Channel*)m_evs[i].data.ptr;
one->SetRevent(m_evs[i].events);
ch_vec.push_back(one);
}
return std::move(ch_vec);
}
再用指针即可,记得设置revent。
这里还有一个浅层的注意事项:关于m_evs[i].events的类型,我们存进去的时候是我们期望的关注的事件event:EPOLLIN,但是在使用了wait函数之后,会修改成了实际发生的事件revent:EPOLLIN(这里大多数都是不会变的,但是确实也有变化的情况)。
所以我们用Channel类后,就需要专门拉出来设两个变量,一个是event,一个是revent。
大体就讲到这里了,后面的就看代码了。
代码:
server.cpp:
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdio.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/fcntl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <iostream>
#include <functional>
#include "InetAddress.h"
#include "Socket.h"
#include "Epoll.h"
#include "Channel.h"
using std::cin;
using std::cout;
using std::endl;
int main(int argc, char* argv[]) {
if (argc != 3) {
cout << "./server 192.168.11.132 5005" << endl;
return 0;
}
Socket listen(CreatNonBlocking());
int listenfd = listen.GetFD();
listen.KeepAlive(1);
listen.NoDelay(1);
listen.ReuseAddr(1);
listen.ReusePort(1);
InetAddress serv_addr(argv[1], atoi(argv[2]));
listen.Bind(serv_addr);
listen.Listen(128);
Epoll ep;
Channel* serv_ch = new Channel(&ep, listen.GetFD());
serv_ch->EnableReading();
serv_ch->SetReadCallback(std::bind(&Channel::NewConnection, serv_ch, &listen));
ep.UpdateChannel(serv_ch);
while (true) {
auto new_evs = ep.NewEvents();
for (auto& ev : new_evs) {
ev->HandleEvent();
}
}
return 0;
}
Channel.h:
#pragma once
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdio.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/fcntl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <iostream>
#include <functional>
class Epoll;
class Socket;
class Channel {
private:
int m_fd = -1;
Epoll* m_ep = nullptr;
bool m_is_in_epoll = false;
uint32_t m_event = 0;
uint32_t m_revent = 0;
std::function<void()> read_callback;
public:
Channel(Epoll* ep, int fd);
~Channel();
void EnableReading();
void EnableET();
void SetInEpoll();
void SetRevent(const uint32_t& ev);
void SetReadCallback(std::function<void()> func);
bool IsInEpoll();
int GetFD();
uint32_t GetEvent();
uint32_t GetRevent();
void HandleEvent();
void NewConnection(Socket* listen);
void OnMessage();
};
Epoll.h:
#pragma once
#include <sys/epoll.h>
#include <unistd.h>
#include <vector>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
class Channel;
class Epoll {
private:
static const int MAXSIZE = 10;
int m_epollfd;
std::vector<epoll_event> m_evs;
public:
Epoll();
~Epoll();
void AddFD(int fd, uint32_t opt);
void UpdateChannel(Channel* ch);
std::vector<Channel*> NewEvents(int timeout = -1);
};
Socket.h:
#pragma once
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
class InetAddress;
int CreatNonBlocking();
class Socket {
private:
int m_fd;
public:
Socket(int fd);
~Socket();
void ReuseAddr(bool opt);
void ReusePort(bool opt);
void KeepAlive(bool opt);
void NoDelay(bool opt);
void Bind(const InetAddress& addr);
void Listen(int siz = 128);
int Accept(InetAddress& addr);
const int GetFD() const;
};
InetAddress.h:
#pragma once
#include <sys/socket.h>
#include <string>
#include <netinet/in.h>
#include <arpa/inet.h>
class InetAddress {
private:
sockaddr_in m_addr;
public:
InetAddress() = default;
InetAddress(const std::string& ip, uint16_t port);
InetAddress(const sockaddr_in& addr);
~InetAddress();
void SetAddress(const sockaddr_in& one);
const sockaddr* Addr() const;
const char* IP() const;
const short Port() const;
};
Channel.cpp:
#include "Channel.h"
#include "Epoll.h"
#include "InetAddress.h"
#include "Socket.h"
Channel::Channel(Epoll* ep, int fd) {
m_ep = ep;
m_fd = fd;
}
Channel::~Channel() {
}
void Channel::EnableReading() {
m_event |= EPOLLIN;
}
void Channel::EnableET() {
m_event |= EPOLLET;
}
void Channel::SetInEpoll() {
m_is_in_epoll = true;
}
void Channel::SetRevent(const uint32_t& ev) {
m_revent = ev;
}
void Channel::SetReadCallback(std::function<void()> func) {
read_callback = func;
}
bool Channel::IsInEpoll() {
return m_is_in_epoll;
}
int Channel::GetFD() {
return m_fd;
}
uint32_t Channel::GetEvent() {
return m_event;
}
uint32_t Channel::GetRevent() {
return m_revent;
}
void Channel::HandleEvent() {
if (this->GetRevent() & EPOLLIN) {
read_callback();
}
else if (this->GetRevent() & EPOLLOUT) {
}
else {
printf("error: (fd: %d) \n", this->GetFD());
close(this->GetFD());
}
}
void Channel::NewConnection(Socket* listen) {
InetAddress client_addr1;
Socket* client = new Socket(listen->Accept(client_addr1));
int clientfd = client->GetFD();
printf("New Client fd:%d ip:%s port:%hu \n", clientfd, client_addr1.IP(), client_addr1.Port());
Channel* client_ch = new Channel(m_ep, clientfd);
client_ch->EnableET();
client_ch->EnableReading();
client_ch->SetReadCallback(std::bind(&Channel::OnMessage, client_ch));
m_ep->UpdateChannel(client_ch);
}
void Channel::OnMessage() {
char buf[1024];
while (true) {
memset(buf, 0, sizeof buf);
ssize_t read_siz = read(this->GetFD(), buf, sizeof buf);
if (read_siz > 0) {
printf("recv: (fd: %d) %s\n", this->GetFD(), buf);
send(this->GetFD(), buf, sizeof buf, 0);
}
else if (read_siz == -1 and errno == EINTR) {
continue;
}
else if (read_siz == -1 and (errno == EAGAIN || errno == EWOULDBLOCK)) {
break;
}
else if (read_siz == 0) {
printf("disconnect: (fd: %d) \n", this->GetFD());
close(this->GetFD());
break;
}
}
}
Epoll.cpp:
#include "Channel.h"
#include "Epoll.h"
Epoll::Epoll() { m_epollfd = epoll_create(1); }
Epoll::~Epoll() { close(m_epollfd); }
void Epoll::UpdateChannel(Channel* ch) {
epoll_event one;
one.data.ptr = ch;
one.events = ch->GetEvent();
if (ch->IsInEpoll()) {
if (epoll_ctl(m_epollfd, EPOLL_CTL_MOD, ch->GetFD(), &one) < 0) {
perror("epoll_ctl");
exit(-1);
}
}
else {
if (epoll_ctl(m_epollfd, EPOLL_CTL_ADD, ch->GetFD(), &one) < 0) {
perror("epoll_ctl");
exit(-1);
}
ch->SetInEpoll();
}
}
std::vector<Channel*> Epoll::NewEvents(int timeout) {
m_evs.resize(MAXSIZE);
int newthings = epoll_wait(m_epollfd, m_evs.data(), MAXSIZE, timeout);
if (newthings < 0) {
perror("epoll_wait");
exit(-1);
}
else if (newthings == 0) {
return std::vector<Channel*>();
}
std::vector<Channel*> ch_vec;
for (int i = 0; i < newthings; i++) {
Channel* one = (Channel*)m_evs[i].data.ptr;
one->SetRevent(m_evs[i].events);
ch_vec.push_back(one);
}
return std::move(ch_vec);
}
Socket.cpp:
#include "Socket.h"
#include "InetAddress.h"
int CreatNonBlocking() {
return socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
}
Socket::Socket(int fd) :m_fd(fd) {
}
Socket::~Socket() {
close(m_fd);
}
void Socket::ReuseAddr(bool opt) {
setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, &opt, static_cast<socklen_t>(sizeof opt));
}
void Socket::ReusePort(bool opt) {
setsockopt(m_fd, SOL_SOCKET, SO_REUSEPORT, &opt, static_cast<socklen_t>(sizeof opt));
}
void Socket::KeepAlive(bool opt) {
//套接字开启保活机制,如果长时间没有传输数据会检测是否链接,没有链接就会释放资源
setsockopt(m_fd, SOL_SOCKET, SO_KEEPALIVE, &opt, static_cast<socklen_t>(sizeof opt));
}
void Socket::NoDelay(bool opt) {
//禁用nagle算法,减少数据延迟
setsockopt(m_fd, SOL_SOCKET, TCP_NODELAY, &opt, static_cast<socklen_t>(sizeof opt));
}
void Socket::Bind(const InetAddress& addr) {
if (bind(m_fd, addr.Addr(), sizeof(sockaddr_in)) < 0) {
perror("Bind");
exit(-1);
}
}
void Socket::Listen(int siz) {
if (listen(m_fd, siz) < 0) {
perror("Listen");
exit(-1);
}
}
int Socket::Accept(InetAddress& addr) {
struct sockaddr_in one;
socklen_t len = sizeof(sockaddr_in);
int clientfd = accept4(m_fd, (sockaddr*)&one, &len, SOCK_NONBLOCK);
if (clientfd == -1) {
perror("accept4");
exit(-1);
}
addr.SetAddress(one);
return clientfd;
}
const int Socket::GetFD()const {
return m_fd;
}
InetAddress.cpp:
#include "InetAddress.h"
InetAddress::InetAddress(const std::string& ip, uint16_t port) {
m_addr.sin_family = AF_INET;
m_addr.sin_port = htons(port);
m_addr.sin_addr.s_addr = inet_addr(ip.c_str());
}
InetAddress::InetAddress(const sockaddr_in& addr) { m_addr = addr; }
InetAddress::~InetAddress() {}
const sockaddr* InetAddress::Addr() const { return (sockaddr*)&m_addr; }
const char* InetAddress::IP() const { return inet_ntoa(m_addr.sin_addr); }
const short InetAddress::Port() const { return htons(m_addr.sin_port); }
void InetAddress::SetAddress(const sockaddr_in& one) { m_addr = one; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战