setsockopt(2)でSO_SNDTIMEO, SO_RCVTIMEOが指定できない?
忘れたころに使いどころが出てくるソケットまわりの話.socketの設定にはsetsockopt(2)でいろいろやるのだけれど,今回は以下の2ケースのタイムアウト設定に挑戦して,ぜーんぶうまくいかなかったけれど,いろいろ試行錯誤したから失敗記事にしてみる.
今回は,以下の環境で実験した.ふたつの環境で挙動が違ったから,また泣きたくなる.
(1) connectタイムアウトの設定
まず(1)から.以下の記事を見ると,どうやらsetsockopt(2)でSO_SNDTIMEOを設定すればよさそう.
なんだ,簡単じゃないかと,エラー処理のない書き殴りソースを書いてみる.
- connect.c
// // % echo "fugafuga" | nc -l -p 4649 // #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> int main (int argc, char *argv[]) { int sock = socket( PF_INET, SOCK_STREAM, 0 ); struct timeval send_tv; send_tv.tv_sec = 1; send_tv.tv_usec = 0; setsockopt( sock, SOL_SOCKET, SO_SNDTIMEO, &send_tv, sizeof(send_tv) ); struct timeval recv_tv; recv_tv.tv_sec = 5; recv_tv.tv_sec = 0; // コメントアウトすると1秒でTIMEOUTされない setsockopt( sock, SOL_SOCKET, SO_RCVTIMEO, &recv_tv, sizeof(recv_tv) ); struct sockaddr_in sa; memset( &sa, 0, sizeof( sa ) ); // sa.sin_len = sizeof( sa ); sa.sin_family = AF_INET; sa.sin_port = htons( 4649 ); sa.sin_addr.s_addr = inet_addr( "127.0.0.1" ); connect( sock, (struct sockaddr *)&sa, sizeof( sa ) ); char buff[ 1024 ]; read( sock, buff, 1024 ); printf("%s\n", buff ); return 0; }
ちゃんとつながったかどうかを確認するために,netcatを使って4649ポートをlistenする.
% echo "fugafuga" | nc -l -p 4649
別の端末から以下を実行
% gcc -o connect connect.c % ./connect fugafuga
ちゃんと4649ポートに接続してメッセージを受け取ったことがわかる.
さて疑似的にホストがダウンしていることを再現するため,iptablesによるパケットフィルタリングを行う.
# iptables -A INPUT -p tcp -s 127.0.0.1 --dport 4649 -j DROP (設定解除は # iptables -D INPUT -p tcp -s 127.0.0.1 --dport 4649 -j DROP)
これで,localhostから来る4649ポート宛のパケットはdropされる.再びconnectを実行
% ./connect (X秒経過) %
(A)では,SO_SNDTIMEOの時間でタイムアウトした.ただし,SO_RCVTIMEOを設定しないと指定した時間でタイムアウトしなかった.(謎の挙動)
(B)では,SO_SNDTIMEO,SO_RCVTIMEOいずれの時間とも関係のない21秒でタイムアウトした.
(2) readタイムアウトの設定
今度は「接続は成功したのだけれど,readのタイミングでホストの応答がなくなった時にタイムアウトする時間」を指定したいというケース.このケースだと上記のnetcatを使ったサーバだと接続したタイミングでクライアントのreadバッファに入ってしまうので,scanf()で待ち時間を作るというテクい方法で再現してみる.
実験手順
- serverを起動
- clientを起動,scanf()で待ちが発生
- iptables でパケットフィルタする
- clientのscanf()を実行,readができない状態に陥る
という感じ.ソースコードは以下のとおり.
- server.c
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> int main (int argc, char *argv[]) { int sock = socket( PF_INET, SOCK_STREAM, 0 ); struct timeval send_tv; send_tv.tv_sec = 1; send_tv.tv_usec = 0; // 意味ない? setsockopt( sock, SOL_SOCKET, SO_SNDTIMEO, &send_tv, sizeof(send_tv) ); struct timeval recv_tv; recv_tv.tv_sec = 5; recv_tv.tv_sec = 0; // 意味ない? setsockopt( sock, SOL_SOCKET, SO_RCVTIMEO, &recv_tv, sizeof(recv_tv) ); struct sockaddr_in sa; memset( &sa, 0, sizeof( sa ) ); // sa.sin_len = sizeof( sa ); sa.sin_family = AF_INET; sa.sin_port = htons( 4649 ); sa.sin_addr.s_addr = inet_addr( "127.0.0.1" ); // localhostからのみ struct sockaddr_in client; int client_len = sizeof( client ); bind( sock, (struct sockaddr *)&sa, sizeof( sa ) ); listen( sock, 1 ); int client_sock = accept( sock, (struct sockaddr *)&client, (socklen_t *)&client_len ); char buff[ 1024 ]; read( client_sock, buff, 1024 ); printf("%s\n", buff ); char *msg = "bye"; write( client_sock, msg, strlen(msg) + 1 ); return 0; }
- client.c
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> int main (int argc, char *argv[]) { int sock = socket( PF_INET, SOCK_STREAM, 0 ); struct timeval send_tv; send_tv.tv_sec = 2; send_tv.tv_usec = 0; setsockopt( sock, SOL_SOCKET, SO_SNDTIMEO, &send_tv, sizeof(send_tv) ); struct timeval recv_tv; recv_tv.tv_sec = 5; recv_tv.tv_sec = 0; setsockopt( sock, SOL_SOCKET, SO_RCVTIMEO, &recv_tv, sizeof(recv_tv) ); struct sockaddr_in sa; memset( &sa, 0, sizeof( sa ) ); // sa.sin_len = sizeof( sa ); sa.sin_family = AF_INET; sa.sin_port = htons( 4649 ); sa.sin_addr.s_addr = inet_addr( "127.0.0.1" ); connect( sock, (struct sockaddr *)&sa, sizeof( sa ) ); char input[ 1024 ]; printf("Input message> "); scanf("%s", input); write( sock, input, strlen( input ) + 1 ); char buff[ 1024 ]; read( sock, buff, 1024 ); printf("%s\n", buff ); return 0; }
実験してみる.
端末1でサーバを起動
% ./server
端末2でクライアントを起動
% ./client Input message>
いまだ! iptablesを設定
# iptables -A INPUT -p tcp -s 127.0.0.1 --dport 4649 -j DROP
いまだ! clientでメッセージを入力
% ./client Input message> hoge
そして設定したSO_RCVTIMEO通りに動くか確認.ちーん.5秒でも1秒でも返ってきませんでした.なんでー??
というわけで,今回は疲れたのでwriteの方は確認していないけれど,おそらく同じような挙動になると思われる.うーむ.
どこかのページでSO_RCVTIMEOの実装はPOSIXではあくまで推奨になっているので環境によっては実装されてないよ,Linuxでは実装されてないよーという書き込みを見たのだけれど,見つからなくなっていた...まぼろし?