までに準備が整ったことを報告します
select
通常、プログラムの制御外にあるイベントを待機します。本質的に、 select
を呼び出すことによって
指定する条件は一連のイベントであり、そのいずれかがあなたを目覚めさせます。
たとえば、何かをダウンロードしている場合、ループは新しいデータが到着するのを待つ必要があり、転送がスタックしている場合はタイムアウトが発生するか、ユーザーが中断する必要があります。これはまさにselect
です。
複数のダウンロードがある場合、いずれかの接続でデータが到着すると、プログラムのアクティビティがトリガーされます (データをディスクに書き込む必要があります)。そのため、すべてのダウンロード接続のリストを select
に渡します。 「読み取り」を監視するファイル記述子のリスト内。
同時にどこかにデータをアップロードするときは、再び select
を使用します 接続が現在データを受け入れているかどうかを確認します。反対側がダイヤルアップしている場合、データの確認はゆっくりと行われるため、ローカルの送信バッファーは常にいっぱいになり、さらにデータを書き込もうとすると、バッファー スペースが使用可能になるまでブロックされるか、失敗します。送信先のファイル記述子を select
に渡すことによって 「書き込み」記述子として、送信用のバッファ スペースが利用可能になるとすぐに通知を受け取ります。
一般的な考え方は、プログラムがイベント駆動型になるということです 、つまり、順次操作を実行するのではなく、共通のメッセージ ループからの外部イベントに反応します。カーネルに「これは私が何かをしたい一連のイベントです」と伝えると、カーネルは発生した一連のイベントを返します。 2 つのイベントが同時に発生することはかなり一般的です。たとえば、TCP 確認応答がデータ パケットに含まれていた場合、同じ fd が読み取り可能 (データが利用可能) と書き込み可能 (確認済みのデータが送信バッファーから削除された) の両方になる可能性があるため、すべてのイベントを処理する準備ができている必要があります。 select
を呼び出す前に
細かい点の 1 つは、select
です。 基本的に read
を 1 回呼び出すことを約束します。 または write
呼び出し自体について保証することなく、ブロックしません。たとえば、1 バイトのバッファ スペースが利用可能な場合、10 バイトを書き込もうとすると、カーネルが戻ってきて「1 バイトを書きました」と言うので、この場合も同様に処理できるように準備する必要があります。典型的なアプローチは、「この fd に書き込まれるデータ」というバッファを持つことであり、それが空でない限り、fd は書き込みセットに追加され、「書き込み可能」イベントはすべてを書き込もうとすることによって処理されます。現在バッファにあるデータ。その後、バッファが空であれば問題ありません。そうでない場合は、再び「書き込み可能」になるまで待ちます。
「例外」セットはめったに使用されません。データ転送がブロックされる可能性がある帯域外データを持つプロトコルに使用されますが、他のデータは通過する必要があります。プログラムが現在「読み取り可能な」ファイル記述子からデータを受け入れることができない場合 (たとえば、ダウンロード中で、ディスクがいっぱいである場合)、イベントを処理できないため、記述子を「読み取り可能な」セットに含めたくありません。および select
再度呼び出されるとすぐに戻ります。受信者が「例外」セットに fd を含め、送信者が IP スタックに「緊急」データを含むパケットを送信するように要求した場合、受信者は起動され、未処理のデータを破棄して送信者と再同期することを決定できます。 . telnet
プロトコルはこれを、たとえば Ctrl-C 処理に使用します。そのような機能を必要とするプロトコルを設計している場合を除き、問題なく簡単に省略できます。
必須のコード例:
#include <sys/types.h>
#include <sys/select.h>
#include <unistd.h>
#include <stdbool.h>
static inline int max(int lhs, int rhs) {
if(lhs > rhs)
return lhs;
else
return rhs;
}
void copy(int from, int to) {
char buffer[10];
int readp = 0;
int writep = 0;
bool eof = false;
for(;;) {
fd_set readfds, writefds;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
int ravail, wavail;
if(readp < writep) {
ravail = writep - readp - 1;
wavail = sizeof buffer - writep;
}
else {
ravail = sizeof buffer - readp;
wavail = readp - writep;
}
if(!eof && ravail)
FD_SET(from, &readfds);
if(wavail)
FD_SET(to, &writefds);
else if(eof)
break;
int rc = select(max(from,to)+1, &readfds, &writefds, NULL, NULL);
if(rc == -1)
break;
if(FD_ISSET(from, &readfds))
{
ssize_t nread = read(from, &buffer[readp], ravail);
if(nread < 1)
eof = true;
readp = readp + nread;
}
if(FD_ISSET(to, &writefds))
{
ssize_t nwritten = write(to, &buffer[writep], wavail);
if(nwritten < 1)
break;
writep = writep + nwritten;
}
if(readp == sizeof buffer && writep != 0)
readp = 0;
if(writep == sizeof buffer)
writep = 0;
}
}
使用可能なバッファー スペースがあり、読み取り側でファイルの終わりやエラーが発生していない場合は読み取りを試み、バッファーにデータがある場合は書き込みを試みます。ファイルの終わりに達し、バッファが空の場合、完了です。
このコードの動作は明らかに最適ではありません (これはサンプル コードです) が、読み取りと書き込みの両方で要求したよりも少ない処理をカーネルが実行しても問題ないことがわかるはずです。あなたは準備ができています」、そしてそれがブロックされるかどうかを尋ねずに読み書きすることは決してありません.
同じ man ページから:
<ブロック引用>終了時に、どのファイル記述子が実際にステータスを変更したかを示すために、セットが適切に変更されます。
FD_ISSET()
を使用してください select に渡されたセットで、どの FD が準備完了になったかを判断します。