地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

TCP_DEFER_ACCEPTが気になったので調べた

記事を書き始めた経緯

blog.yuuk.io

上記の記事を読んでいる際にTCP_DEFER_ACCEPTなるものがあることを知ったので気になったので調べてみたメモです。

TCP_DEFER_ACCEPTとは

何はともあれmanを見ます。

linuxjm.osdn.jp

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.

mogile.web.fc2.com

apache/httpdでは

一応こちらでも指定は可能。

httpd.apache.org