プリフォークの TCP サーバでプロセス数を超えた接続に RST を返す

下記のコードのようにプロセスをプリフォークして子プロセスで `accept` した場合、プロセス数が２なので同時に処理できるクライアントは２個までということになりますが、接続するだけなら２よりも多くできます。

Backlog という確立済みの TCP 接続をカーネルが保持しておいて、`accept` でユーザー空間にソケットのファイルディスクリプタを返す、という仕組みがあるためで、ユーザー空間で `accept` しなくても Backlog の数まではクライアントからの接続が受け入れられます。

なので、接続数が２を超えた場合にクライアントから見ると、接続できる、データも送信できる（サーバ側の受信バッファがいっぱいになるまでは）、けどなにも応答が帰ってこない、という状態になります。

```php
require_once __DIR__ . '/vendor/autoload.php';

use Parallel\Prefork;

function main()
{
    $pp = new Prefork(array(
        'max_workers'  => 2,
    ));

    init();

    while ($pp->signalReceived() !== SIGTERM) {
        if ($pp->start()) {
            continue;
        }
        work();
        $pp->finish();
    }
    $pp->waitAllChildren();
}

$listenSocket = null;
$listenPort = 12345;

function init()
{
    global $listenSocket;
    global $listenPort;

    $listenSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    socket_set_option($listenSocket, SOL_SOCKET, SO_REUSEADDR, 1);
    socket_set_option($listenSocket, SOL_SOCKET, SO_REUSEPORT, 1);
    socket_bind($listenSocket, "0.0.0.0", $listenPort);
    socket_listen($listenSocket, 1);

    printf("server listen %d\n", $listenPort);
}

function work()
{
    global $listenSocket;
    global $listenPort;

    printf("start worker process %d\n", posix_getpid());

    for (;;) {
        $socket = socket_accept($listenSocket);

        socket_getpeername($socket, $addr, $port);
        printf("connected %s:%s\n", $addr, $port);

        for (;;) {
            $str = socket_read($socket, 1024);
            if (strlen($str) === 0) {
                break;
            }
            printf("receive %d byte\n", strlen($str));
            while (strlen($str) > 0) {
                $len = socket_write($socket, $str);
                $str = substr($str, $len);
            }
        }

        printf("disconnect %s:%s\n", $addr, $port);
        $socket = null;
    }
}

main();
```

----

接続数が上限を超えたら直ちにクライアントに RST を応答するようにはできないものか、と思って調べていたら下記を発見しました。

- http://stackoverflow.com/questions/5145392/setting-listen-backlog-to-0

こんなのがありなのかどうかわかりませんが、次のように `accept` したら直ぐにリッスンソケットをクローズするようにすれば、接続数が２を超えるとクライアントに直ちに RST が返されるようになります。

```php
require_once __DIR__ . '/vendor/autoload.php';

use Parallel\Prefork;

function main()
{
    $pp = new Prefork(array(
        'max_workers'  => 2,
    ));

    while ($pp->signalReceived() !== SIGTERM) {
        if ($pp->start()) {
            continue;
        }
        work();
        $pp->finish();
    }
    $pp->waitAllChildren();
}

$listenSocket = null;
$listenPort = 12345;

function work()
{
    global $listenSocket;
    global $listenPort;

    printf("start worker process %d\n", posix_getpid());

    for (;;) {
        $listenSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        socket_set_option($listenSocket, SOL_SOCKET, SO_REUSEADDR, 1);
        socket_set_option($listenSocket, SOL_SOCKET, SO_REUSEPORT, 1);
        socket_bind($listenSocket, "0.0.0.0", $listenPort);
        socket_listen($listenSocket, 1);
        $socket = socket_accept($listenSocket);
        socket_close($listenSocket);
        $listenSocket = null;

        socket_getpeername($socket, $addr, $port);
        printf("connected %s:%s\n", $addr, $port);

        for (;;) {
            $str = socket_read($socket, 1024);
            if (strlen($str) === 0) {
                break;
            }
            printf("receive %d byte\n", strlen($str));
            while (strlen($str) > 0) {
                $len = socket_write($socket, $str);
                $str = substr($str, $len);
            }
        }

        printf("disconnect %s:%s\n", $addr, $port);
        $socket = null;
    }
}

main();
```

つまり、接続数が２になるとリッスンソケットが全部閉じられるのでリッスンしていないことになり、それ以上の接続に対しては RST が返されるようになります。