この記事は、ファーウェイ クラウド コミュニティ「ネットワーク技術の将来の制御: 今日のアプリケーション分野におけるリアクター ネットワーク モデルの革新の探索」(著者: Lion Long) から共有されたものです。
この記事では、Linux ネットワーク設計における Reactor ネットワーク モデルと、実際のアプリケーションにおけるその重要性を紹介します。Reactor モデルは、高性能でスケーラブルな Web サーバーの構築に広く使用されている古典的なイベント駆動型の設計パターンです。Reactor モデルの基礎とコンポーネントを探り、Linux ネットワーク プログラミングで Reactor モデルがどのように実装されるかを詳しく説明します。
1. 原子炉ネットワークモデルプログラミングの概要
Reactor は、IO の検出をイベントの処理に変換する非同期イベント メカニズムです。リアクターは IO 検出に IO 多重化を使用します。IO マルチプレクサーは通常、select、poll、epoll です。
リアクターの一般的なロジック:
(1)socket() はソケット listenfd を作成します。
(2) binding()、listen() は listenfd を構成し、バインドして監視します。
(3) listenfd は、epoll によって管理される読み取りイベントを登録します。
(4) イベントトリガーを読み取り、コールバックを受け入れます。
(5) クライアントは clientfd に接続して読み取りイベントを形成します。
(6) 関連イベントが関連コールバック関数を呼び出す
1.1. 接続を確立する
クライアント接続を受信します。
//... int epfd=epoll_create(1);//epoll オブジェクトを作成 //... int listenfd=socket(AF_INET,SOCK_STREAM,0);//ソケットを作成 //... 構造体 epoll_event ev; ev.events=エポリン; epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev)//イベントを登録する //... // listenfd の read イベントがトリガーされると、accept を呼び出して接続を受信します struct sockaddr_in clientaddr; socklen_t len=sizeof(clientaddr); int clientfd=accept(listenfd,(struct sockaddr *)&clientaddr,&len); 構造体 epoll_event ev; ev.events=エポリン; epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev)//新しい接続の読み取りイベントを登録します //...
サードパーティのサービスに接続します。
//... int epfd=epoll_create(1);//epoll オブジェクトを作成 //... int fd=socket(AF_INET,SOCK_STREAM,0);//ソケットを作成 //... struct sockaddr_in clientaddr; socklen_t len=sizeof(clientaddr); connect(fd,(struct sockaddr *)&clientaddr,&len);//接続サービス //... 構造体 epoll_event ev; ev.events=EPOLLOUT; epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev)//イベントを登録 //... // fd の書き込みイベントがトリガーされると、接続は正常に確立されます if(status==e_connecting && ev.events==EPOLLOUT) { ステータス=e_接続済み; epoll_ctl(epfd,EPOLL_CTL_DEL.fd,NULL); } //...
1.2. 切断
//... if(ev.events & EPOLLRDHUP) { // サーバーリーダーを閉じる 閉じる_読み取り(fd); } if(ev.events & EPOLLHUP) { // サーバーの読み取りと書き込みの終了を閉じます 閉じる(fd); } //...
1.3. データ到着
// ... if(ev.イベント & EPOLLIN) { その間(1) { int n=recv(clientfd,buffer,buffer_size,0); if(n<0) { if(errno==EINTR) 続く; if(errno==EWOULDBLOCK) 壊す; 閉じる(clientfd); } それ以外の場合 (n==0) { close_read(); } それ以外 { // ビジネスを処理します } } // ... } // ...
1.4. データ送信
// ... if(ev.events & EPOLLOUT) { int n=send(clientfd,buffer,buffer_size,0); if(n<0) { if(errno==EINTR) 続く; if(errno==EWOULDBLOACK) { struct epoll_event e; e.events=EPOLLOUT; epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &e)//イベントを登録 戻る; // 壊す; } 閉じる(clientfd); } else if(n==buffer_size) { epoll_ctl(epfd,EPOLL_CTL_DEL,clientfd,NULL); // epoll_ctl(epfd,EPOLL_CTL_MOD,clientfd,&e); } // ... } //...
1.5. リアクターに関するよくある質問
1.エポールショックグループ
「衝撃集団」とは何ですか?ネットワーク プログラミングでは、マルチスレッドおよびマルチプロセス モデルがよく使用されます。各スレッドまたはプロセスには epoll オブジェクトがあります。socket()、bind()、および listen() によって生成された listenfd は、複数の epoll オブジェクトを管理する場合があります。accept が到着すると、すべての epoll に通知され、すべてのプロセスまたはスレッドがこのイベントに同時に応答します。ただし、最終的に成功するのは 1 つの accept だけです。こちらは「ショッキンググループ」です。
2. 水平トリガーとエッジトリガー
水平トリガ:リードバッファにデータがある場合、データが読み込まれるまでトリガします。
エッジトリガー:イベント発生時に1回トリガーします。通常、読み取りおよび書き込み操作は、すべての読み取りおよび書き込みを完了するためにサイクルと連携する必要があります。
3. ノンブロッキング IO でリアクターを使用する必要があるのはなぜですか?
主に次の 3 つの理由があります。
(1) マルチスレッド環境では、listenfd は複数の epoll (IO マルチプレクサ) オブジェクトによって管理されます。接続が到着すると、すべての epoll に通知され、すべての epoll が応答しますが、最終的に成功するのは 1 つの epoll だけです。ブロッキングが使用されている場合、他の epoll は常にブロックされます。したがって、時間内に戻るためにはノンブロッキング IO を使用することをお勧めします。
(2) エッジ トリガーでは、イベント トリガーがイベントを読み取り、イベント ループでバッファーを空に読み取る必要があります。ブロッキング モードが使用されている場合、読み取りバッファー内のデータが読み取られるとき、常にブロックされ、戻ることはできません。
(3) バグを選択します。新しいデータ セグメントがソケットの受信バッファに到着すると、select はソケット記述子が読み取り可能であることを報告しますが、プロトコル スタックは新しいセグメントにチェックサム エラーがあることを検出し、セグメントを破棄します。この時点では、recv/read を呼び出すときに読み取るデータはありません。ソケットがノンブロッキングに設定されていない場合、この recv/read は現在のスレッドをブロックします。
4. IO 多重化はノンブロッキング IO と組み合わせる必要がありますか?
いいえ、ブロッキング モードも使用できます。たとえば、MySQL は select を使用して接続を受信し、次に 1 つのスレッドを使用して接続を処理します。また、システム コールを使用して最初に読み取りバッファ内のバイト数を取得し、その後データを 1 回読み取ることもできますが、これでは効率が比較的低くなります。
int n=EVBUFFER_MAX_READ_DEFAULT; ioctl(fd,FIONREAD,&n);//読み取りバッファ内のデータバイト数を取得します
2 つの原子炉応用シナリオ
単一のリアクターを使用するシナリオと複数のリアクターを使用するシナリオ。複数のリアクターを使用すると、マルチスレッドとマルチプロセッシングのさまざまな用途が生まれます。
2.1、redis - 単一のリアクターを使用する
Redis は、キーと値の構造、豊富なデータ構造、およびメモリ上の操作を備えたネットワーク データベース コンポーネントです。Redis コマンドの処理はシングルスレッドです。
2.1.1. Redis が単一のリアクターを使用するのはなぜですか?
redis が単一のリアクターのみを使用する理由を理解するには、redis コマンドの処理がシングルスレッドであることを理解する必要があります。
Redis は豊富なデータ構造を提供し、これらのデータ構造のロックは非常に複雑であるため、Redis は処理に単一のスレッドを使用します。コマンドの処理に単一のスレッドが使用され、コアのビジネス ロジックが単一のスレッドであるため、処理にリアクターをいくら使用しても限界があるため、Redis は単一のリアクターを使用します。
さらに、特定のコマンドを実行する Redis の時間計算量は比較的低く、複数のリアクターを使用する必要はありません。
2.1.2、redis 処理リアクターのブロック図
2.1.3、リアクターのredis最適化
ビジネス ロジックを最適化し、IO スレッドを導入しました。
データを受信した後は、データを IO スレッドにスローして処理し、データを送信する前に、パッケージ化されたデータを IO スレッドに入れて処理し、送信します。上図でいうと、(read+decode)を処理用スレッドに置き、(encode+write)を処理用スレッドに入れることになります。
理由:
シングルスレッドの場合、送受信するデータが大きすぎるとスレッドの負荷が大きくなり、IOデータ処理には複数のスレッドを使用する必要があります。特にプロトコル解決プロセスでは、データが膨大で時間がかかり、処理のために IO スレッドを開く必要があります。
シナリオの例:
クライアントはログ レコードをアップロードし、リーダーボード レコードを取得します。
2.1.4. Redis ソースコードをリアクターの観点から見る
epoll オブジェクトを作成します。
ソケットを作成し、リスナーをバインドします。
listenfd を epoll 管理に追加します。
イベントをリッスンします。
イベントを処理します。
clientfd の読み取りイベントを登録します。
2.2、memcached - マルチスレッド モードで複数のリアクターを使用する
Memcached はキーと値の構造であり、メモリ上で動作するネットワーク データベース コンポーネントです。Memcached のコマンド処理はマルチスレッドです。
Memcached にはイベント駆動型ライブラリである libevent が必要であり、memcached はネットワーク使用のために libevent に基づいています。
2.2.1. memcached が複数のリアクターを使用するのはなぜですか?
redis とは異なり、memcached のキーと値の構造は豊富なデータ構造をサポートしており、その値で使用されるデータ構造は比較的単純で、ロックも比較的簡単です。したがって、マルチスレッドを導入して効率を向上させることができます。
2.2.2. memcached はリアクターをどのように処理しますか?
memcached のメインスレッドにはリアクタがあり、主に接続の受信を担当します。接続を受信した後、負荷分散後、パイプ (パイプライン) 経由で子スレッドのリアクタに通知し、管理のためにクライアントの fd をスレッドのリアクタに渡し、各スレッドが対応するビジネス ロジックを処理します。
2.2.3. リアクターの観点から memcached ソースコードを見る
最新のmemcached をgithub からダウンロードします。
ソースコード分析を開始します。
ソケットを作成し、リスナーをバインドします。
listenfd 読み取りイベントを登録します。
clientfd を特定のスレッドに割り当て、読み取りイベントを追加します。
2.3、nginx - マルチプロセスモードで複数のリアクターを使用する
Nginx はリバース プロキシを使用し、マルチプロセスを使用してビジネスを処理できます。
マスターは listenfd を作成し、バインドしてリッスンし、複数のプロセスをフォークし、各プロセスは独自の epoll オブジェクトを持ち、listenfd は複数の epoll オブジェクトによって管理されます。現時点では、対処する必要がある衝撃的なグループが存在します。イベントは負荷分散を通じて処理されます。
2.3.1. 「衝撃的なグループ」問題を解決する
ロック方式。Nginx は共有メモリをオープンし、共有メモリにロックを設定します。すると、複数のプロセスがロックをめぐって競合し、ロックをめぐって競合するプロセスのみが接続を受け入れることができます。
2.3.2. 負荷分散
プロセスの最大接続数を定義します。接続数が総接続数の 7/8 を超えると、プロセスは接続の受け入れを一時停止し、他のプロセスに接続の機会を残します。
このようにして、あるプロセスの接続数が多すぎることがなくなり、他のプロセスの接続数が少なすぎるため、各プロセスの接続数は相対的にバランスがとれます。
すべてのプロセスによって受け入れられる接続数が総接続数の 7/8 に達すると、nginx が接続を受け入れるのが非常に遅くなります。
要約する
この記事では、Linux Reactor ネットワーク モデルについて詳しく説明し、実際のアプリケーションにおけるその重要性と利点を強調します。Reactors モデルは、同時接続の処理に優れた効率的なネットワーク設計パターンであり、高性能でスケーラブルなネットワーク アプリケーションを構築できます。
まず、Reactors モデルの基本原理を理解します。イベント駆動型のアプローチを使用して、メイン ループを通じて入力イベントを監視し、イベントが発生すると、対応するハンドラーが呼び出されます。このノンブロッキング設計により、サーバーは接続ごとにスレッドを作成することなく、多数の同時接続を効率的に処理できます。
次に、Linux ネットワーク設計における Reactors モデルの実際の適用について説明します。また、イベント処理とコールバック メカニズムを詳しく分析し、ネットワーク アプリケーションの設計を最適化する方法を読者が理解できるようにします。
従来のマルチスレッドまたはマルチプロセス モデルと比較して、Reactors モデルはシステム リソースをより有効に活用し、コンテキストの切り替えとスレッド作成のオーバーヘッドを削減し、アプリケーションの同時処理能力を向上させることができます。
この記事の目的は、読者が Linux Reactor ネットワーク モデルを完全に理解し、このモデルを独自のネットワーク アプリケーションで使用して、より高性能で信頼性の高いネットワーク アプリケーションを構築することを奨励することです。Reactor モデルの知識を習得すると、読者はより自信を持ってネットワーク テクノロジーの将来をナビゲートし、刻々と変化する課題に対処できるようになります。
クリックしてフォローして、Huawei Cloudの最新テクノロジーについて初めて学びましょう~
2023 年に最も需要の高い 8 つのプログラミング言語: PHP は好調、C/C++ の需要は鈍化 Programmer's Notes CherryTree 1.0.0.0 リリース CentOS プロジェクトは「誰にでもオープン」と宣言 MySQL 8.1 と MySQL 8.0.34 正式リリース GPT-4 はますますバカになっている?精度率は 97.6% から 2.4% に低下しました Microsoft: Windows 11 で Rust Meta を使用するための取り組みを強化 拡大: オープンソースの大規模言語モデル Llama 2 をリリースし、商用利用は無料です C# と TypeScript の父が最新の オープンソース プロジェクトを発表しました: TypeChat は レンガを移動したくないが、要件も満たしたいと考えていますか? おそらく、この 5,000 スター GitHub オープン ソース プロジェクトが役立つかもしれません - MetaGPT Wireshark の 25 周年記念、最も強力なオープン ソース ネットワーク パケット アナライザー