Swoole Event Loop

进程

  • 子进程会复制父进程的IO句柄(fd描述符)

进程间通信方式

管道

  • 管道是一组两个特殊的文件描述符
  • 管道需要在fork函数调用前创建
  • 如果某一端主动关闭管道,另一端的读取操作会直接返回0。
管道

消息队列

  • 通过指定key创建一个消息队列
  • 在消息队列中传递的数据有大小限制
  • 消息队列会一直保留直到被主动关闭
消息队列

IO多路复用

  • epoll函数会监听注册在自己名下的所有的socket描述符
  • 当有socket感兴趣的事件发生时,epoll函数才会响应,并返回有事件发生的socket集合。
  • epoll的本质是阻塞IO,它的有点在于能同时处理大量socket连接。
epoll

Event Loop 事件循环

  • 事件循环是一个Reactor线程,其中运行了一个epoll实例。
  • 可以通过接口添加socket描述符到epoll监听中并指定事件响应的回调函数
  • 事件循环不可用于FPM环境下

对于异步的任务来说,服务器端的Master主进程与Worker工作进程会自动地将异步的事件添加到Reactor的事件循环中去,TaskWorker任务进程不允许存在异步任务。

对于异步的客户端、swoole_process::signalswoole_timer来说,PHP代码并不存在Reactor事件循环,此时Swoole会为PHP代码创建相应的swoole_event的Reactor事件循环来模拟异步事件。

除了异步服务器和客户端库之外,Swoole扩展还提供了直接操作底层epollkqueue事件循环的接口,可将其他扩展创建的Socket,PHP代码中的streamsocket扩展创建的socket等加入到Swoole的事件循环中。只有了解了swoole_event的原理才能更好的使用Swoole中的定时器、信号、客户端等异步事件接口。

为什么开启事件循环的程序会一直运行下去而不停止呢?

因为开启事件循环后,程序内会启动一个线程并一直阻塞在epoll的监听上,因此不会退出。

如何关闭事件循环呢?

调用swoole_event_exit函数即可关闭事件循环,需要注意的是swoole_server程序中此函数无效。

Event Loop

添加异步事件swoole_event_add

Swoole提供了swoole_event_add函数可用于实现异步,它会将一个Socket加入到底层的Reactor事件监听中,可用于服务器或客户端模式下。swoole_event_add属于AsyncIO,必须运行在CLI模式下。

底层

  • swoole_event_add函数利用zend_parse_parameters解析传入的参数信息,并复制给zfdcb_read读回调函数、cb_write写回调函数、event_flag监控事件。
  • 利用swoole_convert_to_fd将传入的zfd转换为文件描述符
  • 新建php_reactor_fd对象并对其设置文件描述符,读写回调函数。
  • php_swoole_check_reactor检测是否存在Reactor并对其进行初始化
  • 设置套接字文件描述符为非阻塞,在Reactor中添加文件描述符。

原型

bool swoole_event_add(
  mixed $sock,
  mixed $read_callback, 
  mixed $write_callback = null,
  int $flag = null
)

参数

参数1:mixed $sock

$sock支持四种类型

  1. int 文件描述符,包括swoole_client->$sockswoole_process->$pipe或其他fd
  2. stream资源,也就是stream_socket_clientfsocketopen创建的资源
  3. sockets资源,也就是sockets扩展中socket_create创建的资源,需要在编译时加入./configure --enable-sockets
  4. objectswoole_processswoole_client,底层自动转换为管道或客户端连接的socket

例如:在某一客户端输入,另一客户端能实时接收到,反之也可以。

参数2:mixed $read_callback

$read_callback为可读事件回调函数

  • 在可读事件回调函数中必须使用freadrecv等函数读取socket缓存区中的数据,否则事件会持续触发,如果不希望继续读取必须使用Swoole\Event:del移除事件监听。
  • 在可写事件回调函数中,写入socket之后必须调用Swoole\Event::del移除事件监听,否则可写事件会持续触发。
  • 执行freadsocket_recvsocket_readSwoole\Client:recv返回false,并且错误码为EAGAIN时表示当前socket接收缓存区内没有任何数据,此时需要添加可读监听等待事件循环通知。
  • 执行fwritesocket_writesocket_sendSwoole\Clinet::send操作返回false,并且错误码为EAGAIN时表示当前socket发送缓冲区已满,暂时不能发送数据,需要监听可写事件等待事件循环通知。

参数3:mixed $write_callback

$write_callback为可写事件回调函数,此参数可以是字符串函数名、对象加方法、类静态方法、匿名函数,因此socket可读或可写时回调指定的函数。

参数4:$flag

$flag为事件类型的掩码,可选择关闭或开启可读可写事件,如SWOOLE_EVENT_READSWOOLE_EVENT_WRITE或者是SWOOLE_EVENT_READ以及SWOOLE_EVENT_WRITE

在服务器程序中使用时,必须在Worker进程启动后使用,在Swoole::start之前不得调用任何异步IO接口。

返回值

  • 添加事件监听成功后返回true
  • 添加失败返回false可使用swoole_last_error获取错误码
  • 已添加过的socket不能重复添加,可以使用swoole_event_set修改socket对应的回调函数和事件类型。

使用swoole_event_addsocket加入到事件监听后,底层会自动将该socket设置为非阻塞模式。

移除异步事件swoole_event_del

swoole_event_del函数用于从Reactor线程中移除监听的Socket,swoole_event_del应当与swoole_event_add成对使用。

原型

bool swoole_event_del(mixed $sock)

参数

mixed $sock表示socket的文件描述符

注意

必须在socket关闭前使用swoole_event_del移除事件监听,否则可能会产生内存泄漏。

退出事件轮询swoole_event_exit

退出事件循环,此函数仅在客户端程序使用有效。

原型

void swoole_event_exit(void)

案例

在某一客户端输入,另一客户端能实时接收到,反之也可以。

服务器

$ vim server.php
on("Start", function(swoole_server $server){
    echo "[start] master {$server->master_pid} manager {$server->manager_pid}".PHP_EOL;
});

//Worker进程 监听客户端连接
$server->on("Connect", function(swoole_server $server, $fd){
    echo "[connect] client {$fd}".PHP_EOL;
});

//Worker进程 接收来自客户端的连接
$server->on("Receive", function(swoole_server $server, $fd, $reactor_id, $data){
    echo "[receive] reactor {$reactor_id} client {$fd}: {$data}".PHP_EOL;
    foreach($server->connections as $connection){
        if($fd != $connection){
            $server->send($connection, $data);
        }
    }
});

//Worker进程 监听客户端连接断开时触发
$server->on("Close", function(swoole_server $server, $fd){
    echo "[close] client {$fd}".PHP_EOL;
});

//启动服务器
$server->start();

客户端

$ vim client.php

未完待续...

你可能感兴趣的:(Swoole Event Loop)