php socket select IO复用

来源:互联网 发布:苏亚雷斯实况巅峰数据 编辑:程序博客网 时间:2024/06/15 05:58

此篇博客是接着上篇php socekt阻塞模型PHP代码(php socket IO阻塞方式的Server/Client)的进阶,IO阻塞模型只能是同一个时刻只能由一个客户端进行访问,除非利用多进程或多线程才能达到多个用户并发访问的,因涉及到多进程和多线程,暂时跳过,

此片为linux的IO操作的5大模型第三种模型:IO复用,而IO复用又有多种方式实现,常见的如select、poll、epoll函数。这几个函数也会使进程阻塞,但是和阻塞I/O所不同的的,这些函数可以同时阻塞多个I/O操作。而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操作函数,这些定义网上资料都很多,我这就不一一描述,如有需要可参考:socket阻塞与非阻塞,同步与异步

下面是socket IO复用 select 模型代码PHP 的代码描述,讲述如何使用PHP代码实现select模型。其中也对socket_select的作用,进行自我总结


select_server.php

<?php/** * server.php. * User: lvfk * Date: 2017/12/1 0001 * Time: 16:47 * Desc: */set_time_limit(0);class SelectSocketServer{    private static $socket;    private static $timeout = 60;    private static $maxconns = 1024;    private static $connections = array();    function __construct($port)    {        global $errno, $errstr;        if ($port < 1024) {            die("Port must be a number which bigger than 1024\n");        }        $socket = socket_create_listen($port);        if (!$socket) die("Listen $port failed");        socket_set_nonblock($socket); // 非阻塞        while (true)        {            $readfds = array_merge(self::$connections, array($socket));            $writefds = array();            // 选择一个连接,获取读、写连接通道            $e = NULL;            /*             * socket_select是阻塞,有数据请求才处理,否则一直阻塞             * 此处$readfds会读取到当前活动的连接             * 比如执行socket_select前的数据如下(描述socket的资源ID):             * $socket = Resource id #4             * $readfds = Array             *       (             *           [0] => Resource id #5 //客户端1             *           [1] => Resource id #4 //server绑定的端口的socket资源             *       )             * 调用socket_select之后,此时有两种情况:             * 情况一:如果是新客户端2连接,那么 $readfds = array([1] => Resource id #4),此时用于接收新客户端2连接             * 情况二:如果是客户端1(Resource id #5)发送消息,那么$readfds = array([1] => Resource id #5),用户接收客户端1的数据             *             * 通过以上的描述可以看出,socket_select有两个作用,这也是实现了IO复用             * 1、新客户端来了,通过 Resource id #4 介绍新连接,如情况一             * 2、已有连接发送数据,那么实时切换到当前连接,接收数据,如情况二            */            if (socket_select($readfds, $writefds, $e, self::$timeout))            {                // 如果是当前服务端的监听连接                if (in_array($socket, $readfds)) {                    echo "socket_accept\n";                    // 接受客户端连接                    $newconn = socket_accept($socket);                    $i = (int) $newconn;                    $reject = '';                    if (count(self::$connections) >= self::$maxconns) {                        $reject = "Server full, Try again later.\n";                    }                    // 将当前客户端连接放入 socket_select 选择                    self::$connections[$i] = $newconn;                    // 输入的连接资源缓存容器                    $writefds[$i] = $newconn;                    // 连接不正常                    if ($reject) {                        socket_write($writefds[$i], $reject);                        unset($writefds[$i]);                        self::close($i);                    } else {                        echo "Client $i come.\n";                    }                    // remove the listening socket from the clients-with-data array                    $key = array_search($socket, $readfds);                    unset($readfds[$key]);                }                // 轮循读通道                foreach ($readfds as $rfd) {                    // 客户端连接                    $i = (int) $rfd;                    // 从通道读取                    $line = @socket_read($rfd, 2048, PHP_NORMAL_READ);                    if ($line === false) {                        // 读取不到内容,结束连接                        echo "Connection closed on socket $i.\n";                        self::close($i);                        continue;                    }                    $tmp = substr($line, -1);                    if ($tmp != "\r" && $tmp != "\n") {                        // 等待更多数据                        continue;                    }                    // 处理逻辑                    $line = trim($line);                    if ($line == "quit") {                        echo "Client $i quit.\n";                        self::close($i);                        break;                    }                    if ($line) {                        echo "Client $i >>" . $line . "\n";                        //发送客户端                        socket_write($rfd,  "$i=>$line\n");                    }                }                // 轮循写通道                foreach ($writefds as $wfd) {                    $i = (int) $wfd;                    socket_write($wfd, "Welcome Client $i!\n");                }            }        }    }    function close ($i)    {        socket_shutdown(self::$connections[$i]);        socket_close(self::$connections[$i]);        unset(self::$connections[$i]);    }}new SelectSocketServer(3000);

select_client.php

<?php/** * client.php. * User: lvfk * Date: 2017/12/1 0001 * Time: 17:05 * Desc: */function debug ($msg){    error_log($msg, 3, '/tmp/socket.log');}if ($argv[1]) {    $socket_client = stream_socket_client('tcp://127.0.0.1:3000', $errno, $errstr, 30);//stream_set_timeout($socket_client, 0, 100000);    if (!$socket_client) {        die("$errstr ($errno)");    } else {        $msg = trim($argv[1]);        for ($i = 0; $i < 5; $i++) {            $res = fwrite($socket_client, "$msg($i)\n");            usleep(100000);debug(fread($socket_client, 1024)); // 将产生死锁,因为 fread 在阻塞模式下未读到数据时将等待        }        fwrite($socket_client, "quit\n"); // add end token        debug(fread($socket_client, 1024));        fclose($socket_client);    }}else {    $phArr = array();    for ($i = 0; $i < 5; $i++) {        $phArr[$i] = popen("php ".__FILE__." '{$i}:test'", 'r');    }    foreach ($phArr as $ph) {        pclose($ph);    }}



原创粉丝点击