8 よくある質問

8.1 ヘッダーファイルはどこで入手できますか?

もし、あなたのシステムに既にないのであれば、おそらく必要ないでしょう。あなたのプラットフォームのマニュアルを参照してください。Windows 用にビルドする場合は、#include <winsock.h> だけが必要です。

8.2 bind() が "Address already in use" と報告した場合、どうすればよいのでしょうか?

リスニングしているソケットで SO_REUSEADDR オプションを指定して setsockopt() を使用する必要があります。例として、bind() の章select() の章をチェックしてみてください。

8.3 システム上のオープンソケットのリストを取得するにはどうすればよいですか?

netstat を使用します。詳しくは man ページをチェックしてください。しかし、タイプするだけで良い出力が得られるはずです。

$ netstat

唯一のコツは、どのソケットがどのプログラムに関連付けられているかを判断することです。:-)

8.4 ルーティングテーブルを見るにはどうしたらいいですか?

route コマンド(ほとんどの Linux では /sbin にあります)または netstat -r コマンドを実行します。

8.5 クライアントとサーバのプログラムが1台しかない場合、どのように実行すればよいのでしょうか?ネットワークプログラムを書くのにネットワークは必要ないのですか?

幸いなことに、事実上すべてのマシンは、カーネル内に配置され、ネットワークカードのふりをするループバックネットワーク "デバイス" を実装しています。(これはルーティングテーブルで "lo" としてリストされるインターフェースです。)

あなたが "goat" という名前のマシンにログインしていると仮定します。あるウィンドウでクライアントを、別のウィンドウでサーバを実行します。あるいは、サーバをバックグラウンドで起動し("server &")、同じウィンドウでクライアントを実行します。ループバックデバイスの利点は、client goatclient localhostlocalhost/etc/hosts ファイルで定義されていると思われるので)を使えば、ネットワークなしでクライアントがサーバと通信できるようになることです!

つまり、ネットワークに接続されていない1台のマシンで動作させるために、コードの変更は一切必要ないのです!ハッザー!

8.6 リモート側が接続を閉じたかどうかを判断するにはどうすればよいですか?

recv()0 を返すのでわかります。

8.7 "ping" ユーティリティを実装するには?ICMP とは何ですか?生ソケットと SOCK_RAW についてもっと詳しく知りたいのですが、どこに行けばよいですか?

生ソケットに関するすべての疑問は、W. Richard Stevens の UNIX Network Programming books で解決されるでしょう。また、オンラインの Stevens の UNIX Network Programming のソースコードの ping/ サブディレクトリも見てみてください。

8.8 connect() の呼び出しのタイムアウトを変更または短縮するにはどうすればよいですか?

W. Richard Stevens と全く同じ答えを出すのではなく、UNIX Network Programming のソースコードにある lib/connect_nonb.c を参照することにしましょう。

その要点は、socket() でソケット記述子を作成し、それをノンブロッキングに設定して connect() を呼び、すべてがうまくいけば connect() は直ちに -1 を返して errnoEINPROGRESS に設定されるということです。その後、好きなタイムアウトで select() を呼び出し、ソケット記述子をリードとライトの両方で渡します。タイムアウトしなければ、connect() の呼び出しが完了したことになります。このとき、getsockopt()SO_ERROR オプションをつけて connect() 呼び出しからの戻り値を取得する必要があります(エラーがなければ 0 になるはずです)。

最後に、データ転送を開始する前に、ソケットを再びブロッキングに設定する必要があります。

この方法には、接続中に何か他のことをすることができるという利点もあることに注意してください。例えば、タイムアウトを 500ms のような低い値に設定し、タイムアウトするたびに画面上のインジケータを更新し、それから再び select() を呼び出すことができます。例えば、select() を20回ほど呼び出してタイムアウトしたら、そろそろ接続をあきらめる時期だとわかるでしょう。

私が言ったように、スティーブンスのソースに完璧に優れた例があるので、チェックしてみてください。

8.9 Windows 用にビルドするにはどうしたらいいですか?

まず、Windows を削除し、Linux または BSD をインストールします。};-)。いや、実際には、導入部の Windows 用のビルドの章を見ればいいんですけどね。

8.10 Solaris/SunOS 用にビルドするにはどうしたらいいですか?コンパイルしようとするとリンカエラーが出ます!

リンカーエラーは、Sun ボックスがソケットライブラリを自動的にコンパイルしないために起こります。この方法の例については、導入部の Solaris/SunOS 用のビルドに関する章 を参照してください。

8.11 select() がシグナルで落ち続けるのはなぜか?

シグナルはブロックされたシステムコールに -1 を返し、errnoEINTR をセットする傾向があります。sigaction() でシグナルハンドラをセットアップするときに、フラグ SA_RESTART を設定することができます。これは、システムコールが中断された後に再開することを想定したものです。

当然ながら、いつもうまくいくわけではありません。

私のお気に入りの解決策は、goto ステートメントを使うことです。これは教授をイライラさせるだけなので、ぜひやってみてください。

select_restart:
if ((err = select(fdmax+1, &readfds, NULL, NULL, NULL)) == -1) {
    if (errno == EINTR) {
        // some signal just interrupted us, so restart
        goto select_restart;
    }
    // handle the real error here:
    perror("select");
}

もちろん、この場合は goto を使う必要はありません。他の構造体を使用して制御することができます。しかし、私は goto 文の方が実際きれいだと思うんです。

8.12 recv() の呼び出しにタイムアウトを実装するにはどうすればよいですか?

select()を使いましょう!これは、読み込みたいソケット記述子に対して、タイムアウトパラメータを指定することができます。あるいは、このように関数全体を一つの関数で包むこともできます。

#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>

int recvtimeout(int s, char *buf, int len, int timeout)
{
    fd_set fds;
    int n;
    struct timeval tv;

    // set up the file descriptor set
    FD_ZERO(&fds);
    FD_SET(s, &fds);

    // set up the struct timeval for the timeout
    tv.tv_sec = timeout;
    tv.tv_usec = 0;

    // wait until timeout or data received
    n = select(s+1, &fds, NULL, NULL, &tv);
    if (n == 0) return -2; // timeout!
    if (n == -1) return -1; // error

    // data must be here, so do a normal recv()
    return recv(s, buf, len, 0);
}
.
.
.
// Sample call to recvtimeout():
n = recvtimeout(s, buf, sizeof buf, 10); // 10 second timeout

if (n == -1) {
    // error occurred
    perror("recvtimeout");
}
else if (n == -2) {
    // timeout occurred
} else {
    // got some data in buf
}
.
.
.

recvtimeout() はタイムアウトした場合、-2 を返すことに注意してください。なぜ 0 を返さないのでしょうか?そうですね、思い出してみてください、recv() を呼び出したときに 0 という値が返ってきたら、リモート側が接続を閉じたことを意味します。そのため、この戻り値はすでに話されており、-1 は "error" を意味します。そこで、タイムアウトのインジケータとして -2 を選択しました。

8.13 ソケットで送信する前にデータを暗号化または圧縮するにはどうすればよいですか?

暗号化を行う簡単な方法として、SSL(セキュア・ソケット・レイヤー)を使用する方法がありますが、このガイドの範囲を超えています。(詳しくは OpenSSL プロジェクトを見てください。)

しかし、独自のコンプレッサーや暗号化システムを導入する場合は、データが両端間で一連のステップを通過すると考えればよいのです。各ステップは、何らかの形でデータを変更します。

  1. サーバがファイルやどこからでもデータを読み取ります。
  2. サーバがデータを暗号化・圧縮します(この部分を追加します)。
  3. サーバが暗号化されたデータを send() します。

今度はその逆です。

  1. クライアントが暗号化されたデータを recv() します。
  2. クライアントがデータを復号・伸張します(この部分はあなたが追加します)。
  3. クライアントがデータをファイル(または任意の場所)に書き込みます。

圧縮して暗号化するなら、最初に圧縮することだけは忘れないでください。:-)

ただ、サーバがやったことをクライアントがきちんと元に戻してくれれば、いくら中間ステップを追加しても、最終的にはデータは大丈夫です。

つまり、私のコードを使うために必要なことは、データが読み込まれる場所と、ネットワーク上で(send() を使って)データが送信される場所の間にある場所を見つけ、そこに暗号化を行うコードを貼り付けるだけです。

8.14 "PF_INET" をずっと見ているのですが、これは何ですか?AF_INETと関係があるのでしょうか?

はい、そうです。詳しくは socket() の章を参照してください。

8.15 クライアントからシェルコマンドを受け取って実行するサーバを書くにはどうしたらいいでしょうか?

簡単のために、クライアントが connect()s, send()s, close()s で接続したとしましょう(つまり、クライアントが再び接続しない限り、その後のシステムコールはありません)。

クライアントがたどるプロセスはこうです。

  1. サーバに connect() します。
  2. send("/sbin/ls > /tmp/client.out")
  3. 接続を close() します。

一方、サーバはデータを処理し、実行します。

  1. クライアントからの接続を accept() します。
  2. コマンド文字列を recv(str) します。
  3. 接続を close() します。
  4. system(str) を使ってコマンドを実行します。

ご注意ください!サーバがクライアントの言うことを実行することは、リモートシェルアクセスを与えるようなもので、サーバに接続すると、あなたのアカウントに何かすることができます。例えば、上記の例で、クライアントが "rm -rf ~" と送ったらどうでしょう?それはあなたのアカウント内のすべてを削除してしまいます。

そこで、賢明にも、foobar ユーティリティのような、安全だとわかっているいくつかのユーティリティを除いて、クライアントが何も使えないようにするのです。

if (!strncmp(str, "foobar", 6)) {
    sprintf(sysstr, "%s > /tmp/server.out", str);
    system(sysstr);
}

しかし、残念ながらまだ安全ではありません。クライアントが "foobar; rm -rf ~" と入力したらどうでしょうか。最も安全なのは、コマンドの引数に含まれるすべての非英数字(適切であればスペースを含む)の前にエスケープ文字("\")を置く小さなルーチンを書くことです。

このように、クライアントが送信したものをサーバが実行し始めると、セキュリティはかなり大きな問題になります。

8.16 slew でデータを送っているのですが、recv() すると、一度に 536 バイトか 1460 バイトしか受信できません。しかし、私のローカルマシンで実行すると、すべてのデータを同時に受信することができます。どうなっているのでしょうか?

物理媒体が処理できる最大サイズであるMTUに当たっています。ローカルマシンでは、8K 以上を問題なく処理できるループバックデバイスを使用しています。しかし、Ethernet では、ヘッダーを含めて 1500 バイトしか扱えないので、その限界にぶつかってしまうのです。モデムでは、576 MTU(これもヘッダ付き)で、さらに低い制限にぶつかります。

まず、すべてのデータが送信されていることを確認する必要があります。(詳しくは sendall() 関数の実装を参照してください。)それが確認できたら、すべてのデータが読み込まれるまで、ループで recv() を呼び出す必要があります。

recv() を複数回呼び出して完全なデータパケットを受信する方法については、7.6 データカプセル化の子 の章を読んでください。

8.17 私は Windows マシンを使っていますが、fork() システムコールも struct sigaction のようなものも持っていません。どうしたらいいでしょうか?

もし、どこかにあるとすれば、コンパイラに同梱されているであろう POSIX ライブラリでしょう。私は Windows を持っていないので、答えようがないのですが、Microsoft には POSIX 互換性レイヤーがあり、そこに fork() があるように記憶しています。(そして多分、sigaction も。)

VC++ 付属のヘルプで "fork" または "POSIX" を検索して、何か手がかりがないか見てみてください。

もし、それでうまくいかない場合は、fork()/sigaction を捨てて、Win32 の CreateProcess() に置き換えてください。私は CreateProcess() の使い方を知りません。引数をたくさん取りますが、VC++ に付属するドキュメントに記載されているはずです。

8.18 ファイアウォールの内側にいるのですが、ファイアウォールの外側にいる人に私の IP アドレスを知らせて、私のマシンに接続できるようにするにはどうしたらよいでしょうか?

残念ながら、ファイアウォールの目的は、ファイアウォール外の人がファイアウォール内のマシンに接続するのを防ぐことなので、それを許可することは基本的にセキュリティ違反とみなされます。

これは、すべてが失われたということではありません。ひとつには、ファイアウォールがマスカレードやNATのようなことを行っている場合、しばしば connect() を通して接続することができます。自分が常に接続を開始するようにプログラムを設計すれば、うまくいくでしょう。

もしそれで満足できないなら、システム管理者に頼んでファイアウォールに穴を開けてもらい、人々があなたに接続できるようにすることができます。ファイアウォールは NAT ソフトウェアやプロキシなどを通して、あなたに転送することができます。

ファイアウォールの穴は、決して軽視できないものであることを認識しておいてください。悪い人が内部ネットワークにアクセスできないようにしなければなりません。初心者の場合、ソフトウェアを安全にするのは想像以上に難しいのです。

シスアドを怒らせないようにね。;-)

8.19 パケットスニファーの書き方を教えてください。イーサネットインタフェースをプロミスキャスモードにするにはどうしたらよいですか?

ネットワークカードが "プロミスカスモード" の場合、このマシン宛のパケットだけでなく、すべてのパケットをオペレーティングシステムに転送することをご存じない方のために説明します。(しかし、イーサネットは IP より下位の層なので、すべての IP アドレスは効果的に転送されます。より詳しくは、2.2 低レベルのナンセンスとネットワーク理論 の章をご覧ください。)

これは、パケットスニファーの仕組みの基本です。インターフェイスをプロミスカスモードにし、OSはワイヤー上を通過するすべてのパケットを取得します。 このデータを読むために、ある種のソケットを持っているはずです。

残念ながら、その答えはプラットフォームによって異なりますが、例えば "windows promiscuous ioctl" でググれば、おそらくどこかにたどり着けるはずです。Linux の場合は、Stack Overflow のスレッドにも有用そうなものがあります。

8.20 TCP/UDP ソケットにカスタムタイムアウト値を設定するにはどうすればよいですか?

お使いのシステムに依存します。ネットで SO_RCVTIMEOSO_SNDTIMEOsetsockopt() で使用)を検索して、あなたのシステムがその機能をサポートしているかどうかを確認するとよいでしょう。

Linux のマニュアルでは,alarm()setitimer() で代用することを勧めています.

8.21 どのポートが使用可能か、どうすれば分かりますか?"公式" なポート番号の一覧はありますか?

通常、これは問題ではありません。もしあなたが、例えばウェブサーバを書いているのであれば、よく知られているポート 80 をあなたのソフトウェアに使用するのは良い考えです。もし、自分専用のサーバを書くのであれば、適当にポートを選んで(ただし 1023 より大きい)試してみてください。

そのポートがすでに使われている場合、bind() しようとすると "Address already in use" というエラーが表示されます。他のポートを選んでください。(ソフトウェアのユーザが、設定ファイルやコマンドラインスイッチで別のポートを指定できるようにしておくとよいでしょう。)

IANA(Internet Assigned Numbers Authority)が管理している公式ポート番号一覧があります。そのリストにあるもの(1023 以上)があるからといって、そのポートを使ってはいけないというわけではありません。例えば、Id Software 社の DOOM は、それが何であれ、"mdqs" と同じポートを使っています。重要なのは、あなたがそのポートを使いたいときに、同じマシンの他の誰もそのポートを使っていないことです。