1 SO_SNDTIMEOとは?
SO_SNDTIMEOは、connectシステムコールや送信系(write,send,sendto,sendmsg)システムコールに対して使用するソケットオプションです。これらシステムコールのデフォルト動作はブロッキングモードです。そのため、以下の状況のとき、システムコールは実行完了しません(ブロックしたままになります)。しかし、SO_SNDTIMEOを使用すると、指定した時間でシステムコールの実行を完了することができます。
・connectシステムコール:相手からSYN+ACKパケットを受信できない場合(別途記事作成予定)
・送信系システムコール:ソケットの送信バッファに空きスペースがない場合(今回の記事はこちらが対象)
ここでは、送信系システムコールの動作確認をしてみたいと思います。
なお、SO_RCVTIMEOについては、ソケットオプションの使い方(SO_RCVTIMEO編) - hana_shinのLinux技術ブログを参照してください。
2 検証環境
2.1 ネットワーク構成
サーバとクライアントの2台構成です。図中のenp1s0はNICの名前です。
192.168.122.0/24 client(enp1s0) ------------------------------------------(enp1s0) server .177 .68
2.2 版数
サーバとクライアントのAlmaLinuxの版数は以下のとおりです。
[root@server ~]# cat /etc/redhat-release AlmaLinux release 8.6 (Sky Tiger)
カーネル版数は以下のとおりです。
[root@server ~]# uname -r 4.18.0-372.9.1.el8.x86_64
クライアントからサーバの11111番ポートにアクセスできるようにするため、TCPの11111番ポートを解放します。なお、firewall-cmdの使い方は、firewall-cmdの使い方は、firewall-cmdの使い方 - hana_shinのLinux技術ブログを参照してください。
[root@server ~]# firewall-cmd --add-port=11111/tcp success
ルールを確認します。11111番ポートへのアクセスを許可するルールが追加されたことがわかります。
[root@server ~]# firewall-cmd --list-ports 11111/tcp
3 テストプログラム(以降TP)の作成
・サーバ側:ncコマンドを使います。なお、ncコマンドのインストール方法、使い方は、ncコマンドの使い方(ネットワーク実験の幅が広がるなぁ~) - hana_shinのLinux技術ブログを参照してください。
・クライアント側:以下のTPをコンパイルして使用します。なお、見やすくするため、不要な異常処理は意図的に省略しています。
[root@client ~]# cat cl.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <time.h> #include <arpa/inet.h> int main(int argc, char *argv[]) { int sock, n, i; struct sockaddr_in server; struct timespec req = {0, 20 * 1000000}; //20ms struct timeval timeout; char buf[4096]; sock = socket(AF_INET, SOCK_STREAM, 0); server.sin_family = AF_INET; server.sin_port = htons(11111); server.sin_addr.s_addr = inet_addr("192.168.122.68"); if(atoi(argv[1]) != 0) { timeout.tv_sec =atoi(argv[1]); timeout.tv_usec = 0; setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof timeout); } connect(sock, (struct sockaddr *)&server, sizeof(server)); for(i=0; i<2000; i++) { nanosleep(&req, NULL); n = write(sock, buf, sizeof(buf)); if(n < 0) { perror("write"); close(sock); exit(1); } } close(sock); exit(0); }
TPの第1引数の意味は次のとおりです。
第1引数 | 意味 |
---|---|
0 | ソケットの送信バッファに空きスペースがなく、プロセスが送信データの書き込みができないと、システムコールの実行が完了しません。つまり、送信バッファに空きスペースができるまで、ブロックしたままになります |
0以外 | システムコールの実行時間を指定します。ソケットの送信バッファに空きスペースがなく、プロセスが送信データの書き込みができない場合、指定した時間でシステムコールの実行が完了します |
TPをコンパイルします。
[root@client ~]# gcc -Wall -o cl cl.c
4 検証結果
サー側でncコマンドを実行します。
[root@server ~]# nc -kl 11111 > /dev/null
4.1 TPの引数に1を指定した場合
クライアント側でTPを実行します。このとき、writeシステムコールの実行時間を確認するため、straceコマンドを使用します。なお、straceコマンドの使い方は、straceコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。
[root@client ~]# strace -e trace=write -ttT -f ./cl 1
クライアント側でTPを実行したら、サーバ側ですぐにCtrl+zを押下してncプロセスを停止します。
[root@server ~]# nc -kl 11111 > /dev/null ^Z [1]+ 停止 nc -kl 11111 > /dev/null
サーバ側でssコマンドの実行結果を確認します。ncプロセスが停止しているため、ソケットの受信バッファから受信データを読み出すことができません。そのため、ソケットの受信バッファに受信データ(220KBほど)が残ったままになっていることがわかります。なお、 Recv-QおよびSend-Qの見方は、ssコマンドのRecv-Q,Send-Qの意味について(ESTABLISHED状態のとき) - hana_shinのLinux技術ブログを参照してください。
[root@server ~]# ss -notp4 'sport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process ESTAB 220232 0 192.168.122.68:11111 192.168.122.177:55924 users:(("nc",pid=1930,fd=4))
クライアント側でssコマンドを実行します。ソケットの送信バッファに送信データ(130KBほど)が残っていることがわかります。これは、サーバ側のncプロセスが受信データを読み出せないため、クライアント側ソケットの送信バッファに送信データが残ったままになっています。
[root@client ~]# ss -notp4 'dport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process ESTAB 0 130320 192.168.122.177:55924 192.168.122.68:11111 users:(("cl",pid=2091,fd=3)) timer:(persist,282ms,1)
クライアント側でTPの実行結果を確認します。しばらくすると、TPが異常終了します。この理由は、ソケットの送信バッファに空きスペースがなく、TPが送信データを送信バッファに書き込むことができずタイムアウトが発生したからです。約1.00秒でwriteシステムコールがタイムアウトしていることがわかります。タイムアウトの時間は、TPの引数に指定した1秒におおよそ一致していることがわかります。
[root@client ~]# strace -e trace=write -ttT -f ./cl 1 -snip- 20:30:06.071264 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 272 <1.032207> 20:30:07.124736 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 EAGAIN (リソースが一時的に利用できません) <1.002016> 20:30:08.127457 write(4, "write: Resource temporarily unav"..., 40write: Resource temporarily unavailable ) = 40 <0.000025> 20:30:08.128205 +++ exited with 1 +++
次の検証のため、ncプロセスを再開します。
[root@server ~]# fg nc -kl 11111 > /dev/null
4.2 TPの引数に2を指定した場合
クライアント側でTPを実行します。
[root@client ~]# strace -e trace=write -ttT -f ./cl 2
クライアント側でTPを実行したら、サーバ側ですぐにCtrl+zを押下してncプロセスを停止します。
[root@server ~]# nc -kl 11111 > /dev/null ^Z [1]+ 停止 nc -kl 11111 > /dev/null
サーバ側でssコマンドの実行結果を確認します。ncプロセスが停止しているため、ncプロセスがソケットの受信バッファから受信データを読み出せないため、ソケットの受信バッファに受信データ(172KBほど)が残ったままになっていることがわかります
[root@server ~]# ss -notp4 'sport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process ESTAB 172032 0 192.168.122.68:11111 192.168.122.177:55926 users:(("nc",pid=1933,fd=4))
クライアント側でssコマンドを実行します。ソケットの送信バッファに送信データ(130KBほど)が残っていることがわかります。
[root@client ~]# ss -notp4 'dport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process ESTAB 0 130320 192.168.122.177:55926 192.168.122.68:11111 users:(("cl",pid=2099,fd=3)) timer:(persist,892ms,1)
クライアント側でTPの実行結果を確認します。しばらくすると、TPが異常終了します。この理由は、ソケットの送信バッファに空きスペースがなく、TPが送信データを送信バッファに書き込むことができずタイムアウトが発生したからです。約2.03秒でwriteシステムコールがタイムアウトしていることがわかります。タイムアウトの時間は、TPの引数に指定した2秒におおよそ一致していることがわかります。
[root@client ~]# strace -e trace=write -ttT -f ./cl 2 -snip- 20:32:45.464058 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 2272 <2.022807> 20:32:47.508146 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = -1 EAGAIN (リソースが一時的に利用できません) <2.026723> 20:32:49.535828 write(4, "write: Resource temporarily unav"..., 40write: Resource temporarily unavailable ) = 40 <0.000020> 20:32:49.536596 +++ exited with 1 +++
次の検証のため、ncプロセスを再開します。
[root@server ~]# fg nc -kl 11111 > /dev/null
4.3 TPの引数に0を指定した場合
クライアント側でTPを実行します。
[root@client ~]# strace -e trace=write -ttT -f ./cl 0
クライアント側でTPを実行したら、サーバ側ですぐにCtrl+zを押下してncプロセスを停止します。
[root@server ~]# nc -kl 11111 > /dev/null ^Z [1]+ 停止 nc -kl 11111 > /dev/null
サーバ側でssコマンドの実行結果を確認します。ncプロセスが停止しているため、ncプロセスがソケットの受信バッファから受信データを読み出せないため、ソケットの受信バッファに受信データ(242KBほど)が残ったままになっていることがわかります
[root@server ~]# ss -notp4 'sport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process ESTAB 242808 0 192.168.122.68:11111 192.168.122.177:55928 users:(("nc",pid=1933,fd=4))
クライアント側でssコマンドを実行します。ソケットの送信バッファに送信データ(130KBほど)が残っていることがわかります。
[root@client ~]# ss -notp4 'dport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process ESTAB 0 130320 192.168.122.177:55928 192.168.122.68:11111 users:(("cl",pid=2105,fd=3)) timer:(persist,71582min,0)
クライアント側でTPの実行結果を確認します。ソケットの送信バッファに空きスペースがなくても、writeシステムコールはタイムアウトしません。ブロックしたままになります(期待値)。
[root@client ~]# strace -e trace=write -ttT -f ./cl 0 -snip- 20:38:08.973778 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 <0.000066> 20:38:08.994808 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 <0.000061> 20:38:09.015844 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096
Z 参考情報
私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ