【Linux】C++プロジェクト実践編~実践編~

オレンジ色

一般的な IO (ネットワーク IO) の 2 段階

I と O はそれぞれ入力と出力を表します。ネットワーク通信は、実際にはカーネル内のバッファであるネットワーク ソケットを通じて実装されます。

ネットワーク IO は、データの準備とデータの読み取りと書き込みの 2 つの段階に分かれています (これら 2 つの概念は異なるため、混同することはできません)。

データの準備: システム IO 操作の準備状況に応じて、次のように分類されます。阻塞、非阻塞

データの読み取りと書き込み: カーネルとアプリケーション間の対話モードに従って、次のように分類されます。同步、异步

IO の処理時は、ブロッキングとノンブロッキングの両方が同期されますが、特殊な API が呼び出された場合のみ非同期となります。

ここに画像の説明を挿入します
概要:
     従来のネットワーク IO は、データの準備とデータの読み取りと書き込みの 2 つの段階に分かれています。データの準備状況は、ブロッキングとノンブロッキングに分けられます。ブロッキングとは、IO メソッドを呼び出し、スレッドがブロッキング状態 (データが到着しない場合は戻らず、ブロックしてデータの到着を待つ)、ノンブロッキング (データが到着していなくてもリターンする、スレッドによって判断される) になることを意味します。戻り値) はスレッドの状態を変更せず、戻り値で判断します。read、recvに関わらず戻り値が-1の場合、バッファにデータがないか、シグナルによるソフト割り込みがあったかどうかをリターンステータスから判断し、再度呼び出すかどうかを判断します。 。

     データの読み書きは同期と非同期に分けられます ソケットはカーネル内のバッファであることが分かります 同期とはアプリケーションがカーネルからデータを読み取ることを意味します このときアプリケーションプログラムはカーネルからデータを読み取るだけで何もしません. その他。非同期により、カーネルはデータの読み取りおよび書き込み操作の完了を支援できます。データの読み取りおよび書き込みが完了すると、通知メソッドを通じてアプリケーションに通知されます。このようにして、カーネルがデータの読み取りを実行している間、アプリケーションは他のことを行うことができますデータの読み書きが完了すると、データの読み書きを処理できるようになりますが、通常はファイルディスクリプタ(ソケット)、buf(データのコンテナ)、通知メソッド(sigioシグナル)を使用する必要があります。特別な API 呼び出し、aio_read aio_write を通じて非同期的に

     同期メソッドは非同期メソッドよりも効率が低く、実装はより複雑です。

Linux 上の 5 つの IO モデル

1.ブロッキング

呼び出し元は関数を呼び出し、関数が戻るのを待ちます。この間、呼び出し元は何もせず、関数が戻ったかどうかを確認し続けます。次のアクションを実行する前に関数が戻るまで待つ必要があります。スレッド (またはプロセス) は次のとおりです。ブロックされており、CPU は消費されません
ここに画像の説明を挿入します
が、スレッド (プロセス) は他のことを行うことができません

2. ノンブロッキング

ノンブロッキング待機。IO イベントの準備ができているかどうかを時々チェックします。他のことをする準備をしないでください。ノンブロッキングI/O実行システムコールは、イベントの発生の有無に関わらず、常に即時にリターンします。イベントが発生していない場合は、-1を返します。このとき、2つの状況は、errnoによって区別できます。acceptの場合は、 recv と send の場合、イベントは発生しません。通常、errno は EAGAIN に設定されます。

ここに画像の説明を挿入します
一般的には while ポーリング方式が使用され、バッファにデータが到着しない場合は他の処理を行うことができます。システムリソースを比較的消費します。ノンブロッキングはfcntl経由で設定できます。たとえば、データを送信するクライアントが 10,000 あり、各サイクルには 10,000 のシステム コールが発生します。

3. IOの再利用

Linux は、select/poll/epoll 関数を使用して IO 多重化モデルを実装します。これらの関数もプロセスをブロックしますが、IO のブロックとの違いは、これらの関数が複数の IO 操作を同時にブロックできることです。さらに、複数の読み取り操作と書き込み操作の IO 機能を同時に検出できます。IO 演算関数は、読み取りまたは書き込みするデータが存在するまで実際には呼び出されません。
ここに画像の説明を挿入します
複数の IO を同時に検出でき、監視作業は通常カーネルに任せられ、データが到着すると、アプリケーションは read を呼び出してデータの読み取りと書き込みを行います。これは、ノンブロッキングおよびビジー ポーリング方式の改良版として理解できます。1 つのスレッドまたはプロセスで複数のクライアントを同時に検出できます

4. 信号ドライバー

Linux はシグナル駆動型 IO にソケットを使用し、シグナル処理機能をインストールします。プロセスはブロックせずに実行を継続します。IO イベントの準備ができると、プロセスは SIGIO シグナルを受信し、IO イベントを処理します。
ここに画像の説明を挿入します
SIGIO シグナルを通じて処理され、データの準備ができたらシグナルを送信し、シグナルをキャプチャして read を呼び出してデータの読み取りと書き込みを行います。

カーネルは、第 1 段階では非同期であり、第 2 段階では同期です。ノンブロッキング IO との違いは、メッセージ通知メカニズムを提供することです。これにより、ユーザー プロセスが継続的にポーリングおよびチェックする必要がなく、システム API の数が削減されます。通話と効率の向上。

質問: IO 多重化と信号ドライバーの違いは何ですか? データの準備ができたら、バッファ内のデータを処理するようにプログラムに通知しませんか?
回答: 多重化とは、カーネルが複数のファイル記述子を監視し、選択などの監視対象の関数をブロックすることを意味します。データのコピーもブロックされます。多重化は、特定の IO イベントをブロックした後、プロセスが時間内に他の IO を処理できなくなるだけです。シグナル ドライバーは最初に信号処理関数を登録し、データが準備されると、カーネルは処理のためにシグナルをプロセスに送信します。シグナルドライバーはデータ準備プロセスではブロックしませんが、データコピーではブロックするため、両方とも同期 IO です。

5. 非同期

Linux では、aio_read 関数を呼び出して、記述子バッファ ポインタとバッファ サイズ、ファイル オフセット、通知方法をカーネルに伝え、すぐに戻ります。カーネルがデータをバッファにコピーすると、アプリケーションに通知します。
ここに画像の説明を挿入します
カーネルは監視と読み書き動作を完了し、データの読み書きが完了するとアプリケーションプログラムにデータ処理を通知します。

/* Asynchronous I/O control block. */
struct aiocb
{
    
    
int aio_fildes; /* File desriptor. */
int aio_lio_opcode; /* Operation to be performed. */
int aio_reqprio; /* Request priority offset. */
volatile void *aio_buf; /* Location of buffer. */
size_t aio_nbytes; /* Length of transfer. */
struct sigevent aio_sigevent; /* Signal number and value. */
/* Internal members. */
struct aiocb *__next_prio;
int __abs_prio;
int __policy;
int __error_code;
__ssize_t __return_value;
#ifndef __USE_FILE_OFFSET64
__off_t aio_offset; /* File offset. */
char __pad[sizeof (__off64_t) - sizeof (__off_t)];
#else
__off64_t aio_offset; /* File offset. */
#endif
char __glibc_reserved[32];
};

ウェブサーバー

Web サーバーとは、サーバー ソフトウェア (プログラム)、またはサーバー ソフトウェアを実行するハードウェア (コンピューター) です。その主な機能は、HTTP プロトコルを通じてクライアント (通常はブラウザ) と通信し、クライアントからの HTTP 要求を受信、保存、処理し、要求に対する HTTP 応答を作成し、要求された情報をクライアントに返すことです。 、Web ページなど)、またはエラー メッセージを返します。
ここに画像の説明を挿入します
通常、ユーザーは Web ブラウザを使用して、対応するサーバーと通信します。ブラウザに「ドメイン名」または「P アドレス: ポート番号」を入力すると、ブラウザはまずドメイン名を対応する IP アドレスに解決するか、IP アドレスに基づいて対応する Web サーバーに HTTP リクエストを直接送信します。このプロセスでは、まず TCP プロトコルの 3 ウェイ ハンドシェイクを通じてターゲット Web サーバーとの接続が確立され、次に HTTP プロトコルによってターゲット Web サーバーへの HTTP 要求メッセージが生成され、そのメッセージが TCP、IP 経由でターゲット Web サーバーに送信されます。および他のプロトコル。

HTTPプロトコル(アプリケーション層プロトコル)

導入

ハイパーテキスト転送プロトコル (HTTP) は、通常 TCP 上で実行される単純な要求/応答プロトコルです。クライアントがサーバーに送信できるメッセージの種類と、クライアントが取得する応答の種類を指定します。要求および応答メッセージのヘッダーは ASCII 形式で指定され、メッセージの内容は MIME に似た形式になります。HTTP は、World Wide Web 上のデータ通信の基礎です。

概要

HTTP は、クライアント端末 (ユーザー) とサーバー側 (Web サイト) の要求と応答 (TCP) の標準です。Web ブラウザ、Web クローラー、またはその他のツールを使用して、クライアントはサーバー上の指定されたポート (デフォルトのポートは 80) への HTTP リクエストを開始します。このクライアントをユーザー エージェントと呼びます。応答サーバーには、HTML ファイルや画像などのリソースが保存されます。この応答サーバーをオリジン サーバーと呼びます。ユーザー エージェントとオリジン サーバーの間には、プロキシ サーバー、ゲートウェイ、トンネルなどの複数の「中間層」が存在する場合があります。

TCP/IP プロトコルはインターネット上で最も人気のあるアプリケーションですが、HTTP プロトコルには、TCP/IP プロトコルやサポートする層を使用するための要件はありません。実際、HTTP は任意のインターネット プロトコルや他のネットワーク上で実装できます。HTTP は、その基礎となるプロトコルが信頼性の高いトランスポートを提供することを前提としています。したがって、そのような保証を提供できる任意のプロトコルを使用できます。したがって、TCPIP プロトコル スイートのトランスポート層として TCP を使用します。

通常、HTTP クライアントは、サーバーの指定されたポート (デフォルトはポート 80) への TCP 接続を作成するリクエストを開始します。HTTP サーバーは、そのポートでクライアントの要求をリッスンします。リクエストを受信すると、サーバーは「HTTP/1.1 200 OK」などのステータスと、リクエストされたファイル、エラー メッセージ、その他の情報などのコンテンツをクライアントに返します。

動作原理

HTTP プロトコルは、Web クライアントが Web サーバーに Web ページを要求する方法、およびサーバーが Web ページをクライアントに配信する方法を定義します。HTTP プロトコルは要求/応答モデルを使用します。クライアントは、リクエスト メソッド、URL、プロトコル バージョン、リクエスト ヘッダー、およびリクエスト データを含むリクエスト メッセージをサーバーに送信します。サーバーは、プロトコルのバージョン、成功またはエラー コード、サーバー情報、応答ヘッダー、および応答データを含むステータス行で応答します。

HTTP リクエスト/レスポンスの手順は次のとおりです。

  1. Web サーバーへのクライアント接続 HTTP クライアント (通常はブラウザ) は、Web サーバーの HTTP ポート (デフォルトは 80) との TCP ソケット接続を確立します。たとえば、http://www.baidu.com。
  2. HTTP リクエストを送信するには、クライアントは TCP ソケット経由で Web サーバーにテキスト リクエスト メッセージを送信します。リクエスト メッセージは、リクエスト行、リクエスト ヘッダー、空行、リクエスト データの 4 つの部分で構成されます。
  3. サーバーはリクエストを受け入れて HTTP レスポンスを返し、Web サーバーはリクエストを解析して、リクエストされたリソースを見つけます。サーバーはリソースのコピーを TCP ソケットに書き込み、クライアントがそれを読み取ります。応答は、ステータス行、応答ヘッダー、空行、応答データの 4 つの部分で構成されます。
  4. TCP 接続を解放します。接続モードがクローズの場合、サーバーはアクティブに TCP 接続を閉じ、クライアントは受動的に接続を閉じて TCP 接続を解放します。接続モードがキープアライブの場合、接続は一定期間維持されます。 ; この間、リクエストは引き続き受信できます。
  5. クライアント ブラウザは、HTML コンテンツを解析します。クライアント ブラウザは、まずステータス行を解析して、リクエストが成功したかどうかを示すステータス コードを確認します。次に、各応答ヘッダーが解析され、応答ヘッダーは次の HTML ドキュメント (バイト数) とドキュメントの文字セットを伝えます。クライアントのブラウザは、応答データ HTML を読み取り、HTML の構文に従ってフォーマットし、ブラウザ ウィンドウに表示します。

たとえば、ブラウザのアドレス バーに URL を入力して Enter キーを押すと、次のプロセスが実行されます。

  1. ブラウザは、URL 内のドメイン名に対応する IP アドレスを解決するように DNS サーバーに要求します。
  2. IP アドレスを解析した後、IP アドレスとデフォルトのポート 80 に基づいてサーバーとの TCP 接続を確立します。
  3. ブラウザはファイル(URLのドメイン名以降に相当するファイル)を読み込むHTTPリクエストを発行し、そのリクエストメッセージがTCPスリーウェイハンドシェイクの3番目のメッセージのデータとしてサーバに送信されます。
  4. サーバーはブラウザのリクエストに応答し、対応する HTML テキストをブラウザに送信します。
  5. TCP 接続を解放します。
  6. ブラウザはこの HTML テキストを取得し、コンテンツを表示します。

HTTPリクエスト形式

ここに画像の説明を挿入します
リクエスト行、リクエストヘッダー、空行、リクエストデータの 4 つの部分に分かれていることがわかります。
ここに画像の説明を挿入します

1行目がリクエストライン、残りがリクエストヘッダであり、ここでは図示していないがリクエストデータはバイナリコードの列である。

HTTPレスポンスメッセージのフォーマット

ここに画像の説明を挿入します
ステータス行、応答ヘッダー、空行、応答データの 4 つの部分に分かれていることがわかります。

ここに画像の説明を挿入します

HTTPリクエストメソッド(理解のみ)

HTTP/1.1 プロトコルは、指定されたリソースをさまざまな方法で操作するための合計 8 つのメソッド (「アクション」とも呼ばれます) を定義します。

  1. GET: 指定されたリソースに対して「表示」リクエストを行います。GET メソッドはデータを読み取るためにのみ使用する必要があり、Web アプリケーションなどの「副作用」を生み出す操作には使用しないでください。理由の 1 つは、GET が Web スパイダーなどによってランダムにアクセスされる可能性があることです。
  2. HEAD: GET メソッドと同様に、指定されたリソースをサーバーにリクエストします。サーバーがリソースのテキスト部分を返さないだけです。この方法を使用すると、コンテンツ全体を送信することなく「リソースに関する情報」(メタ情報またはメタデータ)を取得できることが利点です。
  3. POST: 指定されたリソースにデータを送信し、サーバーに処理を要求します (パスワードでのログイン、フォームの送信、ファイルのアップロードなど)。データはリクエスト記事に含まれています。このリクエストは、新しいリソースを作成するか、既存のリソースを変更するか、あるいはその両方を行う場合があります。
  4. PUT: 最新のコンテンツを指定されたリソースの場所にアップロードします。
  5. DELETE: Request-URI で識別されるリソースを削除するようにサーバーに要求します。
  6. TRACE: サーバーが受信したリクエストをエコーし​​、主にテストまたは診断に使用されます。
  7. オプション: このメソッドを使用すると、サーバーはリソースでサポートされているすべての HTTP リクエスト メソッドを返すことができます。"" を使用してリソース名を置き換え、ОPTIONS リクエストを Web サーバーに送信して、サーバー機能が適切に機能しているかどうかをテストします。
  8. CONNECT: HTTP/1.1 プロトコルは、パイプラインへの接続を変更できるプロキシ サーバー用に予約されています。通常、SSL 暗号化サーバーへのリンクに使用されます (暗号化されていない HTTP プロキシ経由)。

HTTPステータスコード

すべての HTTP 応答の最初の行はステータス行で、現在の HTTP バージョン番号、3 桁のステータス コード、およびステータスを説明するフレーズがスペースで区切られて含まれます。
ステータス コードの最初の桁は、現在の応答のタイプを表します。

1xx メッセージ - リクエストはサーバーによって受信されました。処理を続行します

2xx 成功 - リクエストはサーバーによって正常に受信、理解され、受け入れられました。

3xx リダイレクト - このリクエストを完了するには後続のアクションが必要です

4xx リクエスト エラー - リクエストに語彙エラーが含まれているか、実行できません。

5xx サーバー エラー - サーバーは正しいリクエストの処理中にエラーが発生しました。

RFC 2616 では、「200 OK」、「404 Not Found」などのステータスを説明するフレーズを推奨していますが、Web 開発者はローカライズされたステータスの説明やカスタム情報を表示するためにどのフレーズを使用するかを決定できます。

サーバープログラミングの基本フレームワーク

サーバープログラムには多くの種類がありますが、基本的な枠組みは同じであり、違いは論理的な処理にあります。

ここに画像の説明を挿入します
I/O 処理ユニットは、クライアント接続を管理するサーバーのモジュールです。通常、新しいクライアント接続を待機して受け入れ、クライアント データを受信し、サーバー応答データをクライアントに返します。ただし、データの送受信は必ずしもI/O処理単位で行われる必要はなく、論理単位で行われる場合もあり、具体的な場所はイベント処理モードに依存する。論理ユニットは通常、プロセスまたはスレッドです。顧客データを分析および処理し、結果を I/O 処理ユニットに渡すか、またはクライアントに直接渡します (イベント処理モデルに応じて)。通常、サーバーには複数の論理ユニットがあり、複数のクライアント タスクの同時処理を可能にします。ネットワーク ストレージ ユニットはデータベース、キャッシュ、ファイルにすることができますが、必ずしもそうである必要はありません。リクエスト キューは、ユニットの通信方法を抽象化したものです。I/O 処理ユニットがクライアント要求を受信すると、その要求を処理するために論理ユニットに何らかの方法で通知する必要があります。同様に、複数の論理ユニットがストレージ ユニットに同時にアクセスする場合、競合状態を調整して処理するために何らかのメカニズムも必要になります。リクエスト キューは通常、プールの一部として実装されます。

2 つの効率的なイベント処理モード

サーバー プログラムは通常、I/O イベント、シグナル、および時間イベントの 3 種類のイベントを処理する必要があります。効率的なイベント処理モードには、Reactor と Proactor の 2 つがあり、通常、同期 I/O モデルは Reactor モードの実装に使用され、非同期 I/O モデルは通常、Proactor モードの実装に使用されます。

リアクターパターン

メイン スレッド (I/O 処理ユニット) は、ファイル記述子上でイベントが発生したかどうかを監視することのみが必要で、イベントが発生した場合は、すぐにワーカー スレッド (論理ユニット) にイベントを通知し、ソケットの読み取り可能および書き込み可能なイベントをソケットに書き込みます。リクエストキューをワーカースレッドに渡します。これとは別に、メインスレッドは他の実質的な作業を行いません。データの読み取りと書き込み、新しい接続の受け入れ、顧客リクエストの処理はすべてワーカー スレッドで行われます。

同期 I/O を使用して実装された Reactor モードのワークフロー (epoll_wait を例にします) は次のとおりです。

  1. メインスレッドは、ソケットの読み取り準備完了イベントを epoll カーネル イベント テーブルに登録します。
  2. メインスレッドは epoll_wait を呼び出して、ソケット上でデータが読み取られるのを待ちます。
  3. ソケット上に読み取るデータがある場合、epoll_wait はメインスレッドに通知します。メインスレッドはソケット読み取り可能イベントを要求キューに入れます。
  4. 要求キューでスリープしているワーカー スレッドが目覚め、ソケットからデータを読み取り、クライアント要求を処理して、ソケットの書き込み準備完了イベントを epoll カーネル イベント テーブルに登録します。
  5. メインスレッドが epoll_wait を呼び出すと、ソケットが書き込み可能になるまで待機します。
  6. ソケットが書き込み可能になると、epoll_wait がメインスレッドに通知します。メインスレッドはソケット書き込み可能イベントを要求キューに入れます。
  7. リクエストキューでスリープしているワーカースレッドが目覚め、サーバーがクライアントリクエストを処理した結果をソケットに書き込みます。

ここに画像の説明を挿入します

プロアクターモード

プロアクター モードでは、すべての I/O 操作がメイン スレッドとカーネルに渡されて処理 (読み取りと書き込み) が行われ、ワーカー スレッドはビジネス ロジックのみを担当します。
非同期 I/O モデルを使用して実装された Proactor モードのワークフロー (aio_read と aio_write を例にします) は次のとおりです。

  1. メインスレッドは aio_read 関数を呼び出して、ソケット上の読み取り完了イベントをカーネルに登録し、読み取りバッファの場所と、読み取り操作が完了したときにアプリケーションに通知する方法をカーネル ユーザーに伝えます (ここではシグナルが取得されます)。例として)。
  2. メインスレッドは他のロジックの処理を続けます。
  3. ソケット上のデータがユーザー バッファーに読み込まれると、カーネルはアプリケーションに信号を送信して、データが利用可能であることをアプリケーションに通知します。
  4. アプリケーションの事前定義された信号処理関数は、クライアント要求を処理するワーカー スレッドを選択します。ワーカー スレッドはクライアント要求を処理した後、aio_write 関数を呼び出してソケット上の書き込み完了イベントをカーネルに登録し、カーネル ユーザーに書き込みバッファの場所と、書き込み操作が完了したときにアプリケーションに通知する方法を伝えます。 。
  5. メインスレッドは他のロジックの処理を続けます。
  6. ユーザー バッファー内のデータがソケットに書き込まれると、カーネルはアプリケーションに信号を送信して、データが送信されたことをアプリケーションに通知します。
  7. アプリケーションによって事前定義された信号処理関数は、ソケットを閉じるかどうかの決定などの後処理を実行するワーカー スレッドを選択します。

ここに画像の説明を挿入します

プロアクターモードをシミュレート

同期 I/O メソッドを使用して Proactor モードをシミュレートします。原理としては、メインスレッドがデータの読み書き操作を実行し、読み書きが完了すると、メインスレッドが作業スレッドに「完了イベント」を通知します。したがって、ワーカースレッドの観点からは、データの読み取りと書き込みの結果を直接取得し、次に行うことは読み取りと書き込みの結果を論理的に処理することです。
同期 I/O モデル (例として epoll_wait を使用) を使用してシミュレートされた Proactor モードのワークフローは次のとおりです。

  1. メインスレッドは、ソケットの読み取り準備完了イベントを epoll カーネル イベント テーブルに登録します。
  2. メインスレッドは epoll_wait を呼び出して、ソケット上でデータが読み取られるのを待ちます。
  3. ソケット上に読み取るデータがある場合、epoll_wait はメインスレッドに通知します。メインスレッドは、読み取るデータがなくなるまでループでソケットからデータを読み取り、読み取ったデータをリクエスト オブジェクトにカプセル化し、リクエスト キューに挿入します。
  4. リクエスト キュー上でスリープしているワーカー スレッドが目覚め、リクエスト オブジェクトを取得してクライアント リクエストを処理し、ソケット上の書き込み準備完了イベントを epoll カーネル イベント テーブルに登録します。
  5. メインスレッドは epoll_wait を呼び出して、ソケットが書き込み可能になるのを待ちます。
  6. ソケットが書き込み可能になると、epoll_wait がメインスレッドに通知します。メインスレッドは、サーバーがクライアント要求を処理した結果をソケットに書き込みます。
    ここに画像の説明を挿入します

スレッドプール

スレッド プールはサーバーによって事前に作成されたサブスレッドのグループであり、スレッド プール内のスレッドの数は CPU の数とほぼ同じである必要があります。スレッド プール内のすべての子スレッドは同じコードを実行します。新しいタスクが到着すると、メインスレッドはスレッド プール内のサブスレッドを選択して、何らかの方法でタスクを処理します。サブスレッドを動的に作成する場合と比較して、既存のサブスレッドを選択するコストは明らかにはるかに小さくなります。

メインスレッドが新しいタスクを処理するためにどのサブスレッドを選択するかについては、さまざまな方法があります。

  • メインスレッドは、何らかのアルゴリズムを使用して子スレッドをアクティブに選択します。最も単純で最も一般的に使用されるアルゴリズムはランダム アルゴリズムとラウンド ロビン アルゴリズムですが、より優れた賢いアルゴリズムは、タスクをさまざまなワーカー スレッド間でより均等に分散するため、サーバーに対する全体的な負荷が軽減されます。
  • メインスレッドとすべての子スレッドは共有ワークキューを通じて同期され、子スレッドはワークキュー上でスリープします。新しいタスクが到着すると、メインスレッドはそのタスクを作業キューに追加します。これにより、タスクを待っている子スレッドが起動されますが、新しいタスクの「引き継ぎ権」を取得できるのは 1 つの子スレッドだけです。その子スレッドはワーク キューからタスクを取得して実行でき、他の子スレッドは続行します。作業キューでスリープします。

スレッド プールの一般的なモデルは次のとおりです。

ここに画像の説明を挿入します
スレッド プール内のスレッド数を最も直接的に制限する要因は、中央処理装置 (CPU) のプロセッサ/コアの数 N です。CPU が 4 コアの場合、CPU を大量に使用するタスク (ビデオ編集など) の場合CPU コンピューティング リソースを消費するタスクの場合は、スレッド プール内のスレッドの数を 4 (または、他の要因によるスレッドのブロックを防ぐために +1) に設定するのが最適です。IO 集中型のタスクの場合は、通常、スレッド プールのスレッド数を 4 より多く設定します。 CPU の数 コアの数。スレッドは CPU コンピューティング リソースをめぐって競合するのではなく、IO をめぐって競合するためです。IO 処理は一般に遅くなります。コアの数が多いスレッドは、CPU のより多くのタスクをめぐって競合するため、スレッドの処理中に CPU がアイドル状態になることはありません。 IO. リソースの無駄遣いにつながります。

スペースは時間と交換され、サーバーのハードウェア リソースは運用効率と引き換えに浪費されます。プールは、サーバーの起動時に完全に作成および初期化されるリソースの集合であり、静的リソースと呼ばれます。サーバーが正式な運用段階に入り、顧客リクエストの処理を開始すると、関連リソースが必要な場合、動的割り当てを行わずにプールから直接リソースを取得できます。サーバーは、クライアント接続の処理を完了すると、リソースを解放するためのシステム コールを実行せずに、関連リソースをプールに戻すことができます。

  • スペースは時間と交換され、サーバーのハードウェア リソースは運用効率と引き換えに浪費されます。
  • プールは、サーバーの起動時に完全に作成および初期化されるリソースの集合であり、静的リソースと呼ばれます。
  • サーバーが正式な運用段階に入り、顧客リクエストの処理を開始すると、関連リソースが必要な場合、動的割り当てを行わずにプールから直接リソースを取得できます。
  • サーバーは、クライアント接続の処理を完了すると、リソースを解放するためのシステム コールを実行せずに、関連リソースをプールに戻すことができます。

エポロンショットイベント

ET モードが使用できる場合でも、ソケット上のイベントが複数回トリガーされる可能性があります。これにより、並行プログラムで問題が発生する可能性があります。たとえば、スレッドは特定のソケットでデータを読み取った後、データの処理を開始します。データ処理プロセス中に、ソケットで新しいデータを読み取ることができます (EPOLLIN が再度トリガーされます)。この時点で、別のスレッドが起動されます。この新しいデータを読むデータ。したがって、2 つのスレッドが同時にソケットを操作する状況が発生します。ソケット接続は常に 1 つのスレッドによってのみ処理されます。これは、epoll の EPOLLONESHOT イベントを使用して実現できます。

EPOLLONESHOT イベントに登録されたファイル記述子の場合、ファイル記述子に登録された EPOLLONESHOT イベントをリセットするために epoll_ctl 関数を使用しない限り、オペレーティング システムは、ファイル記述子に登録された読み取り可能、書き込み可能、​​または例外イベントを最大 1 つ、しかも 1 回だけトリガーします。このようにして、1 つのスレッドが特定のソケットを処理しているとき、他のスレッドはそのソケットを操作する機会を得ることができません。しかし、逆に考えると、EPOLLONESHOT イベントに登録されたソケットがスレッドによって処理されると、スレッドはソケットの EPOLLONESHOT イベントをすぐにリセットして、次回ソケットが読み取り可能になったときに EPOLLIN イベントを確実にトリガーできるようにする必要があります。これにより、他のワーカー スレッドにこのソケットの処理を継続する機会が与えられます。

有限状態マシン

論理ユニット内の効率的なプログラミング方法: 有限状態マシン。一部のアプリケーション層プロトコル ヘッダーにはパケット タイプ フィールドが含まれており、各タイプを論理ユニットの実行状態にマッピングでき、サーバーはそれに基づいて対応する処理ロジックを作成できます。以下は、状態に依存しない有限状態マシンです。

STATE_MACHINE( Package _pack )
{
    
    
PackageType _type = _pack.GetType();
switch( _type )
{
    
    
case type_A:
process_package_A( _pack );
break;
case type_B:
process_package_B( _pack );
break;
}
}

これは単純な有限状態マシンですが、状態マシンの各状態が互いに独立している、つまり状態間の遷移がない点が異なります。次のコードに示すように、状態間の遷移にはステート マシンの内部ドライバーが必要です。

STATE_MACHINE()
{
    
    
State cur_State = type_A;
 
while( cur_State != type_C )
{
    
    
Package _pack = getNewPackage();
switch( cur_State )
{
    
    
case type_A:
process_package_state_A( _pack );
cur_State = type_B;
break;
case type_B:
process_package_state_B( _pack );
cur_State = type_C;
break;
}
}
}

ステート マシンには、type_A、type_B、type_C の 3 つの状態が含まれます。type_A はステート マシンの開始状態、type_C はステート マシンの終了状態です。ステート マシンの現在の状態は、cur_State 変数に記録されます。ループ中、ステート マシンはまず getNewPackage メソッドを通じて新しいデータ パケットを取得し、次に cur_State 変数の値に基づいてデータ パケットを処理する方法を決定します。データ パケットが処理された後、ステート マシンはターゲット状態値を cur_State 変数に渡すことによって状態遷移を実装します。その後、ステート マシンが次のサイクルに入ると、新しいステートに対応するロジックが実行されます。

おすすめ

転載: blog.csdn.net/mhyasadj/article/details/131490664