网络编程:PHP 实现 “IO多路复用模型 - select“

相关知识点

如果看不明白,可以翻开我关于网络编程的其他代码;从简单到复杂的实现

  • 基于信号的秒级定时器
  • 长连接压测基本思路
  • IO多路复用 - select 的实现和fd限制
    本质上:在linux下的IO多路复用其实就是增加了套接字状态,让我们知道哪些socket有消息到达

服务端代码

<?php

// 自定义流格式请查看
//  - https://www.php.net/manual/zh/function.stream-filter-register
// 注意: UNIX、TCP是流; UDP是数据包。
$addr = "tcp://0.0.0.0:6666";
$mode = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;

$socket = stream_socket_server($addr, $errno, $errmsg, $mode);
if (! $socket) {
    die("{$errmsg} ({$errno})" . PHP_EOL);
}

// 设定非阻塞IO
stream_set_blocking($socket, false);

// 设定读缓冲区
//  - 这里设置为0时, 兼容 hhvm
stream_set_read_buffer($socket, 0);

// 在linux下,多路复用的本质其实就是增加了socket的状态
// 告诉你什么时候可以读

$queues = [];
$read = [];
$write = null;
$except = null;

while(true) {
    // 获取连接
    $connection = @stream_socket_accept($socket, count($queues) ? 0 : -1, $remoteAddress);
    if ($connection) {
        $queues[$remoteAddress] = $connection;
        stream_set_blocking($connection, false);
        echo "accept connection: {$remoteAddress} - total accept:" . count($queues) . PHP_EOL;
    }


    $read = $queues;
    $write = $except = null;

    // 观察 $read 的数量 --- total accept
    $readyStreams = stream_select($read, $write, $except, 0, 0);
    if ($readyStreams === false) {
        echo "存在错误" . PHP_EOL;
    } else if ($readyStreams > 0) {
        foreach ($read as $address=>$connection) {
            if (feof($connection) || ! is_resource($connection)) {
                echo "{$address}:连接已经断开" . PHP_EOL;
                stream_socket_shutdown($connection,STREAM_SHUT_RDWR);
                unset($queues[$address]);
                continue;
            }

            // 应答客户端
            $buffer = stream_socket_recvfrom($connection, 655350);
            $buffer = trim($buffer, "\r\n");
            if ($buffer) {
                $message = "收到{$buffer}";
                var_dump($message);
                stream_socket_sendto($connection, "{$message}\n");
            }
        }
    }
}

echo 'stop' . PHP_EOL;
fclose($socket);

客户端代码

<?php

// 发起1020个客户端请求
define("REQUEST_CLIENT_NUM", 1020);
define("TIMER_ALARM", 1);

// 定时任务列表
$callbackMetadata = [];

// 注册事件回调
// 注意与 declare 的区别
// 时间轮算法;实现延时队列的一种常见方案
pcntl_signal(SIGALRM, function( $signal ) {
    global $callbackMetadata;

    $time = time();
    if (! isset($callbackMetadata[$time])) {
        $callbackMetadata[$time] = [];
    }

    foreach ($callbackMetadata[$time] as $index=>$metadata) {
        list($interval, $persist, $callback, $params) = $metadata;

        // 触发运行
        $status = $callback (... $params);

        // 继续运行
        if ($persist === true && $status) {
            $nextTime = $time + $interval;
            $callbackMetadata[$nextTime][] = $metadata;
        }
    }

    // alarm闹钟信号 为一次性的;需要在此设置
    pcntl_alarm(TIMER_ALARM);
});

$addr = "tcp://10.166.166.166:6666";
$mode = STREAM_CLIENT_ASYNC_CONNECT|STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT;

// 启动所有客户端
$clients = [];
for ($i=0; $i<REQUEST_CLIENT_NUM; $i++) {

    // 客户端启动
    $connection = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    socket_connect($connection, '10.166.166.166', 6666);

    // 连接设置
    socket_set_nonblock($connection);

    // 缓存客户端
    socket_getsockname($connection, $address, $port);
    $clients["{$address}:{$port}"] = $connection;
}

$counter = 0;

// 设定定时任务
foreach ($clients as $addr=>$client) {
    $callbackMetadata[(time() + 5)][] = [rand(1, 6), true, function ($addr) {
        global $clients, $counter;

        $client = $clients[$addr];
        if (! $client || ! is_resource($client)) {
            echo "{$addr}: 连接已经断开:" . socket_strerror(socket_last_error($client)) . PHP_EOL;
            socket_close($client);
            unset($clients[$addr]);
            return false;
        }

        socket_write($client, "i'm {$addr}\n");

        $counter ++ ;

        return true;
    }, [$addr]];
}

pcntl_alarm(1);
while(1) {
    echo "send {$counter}" . PHP_EOL;
    pcntl_signal_dispatch();
    sleep(1);
}

测试截图

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值