下記のコードのようにプロセスをプリフォークして子プロセスで accept
した場合、プロセス数が2なので同時に処理できるクライアントは2個までということになりますが、接続するだけなら2よりも多くできます。
Backlog という確立済みの TCP 接続をカーネルが保持しておいて、accept
でユーザー空間にソケットのファイルディスクリプタを返す、という仕組みがあるためで、ユーザー空間で accept
しなくても Backlog の数まではクライアントからの接続が受け入れられます。
なので、接続数が2を超えた場合にクライアントから見ると、接続できる、データも送信できる(サーバ側の受信バッファがいっぱいになるまでは)、けどなにも応答が帰ってこない、という状態になります。
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 を応答するようにはできないものか、と思って調べていたら下記を発見しました。
こんなのがありなのかどうかわかりませんが、次のように accept
したら直ぐにリッスンソケットをクローズするようにすれば、接続数が2を超えるとクライアントに直ちに RST が返されるようになります。
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();
つまり、接続数が2になるとリッスンソケットが全部閉じられるのでリッスンしていないことになり、それ以上の接続に対しては RST が返されるようになります。