ゼロからRedis-2を書く
ReactorとLog
文書ディレクトリ ReactorとLog 1. Logger 1.1 EasyLogging++ 1.2 muduo::Logging
2. Reactor 2.1私の記事は を参照してください. 2.2簡単な説明 2.3第1版 2.3.1 Event 2.3.2 EventLoop 2.3.3 Poller 2.3.4例 2.4第2版 3. Acceptor 3.1ソースコード実現 3.1 echoサーバテスト 4. 結語
1. Logger
1.1 EasyLogging++
正式に始まる前に、私达は先に日志の问题を考虑して、前にmuduoを学ぶのは1つのLogを书いたことがあって、しかしそのLogはただ使うことを学ぶためで、だから比较的に粗末で、私のここはまた多すぎる时间を费やしてLogの上で、だから1つの軽量なレベルの日志のフレームワークを选びました.
使用方法
そのため、ログについては、すぐにスキップします.
1.2 muduo::Logging
2つ目の使用オプションはmuduoのLoggingで、持ってきて直したほうが使いやすいと思います.
2. Reactor
2.1私の記事の参考
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
2.2簡単な説明
私から見れば、Reactorの利点は主にIO多重化、すなわちepoll、selectまたはpoll、すなわち半同期半非同期方式を利用することである.
実際には、ブロック呼び出しを使用して接続を待つ必要はありません.あるいは、クライアントのデータが来るのを待つ必要はありません.代わりにIO多重化されて通知される.
最初のバージョンのReactorは、読みやすいようにRedisを直接真似して作ったものに相当します.
2番目のバージョンのReactorは、より使いやすいようにしています.手法はmuduoに似ている.
2.3最初のバージョン
2.3.1 Eventから分かるように、FileEventはHandlerすなわちコールバック関数を登録するためのインタフェースのみを露出している.現在、コールバック関数の形式はあまり完全に考慮されていないため、 の2つのプライベート関数は、主にFileEventの友元クラスEventLoopに提供され、イベントが準備されたときにコールバック関数を実行するために使用されます. maskとrmaskは実際にlibeventの使用を学習しています.maskはFileEventが注目するイベントを表し、rmaskはepollから戻るときに準備されたイベントを表します. FiredEventは、準備されたイベントを表します.
2.3.2 EventLoop EventLoopにとって,実際には2つのインタフェースに注目する.一つはPollerにイベントを登録する登録関数で、一つはLoop関数で、実際にはepoll_を呼び出すことです.wait. fileEvents_実はハッシュテーブルです.そのインデックスはfdに対応するので、実際には firedEventsは準備完了イベントのリストです. beforeWaitは実際にコールバック関数を登録できる形式に書くつもりで、epoll_waitの前にいくつかのinit化関数を追加して、もちろんもっと良いのはTaskBufferの形式を作って、muduoに似ています. Loopを表示すると、Pollは準備完了のイベントリストを返し、コールバック関数が順次実行されることがわかります.
2.3.3 Poller
Pollerの主な役割はepollをカプセル化することであることがわかる.Poll関数の唯一の変化は、返される準備完了イベントのリストを設定することです.
2.3.4例
プロセスの使用 listenfdを作成し、イベントを作成します. 対応するコールバック関数onAcceptを登録します. そしてEventLoopはこのイベントを登録します.2,3の2つの事柄は一歩で完成することができ,バージョン2はこれに対して最適化されることが分かった. イベントループを開始します. 接続が来ると、onAcceptにコールバックします. は、connfdを取得し、その後、イベントの登録を継続する.
このように使うことで、私はグローバルなEventLoopが必要であることに気づきました.実際には、1つのEventLoopが複数のFileEventを持つことができ、各FileEventは1つのEventLoopにしか属していないことがわかります.したがって、EventLoopのないFileEventは意味がありません.したがってFileEventはEventLoopに依存する.
2.4第2バージョン
https://github.com/patientCat/Jedis
最初のバージョンと2番目のバージョンはそれぞれReactor_に配置されています.0 x中.コードの簡略化を発見するために、自分で比較することができます.
3. Acceptor
あるいはListenSocketですが、よくサーバーを書くと、私たちはいつもsocket-bind-listenをlistenfdにしなければならないことがわかります.実際にはAcceptorを抽象化することができます
サーバーが起動する前に、この手順を完了し、自分のOnAcceptHandler()をEventLoopに登録します.
ここでは少し難しいかもしれませんが、bind/functionを利用して関数を登録することに慣れなければなりません.Acceptorは実際に私たちのEventLoopが起動する鍵です.これは最初のイベントです.後から続く事件は実際にはここから来ている.
muduoソースを見たことがあるので、私はいつも思わずmuduoを真似して、仕方がありません.
3.1ソースコード実装
3.1 echoサーバテスト
今、私たちは既存のものを使ってテストをします.
このサーバクラスをコールバック関数を提供する方法として書くと、サーバクラスを直接使用して、いくつかのコールバック関数を登録して、私たちのサーバ設定を完了することができます.
4.結語
github上記コードは主にReactor_に格納されています2ディレクトリの下
文書ディレクトリ
1. Logger
1.1 EasyLogging++
正式に始まる前に、私达は先に日志の问题を考虑して、前にmuduoを学ぶのは1つのLogを书いたことがあって、しかしそのLogはただ使うことを学ぶためで、だから比较的に粗末で、私のここはまた多すぎる时间を费やしてLogの上で、だから1つの軽量なレベルの日志のフレームワークを选びました.
EasyLogging++
を選択使用方法
そのため、ログについては、すぐにスキップします.
1.2 muduo::Logging
2つ目の使用オプションはmuduoのLoggingで、持ってきて直したほうが使いやすいと思います.
2. Reactor
2.1私の記事の参考
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
2.2簡単な説明
私から見れば、Reactorの利点は主にIO多重化、すなわちepoll、selectまたはpoll、すなわち半同期半非同期方式を利用することである.
実際には、ブロック呼び出しを使用して接続を待つ必要はありません.あるいは、クライアントのデータが来るのを待つ必要はありません.代わりにIO多重化されて通知される.
最初のバージョンのReactorは、読みやすいようにRedisを直接真似して作ったものに相当します.
2番目のバージョンのReactorは、より使いやすいようにしています.手法はmuduoに似ている.
2.3最初のバージョン
2.3.1 Event
#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)
というタイプしか使用されていない.2.3.2 EventLoop
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実装を採用した.2.3.3 Poller
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関数の唯一の変化は、返される準備完了イベントのリストを設定することです.
2.3.4例
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が必要であることに気づきました.実際には、1つのEventLoopが複数のFileEventを持つことができ、各FileEventは1つのEventLoopにしか属していないことがわかります.したがって、EventLoopのないFileEventは意味がありません.したがってFileEventはEventLoopに依存する.
2.4第2バージョン
https://github.com/patientCat/Jedis
最初のバージョンと2番目のバージョンはそれぞれReactor_に配置されています.0 x中.コードの簡略化を発見するために、自分で比較することができます.
3. Acceptor
あるいはListenSocketですが、よくサーバーを書くと、私たちはいつもsocket-bind-listenをlistenfdにしなければならないことがわかります.実際にはAcceptorを抽象化することができます
サーバーが起動する前に、この手順を完了し、自分のOnAcceptHandler()をEventLoopに登録します.
ここでは少し難しいかもしれませんが、bind/functionを利用して関数を登録することに慣れなければなりません.Acceptorは実際に私たちのEventLoopが起動する鍵です.これは最初のイベントです.後から続く事件は実際にはここから来ている.
muduoソースを見たことがあるので、私はいつも思わずmuduoを真似して、仕方がありません.
3.1ソースコード実装
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_;
};
3.1 echoサーバテスト
今、私たちは既存のものを使ってテストをします.
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();
}
このサーバクラスをコールバック関数を提供する方法として書くと、サーバクラスを直接使用して、いくつかのコールバック関数を登録して、私たちのサーバ設定を完了することができます.
4.結語
github上記コードは主にReactor_に格納されています2ディレクトリの下