ネットワークプログラミング-5つのIOモデル
IOのブロック
ブロックIOとは
、ブロックIOによって実行されたシステムコールがオペレーティングシステムによって一時停止される可能性があることを意味します。これは、待機イベントが発生するまですぐに完了できないためです。システムは関数を呼び出し、関数が戻るかどうかを常にチェックし、戻って次のステップに進むのを待つ必要があります。
ソケットの基本APIでは、ブロックされる可能性のあるシステムコールには、accept、send、recv、およびconnectが含まれます。
ブロッキングと非ブロッキングの概念は、関数ではなくファイル記述子(socket、fd)に適用されることに注意してください。
プロセスは読み取り(システムコール)を実行します。カーネルデータの準備ができていない場合、プロセスはデータをブロックして待機します。
データの準備ができたら、カーネルスペースからユーザースペースへのデータのコピーを開始します。
コピーが完了すると、プロセスは読み取りを実行できます。
ブロッキング原理
マルチタスクをサポートするために、オペレーティングシステムはプロセススケジューリングの機能を実装し、プロセスを「実行中」や「待機中」などのいくつかの状態に分割します。実行状態は、プロセスがCPUを使用する権利を取得し、コードが実行されている状態です。待機状態はブロッキング状態です。たとえば、プログラムがrecvまで実行されると、プログラムは実行状態から待機状態になり、データを受信すると実行状態に戻ります。オペレーティングシステムは、タイムシェアリング方式で各実行状態のプロセスを実行します。高速であるため、複数のタスクを同時に実行しているように見えます。
プロセスAがソケット通信プロセスでソケットの作成を実行すると、オペレーティングシステムは、送信バッファ、受信バッファ、および待機キューを含むソケットオブジェクトを作成します。プログラムがrecvに実行されると、オペレーティングシステムはプロセスAをワークキューから待機キューに移動し、CPUはワークキュー内のプロセスを実行し続け、プロセスAはブロックされます(コードは実行されません)。ダウンし、CPUリソースを占有しません)。データを受信した後でのみ、オペレーティングシステムはプロセスAをワークキューに戻し、実行を続行します。
ノンブロッキングIO
ノンブロッキングIO
ノンブロッキング待機。IOイベントの準備ができているかどうかを定期的に確認してください。準備ができていない場合は、他のことを行うことができます。
ノンブロッキングI / O実行システムコールは、イベントが発生したかどうかに関係なく、常にすぐに戻り、イベントが発生しなかった場合は-1を返します。このとき、errnoによって2つのケースを区別できます。 accept、recv、sendの場合、イベントは発生していません。この時点で、errnoは通常EAGAINに設定されています。
明らかに、プログラムの効率は、イベントが発生したときに非ブロッキングIOを操作することによってのみ改善できます。
したがって、非ブロッキングIOは通常、IO多重化やSIGNO信号などの他のIO通知メカニズムで使用されます。
IO多重化
IO多重化
IO多重化は、最も一般的に使用されるIO通知メカニズムです。これは、アプリケーションがIO多重化機能を介して一連のイベントをカーネルに登録し、カーネルがIO多重化機能を介して準備完了イベントをアプリケーションに通知することを意味します。
IO多重化機能自体もブロックしていますが、複数のIOイベントを同時に監視できるため(複数のIO操作を同時にブロックできます)、プログラムの効率を向上させることができます。
Linuxで一般的に使用される3つのIO多重化機能は、select / poll / epollです。
IO多重化プロセスは、次の図で表すことができます。
プログラムはIO多重化システムコールでブロックされ、ソケットが読み取り可能になるのを待ちます。データの準備ができたら、読み取り可能なファイル記述子を返します。次に、読み取りを使用してデータを読み取ります。IO自体の読み取りおよび書き込み操作は非ブロッキングです。データの読み取りは、カーネルスペースからユーザースペースにコピーする必要があります。
選択する
本旨
- 最初にファイル記述子fds(配列)のリストを作成し、監視するファイル記述子をリストに追加します
- システム関数selectを呼び出して、これらのファイル記述子の1つ以上がIO操作を実行し、関数が戻り、割り込みプログラムがプロセスをウェイクアップする(プロセスを待機キューから作業キューに移動する)まで、リスト内のファイル記述子を監視します。 。
a。関数がブロックされている
b。ファイル記述子に対する関数の検出操作がカーネルによって完了している - 戻るときに、IO操作を実行するファイル記述子の数をプロセスに通知します。
- fdsをトラバースし、FD_ISSETを介してデータを受信したソケットを判別します。
プロセス擬似コードを選択
int s = socket(AF_INET, SOCK_STREAM, 0);
bind(s, ...)
listen(s, ...)
int fds[] = 存放需要监听的socket
while(1){
int n = select(..., fds, ...)
for(int i=0; i < fds.size; i++){
if(FD_ISSET(fds[i], ...)){
//fds[i]的数据处理
}
}
}
APIを選択
int select(int nfds、fd_set * readfds、fd_set * writefds、fd_set * exceptionfds、struct timeval * timeout);
- パラメータ
-
nfds:カーネルにコミッションして最大ファイル記述子値+1を検出します(ファイル記述子は0からカウントされるため)
-
readfds:相手方から送信されたデータに対応する、検出されるファイル記述子の読み取りセット。検出は読み取りバッファーです。
これは着信および発信パラメータであり、着信が検出され(FD_SETがフラグ位置を1に設定)、FD_ISSETに従ってフラグビットが1であるかどうかを判断し、発信が検出されます(FD_SETで1に設定され、FD_CLRがある場合は0に設定されます)。データはありません))
-
writefds:検出される書き込みファイル記述子のセット。カーネル書き込みバッファに書き込みスペースがあるかどうかを確認します。通常は確認しません。
-
exceptfds:異常なファイル記述子のコレクションを検出します
-
タイムアウト:設定されたタイムアウト時間
-
- 戻り値:
- -1:失敗しました
- > 0(n):検出されたセットで変更されたn個のファイル記述子があります
説明の補足を選択
-
readfds、writefds、およびexceptfdsパラメーターは、それぞれ読み取り可能、書き込み可能、および異常なイベントに対応するファイル記述子のセットを指します。アプリケーションがselect関数を呼び出すと、これら3つのパラメーターを介して対象のファイル記述子を渡します。selectが戻ると、カーネルはそれらを変更して、どのファイル記述子の準備ができているかをアプリケーションに通知します。
-
fd_setは整数配列のみを含む構造体であり、配列の各ビットはファイル記述子をマークします
ビット操作API(マクロ)
void FD_CLR(int fd、fd_set * set);
FD_CLR:パラメータファイル記述子fdに対応するフラグビットを0に設定します
int FD_ISSET(int fd、fd_set * set);
FD_ISSET:fdに対応するフラグビットが0か1かを判別します
void FD_SET(int fd、fd_set * set);
FD_SET:パラメータファイル記述子fdに対応するフラグビットを1に設定します
不利益
- selectを呼び出すたびに、fdコレクションをユーザーモードからカーネルモードにコピーし、プロセスを待機キューに追加し、ウェイクアップするたびにキューから削除する必要があります。
- 同時に、selectを呼び出すたびに、カーネルで渡されたすべてのfdをトラバースする必要があります。
- サポートされるfdの数が少なすぎるため、デフォルトは1024です(1024クライアントのみをサポートできます)(fd_setは128バイト、つまり整数配列である1024ビットであり、各ビットはに対応するフラグビットであるため)ファイル記述子)
- fdコレクションは再利用できず、毎回リセットする必要があります
投票
ポーリングシステムコールはselectに似ており、指定された時間内に特定の数のファイル記述子をポーリングします。これはselectの改善です(selectの欠点3と4を解決します)。
Selectは、128バイトのファイル記述子セットreadfdsを使用して、検出するファイル記述子をマークします。ポーリングでは、構造体配列に置き換えられます。検出を委託されるマークは実際のオカレンスから分離されるため、ファイル記述子はセットは再利用でき、128バイトの制限はありません
API
int poll(struct pollfd * fds、nfds_t nfds、int timeout);
- パラメータ:
- fds:構造体pollfd構造体配列であり、テストが必要なファイル記述子のコレクションです。
- nfds:監視対象のイベントコレクションfdsのサイズを指定します。これは、最初のパラメーター配列の最後の有効な要素の添え字+1です。
- タイムアウト:ブロッキング期間
- 戻り値:
- -1:失敗し、イベントが発生するまで永久にブロックされます
- 0:すぐに戻る
- > 0(n):nは、コレクション内のn個のファイル記述子が変更されたことを意味します
pollfd構造:
struct pollfd{
int fd; //委托内核检测的文件描述符
short events; //注册文件描述符的事件
short revents; //文件描述符实际发生的事件
};
epoll
selectの非効率性の理由は、「待機キューの維持」と「プロセスのブロック」の2つのステップを1つにまとめるためであり、selectが呼び出されるたびにこれらの2つのステップを実行する必要があります。Epollは、これら2つのステップを分離し、最初にepoll_ctlを使用して待機キューを維持し、次にepoll_waitを呼び出してプロセスをブロックします。
epollの原則
新しいeventpollオブジェクトを作成します。
int epfd = int epoll_create(int size);
epoll_createはeventpollオブジェクトを作成し、epfdファイル記述子はこのオブジェクトを操作します。サイズパラメータは無意味です。
このオブジェクトにはさらに2つの重要なデータがあります(待機プロセスを格納するための待機リストもあります)
- 1つは、検出する必要のあるファイル記述子の情報です(赤黒木)
- 1つは、レディソケット(二重リンクリスト)に対応する、検出されたデータ変更のファイル記述子情報を格納するレディリストです。プロセスが起動すると、準備完了リストの内容を取得している限り、どのソケットがデータを受信したかを知ることができます。
偽のコード:
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = lfd;
struct eventpoll{
...
struct rb_root rbr;
struct list_head rdlist;
...
};
int lfd = socket(AF_INET, SOCK_STREAM, 0);
bind(lfd , ...);
listen(lfd , ...);
int cfd = accept(lfd, ...);
int epfd = epoll_create(...);
epoll_ctl(epfd, EPOLL_CTL_ADD,...); //将所有需要监听的socket添加到epfd中
while(1){
int ret = epoll_wait(epfd,...);
for(接收到数据的socket){
//处理
read(...)/recv(...)
}
}
- epollオブジェクトを作成した後、epoll_ctlを使用して、監視するソケットを追加または削除できます。例としてソケットの追加を取り上げます。epoll_ctlを介してsock1、sock2、およびsock3の監視を追加すると、カーネルはこれら3つのソケットの待機キューにeventpollを追加します。
- ソケットがデータを受信すると、割り込みプログラムはソケット参照をeventpollの「readylistrdlist」に追加します。
eventpollオブジェクトは、ソケットとプロセスの間の仲介に相当します。ソケットのデータ受信はプロセスに直接影響しませんが、eventpollの準備完了リストを変更することによってプロセスの状態を変更します。 - プロセスAとプロセスBがコンピューターで実行されており、ある時点で、プロセスAがepoll_waitステートメントに対して実行されていると想定します。カーネルはプロセスAをeventpollの待機キューに入れ、プロセスをブロックします。
- ソケットがデータを受信すると、割り込みプログラムはrdlistを変更し、eventpoll待機キューでプロセスをウェイクアップし、プロセスAは再び実行状態になります。プロセスAは、準備ができたソケットを取得するためにrdlistを継続的にループする必要があるだけです。
- プログラムがepoll_waitまで実行されると、実際にはrdlistをトラバースします。rdlistがすでにソケットを参照している場合、epoll_waitは直接戻ります。rdlistが空の場合、プロセスはブロックされます。
総括する
まず、epollの初期化により、epoll_createを使用してevent_pollオブジェクトが作成されます。このオブジェクトには、準備完了リスト、赤黒木、および待機リストがあります。準備完了リストには準備完了ソケットが格納され、赤黒木にはリッスンしているすべてのソケット参照が格納され、待機リストには待機プロセスが格納されます。
新しい接続を受け入れるたびに、割り込みを呼び出してファイルシステムにfdを作成します。このfdには、受信バッファ領域、送信バッファ領域、待機リストがあります(ps:このfdがすでに存在する場合は、このソケットをこのfdに直接追加します。待機リスト、ここではfdに対応するポートとして理解され、コンピューターには65535ポートがあるため、epollは最大65535をサポートし、ソケットはIPであるため、1つのポートに多くの接続がある場合があります:ポート、異なるIP、同じポートはまた、別のソケットですが、対応するポートfdは1つです)、リスナーコールバック関数が割り込みシステムに登録されています。ソケットが読み取りおよび書き込み操作を行うと、割り込みプログラムはソケットのコールバック関数を呼び出し、ソケット参照をevent_poll readyキューに追加します。whileプログラムでは、常にepoll_wait(プロセス)があります。OnceAプロセスReadyキューレディキューが空でなくなると、Aプロセスはソケットデータを取得してシステム実行キューに入り、待機リスト内の次のプロセスは引き続きepoll_waitを使用してポーリングします。
待機リスト内のプロセスは、システム内にアイドルプロセスがあるたびに、待機リストに入れられてブロックされます。epoll_waitにはtime_outパラメータがあります。この待機時間中に、準備キューにソケットがある場合は、処理されると、ブロックされたプロセスが実行のために呼び出されます。常に空の場合、タイマーが期限切れになると、プロセスは非ブロックになり、動作を継続します。
epoll_ctl()
epoll_ctlは、監視対象のソケットを追加または削除します。
int epoll_ctl(int epfd、int op、int fd、struct epoll_event * event);
- パラメータ
- epfd:epollインスタンスに対応するファイル記述子
- op:実行する操作
- EPOLL_CTL_ADD追加
- EPOLL_CTL_MODの変更
- EPOLL_CTL_DEL削除
- fd:検出されるファイル記述子
- イベント:検出されるイベント
epoll_wait()
データを待っています
int epoll_wait(int epfd、struct epoll_event * events、int maxevents、int timeout)
- パラメータ
- epfd:epollインスタンスに対応するファイル記述子
- イベント:変更されたファイル記述子の情報を保存します
- maxevents:2番目のパラメーター構造体配列のサイズ
- タイムアウト:ブロッキング時間
- 0:ブロッキングなし
- -1:ブロックし、fdデータ送信の変更が検出されるまで、ブロックを解除します
- > 0:ブロックされた時間の長さ
- 戻り値
- -1:失敗しました
- > 0:変更されたファイル記述子の数
2つの動作モード
水平トリガーLT
エッジトリガーET
シグナルドライブ
Linuxは、シグナル駆動型IOにソケットを使用し、シグナル処理機能をインストールします。プロセスはブロックせずに実行を継続します。IOイベントの準備ができると、プロセスはSIGIOシグナルを受信し、IOイベントを処理します。