事件处理机制

2020/09/07 计算机原理 PHP

懂 Jquery 的一定见过以下形式的代码:

$('#btn').on('click', function() {
    alert('Hello world!');
});

以上就是Jquery的事件处理。并不只有Jquery才有事件处理,很多语言框架都有类似的机制。那么事件是怎么实现的呢?

最近在通过PHP学习网络编程,看到on事件处理函数在Workerman或者Swoole框架中简直太多了。下面就用原生的PHP来实现事件处理吧。

原生 PHP 实现事件处理

<?php

class Server {
    private $host = '0.0.0.0';
    private $port = 10086;
    private $listen = null;
    private $client = [];
    private $callback = [];

    public function __construct($host = '', $port = '') {
        if ($host) {
            $this->host = $host;
        }

        if ($port) {
            $this->port = $port;
        }

        $this->listen = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        socket_set_option($this->listen, SOL_SOCKET, SO_REUSEPORT, 1);
        socket_bind($this->listen, $this->host, $this->port);
        socket_listen($this->listen);
        $this->client[] = $this->listen;
    }

    public function on($event = '', callable $callback) {
        $this->callback[$event] = $callback;
    }

    public function runAll() {
        while (true) {
            $read = $write = $exception = $this->client;

            $has = socket_select($read, $write, $exception, null);
            if ($has <= 0) {
                continue;
            }

            // 有新连接
            if (in_array($this->listen, $read)) {
                $conn = socket_accept($this->listen);
                if (!$conn) {
                    continue;
                }

                $this->client[] = $conn;

                // listen 已经完成使命了,unset 它
                $key = array_search($this->listen, $read);
                unset($read[$key]);

                // onConnect 事件
                $callback = isset($this->callback['connect']) ? $this->callback['connect'] : false;
                if (false !== $callback) {
                    call_user_func_array($callback, []);
                }
            }

            foreach ($read as $rk => $rv) {
                socket_recv($rv, $buff, 1024, 0);
                if (!$buff) {
                    unset($this->client[$rk]);
                    socket_close($rv);
                    continue;
                }

                // onMessage 事件
                $callback = isset($this->callback['message']) ? $this->callback['message'] : false;
                if (false !== $callback) {
                    call_user_func_array($callback, [$buff]);
                }

                unset($this->client[$rk]);
                socket_shutdown($rv);
                socket_close($rv);
            }
        }
    }
}

$server = new Server();

$server->on('connect', function () {
    echo "connected." . PHP_EOL;
});

$server->on('message', function ($message) {
    echo "received message: " . $message;
});

$server->runAll();

其实看起来也没多大难度嘛,无非就是注册事件函数,然后在特定位置调用call_user_func_array,跟钩子差不多。在Workerman中的on事件实现原理跟以上代码也差不多,当然那是经过了很多人的试错使代码变得更健壮的,这个示例跟它无法相提并论。Swoole的实现我估计也差不了太远,只不过用了其它语言实现。

Workerman 中的事件处理

以下是WorkermanWorker类源码的一部分:

<?php

class Worker
{
    /**
     * Accept a connection.
     *
     * @param resource $socket
     * @return void
     */
    public function acceptConnection($socket)
    {
        // Accept a connection on server socket.
        \set_error_handler(function(){});
        $new_socket = \stream_socket_accept($socket, 0, $remote_address);
        \restore_error_handler();

        // Thundering herd.
        if (!$new_socket) {
            return;
        }

        // TcpConnection.
        $connection                         = new TcpConnection($new_socket, $remote_address);
        $this->connections[$connection->id] = $connection;
        $connection->worker                 = $this;
        $connection->protocol               = $this->protocol;
        $connection->transport              = $this->transport;
        $connection->onMessage              = $this->onMessage;
        $connection->onClose                = $this->onClose;
        $connection->onError                = $this->onError;
        $connection->onBufferDrain          = $this->onBufferDrain;
        $connection->onBufferFull           = $this->onBufferFull;

        // Try to emit onConnect callback.
        if ($this->onConnect) {
            try {
                \call_user_func($this->onConnect, $connection);
            } catch (\Exception $e) {
                static::log($e);
                exit(250);
            } catch (\Error $e) {
                static::log($e);
                exit(250);
            }
        }
    }
}

看到了没有,$connection->onMessage = $this->onMessage;这类代码就是在指定事件处理函数。Workerman的使用示例如下:

<?php

$text_worker = new Worker("http://0.0.0.0:9003");
$text_worker->count = 4;

$text_worker->onMessage =  function($connection, $data)
{
    $connection->send("hello world");
};

看懂了没?实例化之后通过$text_worker->onMessage来设置回调函数,这个就是 Workerman在接收到客户端内容时调用的函数。

还有些框架可以自定义事件的,如Vuevm.$emit(event, fn);vm.$emit(event, args)原理也是一样的,只不过调用事件的时机需要自己定义。

通过阅读别人优秀的源码可以学习到挺多知识的哈!事件处理的机制应该讲得挺明白了吧?

Search

    Table of Contents