进程
- 子进程会复制父进程的IO句柄(fd描述符)
进程间通信方式
管道
- 管道是一组两个特殊的文件描述符
- 管道需要在
fork
函数调用前创建 - 如果某一端主动关闭管道,另一端的读取操作会直接返回0。
消息队列
- 通过指定key创建一个消息队列
- 在消息队列中传递的数据有大小限制
- 消息队列会一直保留直到被主动关闭
IO多路复用
- epoll函数会监听注册在自己名下的所有的socket描述符
- 当有socket感兴趣的事件发生时,epoll函数才会响应,并返回有事件发生的socket集合。
- epoll的本质是阻塞IO,它的有点在于能同时处理大量socket连接。
Event Loop 事件循环
- 事件循环是一个Reactor线程,其中运行了一个
epoll
实例。 - 可以通过接口添加
socket
描述符到epoll
监听中并指定事件响应的回调函数 - 事件循环不可用于FPM环境下
对于异步的任务来说,服务器端的Master主进程与Worker工作进程会自动地将异步的事件添加到Reactor的事件循环中去,TaskWorker任务进程不允许存在异步任务。
对于异步的客户端、swoole_process::signal
、swoole_timer
来说,PHP代码并不存在Reactor事件循环,此时Swoole会为PHP代码创建相应的swoole_event
的Reactor事件循环来模拟异步事件。
除了异步服务器和客户端库之外,Swoole扩展还提供了直接操作底层epoll
或kqueue
事件循环的接口,可将其他扩展创建的Socket,PHP代码中的stream
或socket
扩展创建的socket
等加入到Swoole的事件循环中。只有了解了swoole_event
的原理才能更好的使用Swoole中的定时器、信号、客户端等异步事件接口。
为什么开启事件循环的程序会一直运行下去而不停止呢?
因为开启事件循环后,程序内会启动一个线程并一直阻塞在epoll的监听上,因此不会退出。
如何关闭事件循环呢?
调用swoole_event_exit
函数即可关闭事件循环,需要注意的是swoole_server
程序中此函数无效。
添加异步事件swoole_event_add
Swoole提供了swoole_event_add
函数可用于实现异步,它会将一个Socket加入到底层的Reactor事件监听中,可用于服务器或客户端模式下。swoole_event_add
属于AsyncIO,必须运行在CLI模式下。
底层
-
swoole_event_add
函数利用zend_parse_parameters
解析传入的参数信息,并复制给zfd
、cb_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
支持四种类型
-
int
文件描述符,包括swoole_client->$sock
、swoole_process->$pipe
或其他fd
。 -
stream
资源,也就是stream_socket_client
或fsocketopen
创建的资源 -
sockets
资源,也就是sockets
扩展中socket_create
创建的资源,需要在编译时加入./configure --enable-sockets
。 -
object
,swoole_process
或swoole_client
,底层自动转换为管道或客户端连接的socket
。
例如:在某一客户端输入,另一客户端能实时接收到,反之也可以。
参数2:mixed $read_callback
$read_callback
为可读事件回调函数
- 在可读事件回调函数中必须使用
fread
、recv
等函数读取socket
缓存区中的数据,否则事件会持续触发,如果不希望继续读取必须使用Swoole\Event:del
移除事件监听。 - 在可写事件回调函数中,写入
socket
之后必须调用Swoole\Event::del
移除事件监听,否则可写事件会持续触发。 - 执行
fread
、socket_recv
、socket_read
、Swoole\Client:recv
返回false
,并且错误码为EAGAIN
时表示当前socket
接收缓存区内没有任何数据,此时需要添加可读监听等待事件循环通知。 - 执行
fwrite
、socket_write
、socket_send
、Swoole\Clinet::send
操作返回false
,并且错误码为EAGAIN
时表示当前socket
发送缓冲区已满,暂时不能发送数据,需要监听可写事件等待事件循环通知。
参数3:mixed $write_callback
$write_callback
为可写事件回调函数,此参数可以是字符串函数名、对象加方法、类静态方法、匿名函数,因此socket
可读或可写时回调指定的函数。
参数4:$flag
$flag
为事件类型的掩码,可选择关闭或开启可读可写事件,如SWOOLE_EVENT_READ
、SWOOLE_EVENT_WRITE
或者是SWOOLE_EVENT_READ
以及SWOOLE_EVENT_WRITE
。
在服务器程序中使用时,必须在Worker进程启动后使用,在Swoole::start之前不得调用任何异步IO接口。
返回值
- 添加事件监听成功后返回
true
- 添加失败返回
false
可使用swoole_last_error
获取错误码 - 已添加过的
socket
不能重复添加,可以使用swoole_event_set
修改socket
对应的回调函数和事件类型。
使用swoole_event_add
将socket
加入到事件监听后,底层会自动将该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
<?php
//创建异步TCP服务器
$host = "0.0.0.0";
$port = 9000;
$server = new swoole_server($host, $port);
//注册事件监听函数
// Master进程 启动服务
$server->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
<?php
//创建客户端
$host = "127.0.0.1:9000";
$timeout = 30;
$socket = @stream_socket_client($host, $errno, $errstr, $timeout);
if(!$socket){
exit("server connect error");
}
function onRead($socket)
{
$msg = stream_socket_recvfrom($socket, 1024);
if(!$msg){
swoole_event_del($socket);
}
echo "[recv] {$msg}".PHP_EOL;
fwrite(STDOUT, "send: ");
}
function onWrite($socket)
{
echo "[write]".PHP_EOL;
}
function onStdin()
{
global $socket;
$message = trim(fgets(STDIN));
if($message == "exit"){
swoole_event_exit();
}
fwrite($socket, $message);//数据量大时使用swoole_event_write
fwrite(STDOUT, "send: ");
}
//添加异步事件
swoole_event_add($socket, "onRead", "onWrite");
swoole_event_add(STDIN, "onStdin");
//swoole_event_add不会阻塞进程代码会顺序执行
fwrite(STDOUT, "send: ");
未完待续...