懂 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 中的事件处理
以下是Workerman
中Worker
类源码的一部分:
<?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
在接收到客户端内容时调用的函数。
还有些框架可以自定义事件的,如Vue
的vm.$emit(event, fn);
和vm.$emit(event, args)
原理也是一样的,只不过调用事件的时机需要自己定义。
通过阅读别人优秀的源码可以学习到挺多知识的哈!事件处理的机制应该讲得挺明白了吧?