記事を書き始めた経緯
上記の記事を読んでいる際にTCP_DEFER_ACCEPT
なるものがあることを知ったので気になったので調べてみたメモです。
TCP_DEFER_ACCEPTとは
何はともあれmanを見ます。
TCP_DEFER_ACCEPT (Linux 2.4 以降) これを用いると、リスナはデータがソケットに到着した時のみ目覚めるようになる。 整数値 (秒) をとり、 TCP が接続を完了しようと試みる回数を制限できる。 移植性の必要なプログラムではこのオプションを用いるべきではない。
TCP_DEFER_ACCEPT
はざっくり言うとaccept(2)を遅延させ実データを送った/送られたタイミングで戻すと言うイメージ。同時接続数=プロセス数(スレッド数)みたいなモデルのサーバで有効的に働く機能とのこと。例えばクライアントが大量に存在するケースで実データをなかなか送ってこないスロークライアントが混ざっているとスロークライアントとコネクションを確立してしまったプロセスが処理遅延してしまうみたいなケースをこのオプションを使えば防ぐことができる。
クライアントで指定した場合とサーバで指定した場合で意味合いが変わります。サーバーの場合、TCP_DEFER_ACCEPTは、3ウェイハンドシェイクが完了した後、クライアントデータが到着するのを待ちます。クライアントの場合、syn->syn/ackが到着すると、ハンドシェイクの最後のackを直接送信せず、遅延ackタイマーをリセットし、実データを一緒に送信します。
実践
#include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/socket.h> #include <netinet/tcp.h> #include <netinet/in.h> #include <time.h> int main(int argc, char *argv[]) { int lfd, cfd; socklen_t len; struct sockaddr_in sv, cl; char buf[32]; time_t start_time, end_time; lfd = socket(AF_INET, SOCK_STREAM, 0); int val = 20; if(setsockopt(lfd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &val, sizeof(int)) == -1) { printf("error\n"); } sv.sin_family = AF_INET; sv.sin_port = htons(11111); sv.sin_addr.s_addr = INADDR_ANY; bind(lfd, (struct sockaddr *)&sv, sizeof(sv)); listen(lfd, 5); memset(buf, 0, sizeof(buf)); len = sizeof(cl); start_time = time(NULL); cfd = accept(lfd, (struct sockaddr *)&cl, &len); end_time = time(NULL); printf("time:%ld\n", end_time - start_time); }
実行結果
ncとかを使って適当にクライアントからデータを送ったりしてみます。
# タイムアウトさせるケース $ ./a.out time:33 ### netstatはsyn_recvになってます tcp 0 0 192.168.1.210:11111 192.168.1.210:54050 SYN_RECV - on (3.16/3/0) # 実データを送ったケース $ ./a.out time:4
クライアントの場合はSND_BUFの設定やサーバでの設定が大変なので今回はやっていませんがサーバ側が実データ受け取りに時間がかかっている場合に有効な設定となるはずです。
nginxでは
listenでオプションとして指定可能とのこと。タイムアウト秒数の指定はパラメータとしては変更できないようで1秒がハードコードされていました。(ドキュメントの説明量の少なさだったり使用事例があまりないのであんま使われないオプションのように見えました。実際はどうなんでしょう)
deferred instructs to use a deferred accept() (the TCP_DEFER_ACCEPT socket option) on Linux.
apache/httpdでは
一応こちらでも指定は可能。