专栏首页可能是东半球最正规的API社区PHP网络编程之抽象一个event-loop(十八节)

PHP网络编程之抽象一个event-loop(十八节)

みなさんこんにちは「老李」,既知の「谢顶道人」。

事情是这样的,做为一个跟我旗鼓相当的「谢顶法师」--- 老赵跟我说:

一部分格局比较大的泥腿子估计内心早就已经预见到了这种「风口」,虽然可能没有做好迎接这种「风口」的物理准备,但也至少做好了心理准备,没关系迟早还会?回来的。

最近的瓜都比较带有黑色幽默的味道,比如「影视表演艺术家」的粉丝们开团群挑一些文学创作网站并成功使之被加入到404名单,其规模、其气势、其打法之凌厉、其战略之大手笔,堪称典范以至于发展到后面,已经冲破了双方所能掌控的范围。

「老李,你公众号更新节奏太慢了」,没辙,这本来也是我业余爱好而已:

  • 首先是我几乎(注意是几乎)只发技术类文章,人生导师、职场教育、副业赚钱、年薪百万的我资历尚浅都讲不了
  • 其次是写技术文章也还是挺麻烦的,一是技术本身是否讲解到位,二是文案助攻是否给力,最后还得考虑配图

今天我们说event-loop,确切说是总结并抽象event-loop,谢顶道人老李带着大家已经从socket基础到select IO复用再到epoll(实际上是event扩展),基本上把基础从头到尾撸了一边,还顺带搞定了同步、异步、阻塞、非阻塞等概念,你要说你没任何收获:

至于你信不信,我反正不信

众所周知,Workerman实际上是一个大一统的结合体,整合了TCP、UDP等一坨协议,纠集了进程控制与管理,囊括了各式各样的event类库,总之就是各种两开花。然而有时候,我们需要独立可拆分的组件,比如啥时候呢?就比如说今年年底,在贵司平台治理部门总包工头轩辕秃顶同志的牵头下,平台治理部联合基础平台部以及安全审计部等数个部门展开了「迎新春、庆鼠年」的服务组件拆分两开花特别行动,而你 --- 欧阳将负责将PHP语言的Event-Loop抽象并组件化,最终成绩算入年底OKR考核并与年终奖直接挂钩。

这就是明摆着老板原上草决意送大家免费福报,而你也决定「多快好省」地完成任务,于是你瞄准了github上赫赫有名的Reactphp:

ReactPHP是如下图这样shai儿得,TA把event-loop直接抽象出来作为了一个底层基础组件,这样基于这个event-loop组件可以快速实现stream服务、http服务、socket服务等...

所以今天欧阳的任务就是综合一下之前的章节,抽象出一个event-loop组件。文件目录树是这样的:

-Event/               // Event目录
|------Libevent.php   // 本次基于Event扩展实现的
|------Select.php     // 课后作业?
|------Libev.php      // 课后作业?
|------Factory.php    // 课后作业?
-index.php            // 入口文件

Event/Libevent.php:

<?php
namespace Event;
class Libevent {
    const  EV_ALL   = 1;
    const  EV_READ  = 2;
    const  EV_WRITE = 4;
    const  EV_EXCEPTION    = 8;
    public $o_event_config = null;
    public $o_event_base   = null;
    /*
     * @desc : 保存事件event
     * */
    public $a_event  = array();
    public $a_client = array();
    public function __construct() {
        $o_event_config = new \EventConfig();
        $o_event_base   = new \EventBase( $o_event_config );
        $this->o_event_config = $o_event_config;
        $this->o_event_base   = $o_event_base;
    }
    /*
     * @desc  : 添加一个事件
     * @param : socket fd
     * @param : event type, EV_READ EV_WRITE
     * @param : callback
     * */
    public function add( $r_fd, $i_event_type, $f_callback ) {
        $i_event_flag = $i_event_type == self::EV_READ ? \Event::READ | \Event::PERSIST : \Event::WRITE | \Event::PERSIST ;
        $o_event = new \Event( $this->o_event_base, $r_fd, $i_event_flag, $f_callback );
        $o_event->add();
        $i_fd = intval( $r_fd );
        $this->a_event[ $i_fd ][ $i_event_type ] = $o_event;
        $this->a_client[ $i_fd ] = $r_fd;
        //echo json_encode( $this->a_client )." | ".json_encode( $this->a_event ).PHP_EOL;
    }
    /*
     * @desc : 删除一个事件
     * */
    public function del( $r_fd, $i_event_type ) {
        $i_fd = intval( $r_fd );
        // 删除所有事件类型
        if ( self::EV_ALL === $i_event_type ) {
            if ( isset( $this->a_event[ $i_fd ] ) ) {
                if ( isset( $this->a_event[ $i_fd ][ self::EV_WRITE ] ) ) {
                    echo "del write".PHP_EOL;
                    $o_event = $this->a_event[ $i_fd ][ self::EV_WRITE ];
                    $o_event->free();
                    unset( $this->a_event[ $i_fd ][ self::EV_WRITE ] );
                }
                if ( isset( $this->a_event[ $i_fd ][ self::EV_READ ] ) ) {
                    echo "del read".PHP_EOL;
                    $o_event = $this->a_event[ $i_fd ][ self::EV_READ ];
                    $o_event->free();
                    unset( $this->a_event[ $i_fd ][ self::EV_READ ] );
                }
                if ( isset( $this->a_event[ $i_fd ][ self::EV_EXCEPTION ] ) ) {
                    echo "del exception".PHP_EOL;
                    $o_event = $this->a_event[ $i_fd ][ self::EV_EXCEPTION ];
                    $o_event->free();
                    unset( $this->a_event[ $i_fd ][ self::EV_EXCEPTION ] );
                }
            }
        }
        // 删除指定事件类型
        else {
            if ( isset( $this->a_event[ $i_fd ] ) ) {
                if ( isset( $this->a_event[ $i_fd ][ $i_event_type ] ) ) {
                    $o_event = $this->a_event[ $i_fd ][ $i_event_type ];
                    $o_event->free();
                    unset( $this->a_event[ $i_fd ][ $i_event_type ] );
                }
            }
        }
        unset( $this->a_client[ $i_fd ] );
    }
    /*
     * @desc : 陷入事件循环
     * */
    public function loop() {
        $this->o_event_base->loop();
    }
}

index.php:

<?php
define( "DS", DIRECTORY_SEPARATOR );
define( "ROOT", __DIR__ );
spl_autoload_register( function( $s_class_name ) {
    $s_path = str_replace( "\\", "/", $s_class_name );
    $s_file = ROOT.DS.$s_path.'.php';
    require_once $s_file;
} );


// 创建一个非阻塞的listen-socket资源
$r_listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );
socket_set_option( $r_listen_socket, SOL_SOCKET, SO_REUSEADDR, 1 );
socket_set_option( $r_listen_socket, SOL_SOCKET, SO_REUSEPORT, 1 );
socket_bind( $r_listen_socket, '0.0.0.0', 6666 );
socket_listen( $r_listen_socket );
socket_set_nonblock( $r_listen_socket );


// 创建一个Event-Loop
// 然后将上面的listen-socket加入到Event-Loop中
// 这里就已经比较直白了,只要你要创建一个资源
// 这个资源可以是socket、可以是stream等等
// 因为Event-Loop已经被我们抽象称了模块
// 所以你创建的资源只要支持Event,那么都很方便地通过Event-Loop模块写出来
$o_event_loop = new Event\Libevent();
$o_event_loop->add( $r_listen_socket, Event\Libevent::EV_READ, function() use( $r_listen_socket, $o_event_loop ) {
    $r_connection_socket = socket_accept( $r_listen_socket );
    $o_event_loop->add( $r_connection_socket, Event\Libevent::EV_WRITE, function() use ( $r_connection_socket, $o_event_loop ) {
        $s_data = "Hello HTTP World!";
        $s_html = "HTTP/1.1 200 OK\r\nContent-Length: ".strlen( $s_data )."\r\n\r\n{$s_data}\n";
        $i_written = socket_write( $r_connection_socket, $s_html, strlen( $s_html ) );
        $o_event_loop->del( $r_connection_socket, Event\Libevent::EV_ALL );
    });
} );
$o_event_loop->loop();

流程和逻辑上是不是清爽无比?说下大概逻辑:

  • 第一步:在PHP里各种能支持Event-Loop的资源,比如socket、比如stream
  • 第二步:初始化Event-Loop,然后将第一步里创建好的资源扔到Event-Loop里
  • 第三步:完成

我留了一些课外作业,主要是我太懒了懒得弄,一个完整的Event-Loop模块还差一个PHP Interface,然后Libevent、Select、Libev都实现这个Interface,而Factory.php则系统则根据系统当前已经具备的IO复用自动选择最好的IO复用方法并初始化Event-Loop对象!

有了这样一个Event-Loop组件,你可以很快基于TA实现一个「基于事件监听的异步非阻塞高性能」HTTP服务器、TCP服务器、PHP版的Redis服务器、PHP版本的Memcache服务器,这叫啥?这就叫「高内聚、低耦合」,玩的就是专业,周杰棍是怎么唱的来着?

文章分享自微信公众号:
高性能API社区

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • PHP网络编程之深入Libevent(十五节)

    大家周末好,这里有趣有用广告少的公众号高性能API社区,我是老李,本文属于《PHP网络编程》系列中的一个章节。

    老李秀
  • 每个JavaScript工程师都应懂的33个概念

    这个项目是为了帮助开发者掌握 JavaScript 概念而创立的。它不是必备,但在未来学习(JavaScript)中,可以作为一篇指南。

    Fundebug
  • 想让服务器跑得快,并不是换个编程语言那么简单

    最近一个读者问我:程序君,我是一个经常被你黑的phper,我想学一门新的语言,做服务器开发,看你好像用过好多语言,能推荐一个么?最好是开发效率高,支持并发,性能...

    tyrchen
  • 每个JavaScript工程师都应懂的33个概念

    这个项目是为了帮助开发者掌握 JavaScript 概念而创立的。它不是必备,但在未来学习(JavaScript)中,可以作为一篇指南。

    Fundebug
  • 以太坊预言机与智能合约开发

    什么是以太坊预言机?智能合约就其性质而言,能够运行各种算法并可以存储和查询数据。预言机可以监控区块链事件并能将监控结果发回智能合约。因为每个节点每次都需要大量计...

    笔阁
  • 为什么要用 Node.js

    传统意义上的 JavaScript 运行在浏览器上,这是因为浏览器内核实际上分为两个部分:渲染引擎和 JavaScript 引擎。前者负责渲染 HTML + C...

    哲洛不闹
  • 【Flink】第二十六篇:源码角度分析Task执行过程

    【Flink】第四篇:【迷思】对update语义拆解D-、I+后造成update原子性丢失

    章鱼carl
  • 从根上理解高性能、高并发(六):通俗易懂,高性能服务器到底是如何实现的

    作为即时通讯技术的开发者来说,高性能、高并发相关的技术概念早就了然与胸,什么线程池、零拷贝、多路复用、事件驱动、epoll等等名词信手拈来,又或许你对具有这些技...

    JackJiang
  • 深入理解PHP高级技巧、面向对象与核心技术

    2.静态变量让函数在多次被调用时记住变量的值,而这些变量并不是全局变量。可以在递归时统计计数。

    硬核项目经理
  • 为什么要用 Node.js

    这是一个移动端工程师涉足前端和后端开发的学习笔记,如有错误或理解不到位的地方,万望指正。 Node.js 是什么 传统意义上的 JavaScript 运行在浏览...

    前朝楚水
  • 如何用domain减少logger的传递

    服务端开发中,全链路日志是硬需。 全链路日志的核心是 traceid,在接收请求的那一刻生成(或者从请求头获取),在请求处理中一直透传,用于附加在每个 log ...

    fjywan
  • swoole 学习第二章 Event Io 与 process

    刚刚才说了,子进程当复制一个父进程的时候会复制它的内存以及它的上下文环境,除了这些之外,子进程会复制父进程的io句柄(fd描述符)

    Marco爱吃红烧肉
  • 从实例看muduo网络库各模块交互过程

    1、channel 2、Poller 和它的子类 EpollPoller 3、EventLoop 4、Thread、EventLoopThread、Eve...

    看、未来
  • PHP进程通信之共享内存+UNIX Socket(二十四节)

    有人跟我说:「老李,你再也不是以前的你了」。他说这句话的时候,我仿佛感觉到了当年马克·查普曼在一枪干掉了约翰·列侬后,对着列侬的尸体说:“ 你变了 ”...

    老李秀
  • 来,老李带你整点儿不一样的(一)

    作为众多打工人中的一员,老李每天早上醒来都是奄奄一息的,那么,怎么着才能打满鸡血变成元气满满的一天呢?当然是拍手舞了,那么拍手舞怎么跳呢?贴心老李自然还要再送你...

    老李秀
  • 用PHP实现高并发服务器

    一提到高并发,就没有办法绕开I/O复用,再具体到特定的平台linux, 就没办法绕开epoll. epoll为啥高效的原理就不讲了,感兴趣的同学可以自行搜索研究...

    猿哥
  • 2021年最新PHP 面试、笔试题汇总(一)

    1.单一职责原则规定一个类有且仅有一个理由使其改变。换句话说,一个类的边界和职责应当是十分狭窄且集中的。我很喜欢的一句话"在类的职责问题上,无知是福"。一个类应...

    码农编程进阶笔记
  • 这次让我们真的读一下Workerman源码(六)

    在经过了一个如沐春风、令人神清气爽而又愉悦的工作周后(具体发生了什么你们心里应该有数),总算可以回到以往周六日的节奏了。实际上对于我来说,没有严格意义上的周六日...

    老李秀

扫码关注云+社区

领取腾讯云代金券