参考代码:https://gitee.com/yegelute/my_-linux-code/tree/master/Reactor
主要以代码为主,下面张贴部分核心结构
附录:
Connection.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include "InetAddr.hpp"
// 封装fd,保证给每一个fd一套缓冲
class Reactor;
class Connection;
using handler_t = std::function<std::string(std::string&)>;
// 基类
class Connection
{
public:
Connection() :_events(0), _owner(nullptr) {}
void SetEvent(const uint32_t& events) { _events = events; }
uint32_t GetEvent() { return _events; }
void SetOwner(Reactor* owner) { _owner = owner; }
Reactor* GetOwner() { return _owner; }
virtual void Recver() = 0;
virtual void Sender() = 0;
virtual void Excepter() = 0;
virtual int GetSockFd() = 0;
void RegisterHandler(handler_t handler)
{
_handler = handler;
}
~Connection() {}
private:
// 关心事件
uint32_t _events;
// 回指指针
Reactor* _owner;
public:
handler_t _handler; // 基类中定义了一个回调函数
};
Accepter.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class Connection;
class TcpServer;
using func_t = std::function<void(Connection *)>;
class Connection
{
public:
Connection(int sockfd, uint32_t events, TcpServer *R)
: _sockfd(sockfd), _events(events), _R(R)
{
}
void RegisterCallback(func_t recver, func_t sender, func_t excepter)
{
_recver = recver;
_sender = sender;
_excepter = excepter;
}
void AddInBuffer(std::string buffer)
{
_inbuffer += buffer; // 追加到inbuffer中
}
void AddOutBuffer(const std::string &buffer)
{
_outbuffer += buffer;
}
bool OutBufferEmpty()
{
return _outbuffer.empty();
}
int SockFd()
{
return _sockfd;
}
uint32_t Events()
{
return _events;
}
void SetEvents(uint32_t events)
{
_events = events;
}
void SetClient(const struct sockaddr_in c)
{
_client = c;
}
std::string &InBuffer()
{
return _inbuffer;
}
std::string &OutBuffer()
{
return _outbuffer;
}
void Close()
{
::close(_sockfd);
}
~Connection()
{
}
private:
// 对应的sockfd
int _sockfd;
// 对应的缓冲区
std::string _inbuffer; // _sockfd 接受缓冲区, 暂时用string代替
std::string _outbuffer; // _sockfd 发送缓冲区
// 关心的事件
uint32_t _events;
// 维护一下client的ip和port信息
struct sockaddr_in _client;
public:
// 对特定connection进行处理的回调函数
func_t _recver;
func_t _sender;
func_t _excepter;
// TcpServer的回指指针 - TODO
TcpServer *_R;
};
class ConnectionFactory
{
public:
static Connection *BuildListenConnection(int listensock, func_t recver,
uint32_t events, TcpServer *R)
{
Connection *conn = new Connection(listensock, events, R);
conn->RegisterCallback(recver, nullptr, nullptr);
return conn;
}
static Connection *BuildNormalConnection(int sockfd,
func_t recver,
func_t sender,
func_t excepter,
uint32_t events,
TcpServer *R)
{
Connection *conn = new Connection(sockfd, events, R);
conn->RegisterCallback(recver, sender, excepter);
return conn;
}
};
HandlerConnction.hpp
cpp
#pragma once
#include <iostream>
#include <cerrno>
#include "Connection.hpp"
#include "Protocol.hpp"
#include "Calculate.hpp"
#include "Log.hpp"
using namespace Protocol;
using namespace CalCulateNS;
const static int buffer_size = 1024;
class HandlerConnection
{
public:
static void HandlerRequest(Connection *conn)
{
std::string &inbuffer = conn->InBuffer();
std::string message; // 表⽰一个符合协议的一个完整的报文
Calculate calulate; // 负责业务处理
Factory factory;
auto req = factory.BuildRequest();
// 1. 明确报文边界,解决粘报问题
while (Decode(inbuffer, &message))
{
// message 一定是一个完整的报文,符合协议的!
// 2. 反序列化
if (!req->Deserialize(message))
continue;
// 3. 业务处理
auto resp = calulate.Cal(req);
// 4. 对相应进行序列化
std::string responseStr;
resp->Serialize(&responseStr);
// 5. 封装完整报文
responseStr = Encode(responseStr);
// 6. 将应答全部追加到outbuffer中
conn->AddOutBuffer(responseStr);
}
// 考虑发送的问题了
if (!conn->OutBufferEmpty())
{
conn->_sender(conn); // 对写事件,直接发!!!--- 不代表能全部发完!
}
}
// 在这⾥读取的时候,我们关系数据是什么格式?协议是什么样⼦的吗?
// 不关心!!!我们只负责把本轮属于完全读取完毕 --- 把读到的字节流数据,交给上层 --- 由上层进行分析处理
static void Recver(Connection *conn)
{
errno = 0;
// 读取流程
char buffer[1024];
while (true)
{
ssize_t n = recv(conn->SockFd(), buffer, sizeof(buffer) - 1, 0);
// ⾮阻塞读取
if (n > 0)
{
buffer[n] = 0;
conn->AddInBuffer(buffer);
}
else
{
// std::cout << "................... errno:" << errno <<
std::endl;
if (errno == EAGAIN)
break;
else if (errno == EINTR)
continue;
else
{
// 真正的读取错误
conn->_excepter(conn); // 直接回调自己的异常处理就可以了!
return;
}
}
}
std::cout << "sockfd# " << conn->SockFd() << ":\n"
<< conn->InBuffer() << std::endl;
// 尝试分析处理报文 -- 半个,一个半, 10个,11个半
HandlerRequest(conn);
}
static void Sender(Connection *conn)
{
errno = 0;
std::string &outbuffer = conn->OutBuffer();
while (true)
{
ssize_t n = send(conn->SockFd(), outbuffer.c_str(),
outbuffer.size(), 0);
if (n >= 0)
{
outbuffer.erase(0, n); // 已经发给OS的,就直接移除了 // conn-
> remove(n);
if (outbuffer.empty())
break;
}
else
{
if (errno == EAGAIN)
break; // 只有这⾥,才会正常退出
else if (errno == EINTR)
continue;
else
{
conn->_excepter(conn);
return;
}
}
}
// ⾛到这⾥,意味着什么?我们本轮发满了,但是数据可能没发完,为什么没发完呢?
// 开启对conn->SockFd() EPOLLOUT的关心!!!!!, 如何开启对于特定一个
connection对应的写事件关心呢??? if (!conn->OutBufferEmpty())
{
conn->_R->EnableReadWrite(conn->SockFd(), true, true);
}
else
{
conn->_R->EnableReadWrite(conn->SockFd(), true, false);
}
}
static void Excepter(Connection *conn)
{
lg.LogMessage(Info, "connection erase done, who: %d\n", conn - > SockFd());
errno = 0;
// 从epoll中移除对conn->Sockfd的关心
// unordered_map 移除conn
conn->_R->RemoveConnection(conn->SockFd());
// 关闭conn->Sockfd
conn->Close();
// delete conn
delete conn;
}
};
Reactor模式是一种事件驱动的编程模式,用于高效地处理并发 I/O 操作。它通过一个或多个事件循
环(Event Loop)来监听和处理各种事件(如⽹络请求、定时器事件等),从而实现高效的并发处理,而⽆需为每个连接创建一个线程或进程。
附录
OneThreadOneLoop多进程方案:
One Thread One Loop (OTOL)是一种设计模式,通常用于描述基于事件驱动编程(Event Driven Programming)的架构,特别是在使用异步I/O(Asynchronous I/O)框架时。这种模式强调每个线程运行一个独立的事件循环(Event Loop),从而实现高效的并发处理。
核心概念
事件循环(Event Loop):
▪ 事件循环是异步编程的核心,负责监听事件(如I/O操作、定时器事件等)并触发相应的回调函数。
▪ 它通常是一个单线程的执行模型,通过多路复用技术(如 epoll 、 select 或 poll )高效地管理多个I/O操作。
单线程模型:
▪ 在 One Thread One Loop 模式中,每个线程运行一个独立的事件循环。
▪ 这种设计避免了多线程编程中的复杂同步问题,同时利用了现代操作系统高效的I/O多路复 用机制。

• 只要把单Reactor写完,扩展多进程很容易
• 每个进程管理的连接和fd,彼此不要重复
• 每个Reacor,自己处理自己的sockfd的完整⽣命周期,不涉及任何IO穿插,乱序问题
OneThreadOneLoop多线程方案1:

注意:管道也能线程间通信,所以我们可以使用同样的方案进行设计
cpp
#include <iostream>
#include <thread>
#include <unistd.h>
#include <cstring>
// 定义管道的读写端
int pipefds[2];
// ⼯作线程函数
void worker_thread()
{
char buffer[256];
int n;
while (true)
{
n = read(pipefds[0], buffer, sizeof(buffer) - 1);
if (n > 0)
{
buffer[n] = '\0'; // 确保字符串结尾
std::cout << "Worker thread received: " << buffer << std::endl;
}
else if (n == 0)
{
std::cout << "No more data to read. Exiting worker thread." << std::endl;
break;
}
else
{
std::cerr << "Error reading from pipe." << std::endl;
break;
}
}
}
int main()
{
// 创建管道
if (pipe(pipefds) == -1)
{
perror("pipe");
return 1;
}
// 创建⼯作线程
std::thread worker(worker_thread);
// 主线程向管道写⼊数据
const char *messages[] = {"Hello from main thread!", "Another message.",
"Goodbye!"};
for (const char *msg : messages)
{
sleep(1);
if (write(pipefds[1], msg, strlen(msg)) < 0)
{
perror("write");
break;
}
sleep(1);
std::cout << "Main thread sent: " << msg << std::endl;
}
// 等待⼯作线程结束
worker.join();
// 关闭管道(可选)
close(pipefds[0]);
close(pipefds[1]);
return 0;
}
// 可以定义多个管道
// struct pipefd
//{
// int fds[2]
// };
// std::vector<struct pipefd> pipes;
另外:
• 多线程是可以共享文件fd的,如果我们把⼯作职责变一下(其实就是修改一下connection的回调方法),让master线程,检测并且直接accepter到新的连接fd列表,然后通过管道传递给每一个⼦进程,每个⼦进程拿到sockfd,直接添加到自己的Reactor中,进行IO处理就可以,这样做会更简单。
• 这样,读取管道内容,按照4字节读取,⾃动序列和反序列化
如果不想使用管道,可以又其他做法。
Eventfd事件驱动
• 如果不想用管道,可以选择下列方案进行驱动
• 注意:基于事件驱动,就必须由多路转接,也就是必须基于fd文件描述符,这也就是为什么我们上面用管道的原因。
cpp
NAME
eventfd - create a file descriptor for event notification
SYNOPSIS
#include <sys/eventfd.h>
int eventfd(unsigned int initval, int flags);
RETURN VALUE
On success, eventfd() returns a new eventfd file descriptor.
On error, -1 is returned and errno is set to indicate the error.
• eventfd 是一个轻量级的事件通知机制,基于文件描述符。
• 它可以与 I/O 多路复用机制(如 epoll )结合使用。
• 内核维护一个 64 位的计数器, write 会增加计数器, read 会减少计数器。
进程间事件通知 - 不写
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/eventfd.h>
int main()
{
int efd = eventfd(0, EFD_CLOEXEC); // 创建 eventfd
if (efd == -1)
{
perror("eventfd");
return 1;
}
pid_t pid = fork();
if (pid == -1)
{
perror("fork");
return 1;
}
if (pid == 0)
{ // ⼦进程
uint64_t value;
read(efd, &value, sizeof(value)); // 从 eventfd 读取事件
printf("Child process received event\n");
close(efd);
return 0;
}
else
{ // ⽗进程
uint64_t value = 1;
write(efd, &value, sizeof(value)); // 向 eventfd 写⼊事件
printf("Parent process sent event\n");
wait(NULL); // 等待⼦进程结束
close(efd);
return 0;
}
}
EFD_CLOEXEC :确保文件描述符在 exec 时被⾃动关闭,防⽌被新程序继承。
线程间事件通知 - 不写
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/eventfd.h>
#include <pthread.h>
void *worker_thread(void *arg)
{
int efd = *(int *)arg;
uint64_t value;
read(efd, &value, sizeof(value)); // 从 eventfd 读取事件
printf("Worker thread received event\n");
return NULL;
}
int main()
{
int efd = eventfd(0, EFD_CLOEXEC); // 创建 eventfd[^33^]
if (efd == -1)
{
perror("eventfd");
return 1;
}
pthread_t thread;
pthread_create(&thread, NULL, worker_thread, &efd); // 创建⼯作线程
uint64_t value = 1;
write(efd, &value, sizeof(value)); // 向 eventfd 写⼊事件
printf("Main thread sent event\n");
pthread_join(thread, NULL); // 等待⼯作线程结束
close(efd);
return 0;
}
特点:
• 低开销: eventfd 内部是一个 64 位计数器,内核维护成本低。
• ⽀持多路复用:可以与 epoll 、 poll 或 select 等 I/O 多路复用机制结合使用。
• 原⼦性:读写操作是原⼦的,适合高并发场景。
• ⼴播通知:可以用于多对多的事件通知,而不仅仅是点对点通信。
• 高效性:相⽐传统管道, eventfd 避免了多次数据拷⻉,且内核开销更⼩。
注意事项:
• 仅用于事件通知: eventfd 不能传递具体的消息内容,仅用于通知事件的发⽣。
• ⾮阻塞模式:建议设置 EFD_NONBLOCK ,避免阻塞操作。
• 信号量语义:如果需要信号量语义(每次读取计数器减 1),需设置 EFD_SEMAPHORE 。
• 资源管理:使用完 eventfd 后,记得关闭文件描述符。
• 结合 I/O 多路复用:在高并发场景下,建议结合 epoll 使用。
Eventfd⼯作模式:
• 普通模式:不设置 EFD_SEMAPHORE ,读取的时候,计数器会清空
• 设置EFD_SEMAPHORE :信号量模式
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/eventfd.h>
int main()
{
// 模式1: 创建 eventfd, 不设置EFD_SEMAPHORE
int efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
// 模式2: 创建 eventfd,设置 EFD_SEMAPHORE 和 EFD_NONBLOCK
// int efd = eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK | EFD_CLOEXEC);
if (efd == -1)
{
perror("eventfd");
return 1;
}
uint64_t value;
// 写⼊ 1
value = 1;
write(efd, &value, sizeof(value));
printf("Wrote 1 to eventfd\n");
// 写⼊ 2
value = 2;
write(efd, &value, sizeof(value));
printf("Wrote 2 to eventfd\n");
value = 0; // 防⽌⼲扰,value直接清空
// 读取一次(计数器减 1)
read(efd, &value, sizeof(value));
printf("Read value: %llu (counter is now 2)\n", (unsigned long long)value);
value = 0; // 防⽌⼲扰,value直接清空
// 再次读取一次(计数器减 1)
read(efd, &value, sizeof(value));
printf("Read value: %llu (counter is now 1)\n", (unsigned long long)value);
value = 0; // 防⽌⼲扰,value直接清空
// 再次读取一次(计数器减 1)
read(efd, &value, sizeof(value));
printf("Read value: %llu (counter is now 0)\n", (unsigned long long)value);
close(efd);
return 0;
}
模式1输出:
bash
Wrote 1 to eventfd
Wrote 2 to eventfd
Read value: 3 (counter is now 2) // 一次读完,直接清空
Read value: 0 (counter is now 1)
Read value: 0 (counter is now 0)
模式2输出:
bash
Wrote 1 to eventfd
Wrote 2 to eventfd
Read value: 1 (counter is now 2) // 每次读取,计数器-1
Read value: 1 (counter is now 1)
Read value: 1 (counter is now 0)
我们后面只需要用eventfd进行事件通知,选择模式1(默认)即可
OneThreadOneLoop多线程方案2:
