1. 基礎知識
1.1 Swoole
Swoole は本番環境ネットワーク向けの php 非同期ですPHP 開発者は、Swoole 通信エンジンを使用して高性能サーバー サービスを開発できます。 Swoole のサーバー部分には多くのコンテンツがあり、多くのナレッジポイントが含まれています。この記事ではサーバーの概要のみを説明し、具体的な実装の詳細については、以降の記事で詳しく紹介します。
推奨 (無料): swoole
1.2 ネットワーク プログラミング
1. ネットワーク通信はこれは、1 つ (または複数) のマシン上で 1 つ (または複数) のプロセスを開始し、1 つ (または複数) のポートでリッスンし、特定のプロトコル (標準プロトコル http、dns の場合もあります。また、自己プロトコルの場合もあります) に従うことを指します。定義されたプロトコル) クライアントと情報を交換します。
2. 現在のネットワーク プログラミングのほとんどは、tcp、udp、または上位層のプロトコルに基づいています。 Swoole のサーバー部分は、tcp および udp プロトコルに基づいています。
3. udp を使用したプログラミングは比較的簡単です。この記事では主に tcp プロトコルに基づくネットワーク プログラミングを紹介します。
4. TCP ネットワーク プログラミングには主に 4 種類のイベントが含まれます
● 接続の確立: 主に、接続を開始するクライアント (connect) と接続を受け入れるサーバー (accept) を指します。
● メッセージ到着: サーバーは、クライアントによって送信されたデータを受信します。このイベントは、TCP ネットワーク プログラミングで最も重要なイベントです。サーバーがこのタイプのイベントを処理する場合、ブロッキングまたはノンブロッキングを使用できます。さらに、サブパッケージ化やアプリケーション層のバッファなどの問題も考慮する必要があります。
#メッセージは正常に送信されました: 正常に送信されたとは、アプリケーション層がデータを正常に送信したことを意味します。送信バッファ内のカーネル ソケット ワードは、クライアントがデータを正常に受信したことを意味するものではありません。トラフィックが少ないサービスの場合、データは通常一度に送信できるため、そのようなイベントを気にする必要はありません。すべてのデータを一度にカーネル バッファに送信できない場合は、メッセージが正常に送信されたかどうかを考慮する必要があります (ブロック プログラミングの場合、送信はシステム コール (write、writev、send など) が返された後に成功します。一方、ノンブロッキングプログラミングの場合は、実際の状況を考慮する必要があります(書き込まれたデータが期待どおりであるかどうか)
##接続の切断:クライアントの切断 (読み取りは 0 を返す) とサーバーの切断 (クローズ、シャットダウン) #5. TCP 接続を確立するプロセスは、次の図に示すとおりです。
# 図中の ACK と SYN はフラグビット、seq と ack は TCP パケットのシーケンス番号と確認シーケンス番号です
#6. TCP 切断のプロセスは次のようになります
● 上の図を考慮します。クライアントがアクティブに切断し、サーバーがアクティブに切断する状況も同様です。## 図内、FIN と ACK はフラグビットを表し、seq と ack は tcp パケットのシーケンス番号と確認シーケンス番号
1.3 プロセス間通信
#1. プロセス間の通信には、名前なしパイプ (pipe)、名前付きパイプ (fifo)、シグナル (signal)、セマフォ (semaphore)、ソケット (socket)、共有メモリ (shared Memory) などのメソッドが含まれます 2. Swoole は、複数のプロセス間の通信に Unix ドメイン ソケット (ソケットの一種) を使用します 通信 (Swoole の内部プロセスを参照)
##1.4 ソケットペア
1.socketpair は、 Pipe と同様にソケット ペアを作成するために使用されます。違いは、 Pipe は一方向通信であり、双方向通信は 2 回作成する必要があることです。Socketpair は 2 回呼び出すことができます。また、ソケットを使用するため、データ交換の方法も定義できます
2. ソケットペアシステムコール
#呼び出しが成功すると、sv[0] と sv[1] にそれぞれファイル記述子が保存されます。
sv[0] に書き込み、sv[1] から読み取ることができます。
sv に書き込みます。 [1]、sv[0]から読み取ることができます
- プロセスはsocketpairを呼び出します。その後、子プロセスをforkすると、子プロセスは2つのファイル記述子sv[0]とsv[1]を継承します。これにより、親プロセスと子プロセス間の通信が可能になります。たとえば、親プロセスは sv[0] に書き込み、子プロセスは sv[1] から読み取ります。子プロセスは sv[1] に書き込み、親プロセスは sv[0]
-
- #1.5 デーモン
-
1. デーモンは特別なバックグラウンド プロセスであり、端末から分離され、定期的に特定のタスクを実行するために使用されます。
2. プロセス グループ
各プロセスはプロセス グループに属します
各プロセス グループにはプロセス グループ番号、つまりグループ リーダーのプロセス番号 (PID) があります。
プロセスは、プロセス自体またはその子プロセスのプロセス グループ番号のみを設定できます
3. セッション-
- セッションには複数のプロセス グループを含めることができます。これらのプロセス グループの中には、フォアグラウンド プロセス グループが 1 つだけ存在することができ (または存在しません)、残りはバックグラウンド プロセス グループです。
- セッションには、次のことができます。プロセス グループは 1 つだけです。制御端末
- ユーザーが端末またはネットワーク経由でログインすると、新しいセッションが作成されます
- プロセスは、システム コール setid を呼び出してセッションを作成します。新しいセッション setsid を呼び出すプロセスを特定のプロセスにすることはできません グループのリーダー。 setsid 呼び出しが完了すると、プロセスはセッションの最初のプロセス (リード プロセス) になり、新しいプロセス グループのリーダーになります。プロセスが以前に制御端末を持っていた場合、プロセスと端末との接続も切断されます
4. デーモン プロセスの作成方法
- 子プロセスをフォークした後、親プロセスが終了し、子プロセスが setsid を実行すると、子プロセスはデーモン プロセスになることができます。 。このようにして、子プロセスはセッションのリーダー プロセスとなり、ターミナルを再度開くことができます。このとき、再度 fork することができます。fork によって生成された子プロセスは、ターミナルを開くことができなくなります (セッションのリーダー プロセスのみが開くことができます)。ターミナルを開きます)。 2 番目のフォークは必要ありません。これは、子プロセスが再びターミナルを開かないようにするためだけです。
- Linux には、デーモン プロセスを作成するためのデーモン関数 (この関数はシステム コールではなく、ライブラリ関数です) が用意されています。
1.6 Swoole tcp サーバーの例
- 上記のコードを cli モードで実行すると、opcode は次のようになります。字句解析と構文解析の後に生成され、実行のために zend 仮想マシンに渡されます
- zend 仮想マシンが $serv->start() を実行すると、Swoole サーバーが起動します
- 上記のコードで設定したイベントコールバックはワーカープロセス内で実行されますが、Swooleサーバーモデルについては後ほど詳しく紹介します
2. Swooleサーバー
#2.1 基本モード
1. 説明
基本モードはマルチプロセス モデルを採用しています。 nginx と一致します。各プロセスにはスレッドが 1 つだけあり、メイン プロセスはワーカー プロセスの管理を担当します。ワーカー プロセスは、ポートのリッスン、接続の受け入れ、リクエストの処理、および接続の終了を担当します。- 複数の場合複数のプロセスが同時にポートをリッスンすると、サンダーリング グループの問題が発生します。3.9 より前の Linux カーネル バージョンでは、Swoole はサンダーリング グループの問題を解決しません
- Linux カーネル 3.9 以降のバージョンでは、新しい機能が提供されます。ソケット パラメータ SO_REUSEPORT により、複数のプロセスが同じポートにバインドできるようになります。カーネルが新しい接続リクエストを受信すると、そのうちの 1 つが処理のために起動されます。カーネルの負荷分散もレベルで行われ、問題を解決できます。前述のサンダーリング グループの問題。Swoole もこのパラメータを追加しました。
- 基本モードでは、reactor_number パラメータは実際には効果がありません。
- ワーカー プロセスの数が 1 に設定されている場合、ワーカープロセスはフォークされず、メイン プロセスがリクエストを直接処理します。このモードはデバッグに適しています
-
2。起動プロセス
php コードは次のように実行されます。 $serv- > start() の場合、メイン プロセスは int swServer_start(swServer *serv) 関数に入ります。この関数はサーバーの起動を担当します。- 関数 swServer_start では、swReactorProcess_start が呼び出され、この関数が呼び出されます。複数のワーカー プロセスをフォークします
-
メイン プロセスとワーカー プロセスはそれぞれ独自のイベント ループに入り、さまざまなイベントを処理します-
2.2 プロセス モード
1. 説明
このモードはマルチプロセスおよびマルチスレッドであり、メイン プロセス、マネージャー プロセス、ワーカー プロセス、および task_worker プロセスを備えています。メイン プロセスの下にある複数のスレッドです。メイン スレッドは、接続を受け入れ、反応するためにそれらを引き渡す責任を負います。スレッドはリクエストを処理します。反応スレッドは、データ パケットを受信し、処理のためにワーカー プロセスにデータを転送し、ワーカー プロセス - マネージャー プロセスから返されたデータを処理することを担当します。このプロセスは単一のスレッドであり、主に以下の役割を果たします。 nginx と同様に、ワーカー プロセスを管理します。メイン プロセス、ワーカー プロセスが異常終了した場合、マネージャー プロセスはワーカー プロセスを再フォークする責任があります。
- ワーカー プロセスはシングルスレッド プロセスであり、特にリクエストを処理する場合
- task_worker プロセス。処理に使用されます。時間のかかるタスクの場合、デフォルトでは有効になっていません。
- ワーカー プロセスは、ドメイン ソケットを使用してメイン プロセスの反応スレッドと通信します。ワーカー プロセス間の通信はありません
-
- 2. 起動プロセス
#Swoole サーバー起動エントリ: swServer_start 関数
#デーモン モードが設定されている場合は、必要なパラメータを確認します。その後、まずデーモン プロセスになり、次にマネージャー プロセスをフォークして、リアクター スレッドを作成します。
メイン プロセスまずマネージャー プロセスをフォークアウトし、マネージャー プロセスはワーカー プロセスと task_worker プロセスをフォークアウトします。ワーカー プロセスが int swWorker_loop (swServer *serv, int worker_id) に入った後、つまり独自のイベント ループに入った後、task_worker についても同様であり、独自のイベント ループに入ります-
-
メイン プロセス pthread_create は反応スレッドを終了します。メイン スレッドと反応スレッドはそれぞれ独自のイベント ループに入ります。リアクター スレッドは static int swRea-torThread_loop (swThreadParam *param) を実行し、処理を待ちます。イベント #3. 構造図
- Swoole プロセス モードの構造を次の図に示します。
上の図では、task_worker プロセスが考慮されていません。 task_worker プロセス数は 0
3. リクエスト処理プロセス (プロセスモード)
3.1 リアクタースレッド間の通信
1. Swoole マスター プロセスとワーカー プロセス間の通信は、次の図に示すとおりです。
- Swoole は SOCK_STREAM の代わりに SOCK_DGRAM を使用します。これは、各リアクター スレッドが複数のリクエストの処理を担当するためです。リクエストを受信した後、リアクターは処理を担当するワーカー プロセスに情報を転送します。SOCK_STREAM が使用される場合、ワーカー プロセスはTCP をサブコントラクトしてリクエストを処理することはできません。
- swFactoryProcess_start 関数は、リアクター スレッドとワーカー プロセス間の通信用のワーカー プロセスの数に基づいて、対応する数のソケット ペアを作成します (詳細については、swPipeUnsock_create 関数を参照してください)。
2. リアクターを想定 スレッドが 2 つ、ワーカー プロセスが 3 つあり、リアクターとワーカー間の通信は下図のようになります。
各リアクター スレッドは複数のワーカー プロセスを監視する役割を果たします。各ワーカー プロセスには、リッスンするリアクター スレッドが 1 つだけあります (reactor_num リアクター スレッドは、ワーカー プロセスのデータを受信した後にデータを処理します。このリアクター スレッドは、リクエストを送信したリアクター スレッドではない可能性があることに注意してください。
- 3. リアクター スレッドとワーカー プロセス間の通信のためのデータ パケット
-
##3.2 リクエスト処理
1. マスター プロセスのメイン スレッドは、ポートをリッスンし (
listen)、接続を受け入れ (accept、fd を生成)、接続を受け入れた後、デフォルトでは fd を介してリアクター スレッドにリクエストします。%actor_number が割り当てられ、その後、fd が
epoll_ctl を介して対応するリアクター スレッドに追加されます。最初に追加されるとき、ソケット書き込みバッファーが存在するため、書き込みイベントが監視されます。新しく受け入れられた接続によって作成された接続は空であるため、書き込み可能である必要があります。これはすぐにトリガーされ、リアクター スレッドがいくつかの初期化操作を実行します
複数のスレッドが epollfd (システムコール
epoll_create) を通じて同時に作成されます
同時に複数のスレッドによって epoll_ctl- を呼び出すことはスレッドセーフです (1 つの epolld に対応します) 1 つのスレッドが実行中の場合、他のスレッドはブロックされます (epoll の基礎となる赤黒ツリーを同時に操作する必要があるため)
epoll_wait## を呼び出すこともスレッドセーフです。 # 同時に複数のスレッドによって受信されますが、イベントは同時に複数のスレッドによって受信される可能性があります。実際には、複数のスレッドが同時に - epoll_wait
を呼び出すことはお勧めできません epollfd。この状況は Swoole には存在しません。Swoole の各リアクター スレッドには独自の epollfd
があります。1 つのスレッドは - epoll_wait
を呼び出し、もう 1 つのスレッドは
epoll_ctl を呼び出します。マニュアルでは、
epoll_ctl 新しく追加された fd の準備ができている場合、 - epoll_wait
を実行しているスレッドは非ブロッキングになります (man
epoll_wait を通じて関連するコンテンツを表示できます)
#2. リアクター スレッド内の fd の書き込みイベントがトリガーされ、リアクター スレッドが処理を担当します。参加時間が経過し、書き込むデータがない場合、読み取りイベントが開始されます。イベント監視、クライアントから送信されたデータを受け入れる準備ができています。
3. リアクター スレッドは、ユーザーのリクエスト データを読み取ります。
Afterリクエスト のデータを受信すると、データがワーカー プロセスに転送されます。デフォルトでは、fd を通じて割り当てられます。 % work_number
リアクターによってワーカー プロセスに送信されるデータ パケットには、次のものが含まれます。
#送信するデータが大きすぎる場合は、データを断片化する必要があります。スペースの制限があるため、データの断片化については後で詳しく説明します複数のリアクター スレッドが同時に同じワーカー プロセスにデータを送信する状況が発生する可能性があるため、Swoole は SOCK_DGRAM モードを採用しています。ワーカー プロセスと通信します。各データ パケットのヘッダーを通じて、ワーカー プロセスは次のことができます。
#4. ワーカー プロセスがリアクターを受信する データ パケットが送信された後、処理されます。 , リクエスト結果はメインプロセスに送信されます。-
- ワーカープロセスからメインプロセスに送信されたデータパケットにはヘッダーも含まれます。リアクタースレッドがデータパケットを受信した後、対応するリアクター スレッド、要求された fd およびその他の情報を知ることができます
##5。メイン プロセスはワーカー プロセスによって送信されたデータ パケットを受信し、これによりリアクター スレッドの処理がトリガーされます-
- このリアクター スレッドは、以前にワーカー プロセスにリクエストを送信したリアクター スレッドである必要はありません。
- メイン プロセスの各リアクター スレッドは、データの監視を担当します。ワーカー プロセスによって送信されるパッケージでは、ワーカーによって送信される各データ パケットは 1 つのリアクター スレッドによってのみ監視されるため、1 つのリアクター スレッドのみがトリガーされます
6. リアクター スレッドはリクエスト処理を処理しますワーカー プロセスによって送信された結果。クライアントにデータを直接送信する場合は、直接送信できます。この接続のリスニング ステータスを変更する必要がある場合 (close
など)、次の操作が必要です。最初にこの接続を監視するリアクター スレッドを見つけてから、この接続のリスニング ステータスを変更します (epoll_ctl
を呼び出すことにより)
- リアクター処理スレッドとリアクター リスニング スレッドが一致しない可能性があります。同じスレッド
- リアクター リスニング スレッドはクライアントから送信されたデータを監視し、その後ワーカー プロセスに転送されます
- リアクター処理スレッドはワーカー プロセスによって送信されたデータを監視しますメイン プロセスにデータを送信し、クライアントにデータを送信します。
four . gdb デバッグ
4.1 プロセス モード起動
4.2 基本モード起動
5. 概要と感想
#1. この記事では主に Swoole サーバーの 2 つのモード、ベース モードとプロセス モードについて詳しく説明します この記事では、ネットワーク プログラミング モデルの 2 つのモードを紹介し、その方法に焦点を当てますプロセス モードでのプロセス間通信、リクエスト処理フローなど。
2. プロセス モードでは、メイン プロセスに複数のプロセスを直接作成しないのはなぜですか? スレッドはリクエストを直接処理します (これにより、プロセス間のオーバーヘッドを回避できます)。 -プロセス通信) ですが、マネージャー プロセスを作成し、次にマネージャー プロセスがワーカー プロセスを作成し、ワーカー プロセスがリクエストを処理しますか?
- 個人的なことですが、PHP がマルチプロセスをサポートしているのかもしれません。スレッド化はあまりフレンドリーではなく、PHP はほとんどの場合シングルスレッド プログラミングのみを実行します
- ZendVM が提供する TSRM はマルチスレッド環境もサポートしていますが、実際にはスレッドごとにメモリを分離するソリューションです。無意味
#3. プロセス モードでは、メイン プロセスの各リアクター スレッドは複数のリクエストを同時に処理できます。複数のリクエストは同時に処理されます。これを 2 つの次元から見ていきます。 ##メイン プロセスの観点から見ると、メイン プロセスは複数のリクエストを同時に処理します。すべてのリクエスト パケットが受信されると、処理のためにワーカー プロセスに転送されます。
ワーカーからプロセスの観点から見ると、このワーカー プロセスによって受信されるリクエストはシリアルです。デフォルトでは、ワーカー プロセスもリクエストをシリアルに処理します。単一のリクエストがブロックされた場合 (Swoole のワーカー プロセスは phper によって記述されたイベント処理関数をコールバックします)、この関数はブロックされる可能性があります。 ), 後続のリクエストは処理できません。これはキューのブロックの問題です。この場合、Swoole のコルーチンを使用できます。コルーチンのスケジューリングにより、単一のリクエストがブロックされても、ワーカー プロセスは他のリクエストの処理を続行できます-
- 4. Swoole を使用して tcp サーバーを作成する場合、tcp はバイト ストリーム プロトコルであるため、下請けする必要があり、Swoole はクライアントとサーバー間の通信プロトコルを知らなければ下請けできません。リアクターからワーカー プロセスに渡されるデータはバイト ストリームのみであり、ユーザーが処理する必要があります。もちろん、一般的にはプロトコルを自分で構築する必要はなく、Swoole は TCP サーバーを使用することで、すでに Http、Https、その他のプロトコルをサポートしています。
以上がSwooleサーバーの簡単な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。