1 はじめに
ssコマンドを実行すると、Recv-QとSend-Q という項目が表示されます。本記事では、LISTEN状態のときのRecv-QとSend-Qの意味について実験を通して確認してみます。ESTABLISHED状態のときの、Recv-QとSend-Qの意味については、ssコマンドのRecv-Q,Send-Qの意味について(ESTABLISHED状態のとき) - hana_shinのLinux技術ブログを参照してください。
[root@server ~]# ss -antpo4 State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=842,fd=5)) ESTAB 0 0 192.168.122.68:22 192.168.122.1:51132 users:(("sshd",pid=1504,fd=5),("sshd",pid=1486,fd=5)) timer:(keepalive,49min,0)
ssコマンドの使い方は、ssコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。
それぞれの意味は以下になります。
項目 | 意味 |
---|---|
Recv-Q | TCPコネクションは確立済したけど、まだアプリケーションがacceptシステムコールを実行していないため、backlog(*)に保留されているTCPコネクション確立済の要求数を表します。最大値は、なぜかbacklog最大値+1の値になります。別途調査予定。 |
Send-Q | ソケットのbacklog(*)の最大値を表します |
(*) backlogは、TCPコネクション確立済の要求をキューイングするキューです。アプリケーションがacceptシステムコールを実行すると、このキューから要求が取り出されます。キュー長の最大値はlistenシステムコールの第2引数で指定します。しかし、カーネルパラメータ(net.core.somaxconn)が示すシステム上限値を超えることはできません。
2 検証環境
2.1 ネットワーク構成
サーバとクライアントの2台構成です。図中のeth0はNICの名前です。
192.168.122.0/24 client(eth0) ------------------------------------------(eth0) 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
3 事前準備
クライアントからサーバの11111番ポートにアクセスできるようにするため、TCPの11111番ポートを解放します。なお、firewall-cmdコマンドの使い方は、firewall-cmdの使い方 - hana_shinのLinux技術ブログを参照してください。
[root@server ~]# firewall-cmd --add-port=11111/tcp
設定を確認します。TCPの11111番ポートへのアクセスを許可するルールが追加されたことがわかります。
[root@server ~]# firewall-cmd --list-ports 11111/tcp
4 テストプログラム(以降TP)の作成
サーバ、クライアントで実行するTPを作成します。ソースコードを見やすくするため、意図的に異常処理は省略しています。また、ソケットオプションの動作確認を目的としているため、実用的なプログラムにはなっていません。
4.1 サーバ側
TPの引数には、backlogの最大値を指定します。
[root@server ~]# cat sv.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/wait.h> #include <netinet/in.h> int main(int argc, char *argv[]) { int lfd, cfd, status, backlog, on=1; socklen_t len; struct sockaddr_in sv, cl; pid_t pid; lfd = socket(AF_INET, SOCK_STREAM, 0); sv.sin_family = AF_INET; sv.sin_port = htons(11111); sv.sin_addr.s_addr = INADDR_ANY; backlog=atoi(argv[1]); setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); bind(lfd, (struct sockaddr *)&sv, sizeof(sv)); listen(lfd, backlog); while (1) { len = sizeof(cl); cfd = accept(lfd, (struct sockaddr *)&cl, &len); printf("accept\n"); if((pid=fork()) == 0){ //child close(lfd); printf("child PID=%d\n", getpid()); close(cfd); exit(0); } else{ // parent close(cfd); waitpid(pid, &status, 0); exit(0); } } }
コンパイルします。
[root@server ~]# gcc -Wall -o sv sv.c
4.2 クライアント側
connectシステムコールを実行して、サーバとTCPコネクションを確立します。TCPコネクション確立後は、データの送受信はしません。TCPコネクション確立後、600秒スリープします
[root@client ~]# cat cl.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> int main(int argc, char *argv[]) { int sock; struct sockaddr_in server; 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"); connect(sock, (struct sockaddr *)&server, sizeof(server)); sleep(600); }
コンパイルします。
[root@client ~]# gcc -Wall -o cl cl.c
5 Send-Qの確認
5.1 TPの引数に1を指定した場合
サーバでTPを実行します。このとき、straceコマンドを実行すると、listenシステムコールの第2引数に1を指定していることがわかります。また、acceptシステムコールが完了していないので、TCPコネクションの確立待ちであることがわかります。TCPのコネクションが確立すると、acceptシステムコールが復帰します。なお、straceコマンドの使い方は、straceコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。
[root@server ~]# strace -ttT -f -e trace=network ./sv 1 21:52:03.856722 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3 <0.000225> 21:52:03.858151 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 <0.000264> 21:52:03.859615 bind(3, {sa_family=AF_INET, sin_port=htons(11111), sin_addr=inet_addr("0.0.0.0")}, 16) = 0 <0.000492> 21:52:03.860493 listen(3, 1) = 0 <0.000830> 21:52:03.862325 accept(3,
ssコマンドを実行すると、backlogの最大値が1であることがわかります。
[root@server ~]# ss -antop4 'sport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 1 0.0.0.0:11111 0.0.0.0:* users:(("sv",pid=2411,fd=3))
5.2 TPの引数に2を指定した場合
サーバでTPを実行します。このとき、straceコマンドを実行すると、listenシステムコールの第2引数に2を指定していることがわかります。
[root@server ~]# strace -ttT -f -e trace=network ./sv 2 21:52:37.688721 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3 <0.000677> 21:52:37.690257 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 <0.000526> 21:52:37.691767 bind(3, {sa_family=AF_INET, sin_port=htons(11111), sin_addr=inet_addr("0.0.0.0")}, 16) = 0 <0.001065> 21:52:37.694699 listen(3, 2) = 0 <0.000927> 21:52:37.696554 accept(3,
ssコマンドを実行すると、backlogの最大値が2であることがわかります。
[root@server ~]# ss -antop4 'sport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 2 0.0.0.0:11111 0.0.0.0:* users:(("sv",pid=2417,fd=3))
5.3 TPの引数に256を指定した場合
サーバでTPを実行します。このとき、straceコマンドを実行すると、listenシステムコールの第2引数に256を指定していることがわかります。
[root@server ~]# strace -ttT -f -e trace=network ./sv 256 21:53:13.642364 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3 <0.000374> 21:53:13.643762 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 <0.000485> 21:53:13.644835 bind(3, {sa_family=AF_INET, sin_port=htons(11111), sin_addr=inet_addr("0.0.0.0")}, 16) = 0 <0.000340> 21:53:13.646043 listen(3, 256) = 0 <0.000609> 21:53:13.647567 accept(3,
ssコマンドを実行すると、backlogの最大値がlistenシステムコールに指定した256ではなく128であることがわかります。これは、カーネルパラメータ(システムの最大値)のbacklog最大値が128(後述)だからです。ソケットオプションの最大値はカーネルパラメータの最大値を超えることはできません。
[root@server ~]# ss -antop4 'sport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 128 0.0.0.0:11111 0.0.0.0:* users:(("sv",pid=2422,fd=3))
カーネルパラメータを確認すると、backlog最大値が128であることがわかります。
[root@server ~]# sysctl -n net.core.somaxconn 128
6 Recv-Qの確認
6.1 TPの引数に1を指定した場合
サーバでTPを実行します。この時、引数に1を指定してbacklog最大値を1に設定します。
[root@server ~]# ./sv 1
psコマンドを実行してプロセスの状態を確認すると、svプロセスがスリープ(S)していることがわかります。なお、psコマンドの使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。
[root@server ~]# ps -C sv -o comm,pid,ppid,state COMMAND PID PPID S sv 2440 1489 S
Ctrl+Zを押下して、svプロセスを停止状態にします。プロセスを停止する理由は、svプロセスがacceptシステムコールを実行してbacklogよりTCPコネクション確立済の要求を取り出さないようにするためです。要求を取り出さないようにすることで、backlogに要求を溜めていきます。
[root@server ~]# ./sv 1 ^Z [1]+ 停止 ./sv 1
psコマンドを実行してプロセスの状態を確認すると、svプロセスが停止(T)していることがわかります。この状態でTCPコネクションが確立しても、acceptシステムコールは復帰しません。
[root@server ~]# ps -C sv -o comm,pid,ppid,state COMMAND PID PPID S sv 2440 1489 T
クライアントでTPを実行します。
[root@client ~]# ./cl
サーバでssコマンドを実行します。TCPコネクション確立済だけど、まだacceptシステムコールが実行されていないため、backlogに保留されている要求が1つあることがわかります。
[root@server ~]# ss -antop4 'sport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 1 1 0.0.0.0:11111 0.0.0.0:* users:(("sv",pid=2440,fd=3)) ESTAB 0 0 192.168.122.68:11111 192.168.122.177:35698
クライアントでTPを実行(2回目)します。
[root@client ~]# ./cl
サーバでssコマンドを実行します。TCPコネクション確立済だけど、まだacceptシステムコールが実行されていないため、backlogに保留されている要求が2つあることがわかります。
[root@server ~]# ss -antop4 'sport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 2 1 0.0.0.0:11111 0.0.0.0:* users:(("sv",pid=2440,fd=3)) ESTAB 0 0 192.168.122.68:11111 192.168.122.177:35698 ESTAB 0 0 192.168.122.68:11111 192.168.122.177:35700
クライアントでTPを実行(3回目)します。
[root@client ~]# ./cl
サーバでssコマンドを実行すると、backlogに保留している要求が3つではなく2つあることがわかります(期待値)。
[root@server ~]# ss -antop4 'sport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 2 1 0.0.0.0:11111 0.0.0.0:* users:(("sv",pid=2440,fd=3)) ESTAB 0 0 192.168.122.68:11111 192.168.122.177:35698 ESTAB 0 0 192.168.122.68:11111 192.168.122.177:35700
6.2 TPの引数に2を指定した場合
サーバでTPを実行します。この時、引数に1を指定してbacklog最大値を2に設定します。
[root@server ~]# ./sv 2
psコマンドを実行して、プロセスの状態を確認すると、スリープ状態であることがわかります。
[root@server ~]# ps -C sv -o comm,pid,ppid,state COMMAND PID PPID S sv 2451 1489 S
Ctrl+Zを押下して、svプロセスを停止状態にします。
[root@server ~]# ./sv 2 ^Z [1]+ 停止 ./sv 2
クライアントでTPを実行(1回目)します。
[root@client ~]# ./cl
サーバでssコマンドを実行します。TCPコネクション確立済だけど、まだacceptシステムコールが実行されていないため、backlogに保留されている要求が1つあることがわかります。
[root@server ~]# ss -antop4 'sport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 1 2 0.0.0.0:11111 0.0.0.0:* users:(("sv",pid=2451,fd=3)) ESTAB 0 0 192.168.122.68:11111 192.168.122.177:35704
クライアントでTPを実行(2回目)します。
[root@client ~]# ./cl
サーバでssコマンドを実行します。TCPコネクション確立済だけど、まだacceptシステムコールが実行されていないため、backlogに保留されている要求が2つあることがわかります。
[root@server ~]# ss -antop4 'sport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 2 2 0.0.0.0:11111 0.0.0.0:* users:(("sv",pid=2451,fd=3)) ESTAB 0 0 192.168.122.68:11111 192.168.122.177:35706 ESTAB 0 0 192.168.122.68:11111 192.168.122.177:35704
クライアントでTPを実行(3回目)します。
[root@client ~]# ./cl
サーバでssコマンドを実行します。TCPコネクション確立済だけど、まだacceptシステムコールが実行されていないため、backlogに保留されている要求が3つあることがわかります。
[root@server ~]# ss -antop4 'sport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 3 2 0.0.0.0:11111 0.0.0.0:* users:(("sv",pid=2451,fd=3)) ESTAB 0 0 192.168.122.68:11111 192.168.122.177:35706 ESTAB 0 0 192.168.122.68:11111 192.168.122.177:35708 ESTAB 0 0 192.168.122.68:11111 192.168.122.177:35704
クライアントでTPを実行(4回目)します。
[root@client ~]# ./cl
サーバでssコマンドを実行すると、backlogに保留している要求が4つではなく3つあることがわかります(期待値)。
[root@server ~]# ss -antop4 'sport == :11111' State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 3 2 0.0.0.0:11111 0.0.0.0:* users:(("sv",pid=2451,fd=3)) ESTAB 0 0 192.168.122.68:11111 192.168.122.177:35706 ESTAB 0 0 192.168.122.68:11111 192.168.122.177:35708 ESTAB 0 0 192.168.122.68:11111 192.168.122.177:35704
Z 参考情報
私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ