ゼロからRedis-2を書く

47575 ワード

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つの軽量なレベルの日志のフレームワークを选びました.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_;
    };
    
    
  • から分かるように、FileEventはHandlerすなわちコールバック関数を登録するためのインタフェースのみを露出している.現在、コールバック関数の形式はあまり完全に考慮されていないため、void(int)というタイプしか使用されていない.
  • の2つのプライベート関数は、主にFileEventの友元クラスEventLoopに提供され、イベントが準備されたときにコールバック関数を実行するために使用されます.
  • maskとrmaskは実際にlibeventの使用を学習しています.maskはFileEventが注目するイベントを表し、rmaskはepollから戻るときに準備されたイベントを表します.
  • FiredEventは、準備されたイベントを表します.

  • 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_);
          }
        }
      }
    }
    
    
  • EventLoopにとって,実際には2つのインタフェースに注目する.一つはPollerにイベントを登録する登録関数で、一つはLoop関数で、実際にはepoll_を呼び出すことです.wait.
  • fileEvents_実はハッシュテーブルです.そのインデックスはfdに対応するので、実際にはのhashmapに等しい.しかしここではvector実装を採用した.
  • firedEventsは準備完了イベントのリストです.
  • beforeWaitは実際にコールバック関数を登録できる形式に書くつもりで、epoll_waitの前にいくつかのinit化関数を追加して、もちろんもっと良いのはTaskBufferの形式を作って、muduoに似ています.
  • Loopを表示すると、Pollは準備完了のイベントリストを返し、コールバック関数が順次実行されることがわかります.

  • 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();
    }
    
    

    プロセスの使用
  • 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ソースコード実装
    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ディレクトリの下