epollからmuduo-9を構築しonWriteComplateコールバックとBufferを追加


mini-muduoバージョン転送ゲートversion 0.00 epollからmuduo-1 mini-muduoを構築version 0.01 epollからmuduo-2を構築する最も簡単なepoll version 0.02 epollからmuduo-3を構築する最初のクラスに追加します.ちなみにreactor version 0.03 epoll構築muduo-4加入Channel version 0.04 epoll構築muduo-5加入AcceptorとTcpConnection version 0.05 epoll構築muduo-6加入EventLoopとEpoll version 0.06 epoll構築muduo-7加入IMuduoUser version 0.07 epoll構築muduo-8加入送信バッファと受信バッファversion 0.08 epoll構築muduo-9加入onWriteComputeコールバックとBufferversion 0.09 epoll構築muduo-10 Timerタイマversion 0.11 epoll構築muduo-11単一スレッドReactorネットワークモデル成形version 0.12 epoll構築muduo-12マルチスレッドコード入場version 0.13 epoll構築muduo-13 Reactor+ThreadPool成形
mini-muduo v0.08バージョンでは、このバージョンでバッファの実装が完了しました.mini-muduoの完全な実行可能な例はgithubからダウンロードすることができ、コマンドgit checkout v 0を使用することができる.08このバージョンに切り替えることができて、オンラインでこのバージョンをブラウズしてここに着きます
このリリースでは、2つの重要な変更点があります.まず、IMuduoUserのonWriteComplateコールバックを実現します.これにより、ユーザーが一度に大量のデータをネットワークライブラリに転送すると、ユーザーはデータ送信が完了した後に通知を受け取ることができます.もちろん、少量のデータ送信が完了した場合にも通知されます.次に、このバージョンではバッファを表すBufferクラスを実装しましたが、このBufferクラスの詳細な実装は非常に簡単です.
以下のエントリ1~5はonWireteComplateの実装の詳細を説明し、エントリ6はBufferを説明する.
1まず、IMuduoUserのonWriteComplateコールバックを見てみましょう.まず、onWriteComplateコールバックがいつ呼び出されるかを明確にします.それは、ユーザーがデータをネットワークライブラリに渡し、ネットワークライブラリがこれらの情報をすべてオペレーティングシステムに転送した後(通過::write()コール)、ユーザーオブジェクトのonWriteComplateがコールバックされます.muduoはlevel trigerのEpollを使用しているので、次の2つの場所でonWriteComplateを呼び出すべきです.この2つは直接通過しないことに注意してください.pUser->onWriteComplate(this);を使用して呼び出されますが、EventLoop::queueLoopメソッドを使用して非同期に呼び出されます.次の説明ではqueueLoopメソッドについて詳しく説明します.元のmuduoでこのコールバックについての説明は<>P 322ページを参照してください.
位置1:size_を最初に呼び出すt n =::write(...len...) その後,nとlenが等しい場合,一度のシステム呼び出しでデータがすべて送信されたことを示し,ユーザに通知する必要がある.呼び出しポイントはTcpConnectionにあります.cc 91行
 82 void TcpConnection::send(const string& message)
 83 {
 84     int n = 0;
 85     if(_outBuf.readableBytes() == 0)
 86     {
 87         n = ::write(_sockfd, message.c_str(), message.size());
 88         if(n < 0)
 89             cout << "write error" << endl;
 90         if(n == static_cast<int>(message.size()))
 91             _pLoop->queueLoop(this); //invoke onWriteComplate
 92     }
 93 
 94     if( n < static_cast<int>(message.size()))
 95     {
 96         _outBuf.append(message.substr(n, message.size()));
 97         if(_pChannel->isWriting())
 98         {
 99             _pChannel->enableWriting(); //add EPOLLOUT
100         }
101     }
102 }

位置2:size_が呼び出されたときt n =::write(...len...) その後、n 63 void TcpConnection::handleWrite() 64 { 65 int sockfd = _pChannel->getSockfd(); 66 if(_pChannel->isWriting()) 67 { 68 int n = ::write(sockfd, _outBuf.peek(), _outBuf.readableBytes()); 69 if( n > 0) 70 { 71 cout << "write " << n << " bytes data again" << endl; 72 _outBuf.retrieve(n); 73 if(_outBuf.readableBytes() == 0) 74 { 75 _pChannel->disableWriting(); //remove EPOLLOUT 76 _pLoop->queueLoop(this); //invoke onWriteComplate 77 } 78 } 79 } 80 } は、この2つの場所にコールバックを設定すると、ユーザーが正確な通知を受けることを保証することができる.
2 EventLoop::queueLoop()メソッド、このバージョンで新しく追加されたメソッドは、非常に重要なメソッドです.この方法がない前にepollを使いましたwait()が受信したすべてのイベントは、EPOLLIN/EPOLLOUTなどのオペレーティングシステムから来ており、epollfdを使用してオペレーティングシステムのネットワーク通知を受信するだけです.今、epollfdにもっと仕事をさせて、ネットワークライブラリが自分で送った通知を受信できるようにする必要があります.この通知には2つの重要な価値がある.
価値1:ネットワーク・ライブラリ内で単一スレッドの場合にイベントを非同期で処理する能力を備えます.
価値2:ネットワークライブラリのIOスレッド(epoll_waitを走るそのスレッド)が、本スレッド以外の送信要求を受信できるようにする.
この通知はeventfdメカニズムによって実現され、eventfdはLinux 2.6.22によって新たに導入され、socketのようにepollfdに傍受され、eventfdに何かを書くと、epollはこの通知を取得して戻ることができる.EventLoopはeventfdをカプセル化することによって非同期処理能力を得た.
EventLoop::wakeup()で呼び出しました::wirte()
 53 void EventLoop::wakeup()
 54 {
 55     uint64_t one = 1;
 56     ssize_t n = ::write(_eventfd, &one, sizeof one);
 57     if (n != sizeof one)
 58     {
 59         cout << "EventLoop::wakeup() writes " << n << " bytes instead of 8" << endl;
 60     }
 61 }
EventLoop::handleRead()で呼び出されました::read()
 73 void EventLoop::handleRead()
 74 {
 75     uint64_t one = 1;
 76     ssize_t n = ::read(_eventfd, &one, sizeof one);
 77     if (n != sizeof one)
 78     {
 79         cout << "EventEventLoop::handleRead() reads " << n << " bytes instead of 8" << endl;
 80     }
 81 }
本当のイベント処理プロセスはEventLoop::loop()メソッドにあり、新しく追加されたEventLoop::doPendingFunctors()メソッドは非同期呼び出しをトリガーするために使用されます.
EventLoop::queueLoop()メソッドの実装を再整理します.このメソッドは、コールバックを表すIrun*をEventLoopのvectorに保存してからeventfdのイベントをトリガーします.今回のループが完了すると、次のEventLoop::loopがepollにループされます.waitの場合、eventfdのトリガによって返されますが、eventfd対応のチャネルが通知され、EventLoop::handleReadメソッドに通知されます.イベントを1回だけトリガするようにします.EventLoop::loopループはdoPendingFunctors()メソッドに呼び出し続け、Irun*を保存するvectorを巡り、すべての非同期イベントが処理されます.
EventLoop::loop()メソッド
 25 void EventLoop::loop()
 26 {
 27     while(!_quit)
 28     {
 29         vector<Channel*> channels;
 30         _pPoller->poll(&channels);
 31 
 32         vector<Channel*>::iterator it;
 33         for(it = channels.begin(); it != channels.end(); ++it)
 34         {
 35             (*it)->handleEvent();
 36         }
 37 
 38         doPendingFunctors();
 39     }
 40 }

EventLoop::queueLoop()メソッド
 47 void EventLoop::queueLoop(IRun* pRun)
 48 {
 49     _pendingFunctors.push_back(pRun);
 50     wakeup();
 51 }

3 doPendingFunctorsメソッドの実装に注意してください.ここでは単純にvectorを巡回してコールバックを呼び出すのではなく、新しいvectorを作成し、vector::swapメソッドを呼び出して配列を交換してから呼び出します.これは、「臨界領域の長さを小さくし、デッドロックを回避する」ことを目的としています.<>P 295ページで詳細に説明されています.もちろんmini-muduoは現在も単一スレッドであり、影響は大きくありません.
4 EventLoop::queueLoop()でonWriteComplateを非同期でトリガするのは、TcpConnectionでonWriteComplateを直接トリガするのではなく、コールバックネストを防止するためだと思います.onMessageでTcpConnection::send()メソッドを呼び出し、onWriteComplateがsendで直接呼び出されると、onMessageネストでonWriteComplateが呼び出され、このようなイベントのシーケンス性が破られるためです.多くの問題が導入されます.
5現在、プログラムは1つのプロセスの唯一のスレッドに走っているため、muduoのすべてのスレッド関連コードはまだ加入していません.後のバージョンのマルチスレッドが加入すると、いくつかの重要なデータはmutexによって保護されます.
6 const string&とstring*をすべてBufferに変えて、muduoと一致を維持して、Bufferの中でいくつかの基本的な方法しか実現していません.例えばappend()はconst string&バージョンだけを実現して(const char*data,int len)バージョンがなくて、Bufferはstd::stringを自分の記憶媒体として使用して、方法の実現も比較的に粗くて、効率が悪くて、メリットは簡単で分かりやすくて原始muduoと同じインターフェースがあることです.muduoの中のBufferの設計の作者はいくつか工夫を凝らして、スタックとスタックの結合の方法を使って、本の中で7.4節はすでに詳しく紹介しました.Buffer::retrieveメソッドの役割は、バッファ内の前のnバイトを破棄することです.mini-muduoはカスタムstringクラスではなくstd::stringを直接使用しています.
7 muduoの他のバッファに関するコールバックはmini-muduoでは実現していませんが、個人的には基礎フレームワークの理解に影響しないと思います.たとえば、HighWaterMarkCallbackの場合、出力バッファの長さがユーザーが設定したサイズを超えるとトリガーされます.このコールバックの実現に興味のある学生はmuduoコードを参照することができる.