PHP Sockets 构建 TCP 服务

基于 sockets 扩展的基本 TCP 服务端代码:

<?php

const HOST = '0.0.0.0';
const PORT = 8090;

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

socket_bind($socket, HOST, PORT);
socket_listen($socket, 3);

socket_set_block($socket);

// accept client to connect
while (true) {
    $client = socket_accept($socket);
    // loop to read special client message
    while (true) {
        $input = socket_read($client, 1024, PHP_NORMAL_READ);
        if (mb_strtolower(trim($input)) === 'quit') {
            socket_write($client, "Bye bye .\r\n");
            socket_close($client);
            break;
        } else {
            $data = 'REPLY: '. $input;
            socket_write($client, $data, strlen($data));
        }
    }
}

使用 telnet 就可以调试此 TCP 服务, 因为 PHP 命令行运行的这个脚本, 而 PHP 本身是单进程的, 所以如果同时开两个 telnet 链接, 只有一个有效,基本是不可用的。

下面是使用 libev 和 sockets 构建的 TCP 服务:

<?php

const HOST = '0.0.0.0';
const PORT = 8090;

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

socket_bind($socket, HOST, PORT);
socket_listen($socket, 3);

socket_set_nonblock($socket);

// accept client to connect
$event = new EvIo($socket, Ev::READ, function(Evio $watcher, $events) use ($socket) {
    $client = socket_accept($socket);
    // loop to read special client message
    $clientEvent = new EvIo($client, Ev::READ, function(Evio $watcher, $events) use ($client) {
        $input = socket_read($client, 1024, PHP_NORMAL_READ);
        if (mb_strtolower(trim($input)) === 'quit') {
            socket_write($client, "Bye bye .\r\n");
            $watcher->stop();
            socket_close($client);
        } else {
            $data = 'REPLY: '. $input;
            socket_write($client, $data, strlen($data));
        }
    });
    Ev::run();
});

Ev::run();

就是将原本的 while (true) 循环用 EvIo 替换,然后将相关代码写在回调里面。这样可以使用 telnet 多个客户端同时连接。