PHP开源Hub
致力于开发者的提升

玩转 PHP 网络编程全套之 I/O 复用

PHP的socket扩展提供了一个多路I/O事件复用select

IO复用

I/O复用函数本身也是阻塞I/O,发起系统调用同样的要被挂起,直到就绪事件发生,才会运行。所以也称为同步I/O模型。
I/O复用函数一般用的有select,epoll,poll,kqueue,devpoll等PHP只提供了一个select。

同步I/O模型与异步I/O模型

同步IO模型是向应用程序通知的是I/O就绪事件,而异步I/O模型则是向应用程序通知的是完成事件,后续我们在编写代码过程会进行模拟异步I/O的实现。

测试源码

$ip = "0.0.0.0";
$port = $argv[1];
$sockefd = socket_create(AF_INET,SOCK_STREAM,0);
socket_set_option($sockefd,SOL_SOCKET,SO_REUSEPORT,1);
socket_bind($sockefd,$ip,$port);
socket_listen($sockefd,5);


$connectionfds = [];
$readfds = [$sockefd];
$writefds = [$sockefd];
$exceptionfds = [$sockefd];
$bufferMessage = "hello,world";
$recvMessage = "";
$remoteIP;
$remoteAddr;
fprintf(STDOUT,"server pid=%d\n",posix_getpid());
$i=0;
while ("server") {

        $read_fds      = $readfds;
        $write_fds     = $writefds;
        $exception_fds = $exceptionfds;

        $connectNum = socket_select($read_fds, $write_fds, $exception_fds, NULL);

        if (!$connectNum) {
            break;
        }

        foreach ($read_fds as $fd) {

            if ($fd == $sockefd) {
                $connfd = socket_accept($sockefd);
                socket_set_nonblock($connfd);

                socket_getpeername($connfd,$remoteIP,$remoteAddr);
                fprintf(STDOUT, "new connection %s ,ip is %s,port is %d\n", $connfd,$remoteIP,$remoteAddr);
                $readfds[]       = $connfd;
                $exceptionfds[]  = $connfd;
                $connectionfds[] = $connfd;
            } else {

                $recvMessage = socket_read($fd, 8192);
                unset($readfds[array_search($fd, $readfds)]);
                if (strcasecmp("QUIT",$recvMessage)){
                    socket_send($fd,"quit\n",6,0);
                    socket_close($fd);
                    unset($exceptionfds[array_search($fd, $exceptionfds)]);
                    unset($writefds[array_search($fd, $writefds)]);
                }
                if (!$recvMessage) {
                    socket_close($fd);
                    unset($exceptionfds[array_search($fd, $exceptionfds)]);
                    unset($writefds[array_search($fd, $writefds)]);
                } else {
                    fprintf(STDOUT, "recv from client msg:%s\n", $recvMessage);
                    array_push($writefds, $fd);
                }

            }
        }

        foreach ($write_fds as $fd) {
            $writeBytes = socket_write($fd, $bufferMessage, strlen($bufferMessage));
            unset($writefds[array_search($fd, $writefds)]);
            if (!$writeBytes) {
                socket_close($fd);
                unset($readfds[array_search($fd, $readfds)]);
                unset($exceptionfds[array_search($fd, $exceptionfds)]);
            } else {
                array_push($readfds, $fd);
            }
        }

        foreach ($exception_fds as $fd) {
            socket_close($fd);
            unset($readfds[array_search($fd, $readfds)]);
            unset($exceptionfds[array_search($fd, $exceptionfds)]);
            unset($writefds[array_search($fd, $writefds)]);
        }


}
socket_close($sockefd);

启动服务

然后阻塞在selectI/O函数
玩转 PHP 网络编程全套之 I/O 复用
玩转 PHP 网络编程全套之 I/O 复用
玩转 PHP 网络编程全套之 I/O 复用
玩转 PHP 网络编程全套之 I/O 复用

第二个客户端连接时
玩转 PHP 网络编程全套之 I/O 复用

玩转 PHP 网络编程全套之 I/O 复用

总结

select的基本功能:就是为了能监听多个客户端的连接,它本身还是并没有特别的地方,但是它也是有缺点的,就是每次都得传递文件描述符集合给它,同时返回的就绪文件也要自己遍历,如果监听太多,性能也不怎么样,更关键的是它最多只能监听1024个文件描述符,毕竟它在内核中是以位形式存储。
原理图:【实在找不到吊炸天的画图软件了】
该原理图请配合select函数亲自测试并调试
玩转 PHP 网络编程全套之 I/O 复用

赞(0) 打赏
未经允许不得转载PHP开源Hub » 玩转 PHP 网络编程全套之 I/O 复用

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

PHP开源Hub-致力于互联网开发者的成长

技术群聊软文发表

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏