PHP网络编程-异步回调on的实现(十六)
发表@2020-02-10 19:04:00
更新@2023-03-27 22:50:13
你都不问问你本子为啥会发热扇风?看下CPU占用率感受一下?

造成这样的原因是什么,一是我前面文章里解释过,二是这篇文章前面也说过了,三是我提醒你结合下非阻塞我举的例子,如果这样你还想不清楚...
这个$listen_socket变成非阻塞IO本是好事,但是非阻塞导致while循环不断打空炮,如果有客户端请求连接还好,但是没有的时候TA就这么一直打空炮,你想想挂了空档猛踩油门,难受不?那能不毁车?所以非阻塞的最佳应用场景是什么,就是当真有客户端来连接的时候,再在这个非阻塞的$listen_socket上发生accept,说白了[ 非阻塞 ]要结合[ 异步事件 ],这样就避免了打空炮行为同时还能保证[ 非阻塞 ]。
异步
先说好了这里的异步并不是指符合APUE书中定义的那个[ 异步 ],而是指上层代码整体流程的异步,更不是指AIO。这里结合下Select IO复用来简单实现一下初步的流程:
```php
$read_fd ) {
socket_recv( $read_fd, $recv_content, 1024, 0 );
if ( !$recv_content ) {
unset( $client[ $read_key ] );
socket_close( $read_fd );
continue;
}
echo $recv_content;
unset( $client[ $read_key ] );
socket_shutdown( $read_fd );
socket_close( $read_fd );
}
}
```
至于Select IO的用法和详解不是本章重点,重点是:
- 首先你先看下你CPU还打空炮不?
- 其次是如何基于上面代码改造成比较优雅的on~
```php
host, $this->port );
socket_listen( $listen_socket );
socket_set_nonblock( $listen_socket );
$this->client = array( $listen_socket );
$this->listen_socket = $listen_socket;
}
// 这个函数就相当于注册回调函数...
public function on( $event_name, Closure $func ) {
$this->closure_array[ $event_name ] = $func;
}
public function run() {
while ( true ) {
$read = $this->client;
$write = array();
$exception = array();
$ret = socket_select( $read, $write, $exception, NULL );
if ( $ret <= 0 ) {
continue;
}
// 就是说,如果 listen-socket 中有事件,listen-socket能有啥事件:就是用新的客户端来了
if ( in_array( $this->listen_socket, $read ) ) {
$connection_socket = socket_accept( $this->listen_socket );
if ( !$connection_socket ) {
continue;
}
$this->client[] = $connection_socket;
$key = array_search( $this->listen_socket, $read );
unset( $read[ $key ] );
// connect时:触发
$func = isset( $this->closure_array['connect'] ) ? $this->closure_array['connect'] : false ;
if ( false !== $func ) {
call_user_func_array( $func, array() );
}
}
// 对于其他socket
foreach( $read as $read_key => $read_fd ) {
socket_recv( $read_fd, $recv_content, 1024, 0 );
if ( !$recv_content ) {
unset( $this->client[ $read_key ] );
socket_close( $read_fd );
continue;
}
// 收到消息时:触发
$func = isset( $this->closure_array['message'] ) ? $this->closure_array['message'] : false ;
if ( false !== $func ) {
call_user_func_array( $func, array( $recv_content ) );
}
unset( $this->client[ $read_key ] );
socket_shutdown( $read_fd );
socket_close( $read_fd );
}
}
}
}
$server = new Server();
$server->init();
// 这里通过on函数来注册
// 这里就是利用了PHP里的Closure,其实下面function就是Closure
$server->on( 'connect', function() {
echo "触发connect".PHP_EOL;
} );
$server->on( 'message', function( $data ) {
echo "触发message,收到数据:".$data.PHP_EOL;
} );
$server->run();
```
用telnet客串客户端感受一下?

有些泥腿子们可能之前用过Workerman,Workerman的回调函数方式是$server->onConnect()这种风格的,而我们用的是和Swoole、NodeJS那种靠拢的$server->on( 'action' )风格的,无论用的哪种方式都不重要,因为这些都是上层的表现风格而已,重要的是什么:
- 一、你的PHP基础知识里是否给了Closure一席之地
- 二、你是否知道call_user_func()以及call_user_func_array()
上述两点是实现PHP版本异步回调用法的基石。