戻ることで準備が整ったことを報告します。
select
通常、プログラムの制御外にあるイベントを待機します。本質的に、あなたのプログラムは を呼び出すことselect
で、「...まで何もすることがありません。プロセスを一時停止してください」と言います。
指定する条件は一連のイベントであり、そのいずれかがあなたを目覚めさせます。
たとえば、何かをダウンロードしている場合、ループは新しいデータが到着するのを待つ必要があり、転送がスタックしている場合はタイムアウトが発生するか、ユーザーが中断する必要がselect
あります。
複数のダウンロードがある場合、いずれかの接続でデータが到着すると、プログラムのアクティビティがトリガーされます (データをディスクに書き込む必要があります)。そのため、select
監視するファイル記述子のリストにすべてのダウンロード接続のリストを指定します。 "読んだ"。
同時にどこかにデータをアップロードするとselect
、接続が現在データを受け入れているかどうかを確認するために再び使用します。反対側がダイヤルアップしている場合、データの確認はゆっくりと行われるため、ローカルの送信バッファーは常にいっぱいになり、さらにデータを書き込もうとすると、バッファー スペースが使用可能になるまでブロックされるか、失敗します。送信先のファイル ディスクリプタをselect
「書き込み」ディスクリプタとして渡すことで、送信用のバッファ スペースが利用可能になるとすぐに通知を受け取ります。
一般的な考え方は、プログラムがイベント駆動型になることです。つまり、順次操作を実行するのではなく、共通のメッセージ ループからの外部イベントに反応します。カーネルに「これは私が何かをしたい一連のイベントです」と伝えると、カーネルは発生した一連のイベントを返します。2 つのイベントが同時に発生することはかなり一般的です。たとえば、TCP 確認応答がデータ パケットに含まれていた場合、同じ fd が読み取り可能 (データが利用可能) と書き込み可能 (確認済みのデータが送信バッファーから削除された) の両方になる可能性があるため、すべてのイベントを処理する準備ができている必要があります。select
もう一度電話する前に。
細かい点の 1 つは、呼び出し自体について何の保証もせずに、 1 回の呼び出しまたはブロックしないselect
という約束を基本的に与えることです。たとえば、1 バイトのバッファ スペースが利用可能な場合、10 バイトを書き込もうとすると、カーネルが戻ってきて「1 バイトを書きました」と言うので、この場合も処理できるように準備する必要があります。典型的なアプローチは、「この fd に書き込まれるデータ」というバッファを持つことです。空でない限り、fd は書き込みセットに追加され、「書き込み可能」イベントはすべての書き込みを試行することによって処理されます。現在バッファにあるデータ。その後バッファが空であれば問題ありません。read
write
「例外」セットはめったに使用されません。データ転送がブロックされる可能性がある帯域外データを持つプロトコルに使用されますが、他のデータは通過する必要があります。プログラムが現在「読み取り可能な」ファイル記述子からデータを受け入れることができない場合 (たとえば、ダウンロード中で、ディスクがいっぱいである場合)、イベントを処理できないため、「読み取り可能な」セットに記述子を含めたくありません。再度呼び出されると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;
}
}
使用可能なバッファー スペースがあり、読み取り側でファイルの終わりやエラーが発生していない場合は読み取りを試み、バッファーにデータがある場合は書き込みを試みます。ファイルの終わりに達し、バッファが空の場合、完了です。
このコードの動作は明らかに最適ではありません (これはサンプル コードです) が、読み取りと書き込みの両方で要求したよりも少ない処理をカーネルが実行しても問題ないことがわかるはずです。あなたは準備ができています」、そしてそれがブロックされるかどうかを尋ねずに読み書きすることは決してない.