Swoole Library分析

Swoolev4版本后内置了Library模块,使用PHP代码编写内核功能,使得底层设施更加稳定可靠。

这篇文章我们就来分析一下如何使用以及编写Library

首先,在Swoole v4的早期版本,swoole-src下面是有一个library目录的,里面就存放了很多的PHP代码,也就是Library库的代码。在后续版本,这个library就独立出了一个仓库

简单介绍了背景之后,我们就来分析下这个Library如何工作以及如何使用的。

如何把Library作为Swoole扩展的内置库

eval

首先,我们要明白Library可以作为Swoole内置库的工作原理。我们先来看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

echo "start\n";
$code = '
class Library
{
public function func1() {
echo "codinghuang\n";
}
}
';
eval($code);
(new Library)->func1();
echo "end\n";

执行结果如下:

1
2
3
start
codinghuang
end

我们发现,通过eval可以把$code对应的字符串作为代码来执行。Swoole也是使用了eval的能力,把PHP代码作为Swoole扩展的内置库。在Swoole扩展层面,就是调用了zend_eval_stringl来执行PHP代码的。

既然如此,Swoole肯定就需要读取写好的PHP Library,然后在扩展加载的时候,执行zend_eval_stringl。然后,这些类、函数、变量等等就生成了。

Swoole如何读取到写好的Library代码

其实,Library代码都在文件swoole-src/php_swoole_library.h里面。我们其中一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
static const char* swoole_library_source_constants =
"\n"
"/**\n"
" * This file is part of Swoole.\n"
" *\n"
" * @link https://www.swoole.com\n"
" * @contact team@swoole.com\n"
" * @license https://github.com/swoole/library/blob/master/LICENSE\n"
" */\n"
"\n"
"declare(strict_types=1);\n"
"\n"
"define('SWOOLE_LIBRARY', true);\n";

可以发现,这段PHP代码作为C++的字符串存在。然后,在函数php_swoole_load_library里面就调用了:

1
zend::eval(swoole_library_source_constants, "@swoole-src/library/constants.php");

来执行这段PHP代码。这样,常量SWOOLE_LIBRARY就被定义了。我们在来看看其他的Library代码(因为太长,我省略了部分):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
static const char* swoole_library_source_core_constant =
"\n"
"/**\n"
" * This file is part of Swoole.\n"
" *\n"
" * @link https://www.swoole.com\n"
" * @contact team@swoole.com\n"
" * @license https://github.com/swoole/library/blob/master/LICENSE\n"
" */\n"
"\n"
"declare(strict_types=1);\n"
"\n"
"namespace Swoole;\n"
"\n"
"class Constant\n"
"{\n"
" public const OPTION_BUFFER_INPUT_SIZE = 'buffer_input_size';\n"
"\n"
" public const OPTION_BUFFER_OUTPUT_SIZE = 'buffer_output_size';\n"
"\n"
" public const OPTION_MESSAGE_QUEUE_KEY = 'message_queue_key';\n"
"\n"
" public const OPTION_BACKLOG = 'backlog';\n"
"\n"
" public const OPTION_KERNEL_SOCKET_RECV_BUFFER_SIZE = 'kernel_socket_recv_buffer_size';\n"
"\n"
" public const OPTION_KERNEL_SOCKET_SEND_BUFFER_SIZE = 'kernel_socket_send_buffer_size';\n"
"\n"
" public const OPTION_TCP_DEFER_ACCEPT = 'tcp_defer_accept';\n"
"\n"
" public const OPTION_OPEN_TCP_KEEPALIVE = 'open_tcp_keepalive';\n"
"\n"
" public const OPTION_OPEN_HTTP_PROTOCOL = 'open_http_protocol';\n"
"\n"
" public const OPTION_OPEN_WEBSOCKET_PROTOCOL = 'open_websocket_protocol';\n"
"\n"
" public const OPTION_WEBSOCKET_SUBPROTOCOL = 'websocket_subprotocol';\n"
"\n"
" public const OPTION_OPEN_WEBSOCKET_CLOSE_FRAME = 'open_websocket_close_frame';\n"
"\n"
" public const OPTION_OPEN_HTTP2_PROTOCOL = 'open_http2_protocol';\n"
"\n"
" public const OPTION_OPEN_REDIS_PROTOCOL = 'open_redis_protocol';\n"
"\n"
" public const OPTION_TCP_KEEPIDLE = 'tcp_keepidle';\n"
"\n"
" public const OPTION_TCP_KEEPINTERVAL = 'tcp_keepinterval';\n"
"\n"
" public const OPTION_TCP_KEEPCOUNT = 'tcp_keepcount';\n"
"\n"
" public const OPTION_TCP_FASTOPEN = 'tcp_fastopen';\n"
"\n"
" public const OPTION_PACKAGE_BODY_START = 'package_body_start';\n"
"\n"
" public const OPTION_SSL_CLIENT_CERT_FILE = 'ssl_client_cert_file';\n"
"\n"
" public const OPTION_SSL_PREFER_SERVER_CIPHERS = 'ssl_prefer_server_ciphers';\n"
"\n"
" public const OPTION_SSL_CIPHERS = 'ssl_ciphers';\n"
"\n"
" public const OPTION_SSL_ECDH_CURVE = 'ssl_ecdh_curve';\n"
"\n"
" public const OPTION_SSL_DHPARAM = 'ssl_dhparam';\n"
"\n"
" public const OPTION_OPEN_SSL = 'open_ssl';\n"
"\n"
" /* }}} OPTION */\n"
"}\n";

我们发现,这里有太多的PHP常量了,并且常量对应的值很多都是Swoole的配置项这种,例如buffer_input_size。所以,这些php_swoole_library.h文件里面的Library肯定不是手写的。

实际上,php_swoole_library.h里面的Library代码是通过swoole-src/remake_library.sh工具自动生成的。

但是,如果你在比较新的swoole-src代码里面直接跑remake_library.sh脚本,是会报错的:

1
2
3
4
5
6
7
[root@64fa874bf7d4 swoole-src]# ./remake_library.sh
rm swoole.lo
rm php_swoole_library.h
sh: line 0: cd: /root/codeDir/cppCode/swoole-src/library: No such file or directory
sh: line 0: cd: /root/codeDir/cppCode/swoole-src/library: No such file or directory
❌ Unable to get commit id of library in [/root/codeDir/cppCode/swoole-src/library]
[root@64fa874bf7d4 swoole-src]#

因为library代码已经不在swoole-src下面了。

如何生成Library代码

所以,我们需要先准备好library代码,我们可以直接在swoole-src下面git clonelibrary代码:

1
git clone git@github.com:swoole/library.git

然后,在目录swoole-src下执行remake_library.sh脚本即可把library的代码生成在php_swoole_library.h文件里面:

1
2
3
4
5
[root@64fa874bf7d4 swoole-src]# ./remake_library.sh
🚀🚀🚀Generated swoole php library successfully!🚀🚀🚀
remake...
done
[root@64fa874bf7d4 swoole-src]#

这样,就可以把library仓库最新的代码生成到php_swoole_library.h文件里面了。

Swoole的那些常量如何编写的

我们发现,在library里面有一个文件library/src/core/Constant.php,这里面包含了很多Swoole内核的常量字符串,比如说:

1
2
3
4
5
6
public const EVENT_RECEIVE = 'receive';
public const EVENT_CONNECT = 'connect';
public const OPTION_SSL_CERT_FILE = 'ssl_cert_file';
public const OPTION_SSL_KEY_FILE = 'ssl_key_file';
public const OPTION_BUFFER_INPUT_SIZE = 'buffer_input_size';
public const OPTION_BUFFER_OUTPUT_SIZE = 'buffer_output_size';

等等,这些要写起来是非常的繁琐,而且很容易漏了。并且,如果你在Swoole内核中修改了配置项,那么我们就需要在Constant.php里面去更新对应的常量,非常的麻烦。所以,官方提供了一个工具swoole-src/tools/constant-generator.php。只要我们跑这个PHP脚本,它就会通过正则匹配,把Swoole内核里面的那些常量字符串找出来,然后生成到文件library/src/core/Constant.php里面,大大的提高了工作效率。我们来演示一下:

1
2
3
[root@64fa874bf7d4 tools]# php constant-generator.php
🚀🚀🚀Constant generator successfully done!🚀🚀🚀
[root@64fa874bf7d4 tools]#

从这里我们发现,library的代码除了手动编写的之外,还有部分是工具生成的。

(未完)