php socket循环接收_TCP/IP学习三PHP原生实现一个TCP例子

  • 简单阻塞版的TCP-Server

php_server.php

简单说下这个逻辑,很简单,创建一个server,然后等待客户端请求,客户端连接上之后接收数据、发送数据,结束

<?phpini_set ("memory_limit", -1);// 创建一个tcp server$server = stream_socket_server("tcp://127.0.0.1:8091", $errno, $errstr);if ($errno) {    throw new Exception("server create err". $errstr);}echo "server start ". PHP_EOL;// 等待请求while ($clientConn = stream_socket_accept($server, -1)) {    $streamId = (string) $clientConn;    echo "get-new-client-connect: ". $streamId. PHP_EOL;    // 接收数据    $data = stream_socket_recvfrom($clientConn, 100);    echo "receive data :". $data. PHP_EOL;    $datas = explode(',', $data);    [$minId,$maxId] = explode('-', $datas[1]);    $start = microtime_float();    // 通过查询DB产生IO    $count = selectDB($minId, $maxId);    $end = microtime_float();    $times = $end-$start;    // 发送数据    stream_socket_sendto($clientConn, "你好客户端: ". $streamId . ' : ' . date("Y-m-d H:i:s") . '  minid: '. $minId. ' maxid: '. $maxId .  ' dbCount: '. $times);    fclose($clientConn);}fclose($server);function selectDB($minId, $maxId){    $mysqlconn = new mysqli('127.0.0.1', 'root', 'root', 'test');    $result = $mysqlconn->query("    select * from user where id between ".$minId." and ". $maxId);    $arr = [];    foreach ($result->fetch_all(MYSQLI_ASSOC) as $k=>$info) {        $arr[] = $info;    }    return $arr;}function microtime_float(){    list($usec, $sec) = explode(" ", microtime());    return ((float)$usec + (float)$sec);}

php_client.php

客户端则是连接server,发送数据,接收数据

<?php // 连接到server$client = stream_socket_client("tcp://127.0.0.1:8091", $errno, $errstr);if ($errno) {    throw new Exception("client create err: ". $errstr);}echo "client connect success". PHP_EOL;$minId = $argv[1];$maxId = $argv[2];$sleep = $argv[3];echo "sleep: ". $sleep. PHP_EOL;// 通过sleep产生阻塞,占用server连接sleep($sleep);// 写入数据fwrite($client, "hello server,{$minId}-{$maxId}");// 读取数据while ($content = fread($client, 100)) {    echo "get content from server :". $content. PHP_EOL;}

启动server

gaoz@nobodyMBP hyperf_study % php hyperf-skeleton/tcpip/php_server.phpserver start // client1 等待10sget-new-client-connect: Resource id #6receive data :hello server,1-2// client要等client1处理完才能被acceptget-new-client-connect: Resource id #8receive data :hello server,1-10

client 1 , 我们给的参数是查询两条数据,等待10s

gaoz@nobodyMBP hyperf_study % php hyperf-skeleton/tcpip/php_client.php 1 2 10client connect successsleep: 10get content from server :你好客户端: Resource id #6 : 2020-09-08 02:31:10  minid: 1 maxid: 2 dbCount: 0.0069458484649658

client 2 , 这边呢是查询10条诗句,等待2s

gaoz@nobodyMBP hyperf_study % php hyperf-skeleton/tcpip/php_client.php 1 10 2 client connect successsleep: 2get content from server :你好客户端: Resource id #8 : 2020-09-08 02:31:10  minid: 1 maxid: 10 dbCount: 0.006360054016113get content from server :3

我们看先后接着启动client1、client2,client2必须要等到client1处理完成之后才能再处理,也就是必须要等待12s才能接受到server的处理,这就是阻塞了,只能一个个处理,server只能一个一个的处理。

  • 简单select多路复用非阻塞版的TCP-Server

我们之前学习过Nginx使用epoll多路复用来处理单个Worker的多个client的处理,这里我们使用select来体验一下,虽然select某些情况下比epoll效率低,但也是可以实现的,这里利用php的系统函数stream_select来搞一下。

php_select_server.php

这里比上一个多了两个地方:

一个是设置server为非阻塞模式 stream_set_blocking ,从文档中可以见到,当启用no-block之后,reade如果没有数据会立刻返回,而不会阻塞等待。

二个是使用 stream_select 监控可读写事件的发生,发生之后进行相关处理即可。对于server来说有新的客户端连接、客户端发送数据过来都是一种可读事件。

<?phpini_set ("memory_limit", -1);// 创建一个tcp server$server = stream_socket_server("tcp://127.0.0.1:8091", $errno, $errstr);// 设置非阻塞模式stream_set_blocking($server, 0);if ($errno) {    throw new Exception("server create err" . $errstr);}echo "server start ". PHP_EOL;$wirtes = $exceps = [];$clients[] = $server;while (true) {    $reads = $clients;    // 调用select去轮询read事件    if (@stream_select($reads, $wirtes, $exceps, 100000) > 0) {        // 如果是主socket也就是server有可读事件,也就是客户端连接        if (in_array($server, $reads)) {            $clients[] = stream_socket_accept($server, 1000, $peername);            $serverK = array_search($server, $reads);            unset($reads[$serverK]);        }        if (count($reads) <= 0) continue;        // 剩下的都是client的消息        foreach ($reads as $ks => $_server) {            // client            $data = fread($_server, 100);            $streamId = (string)$_server;            echo "get-data-from-client :". $streamId. PHP_EOL;            $peername = stream_socket_get_name($_server, true);            echo "receive data :" . $data . PHP_EOL;            // 解析ID            $datas = explode(',', $data);            [$minId, $maxId] = explode('-', $datas[1]);            $start = microtime_float();            $count = selectDB($minId, $maxId);            $end = microtime_float();            $times = $end - $start;            echo $streamId. ": ". $peername. "-select-finish". PHP_EOL;            // 发送数据            fwrite($_server,                "你好客户端: " . $streamId . ':' . date("Y-m-d H:i:s") . ' minid: ' . $minId . ' maxid: ' . $maxId . ' dbCount: ' . $times);            fclose($_server);        }    }}function selectDB($minId, $maxId){    $mysqlconn = new mysqli('127.0.0.1', 'root', 'root', 'test');    $result = $mysqlconn->query("    select * from user where id between ".$minId." and ". $maxId);    $arr = [];    foreach ($result->fetch_all(MYSQLI_ASSOC) as $k=>$info) {        $arr[] = $info;    }    return $arr;}function microtime_float(){    list($usec, $sec) = explode(" ", microtime());    return ((float)$usec + (float)$sec);}

启动server

gaoz@nobodyMBP hyperf_study % php hyperf-skeleton/tcpip/php_select_server.phpserver start get-data-from-client :Resource id #7receive data :hello server,1-10Resource id #7: 127.0.0.1:63980-select-finishget-data-from-client :Resource id #6receive data :hello server,1-2Resource id #6: 127.0.0.1:63975-select-finish

client1: 这里还是查询两条数据,等待10s

gaoz@nobodyMBP hyperf_study % php hyperf-skeleton/tcpip/php_client.php 1 2 10client connect successsleep: 10get content from server :你好客户端: Resource id #6:2020-09-08 02:31:40 minid: 1 maxid: 2 dbCount: 0.0031378269195557

client2: 查询10条数据,等待2s

gaoz@nobodyMBP hyperf_study % php hyperf-skeleton/tcpip/php_client.php 1 10 2 client connect successsleep: 2get content from server :你好客户端: Resource id #7:2020-09-08 02:31:32 minid: 1 maxid: 10 dbCount: 0.0093188285827637

同志们注意看client2的返回时间奥,我们是先启动client1,接着启动client2, 但是明显client2先返回数据,这就是非阻塞的提现,I/O多路复用的优势就提现出来了。

stream_select最终会调用select()系统调用,

stream_select(ReadFds, WriteFds, Exceptions, timeout) ------> select(FdTotals, ReadFds, WriteFds, timeout)

select()大概是这样一种实现,他会不停的去循环检查是否有可读、可写事件发生的FD, 是否有可读、可写是socket连接对应的驱动设备提供的一种能力,所有Linux文件类型如文件、socket、devnull各种都必须实现这种通知机制。这一点大家可以看下下面的文章。

总结

  1. select通过内核监控多个fd的Read、Write事件
  2. select本身会阻塞
  3. select只是通知用户程序,数据准备好可读了,但还是需要用户程序自己去取数据

相关文档:

stream-blocking: https://www.php.net/manual/zh/function.stream-set-blocking.php

stream-select: https://www.php.net/manual/zh/function.stream-select.php

关于select的一篇文章:https://blog.csdn.net/zhougb3/article/details/79792089

具体代码:https://github.com/nobody05/hyperf_study

72069ac4efde0e83317dd917b760b8ea.png
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值