Linux で TCP クライアントを実装する上での注意点・初級編
「ソケットを直に触るプログラムを書くのは初めてなんですが、何かアドバイスないですか?」みたいなことを聞かれたので、入門書には載ってなさそうな注意点をまとめてみる。
とは言っても、私自身、直にソケットを叩いて C や C++ でプログラムを書いていたのは遠い過去のことだし、その道の専門家でもないので、誤りや抜けてる項目に気づいた方や、よりベターな方法を知ってる方は指摘していただければありがたい。
前提
注意点まとめ
- あらゆるシステムコール/ライブラリ関数が失敗する可能性を考慮する。
- 入門書や解説本ではエラー処理を省略している場合がある。
fdopen()
を使ってソケットからFILE
構造体を作ってはいけない。- ライブラリのレベルでバッファリングされると困る。バグの元。
- そういうコードを見たことがあるので……。
SIGPIPE
に注意。SIGPIPE
のデフォルト動作はプロセスの終了。コマンドラインツールならその方が便利だが、ネットワーク通信では困る。signal(SIGPIPE, SIG_IGN)
で無視するなり、適切なハンドラーを書くなりして何とかする。
- システムコールで
EINTR
が返ってきたらリトライする。 - ブロックするシステムコール/ライブラリ関数に注意する。
write()
等の書き込み系システムコールでは、渡したデータが全て書き込まれるとは限らない。(partial write と呼ばれる動作)
各論: gethostbyname() 等のタイムアウト
gethostbyname()
等のリゾルバ関数のタイムアウト時間を変更するには、resolv.h
で定義されている リゾルバ構造体 _res
の値を変更する。
res_state resp = &_res; resp->retrans = 1; // DNS リクエストの再送間隔(単位: 秒). 1以上. resp->retry = 1; // DNS リクエストの試行回数(単位: 回). 1以上.
各論: connect() のタイムアウト
connect()
のタイムアウトを制御するには Non-blocking I/O を使うと良い。基本方針は以下のようになる。
fcntl()
を使ってソケットにO_NONBLOCK
をセット。connect()
を実行。select()
やpoll()
を使って、書き込み可能になるのを待つ。- ここでエラーが返ったりタイムアウトになった場合は適切に処理する。
- ソケットが書き込み可能になったら、
getsockopt()
を使ってエラー情報を取得。getsockopt(fd, SOL_SOCKET, SO_ERROR, ...)
を使う。man 7 socket
を参照。