サーバーの同時IOパフォーマンスを改善する方法-ネットワークプログラミングの基本からepollまで

ネットワークプログラミングの基本概念から言えば

      HTTPプロトコルを使用して、さまざまな形式でデータを送信することがよくあります。実際、アプリケーション層プロトコルの最下層であるHTTPは、トランスポート層のTCPプロトコルに基づいて実装されています。TCPプロトコルは、これらのデータを一連の無意味なデータストリームとしてのみ扱います。つまり、クライアントとサーバーは、確立された接続でバイトストリームを送信することによって通信します。
      このC / Sアーキテクチャの通信メカニズムは、通信する当事者のネットワークアドレスとポート番号の情報を識別する必要があります。クライアントの場合、データの受信者の場所を知る必要があります。ネットワークアドレスとポートを使用してサーバーエンティティを一意に識別します。サーバーの場合、データの送信元を知る必要があります。ネットワークも使用します。アドレスとポート。クライアントエンティティを一意に識別します。次に、通信の両端を一意に識別するために使用されるデータ構造は、ソケットと呼ばれます接続は、両端のソケットアドレスによって一意に決定できます。

(客户端地址:客户端端口号,服务端地址:服务端端口号)

両者のアドレス情報を通信させた後、データ送信を行うことができます。したがって、通信当事者の接続およびデータ送信プロセスを指定するための仕様が必要になります。Unixシステムでは、ソケットインターフェイスのセットが実装され、2者間の通信プロセス全体を記述および標準化します。

socket():ソケット記述子を作成します

connect():クライアントは、接続関数を呼び出してサーバーとの接続を確立します

bind():socket()によって作成されたソケットをサーバーのアドレスとポートに接続するようにカーネルに指示すると、このアドレスとポートは後で監視されます

listen():このソケットをサーバーのようなパッシブエンティティとして扱うようにカーネルに指示します(サーバーはクライアントの接続を待機しているパッシブエンティティであり、カーネルは

socket()によって作成されたソケットはデフォルトでアクティブエンティティであるため、カーネルにアクティブエンティティからパッシブエンティティに変換するように指示するには、listen()関数が必要です)

accept():クライアントの接続要求を待ち、新しい接続記述子を返します

最も単純な単一プロセスサーバー

      Unixの歴史的な問題により、元のソケットインターフェイスは、アドレスやポートなどのデータをカプセル化するのに簡潔ではありません。これらの詳細に注意を払わず、プロセス全体にのみ焦点を当てるために、分析にはPHPを使用します。 。PHPは、Unixのソケット関連インターフェイスをカプセル化します。すべてのソケット関数のプレフィックスはsocket_であり、Unixのファイル記述子fdの代わりにリソースタイプのソケットハンドルが使用されます。以下の説明では、「ソケット」は、Unixのファイル記述子fdを置き換えるために使用されますPHPに実装されている単純なサーバーの擬似コードは次のとおりです。

<?php

if (($listenSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP))=== false) {
    echo '套接字创建失败';
}
if (socket_bind($listenSocket, '127.0.0.1', 8888) === false) {
    echo '绑定地址与端口失败';
}
if (socket_listen($listenSocket) === false) {
    echo '转换主动套接字到被动套接字失败';
}
while (1) {
    if (($connSocket = socket_accept($listenSocket)) === false) {
        echo '客户端的连接请求还没有到达';
    } else {
        socket_close($listenSocket); //释放监听套接字
        socket_read($connSocket);  //读取客户端数据,阻塞
        socket_write($connSocket); //给客户端返回数据,阻塞
        
    }
    socket_close($connSocket);
}

この単純なサーバー作成プロセスを整理しましょう

    socket_create():ソケットを作成します。このソケットは、確立された接続のエンドポイントを表します。最初のパラメーターAF_INETは、使用される基礎となるプロトコルがIPv4であることを示します。2番目のパラメーターSOCK_STREAMは、バイトストリームがデータ送信に使用されることを示します。3番目のパラメーターSQL_TCPは、このレイヤープロトコルがTCPプロトコルであることを示します。ここで作成されたソケットは、接続されたエンドポイントの単なる抽象的な概念です。

    socket_bind():このソケットを特定のサーバーアドレスとポートにバインドして、このソケットを実際にインスタンス化します。パラメータは、以前に作成した抽象ソケット、および特定のネットワークアドレスとポートです。

    socket_listen():以前に作成されたソケットは1つの関数パラメーターのみであることがわかります。一部の学生は、関数呼び出しのこのステップは完全に不要であると考えるかもしれません。しかし、それは私がサーバーであり、ソケットをパッシブエンティティに変換することは実際に大きな効果があることをカーネルに伝えます。

    socket_accept():クライアントからリクエストを受信します。サーバーの起動後は、クライアントがいつ接続するかがわからないためです。したがって、この関数はwhileループで継続的に呼び出す必要があります。接続要求が来ると、新しいソケットが返されます。この新しいソケットを介してクライアントと通信できます。そうでない場合は、次のようになるまでループを続けることができます。リクエストが来ます。

ここでは、ソケットを2つのカテゴリに分けていることに注意してください。1つはリスニングソケットで、もう1つは接続ソケットです。ここでの2つのソケットの違いは、次の説明で使用されることに注意してください。

リスニングソケット:サーバーはポートをリッスンし、このソケットはこのポートを表すために使用されます($ listenSocket)

接続ソケット:サーバーとクライアントは接続を確立しており、すべての読み取りおよび書き込み操作は接続ソケット($ connSocket)で実行する必要があります。

では、このサーバーを分析すると、何が問題になるのでしょうか。

    このようなサーバープロセスの1つは、1つのクライアント接続と関連する読み取りおよび書き込み操作のみを同時に処理できます。クライアント接続要求が来ると、リスニングソケットを受け入れた後、クライアントとのデータ送信プロセスを開始します。データの読み取りと書き込みのプロセスでは、プロセス全体がクライアント接続によって排他的に占有されます。現在のサーバープロセスは、クライアント接続の読み取りと書き込み操作のみを処理でき、他のクライアントの接続要求を処理できません。

IO同時実行パフォーマンスを改善する方法

上記のサーバーのパフォーマンスは、複数のクライアント接続と読み取りおよび書き込み操作を同時に処理するには悪すぎるため、優れた開発者は、サーバーの効率を向上させるために次のソリューションを考え出しました。

シングルプロセスIO多重化に基づくマルチプロセスマルチスレッド(select / poll / epoll)

マルチプログレス

では、単一のプロセスを最適化する方法は?非常に簡単です。1つのプロセスが機能しない場合、プロセスが多数ある場合、複数のクライアント接続を同時に処理できますか?私たちはそれについて考え、コードを書きました:

<?php

if (($listenSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP))=== false) {
    echo '套接字创建失败';
}
if (socket_bind($listenSocket, '127.0.0.1', 8888) === false) {
    echo '绑定地址与端口失败';
}
if (socket_listen($listenSocket) === false) {
    echo '转换主动套接字到被动套接字失败';
}
for ($i = 0; $i < 10; $i++) { //初始创建10个子进程
    if (pcntl_fork() == 0) {
        if (($connSocket = socket_accept($listenSocket)) === false) {
            echo '客户端的连接请求还没有到达';
        } else {
            socket_close($listenSocket); //释放监听套接字
            socket_read($connSocket);  //读取客户端数据
            socket_write($connSocket); //给客户端返回数据
        }
        socket_close($connSocket);
    }
}

      私たちは主にこのforループに関心があり、合計10サイクルは、10に設定した子プロセスの初期数を表します。次に、pcntl_fork()を呼び出して子プロセスを作成しました。クライアント側の接続はサーバー側の受け入れに対応するためです。したがって、各フォークの後の10個の子プロセスでは、すべてacceptシステムコールを実行し、クライアントが接続するのを待ちます。このようにして、10台のサーバープロセスが10台のクライアントからの接続を同時に受け入れ、10台のクライアントに同時に読み取りおよび書き込みデータサービスを提供できます。
      細心の注意を払ってください。子プロセスはすべて事前に作成されているため、リクエストが来たときに子プロセスを作成する必要がなく、各接続リクエストの処理効率も向上します。同時に、プロセスプールの概念も使用できます。これらの子プロセスは、接続要求の処理後すぐにリサイクルされません。fork()システムコールを繰り返さなくても、次のクライアント接続要求を引き続き処理できます。また、改善することもできます。サーバーのパフォーマンス。パフォーマンス。これらのヒントは、PHP-FPMの実装に反映されています。実際、このプロセス作成方法は、静的(静的プロセス番号)モードと呼ばれる3つの動作モードの1つです。

    オンデマンド:オンデマンドで開始します。PHP-FPMは、クライアント接続要求が到着したときにのみ、開始時に子プロセス(ワーカープロセス)を開始しません。

    動的:PHP-FPMが開始されると、一部のサブプロセスが最初に開始され、実行中のプロセス中にワーカーの数が動的に調整されます。

    static:PHP-FPMが開始されると、固定数の子プロセスが開始され、操作中に拡張されません。

トピックに戻ると、マルチプロセスアプローチは、サーバーが同時に1つのクライアント接続要求しか処理できないという問題を解決しますが、このマルチプロセスベースのクライアント接続処理モードには、次の欠点があります。

    fork()などのシステムコールは、プロセスのコンテキストを切り替えますが、これは非常に非効率的です。

    作成されるプロセスの数は、接続要求が増えるにつれて増加します。たとえば、100,000のリクエストが行われた場合、100,000のプロセスをフォークする必要があり、コストがかかりすぎます。

    プロセス間のアドレス空間はプライベートで独立しているため、プロセス間でデータを共有することは困難です。

複数のプロセスのデータ共有とスイッチングのオーバーヘッドについて説明したので、この問題を解決する方法をすぐに考えることができます。それは、複数のプロセスをより軽量なマルチスレッドに変えることです。

Linuxバックエンドサーバーの開発に従事したい人やインターネット業界に切り替えたい人は、長い時間をかけて、不完全なテクノロジースタック、構造化されていないアーキテクチャ、不十分な自己規律を身に付けて自分自身を向上させてください。ここで体系的な学習ビデオを共有するには、リンクをクリックして購読し、無料で聴いてくださいhttps//ke.qq.com/course/417774?flowToken = 1013189

基本から高度なバックエンドアーキテクチャ、完全なシステムコース、原則から実践的な説明まで、お勧めする価値があります。理解に興味のある友人はぜひご覧ください。

マルチスレッド

     スレッドは、プロセスのコンテキストで実行される論理フローです。プロセスには複数のスレッドを含めることができ、複数のスレッドが単一のプロセスコンテキストで実行されるため、このプロセスのアドレススペースのすべてのコンテンツを共有することで、プロセス間の通信の問題を解決できます。同時に、スレッドのコンテキストはプロセスのコンテキストよりもはるかに小さいため、スレッドのコンテキスト切り替えは、プロセスのコンテキスト切り替えよりもはるかに効率的です。スレッドは軽量プロセスであり、プロセスコンテキスト切り替えの効率が低いという問題を解決します。
     PHPにはマルチスレッドの概念がないため、上記の擬似コードでプロセスを作成する部分を変更してスレッドを作成するだけです。コードはほぼ同じなので、ここでは繰り返しません。

IO多重化

先ほどお話ししたのは、プロセスとスレッドの数を増やすことで、複数のソケットを同時に処理することです。また、IO多重化では、複数のソケットを処理するために1つのプロセスのみが必要です。IO多重化という用語は、非常に複雑で深遠なようです。実際、このテクノロジーがもたらす本質的な結果は、サーバープロセスが複数のソケット記述子を同時に処理できるということです。

多方向:複数のクライアント接続(接続はソケット記述子です)

再利用:単一のプロセスを使用して、複数のクライアント接続を同時に処理します

前の説明では、サーバープロセスは同時に1つの接続しか処理できません。複数のクライアント接続を同時に処理する場合は、マルチプロセスまたはマルチスレッドの助けが必要であり、コンテキスト切り替えのオーバーヘッドは避けられません。

IO多重化テクノロジーは、コンテキストスイッチングの問題を解決しますIO多重化技術の開発は、select-> poll-> epollの3つの段階に分けることができます。

IO多重化の中核は、複数のソケットを同時に監視できるソケットセットマネージャーを追加することです。クライアント接続のランダム性と読み取りおよび書き込みイベントの到着により、この管理者は、単一のプロセス内で複数のソケットのイベントを合理的にスケジュールする必要があります。

選択する

      最も初期のソケットセットマネージャはselect()システムコールであり、複数のソケットを同時に管理できます。select()関数は、特定の1つまたは複数のソケットのステータスが読み取り不能から読み取り可能、または書き込み不可から書き込み可能に変更されたときに、サーバーのメインプロセスに通知します。したがって、select()自体の呼び出しはブロックされています。ただし、どのソケットまたはどのソケットが読み取り可能または書き込み可能になるかはわからないため、select()によって返されたすべてのソケットをトラバースして、処理できるソケットを判別する必要があります。そして、これらのソケットは、リスニングソケットと接続ソケット(上記)に分けることができます。PHPが提供するsocket_select()関数を使用できます。select()の関数プロトタイプでは、ソケットはカテゴリに分類されます。読み取り、書き込み、および例外ソケットセットであり、ソケットの読み取り、書き込み、および例外イベントをそれぞれ監視します。

function socket_select (array &$read, array &$write, array &$except, $tv_sec, $tv_usec = 0) {}

     たとえば、クライアントがconnect()を呼び出してサーバーのリスニングソケット($ listenSocket)に接続すると、リスニングソケットのステータスが読み取り不能から読み取り可能に変わります。リスニングソケットは1つしかないため、select()はリスニングソケットでの処理をブロックしています。リスニングソケットはサーバー全体のライフサイクルに存在するため、select()の実装は、リスニングソケットの最適化された管理を反映していません。
     サーバーがaccept()を使用して複数のクライアント接続を受け入れ、複数の接続ソケットを生成する場合、select()の管理機能が反映されます。このとき、select()のリスニングリストにリスニングソケットがあり、多数のクライアントとの接続を確立した後、新しく作成された接続ソケットがあります。このとき、接続されたクライアントのこの束がこの接続ソケットを介してデータを送信し、サーバーが受信するのを待つ可能性があります。5つの接続ソケットがすべて同時にデータを送信していると仮定すると、これらの5つの接続ソケットのステータスはすべて読み取り可能になります。一部のソケットが読み取り可能になったため、select()関数はブロックを解除し、すぐに戻ります。どのソケットまたはどのソケットが読み取り可能または書き込み可能になるかわからないため、select()によって返されたすべてのソケットをトラバースして、書き込み処理を読み取る準備ができているソケットを判別する必要があります。トラバーサルが完了すると、読み取りと書き込みが可能な接続ソケットが5つあることがわかり、複数のソケットの同時管理が実現されます。PHPを使用してselect()を実装するコードは次のとおりです。

<?php
if (($listenSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP))=== false) {
    echo '套接字创建失败';
}
if (socket_bind($listenSocket, '127.0.0.1', 8888) === false) {
    echo '绑定地址与端口失败';
}
if (socket_listen($listenSocket) === false) {
    echo '转换主动套接字到被动套接字失败';
}

/* 要监听的三个sockets数组 */
$read_socks = array(); //读
$write_socks = array(); //写
$except_socks = NULL; //异常

$read_socks[] = $listenSocket; //将初始的监听套接字加入到select的读事件监听数组中

while (1) {
    /* 由于select()是引用传递,所以这两个数组会被改变,所以用两个临时变量 */
    $tmp_reads = $read_socks;
    $tmp_writes = $write_socks;
    $count = socket_select($tmp_reads, $tmp_writes, $except_socks, NULL);
    foreach ($tmp_reads as $read) { //不知道哪些套接字有变化,需要对全体套接字进行遍历来看谁变了
        if ($read == $listenSocket) { //监听套接字有变化,说明有新的客户端连接请求到来
            $connSocket = socket_accept($listenSocket);  //响应客户端连接, 此时一定不会阻塞
            if ($connSocket) {
                //把新建立的连接socket加入监听
                $read_socks[] = $connSocket;
                $write_socks[] = $connSocket;
            }
        } else { //新创建的连接套接字有变化
            /*客户端传输数据 */
            $data = socket_read($read, 1024);  //从客户端读取数据, 此时一定会读到数据,不会产生阻塞
            if ($data === '') { //已经无法从连接套接字中读到数据,需要移除对该socket的监听
                foreach ($read_socks as $key => $val) {
                    if ($val == $read) unset($read_socks[$key]); //移除失效的套接字
                }
                foreach ($write_socks as $key => $val) {
                    if ($val == $read) unset($write_socks[$key]);
                }
                socket_close($read);
            } else { //能够从连接套接字读到数据。此时$read是连接套接字
                if (in_array($read, $tmp_writes)) {
                    socket_write($read, $data);//如果该客户端可写 把数据写回到客户端
                }
            }
        }
    }
}
socket_close($listenSocket);

ただし、select()関数自体の呼び出しはブロックされます。select()は、select()自体のブロックを解除し、読み取りと書き込みを続行するために、状態が変化するソケット(たとえば、リスニングソケットまたは接続ソケットの状態が読み取り不能から読み取り可能に変化する)まで待機する必要があるためです。ソケットが処理されます。ここではブロックしていますが、前の単一プロセスで1つのソケットだけではなく、同時に複数の準備完了ソケットを返すことができるため、効率が大幅に向上します。
要約すると、select()の利点は次のとおりです。

複数のソケットの同時集中管理を実現

すべてのソケットセットをトラバースすることにより、すべての準備ができたソケットを取得でき、これらの準備ができたソケットでの操作はブロックされません

ただし、select()にはまだいくつかの問題があります。

    selectが管理するソケット記述子の数には制限があります。Unixでは、プロセスは最大1024個のソケット記述子を同時に監視できます。

    selectが戻ったとき、どのソケット記述子がすでに準備ができているかわからないので、すべてのソケットをトラバースしてどれが準備ができているかを判断する必要があり、読み取りと書き込みを続行できます。

最初のソケット記述子数制限の問題を解決するために、賢い開発者は、古いマネージャーselectを置き換えるpollと呼ばれる新しいソケット記述子マネージャーを思い付きました。select()は安心してリタイアできます。

投票

ポーリングは、selectによって引き起こされるソケット記述子の最大数の問題を解決しますPHPのソケット拡張には、対応するポーリング実装がないため、UnixC言語のプロトタイプ実装を次に示します。

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

      ポーリングのfdsパラメーターは、selectの読み取り、書き込み、および例外ソケット配列のコレクションであり、1つにまとめられています。投票には1024fdsの制限はありません。一部の記述子ステータスが変更されて準備ができると、pollはselectのように戻ります。ただし、残念ながら、どのソケットの準備ができているかもわかりません。どのソケットの準備ができているかを判断するには、ソケットセットをトラバースする必要があります。これでは、上記の選択の問題は解決されません。2つの質問。selectとpollの2つの実装は両方とも、を返し後に準備ができたソケット記述子を取得するために、すべてのソケット記述子をトラバースする必要
     があると要約できます実際、同時に接続されている多数のクライアントは、一度に準備完了状態にあるクライアントが少ない場合があるため、監視対象記述子の数が増えると、その効率は直線的に低下します。
どの記述子が戻った後に準備ができているかわからないという問題を解決し、すべてのソケット記述子をトラバースすることを回避するために、賢い開発者は、選択とポーリングの問題の存在を完全に解決するepollメカニズムを発明しました。

epoll

Epollは、上記の選択とポーリングに存在する問題を解決する最先端のソケットの管理者です。これは、ブロッキング選択およびポーリングシステムコールを3つのステップに分割します。選択またはポーリングは、1つのepoll_create、複数のepoll_ctl、および複数のepoll_waitで構成されていると見なすことができます。

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

    epoll_create():epollインスタンスを作成します。後続の操作では、

    epoll_ctl():ソケット記述子のセットを追加、削除、変更し、ソケット記述子を監視するために必要なイベントをカーネルに通知します

    epoll_wait():リスニングリスト内の接続イベント(リッスンソケット記述子が発生した場合のみ)または読み取り/書き込みイベント(接続ソケット記述子が発生した場合のみ)を待機します。1つまたはいくつかのソケットイベントの準備ができている場合、これらの準備ができたソケットが返されます

これらの3つの機能は、selectとpollの1つの機能から3つの機能に明確に分離されているようです。ソケット記述子の追加、削除、および変更の操作は、以前のコード実装からepoll_ctl()の呼び出しに変更されました。epoll_ctl()のパラメーターの意味は次のとおりです。

epfd:epoll_create()の戻り値

op:次のソケット記述子fdで実行される操作を表します。EPOLL_CTL_ADD:リスニングリストに記述子を追加します; EPOLL_CTL_DEL:記述子を監視しなくなりました; EPOLL_CTL_MOD:記述子を変更します

fd:上記のop操作のソケット記述子オブジェクト(以前のPHPでは、listenSocketとlistenSocketおよびconnSocketの2つのソケット記述子でした)。たとえば、ソケットをリッスンリストに追加します。

イベント:ソケット記述子を監視するために必要なイベント(読み取りと書き込み、接続など)をカーネルに通知します

     最後に、epoll_wait()を呼び出して、接続や読み取りと書き込みなどのイベントを待機し、ソケット記述子の準備をします。イベントの準備ができると、2番目のパラメーターepoll_event構造に格納されます。この構造にアクセスすることで、イベントを準備したすべてのソケット記述子を取得できます。以前にselectやpollなどのすべてのソケット記述子をトラバースした後、どの記述子の準備ができているかを知る必要はありません。これにより、O(n)トラバースが減少し、効率が大幅に向上します。
     最後に返されるすべてのソケット記述子の中には、前述の2種類の記述子もあります。リスニングソケット記述子と接続ソケット記述子です。次に、準備ができているすべての記述子をトラバースし、ソケット記述子を監視するか接続するかを決定し、必要に応じて受け入れ(ソケットの監視)または読み取り(ソケットの接続)処理を行う必要があります。C言語で記述されたepollサーバーの擬似コードは次のとおりです(コードコメントに焦点を当てます)。

int main(int argc, char *argv[]) {

    listenSocket = socket(AF_INET, SOCK_STREAM, 0); //同上,创建一个监听套接字描述符
    
    bind(listenSocket)  //同上,绑定地址与端口
    
    listen(listenSocket) //同上,由默认的主动套接字转换为服务器适用的被动套接字
    
    epfd = epoll_create(EPOLL_SIZE); //创建一个epoll实例
    
    ep_events = (epoll_event*)malloc(sizeof(epoll_event) * EPOLL_SIZE); //创建一个epoll_event结构存储套接字集合
    event.events = EPOLLIN;
    event.data.fd = listenSocket;
    
    epoll_ctl(epfd, EPOLL_CTL_ADD, listenSocket, &event); //将监听套接字加入到监听列表中
    
    while (1) {
    
        event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); //等待返回已经就绪的套接字描述符们
        
        for (int i = 0; i < event_cnt; ++i) { //遍历所有就绪的套接字描述符
            if (ep_events[i].data.fd == listenSocket) { //如果是监听套接字描述符就绪了,说明有一个新客户端连接到来
            
                connSocket = accept(listenSocket); //调用accept()建立连接
                
                event.events = EPOLLIN;
                event.data.fd = connSocket;
                
                epoll_ctl(epfd, EPOLL_CTL_ADD, connSocket, &event); //添加对新建立的连接套接字描述符的监听,以监听后续在连接描述符上的读写事件
                
            } else { //如果是连接套接字描述符事件就绪,则可以进行读写
            
                strlen = read(ep_events[i].data.fd, buf, BUF_SIZE); //从连接套接字描述符中读取数据, 此时一定会读到数据,不会产生阻塞
                if (strlen == 0) { //已经无法从连接套接字中读到数据,需要移除对该socket的监听
                
                    epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL); //删除对这个描述符的监听
                    
                    close(ep_events[i].data.fd);
                } else {
                    write(ep_events[i].data.fd, buf, str_len); //如果该客户端可写 把数据写回到客户端
                }
            }
        }
    }
    close(listenSocket);
    close(epfd);
    return 0;
}

epollを介してIO多重化サーバーを実装するコード構造を見てみましょう。1つの関数が3つの関数に分割されることを除いて、残りの実行プロセスは基本的にselectとpollに似ています。epollは、すべての記述子のセットではなく、準備ができているソケット記述子のセットのみを返すだけです。IOの効率は、監視fdの数が増えても低下しないため、効率が大幅に向上します。同時に、各ソケット記述子の管理(追加、削除、変更のプロセスなど)を改良および標準化します。さらに、監視するソケット記述子に制限はありません。このようにして、選択とポーリングの前に残っているすべての問題が解決されます。

総括する

最も基本的なネットワークプログラミングから始め、最も単純な同期ブロッキングサーバーからIO多重化サーバーまで、サーバーのパフォーマンス向上の考え方と実装プロセスを最初から最後まで学びました。サーバーの同時パフォーマンスを向上させる方法はこれらをはるかに超えており、コルーチンなどの新しい概念では、比較と分析が必要です。

 

おすすめ

転載: blog.csdn.net/Linuxhus/article/details/111995310