Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit fd3a524

Browse files
新增《PHP扩展开发》-协程-hook原来的sleep
1 parent 0a854ae commit fd3a524

10 files changed

+288
-11
lines changed

‎README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,4 +194,6 @@ docker build -t study -f docker/Dockerfile .
194194

195195
[73、重构Server](./docs/《PHP扩展开发》-协程-重构Server.md)
196196

197-
### 第四阶段 HTTP服务器
197+
### 第四阶段 Hook
198+
199+
[74、hook原来的sleep](./docs/《PHP扩展开发》-协程-hook原来的sleep.md)

‎config.m4

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ if test "$PHP_STUDY" != "no"; then
6767
study_coroutine_channel.cc \
6868
src/coroutine/channel.cc \
6969
study_coroutine_socket.cc \
70-
study_coroutine_server.cc
70+
study_coroutine_server.cc \
71+
study_runtime.cc
7172
"
7273

7374
PHP_NEW_EXTENSION(study, $study_source_file, $ext_shared, ,, cxx)
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
# hook原来的sleep
2+
3+
现在,我们进入一个全新的主题,讲解如何替换掉`PHP`原来那些阻塞的函数。已达到不修改历史代码,就可以直接协程化我们的代码。
4+
5+
我们这篇文章需要实现的方法如下:
6+
7+
```php
8+
Study\Runtime\enableCoroutine()
9+
```
10+
11+
首先,我们创建两个文件`study_runtime.h``study_runtime.cc`
12+
13+
其中,`study_runtime.h`文件的内容如下:
14+
15+
```cpp
16+
#ifndef STUDY_RUNTIME_H
17+
#define STUDY_RUNTIME_H
18+
19+
#include "php_study.h"
20+
21+
#endif /* STUDY_RUNTIME_H */
22+
```
23+
24+
其中,`study_runtime.cc`文件的内容如下:
25+
26+
```cpp
27+
#include "study_runtime.h"
28+
```
29+
30+
然后修改我们的`config.m4`文件,增加`study_runtime.cc`为需要编译的源文件:
31+
32+
```shell
33+
study_source_file="\
34+
study.cc \
35+
study_coroutine.cc \
36+
study_coroutine_util.cc \
37+
src/coroutine/coroutine.cc \
38+
src/coroutine/context.cc \
39+
${STUDY_ASM_DIR}make_${STUDY_CONTEXT_ASM_FILE} \
40+
${STUDY_ASM_DIR}jump_${STUDY_CONTEXT_ASM_FILE} \
41+
src/socket.cc \
42+
src/log.cc \
43+
src/error.cc \
44+
src/core/base.cc \
45+
src/coroutine/socket.cc \
46+
src/timer.cc \
47+
study_coroutine_channel.cc \
48+
src/coroutine/channel.cc \
49+
study_coroutine_socket.cc \
50+
study_coroutine_server.cc \
51+
study_runtime.cc
52+
"
53+
```
54+
55+
然后和往常一样,我们需要先实现`PHP``Study\Runtime`
56+
57+
在文件`study_runtime.cc`里面,我们实现我们的`enableCoroutine`方法。首先,定义一下这个方法的参数。目前我们不需要传递参数:
58+
59+
```cpp
60+
ZEND_BEGIN_ARG_INFO_EX(arginfo_study_runtime_void, 0, 0, 0)
61+
ZEND_END_ARG_INFO()
62+
```
63+
64+
然后是`enableCoroutine`方法的大体框架:
65+
66+
```cpp
67+
extern PHP_METHOD(study_coroutine_util, sleep);
68+
static void hook_func(const char *name, size_t name_len, zif_handler handler);
69+
70+
static PHP_METHOD(study_runtime, enableCoroutine)
71+
{
72+
hook_func(ZEND_STRL("sleep"), zim_study_coroutine_util_sleep);
73+
}
74+
```
75+
76+
(后面我们会去实现`hook_func`这个函数)
77+
78+
这里,我们的意图是把`PHP`原来的`sleep`函数替换成`Study\Coroutine::sleep`这个方法。`zim_study_coroutine_util_sleep`实际上就是我们在文件`study_coroutine_util.cc`中定义的`PHP_METHOD(study_coroutine_util, sleep)`展开后的结果。
79+
80+
然后,收集`enableCoroutine`到结构体`zend_function_entry`里面:
81+
82+
```cpp
83+
static const zend_function_entry study_runtime_methods[] =
84+
{
85+
PHP_ME(study_runtime, enableCoroutine, arginfo_study_runtime_void, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
86+
PHP_FE_END
87+
};
88+
```
89+
90+
然后创建一个模块初始化函数来注册`PHP``Study\Runtime`:
91+
92+
```cpp
93+
/**
94+
* Define zend class entry
95+
*/
96+
zend_class_entry study_runtime_ce;
97+
zend_class_entry *study_runtime_ce_ptr;
98+
99+
void study_runtime_init()
100+
{
101+
INIT_NS_CLASS_ENTRY(study_runtime_ce, "Study", "Runtime", study_runtime_methods);
102+
study_runtime_ce_ptr = zend_register_internal_class(&study_runtime_ce TSRMLS_CC); // Registered in the Zend Engine
103+
}
104+
```
105+
106+
然后,我们在`study.cc`文件的`PHP_MINIT_FUNCTION(study)`函数里面调用`study_runtime_init`:
107+
108+
```cpp
109+
PHP_MINIT_FUNCTION(study)
110+
{
111+
study_coroutine_util_init();
112+
study_coro_server_init(module_number);
113+
study_coro_channel_init();
114+
study_coro_socket_init(module_number);
115+
study_runtime_init(); // 新增的代码
116+
return SUCCESS;
117+
}
118+
```
119+
120+
我们需要在`php_study.h`里面对`study_runtime_init`函数进行声明:
121+
122+
```cpp
123+
void study_coroutine_util_init();
124+
void study_coro_server_init(int module_number);
125+
void study_coro_channel_init();
126+
void study_coro_socket_init(int module_number);
127+
void study_runtime_init(); // 新增的代码
128+
```
129+
130+
`OK`,我们完成了`Study\Runtime`类的注册。
131+
132+
最后,我们需要实现`hook_func`:
133+
134+
```cpp
135+
static void hook_func(const char *name, size_t name_len, zif_handler new_handler)
136+
{
137+
zend_function *ori_f = (zend_function *) zend_hash_str_find_ptr(EG(function_table), name, name_len);
138+
ori_f->internal_function.handler = new_handler;
139+
}
140+
```
141+
142+
代码其实比较简单,就是先去全局的`EG(function_table)`里面查找`sleep`名字对应的`zend_function`,然后把它的`handler`换成我们新的`new_handler`即可。也就是说,`PHP`内核实现的`C`函数实际上是会以函数指针的形式保存在`zend_function.internal_function.handler`里面。
143+
144+
然后,我们重新编译、安装扩展:
145+
146+
```shell
147+
phpize --clean && phpize && ./configure && make && make install
148+
----------------------------------------------------------------------
149+
150+
Build complete.
151+
Don't forget to run 'make test'.
152+
153+
Installing shared extensions: /usr/local/lib/php/extensions/no-debug-non-zts-20180731/
154+
Installing header files: /usr/local/include/php/
155+
```
156+
157+
`OK`,编译、安装扩展成功。
158+
159+
我们来编写测试脚本:
160+
161+
```php
162+
<?php
163+
164+
study_event_init();
165+
166+
Sgo(function ()
167+
{
168+
var_dump(Study\Coroutine::getCid());
169+
sleep(1);
170+
var_dump(Study\Coroutine::getCid());
171+
});
172+
173+
Sgo(function ()
174+
{
175+
var_dump(Study\Coroutine::getCid());
176+
});
177+
178+
study_event_wait();
179+
```
180+
181+
这个脚本没有开启`hook`,执行结果如下:
182+
183+
```shell
184+
~/codeDir/cppCode/study # php test.php
185+
int(1)
186+
int(1)
187+
int(2)
188+
```
189+
190+
符合预期。进程因为第一个协程调用了阻塞的`sleep`函数,所以导致整个进程阻塞了起来,所以打印是顺序的。
191+
192+
我们现在来开启一下`hook`功能:
193+
194+
```php
195+
<?php
196+
197+
study_event_init();
198+
199+
Study\Runtime::enableCoroutine();
200+
201+
Sgo(function ()
202+
{
203+
var_dump(Study\Coroutine::getCid());
204+
sleep(1);
205+
var_dump(Study\Coroutine::getCid());
206+
});
207+
208+
Sgo(function ()
209+
{
210+
var_dump(Study\Coroutine::getCid());
211+
});
212+
213+
study_event_wait();
214+
```
215+
216+
结果如下:
217+
218+
```shell
219+
~/codeDir/cppCode/study # php test.php
220+
int(1)
221+
int(2)
222+
int(1)
223+
```
224+
225+
因为我们已经把`PHP`原先的`sleep`函数替换成了`Study\Coroutine::sleep`方法,所以,进程不会阻塞起来,会在调用`sleep`之后立马切换到第二个协程。

‎docs/《PHP扩展开发》-协程-修复一些bug(十一).md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,5 @@ Installing header files: /usr/local/include/php/
188188
```
189189

190190
我们发现,每次释放的协程栈地址都是不同的了。符合预期。
191+
192+
[下一篇:重构Server](./《PHP扩展开发》-协程-重构Server.md)

‎docs/《PHP扩展开发》-协程-重构Server.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,3 +468,5 @@ Transfer rate: 1100.22 [Kbytes/sec] received
468468
```
469469
470470
可以看出,在百万级别的请求下,服务器也是可以非常稳定的。
471+
472+
[下一篇:hook原来的sleep](./《PHP扩展开发》-协程-hook原来的sleep.md)

‎php_study.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ void study_coroutine_util_init();
5353
void study_coro_server_init(int module_number);
5454
void study_coro_channel_init();
5555
void study_coro_socket_init(int module_number);
56+
void study_runtime_init();
5657

5758
inline zval *st_zend_read_property(zend_class_entry *class_ptr, zval *obj, const char *s, int len, int silent)
5859
{

‎study.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ PHP_MINIT_FUNCTION(study)
5555
study_coro_server_init(module_number);
5656
study_coro_channel_init();
5757
study_coro_socket_init(module_number);
58+
study_runtime_init();
5859
return SUCCESS;
5960
}
6061

‎study_runtime.cc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#include "study_runtime.h"
2+
3+
ZEND_BEGIN_ARG_INFO_EX(arginfo_study_runtime_void, 0, 0, 0)
4+
ZEND_END_ARG_INFO()
5+
6+
extern PHP_METHOD(study_coroutine_util, sleep);
7+
static void hook_func(const char *name, size_t name_len, zif_handler handler);
8+
9+
static PHP_METHOD(study_runtime, enableCoroutine)
10+
{
11+
hook_func(ZEND_STRL("sleep"), zim_study_coroutine_util_sleep);
12+
}
13+
14+
static const zend_function_entry study_runtime_methods[] =
15+
{
16+
PHP_ME(study_runtime, enableCoroutine, arginfo_study_runtime_void, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
17+
PHP_FE_END
18+
};
19+
20+
static void hook_func(const char *name, size_t name_len, zif_handler new_handler)
21+
{
22+
zend_function *ori_f = (zend_function *) zend_hash_str_find_ptr(EG(function_table), name, name_len);
23+
ori_f->internal_function.handler = new_handler;
24+
}
25+
26+
/**
27+
* Define zend class entry
28+
*/
29+
zend_class_entry study_runtime_ce;
30+
zend_class_entry *study_runtime_ce_ptr;
31+
32+
void study_runtime_init()
33+
{
34+
INIT_NS_CLASS_ENTRY(study_runtime_ce, "Study", "Runtime", study_runtime_methods);
35+
study_runtime_ce_ptr = zend_register_internal_class(&study_runtime_ce TSRMLS_CC); // Registered in the Zend Engine
36+
}

‎study_runtime.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#ifndef STUDY_RUNTIME_H
2+
#define STUDY_RUNTIME_H
3+
4+
#include "php_study.h"
5+
6+
#endif /* STUDY_RUNTIME_H */

‎test.php

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@
22

33
study_event_init();
44

5+
Study\Runtime::enableCoroutine();
6+
7+
Sgo(function ()
8+
{
9+
var_dump(Study\Coroutine::getCid());
10+
sleep(1);
11+
var_dump(Study\Coroutine::getCid());
12+
});
13+
514
Sgo(function ()
615
{
7-
$server = new Study\Coroutine\Server("127.0.0.1", 80);
8-
$server->set_handler(function (Study\Coroutine\Socket $conn) use($server) {
9-
$data = $conn->recv();
10-
$responseStr = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 11\r\n\r\nhello world\r\n";
11-
$conn->send($responseStr);
12-
$conn->close();
13-
// Sco::sleep(0.01);
14-
});
15-
$server->start();
16+
var_dump(Study\Coroutine::getCid());
1617
});
1718

1819
study_event_wait();

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /