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
<?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: ");

未完待续...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容

  • 简介 Event Loop是一个Reactor线程,其中运行了一个epoll实例 可通过接口添加socket描述符...
    zshanjun阅读 897评论 0 0
  • 并发IO问题一直是服务器端编程中的技术难题,从最早的同步阻塞直接Fork进程,到Worker进程池/线程池,到现在...
    零一间阅读 1,709评论 1 34
  • 出处:韩天峰 网址:rango.swoole.com/archives/508 并发IO问题一直是后端编程中的技术...
    meng_philip123阅读 2,402评论 1 38
  • 前言 作为一个网络框架,最为核心的就是消息的接受与发送。高效的 reactor 模式一直是众多网络框架的首要选择,...
    leoyang90阅读 783评论 0 0
  • 日精进。今日体验。好多事情都有因为。所以。 有了前因为,才会有所以,一天挺忙,把简单事情做好。
    5cd3c12de64e阅读 146评论 0 0