在正式开始之前,我们先考虑下日志的问题,之前学习muduo是写过一个Log的,但是那个Log仅仅是为了学习使用,所以比较简陋,我这里又不想花费过多的时间再Log上,所以选用了一个轻量级别的日志框架。
选用EasyLogging++
如何使用
因此关于日志,我们就迅速跳过。
第二个使用选项是muduo中的Logging,拿过来改了改觉得比较好用。
https://blog.csdn.net/weixin_43468441/article/details/103336870
https://blog.csdn.net/weixin_43468441/article/details/103336897
https://blog.csdn.net/weixin_43468441/article/details/93775965
在我看来,Reactor的优点主要就是利用IO复用,即epoll,select或者poll,即半同步半异步的方式。
实际上就是我们不用使用阻塞调用去傻傻地等待连接,或者客户端的数据到来。而是取而代之被IO复用来通知。
第一个版本的Reactor,是为了方便阅读,所以相当于直接模仿Redis做的。
第二个版本的Reactor,则是为了更加方便使用。手法更加类似muduo。
#include
#include
#include
#define AE_NONE 0
#define AE_READABLE 1
#define AE_WRITABLE 2
class EventLoop;
class FileEvent;
using FileEventPtr = std::shared_ptr<FileEvent>;
class FileEvent : boost::noncopyable
{
friend class EventLoop;
using Callback = std::function<void(int)>;
public:
FileEvent(int fd);
void RegisterReadHandler(Callback cb);
void RegisterWriteHandler(Callback cb);
private:
void HandleRead(int connfd)
{
if(readHandler_)
readHandler_(connfd);
}
void HandleWrite(int connfd)
{
if(writeHandler_)
writeHandler_(connfd);
}
int FD() const {
return fd_; }
// 返回就绪的事件mask
int Mask() const {
return mask_; }
int RMask() const {
return rmask_; }
private:
const int fd_;
int mask_;
int rmask_;
Callback readHandler_;
Callback writeHandler_;
};
struct FiredEvent
{
FiredEvent() = default;
FiredEvent(int fd, int rmask)
: fd_(fd)
, rmask_(rmask)
{
}
int fd_;
int rmask_;
};
void(int)
这种类型。class FileEvent;
class TimeEvent;
class EventLoop : boost::noncopyable
{
public:
EventLoop()
: maxfd_(10)
, setsize_(1024)
, isStarting_(false)
, fileEvents_(setsize_)
, firedEvents_(setsize_)
, poller_(new Poller())
{
}
~EventLoop() = default;
void RegisterFileEvent(std::shared_ptr<FileEvent> fileEventPtr);
void DeleteFileEvent(int fd);
void RegisterTimeEvent();
void DeleteTimeEvent();
void Exit()
{
isStarting_ = false;
}
void Loop();
void beforeWait();
private:
int maxfd_;
int setsize_;
bool isStarting_;
std::vector<std::shared_ptr<FileEvent>> fileEvents_;
std::vector<FiredEvent> firedEvents_;
std::list<TimeEvent> timeEvents_;
std::unique_ptr<Poller> poller_;
};
void
EventLoop::Loop()
{
isStarting_ = true;
while(isStarting_)
{
poller_->Poll(-1, maxfd_, &firedEvents_);
for(auto &one : firedEvents_)
{
if(one.rmask_ & AE_READABLE)
{
fileEvents_[one.fd_]->HandleRead(one.fd_);
}
if(one.rmask_ & AE_WRITABLE)
{
fileEvents_[one.fd_]->HandleWrite(one.fd_);
}
}
}
}
的hashmap。但是这里采用了vector实现。class Poller : boost::noncopyable
{
public:
Poller();
~Poller();
// 根据mask,来决定关注读或者写事件
bool UpdateEvent(int fd, int mask);
bool DeleteEvent(int fd);
void Poll(int count, int setsize, vector<FiredEvent>* firedEvents);
private:
int epfd_;
};
bool
Poller::UpdateEvent(int fd, int rmask)
{
struct epoll_event ee;
bzero(&ee, sizeof(ee));
if(mask & AE_READABLE)
{
ee.events |= EPOLLIN;
}
if(mask & AE_WRITABLE)
{
ee.events |= EPOLLOUT;
}
ee.data.fd = fd;
//int op = rmask == AE_NONE ? EPOLL_CTL_ADD : EPOLL_CTL_MOD;
int op = EPOLL_CTL_ADD; // for simple
if(epoll_ctl(epfd_, op, fd, &ee) == -1)
{
return -1;
}
return 0;
}
void
Poller::Poll(int count, int setsize, vector<FiredEvent>* activeEvents)
{
int retval = 0;
struct epoll_event events[setsize];
retval = epoll_wait(epfd_, events, setsize, count);
if(retval > 0)
{
int j;
int numevents = retval;
activeEvents->resize(numevents);
for(j = 0; j < numevents; j++)
{
int mask = 0;
struct epoll_event * e = events + j;
if(e->events & EPOLLIN) mask |= AE_READABLE;
if(e->events & EPOLLOUT) mask |= AE_WRITABLE;
if(e->events & EPOLLERR) mask |= AE_WRITABLE;
if(e->events & EPOLLHUP) mask |= AE_WRITABLE;
activeEvents->push_back(FiredEvent {
e->data.fd, mask});
}
}
}
可以看到Poller主要的作用就是为了封装epoll。
而Poll函数中唯一的变化就是需要设置返回的就绪事件列表。
EventLoop eventLoop;
void onReading(int fd)
{
char buff[1024] = {
0};
::read(fd, buff, 1024);
::write(fd, buff, strlen(buff));
}
void onAccept(int fd)
{
cout << "accept" << endl;
int connfd = Socket::Accept(fd);
auto event = make_shared<FileEvent>(connfd);
event->RegisterReadHandler(onReading);
eventLoop.RegisterFileEvent(event);
}
int main()
{
Socket s(Socket::CreateTCPSocket());
SocketAddr addr("127.0.0.1:8888");
Socket::Bind(s.FD(), addr);
Socket::Listen(s.FD());
auto event = make_shared<FileEvent>(s.FD());
event->RegisterReadHandler(onAccept);
eventLoop.RegisterFileEvent(event);
eventLoop.Loop();
}
使用流程
通过这么使用,发现我得需要一个全局的EventLoop才行。实际上我们可以发现一个EventLoop可以拥有多个FileEvent,而每个FileEvent只属于一个EventLoop。因此,没有EventLoop的FileEvent是没有意义的。因此FileEvent是依赖EventLoop的。
https://github.com/patientCat/Jedis
第一个和第二个版本分别放到了Reactor_0x中。可以自己对比,来发现代码中的简化。
或者说是ListenSocket,如果你经常写服务器的话,可以发现,我们总是要socket-bind-listen来做一个listenfd。实际上我们完全可以可以抽象出一个Acceptor来。
在服务器启动前,完成这一步骤,然后将自己的OnAcceptHandler()注册到EventLoop中去。
这里可能有点难度,主要在于要习惯利用bind/function来注册函数,而Acceptor实际上就是我们的EventLoop启动的钥匙。它是第一个发生事件。后面的陆续发生的事件实际上都是源于这里的。
因为看过muduo源码,我总是不自觉地去模仿muduo,没办法。
class EventLoop;
class Acceptor
{
public:
using AcceptHandler = std::function<void(int)>;
explicit Acceptor(EventLoop* loop, const SocketAddr& addr)
: loop_(loop)
, addr_(addr)
, socket_(Socket::CreateNonBlockTCPSocket())
{
Socket::SetReuseAddr(socket_.FD());
Socket::Bind(socket_.FD(), addr);
Socket::Listen(socket_.FD());
FileEventPtr eventPtr = std::make_shared<FileEvent>(socket_.FD());
eventPtr->RegisterReadHandler(std::bind(&Acceptor::OnAcceptHandler, this));
}
void OnAcceptHandler()
{
int cfd = Socket::Accept(socket_.FD());
Socket::SetNonBlock(cfd);
onAcceptHandler_(cfd);
}
void RegisterOnAcceptHandler(AcceptHandler handler)
{
onAcceptHandler_ = handler;
}
private:
EventLoop *loop_;
SocketAddr addr_;
Socket socket_;
AcceptHandler onAcceptHandler_;
};
现在我们用我们已有的东西来做一个测试。
class Server
{
public:
Server(const SocketAddr& addr)
: addr_(addr)
, loop_()
, acceptor_(&loop_, addr)
{
acceptor_.RegisterOnAcceptHandler(std::bind(&Server::OnAcceptHandle, this, placeholders::_1));
}
void OnReading(int fd)
{
char buff[1024] = {
0};
::read(fd, buff, 1024);
::write(fd, buff, strlen(buff));
}
void OnAcceptHandle(int cfd)
{
auto event = make_shared<FileEvent>(&loop_, cfd);
event->RegisterReadHandler(std::bind(&Server::OnReading, this, placeholders::_1));
event->EnableReading();
}
void Start()
{
loop_.Loop();
}
private:
SocketAddr addr_;
EventLoop loop_;
Acceptor acceptor_;
};
int main()
{
SocketAddr addr("127.0.0.1:8888");
Server jedis(addr);
jedis.Start();
}
不难发现,如果我们再将这个Server类写成提供回调函数的方式的话,那么,我们就相当于直接可以使用Server类,然后注册几个回调函数,就能完成的我们的服务器设置了。
github
上述代码主要存放在Reactor_2目录下