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 c2eda95

Browse files
更新《PHP扩展开发》-协程-重构Server
1 parent b8e593c commit c2eda95

File tree

7 files changed

+611
-7
lines changed

7 files changed

+611
-7
lines changed

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

Lines changed: 393 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,396 @@ php_study_fci_fcc* get_handler();
7575
```
7676
7777
分别用来设置回调函数以及获得回调函数。
78+
79+
接着,我们去实现这些方法,在文件`study_coroutine_server.cc`里面。
80+
81+
我们先引入头部内容:
82+
83+
```cpp
84+
#include "study_coroutine_server.h"
85+
#include "study_coroutine_socket.h"
86+
#include "log.h"
87+
88+
using study::phpcoroutine::Server;
89+
using study::PHPCoroutine;
90+
using study::coroutine::Socket;
91+
```
92+
93+
然后实现构造函数:
94+
95+
```cpp
96+
Server::Server(char *host, int port)
97+
{
98+
socket = new Socket(AF_INET, SOCK_STREAM, 0);
99+
if (socket->bind(ST_SOCK_TCP, host, port) < 0)
100+
{
101+
stWarn("Error has occurred: (errno %d) %s", errno, strerror(errno));
102+
return;
103+
}
104+
if (socket->listen(512) < 0)
105+
{
106+
return;
107+
}
108+
}
109+
```
110+
111+
代码很简单,就是去创建一个服务端使用的套接字,然后绑定对应的`ip`以及端口,然后设置`backlog`队列的大小。
112+
113+
然后是析构函数:
114+
115+
```cpp
116+
Server::~Server()
117+
{
118+
}
119+
```
120+
121+
暂时没有需要被释放的资源。
122+
123+
然后是启动服务器的代码:
124+
125+
```cpp
126+
bool Server::start()
127+
{
128+
zval zsocket;
129+
running = true;
130+
131+
while (running)
132+
{
133+
Socket* conn = socket->accept();
134+
if (!conn)
135+
{
136+
return false;
137+
}
138+
139+
php_study_init_socket_object(&zsocket, conn);
140+
PHPCoroutine::create(&(handler->fcc), 1, &zsocket);
141+
zval_dtor(&zsocket);
142+
}
143+
return true;
144+
}
145+
```
146+
147+
代码也比较简单。首先,把`running`设置为`true`,代表服务器已经启动了。然后,执行一个无限循环,然后在循环里面去获取客户端的连接。获取到连接之后,就创建一个协程,去执行用户设置的回调函数。回调函数的参数只有一个,就是对应的连接`socket`,它是`Study\Coroutine\Socket`类型。
148+
149+
其中,`php_study_init_socket_object`的作用是去初始化一个自定义的`PHP`对象,并且让`zsocket`这个容器指向自定义对象里面的`std`对象。我们在文件`study_coroutine_socket.cc`里面去实现这个函数:
150+
151+
```cpp
152+
void php_study_init_socket_object(zval *zsocket, Socket *socket)
153+
{
154+
zend_object *object = study_coro_socket_create_object(study_coro_socket_ce_ptr);
155+
156+
coro_socket *socket_t = (coro_socket *) study_coro_socket_fetch_object(object);
157+
socket_t->socket = socket;
158+
ZVAL_OBJ(zsocket, object);
159+
}
160+
```
161+
162+
然后,在文件`study_coroutine_socket.h`里面去声明这个函数:
163+
164+
```cpp
165+
void php_study_init_socket_object(zval *zsocket, study::coroutine::Socket *socket);
166+
```
167+
168+
这样,在文件`study_coroutine_server.cc`里面就可以调用`php_study_init_socket_object`了。
169+
170+
接着,我们去实现关闭服务器的方法:
171+
172+
```cpp
173+
bool Server::shutdown()
174+
{
175+
running = false;
176+
return true;
177+
}
178+
```
179+
180+
然后,实现设置回调函数的方法:
181+
182+
```cpp
183+
void Server::set_handler(php_study_fci_fcc *_handler)
184+
{
185+
handler = _handler;
186+
}
187+
```
188+
189+
然后,实现获取回调函数的代码:
190+
191+
```cpp
192+
php_study_fci_fcc* Server::get_handler()
193+
{
194+
return handler;
195+
}
196+
```
197+
198+
这样,我们就实现完`study::phpcoroutine::Server`这个类了。我们接下来去实现扩展接口部分。
199+
200+
首先,定义`PHP`类指针以及对象的`handler`:
201+
202+
```cpp
203+
/**
204+
* Define zend class entry
205+
*/
206+
zend_class_entry study_coro_server_ce;
207+
zend_class_entry *study_coro_server_ce_ptr;
208+
209+
static zend_object_handlers study_coro_server_handlers;
210+
```
211+
212+
然后定义自定义`PHP`对象:
213+
214+
```cpp
215+
typedef struct
216+
{
217+
Server *serv;
218+
zend_object std;
219+
} coro_serv;
220+
```
221+
222+
然后,实现一下创建自定义对象以及获取自定义对象的函数:
223+
224+
```cpp
225+
static coro_serv* study_coro_server_fetch_object(zend_object *obj)
226+
{
227+
return (coro_serv *)((char *)obj - study_coro_server_handlers.offset);
228+
}
229+
230+
static zend_object* study_coro_server_create_object(zend_class_entry *ce)
231+
{
232+
coro_serv *serv_t = (coro_serv *)ecalloc(1, sizeof(coro_serv) + zend_object_properties_size(ce));
233+
zend_object_std_init(&serv_t->std, ce);
234+
object_properties_init(&serv_t->std, ce);
235+
serv_t->std.handlers = &study_coro_server_handlers;
236+
return &serv_t->std;
237+
}
238+
```
239+
240+
然后实现释放自定义对象的函数:
241+
242+
```cpp
243+
static void study_coro_server_free_object(zend_object *object)
244+
{
245+
coro_serv *serv_t = (coro_serv *) study_coro_server_fetch_object(object);
246+
if (serv_t->serv)
247+
{
248+
php_study_fci_fcc *handler = serv_t->serv->get_handler();
249+
if (handler)
250+
{
251+
efree(handler);
252+
serv_t->serv->set_handler(nullptr);
253+
}
254+
delete serv_t->serv;
255+
}
256+
zend_object_std_dtor(&serv_t->std);
257+
}
258+
```
259+
260+
这里,我们需要记得去释放`handler`,避免内存泄漏。
261+
262+
接着,我们定义接口的参数:
263+
264+
```cpp
265+
ZEND_BEGIN_ARG_INFO_EX(arginfo_study_coro_server_void, 0, 0, 0)
266+
ZEND_END_ARG_INFO()
267+
268+
ZEND_BEGIN_ARG_INFO_EX(arginfo_study_coro_server_construct, 0, 0, 2)
269+
ZEND_ARG_INFO(0, host)
270+
ZEND_ARG_INFO(0, port)
271+
ZEND_END_ARG_INFO()
272+
273+
ZEND_BEGIN_ARG_INFO_EX(arginfo_study_coro_server_ser_handler, 0, 0, 1)
274+
ZEND_ARG_CALLABLE_INFO(0, func, 0)
275+
ZEND_END_ARG_INFO()
276+
```
277+
278+
然后实现构造函数:
279+
280+
```cpp
281+
PHP_METHOD(study_coro_server, __construct)
282+
{
283+
zend_long port;
284+
coro_serv *server_t;
285+
zval *zhost;
286+
287+
ZEND_PARSE_PARAMETERS_START(2, 2)
288+
Z_PARAM_ZVAL(zhost)
289+
Z_PARAM_LONG(port)
290+
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
291+
292+
server_t = (coro_serv *)study_coro_server_fetch_object(Z_OBJ_P(getThis()));
293+
server_t->serv = new Server(Z_STRVAL_P(zhost), port);
294+
295+
zend_update_property_string(study_coro_server_ce_ptr, getThis(), ZEND_STRL("host"), Z_STRVAL_P(zhost));
296+
zend_update_property_long(study_coro_server_ce_ptr, getThis(), ZEND_STRL("port"), port);
297+
}
298+
```
299+
300+
代码和之前的类似,就不多啰嗦了。
301+
302+
然后实现启动服务器的代码:
303+
304+
```cpp
305+
PHP_METHOD(study_coro_server, start)
306+
{
307+
coro_serv *server_t;
308+
309+
server_t = (coro_serv *)study_coro_server_fetch_object(Z_OBJ_P(getThis()));
310+
if (server_t->serv->start() == false)
311+
{
312+
RETURN_FALSE;
313+
}
314+
RETURN_TRUE;
315+
}
316+
```
317+
318+
代码也很简单,就是直接去调用我们的`start`方法。
319+
320+
然后实现关闭服务器的代码:
321+
322+
```cpp
323+
PHP_METHOD(study_coro_server, shutdown)
324+
{
325+
coro_serv *server_t;
326+
327+
server_t = (coro_serv *)study_coro_server_fetch_object(Z_OBJ_P(getThis()));
328+
if (server_t->serv->shutdown() == false)
329+
{
330+
RETURN_FALSE;
331+
}
332+
RETURN_TRUE;
333+
}
334+
```
335+
336+
然后去实现设置回调函数的这个接口:
337+
338+
```cpp
339+
PHP_METHOD(study_coro_server, set_handler)
340+
{
341+
coro_serv *server_t;
342+
php_study_fci_fcc *handle_fci_fcc;
343+
344+
handle_fci_fcc = (php_study_fci_fcc *)emalloc(sizeof(php_study_fci_fcc));
345+
346+
ZEND_PARSE_PARAMETERS_START(1, 1)
347+
Z_PARAM_FUNC(handle_fci_fcc->fci, handle_fci_fcc->fcc)
348+
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
349+
350+
server_t = (coro_serv *)study_coro_server_fetch_object(Z_OBJ_P(getThis()));
351+
server_t->serv->set_handler(handle_fci_fcc);
352+
}
353+
```
354+
355+
这里,我们不需要从`PHP`脚本那边传递这个函调函数的参数进来,因为,这个回调函数的默认参数就是`Study\Coroutine\Socket`。我们在方法`study::phpcoroutine::Server::start`里面已经做了这个处理。
356+
357+
接着,我们需要注册这些接口:
358+
359+
```cpp
360+
static const zend_function_entry study_coroutine_server_coro_methods[] =
361+
{
362+
PHP_ME(study_coro_server, __construct, arginfo_study_coro_server_construct, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR) // ZEND_ACC_CTOR is used to declare that this method is a constructor of this class.
363+
PHP_ME(study_coro_server, start, arginfo_study_coro_server_void, ZEND_ACC_PUBLIC)
364+
PHP_ME(study_coro_server, shutdown, arginfo_study_coro_server_void, ZEND_ACC_PUBLIC)
365+
PHP_ME(study_coro_server, set_handler, arginfo_study_coro_server_ser_handler, ZEND_ACC_PUBLIC)
366+
PHP_FE_END
367+
};
368+
```
369+
370+
然后,实现模块初始化:
371+
372+
```cpp
373+
void study_coro_server_init(int module_number)
374+
{
375+
INIT_NS_CLASS_ENTRY(study_coro_server_ce, "Study", "Coroutine\\Server", study_coroutine_server_coro_methods);
376+
377+
memcpy(&study_coro_server_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
378+
study_coro_server_ce_ptr = zend_register_internal_class(&study_coro_server_ce TSRMLS_CC); // Registered in the Zend Engine
379+
ST_SET_CLASS_CUSTOM_OBJECT(study_coro_server, study_coro_server_create_object, study_coro_server_free_object, coro_serv, std);
380+
381+
zend_declare_property_string(study_coro_server_ce_ptr, ZEND_STRL("host"), "", ZEND_ACC_PUBLIC);
382+
zend_declare_property_long(study_coro_server_ce_ptr, ZEND_STRL("port"), -1, ZEND_ACC_PUBLIC);
383+
zend_declare_property_long(study_coro_server_ce_ptr, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC);
384+
zend_declare_property_string(study_coro_server_ce_ptr, ZEND_STRL("errMsg"), "", ZEND_ACC_PUBLIC);
385+
}
386+
```
387+
388+
然后,我们需要在文件`study.cc`里面的`PHP_MINIT_FUNCTION(study)`去调用这个模块初始化的代码:
389+
390+
```cpp
391+
PHP_MINIT_FUNCTION(study)
392+
{
393+
study_coroutine_util_init();
394+
study_coro_server_init(module_number); // 修改的地方
395+
study_coro_channel_init();
396+
study_coro_socket_init(module_number);
397+
return SUCCESS;
398+
}
399+
```
400+
401+
然后,我们需要在文件`php_study.h`里面去声明这个函数:
402+
403+
```cpp
404+
void study_coroutine_util_init();
405+
void study_coro_server_init(int module_number); // 修改的一行
406+
void study_coro_channel_init();
407+
void study_coro_socket_init(int module_number);
408+
```
409+
410+
然后重新编译、安装扩展:
411+
412+
```shell
413+
~/codeDir/cppCode/study # make clean ; make ; make install
414+
----------------------------------------------------------------------
415+
416+
Build complete.
417+
Don't forget to run 'make test'.
418+
419+
Installing shared extensions: /usr/local/lib/php/extensions/no-debug-non-zts-20180731/
420+
Installing header files: /usr/local/include/php/
421+
~/codeDir/cppCode/study #
422+
```
423+
424+
接着,编写测试脚本:
425+
426+
```php
427+
<?php
428+
429+
study_event_init();
430+
431+
Sgo(function ()
432+
{
433+
$server = new Study\Coroutine\Server("127.0.0.1", 80);
434+
$server->set_handler(function (Study\Coroutine\Socket $conn) use($server) {
435+
$data = $conn->recv();
436+
$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";
437+
$conn->send($responseStr);
438+
$conn->close();
439+
// Sco::sleep(0.01);
440+
});
441+
$server->start();
442+
});
443+
444+
study_event_wait();
445+
```
446+
447+
然后启动服务器:
448+
449+
```shell
450+
~/codeDir/cppCode/study # php test.php
451+
452+
```
453+
454+
然后进行压测:
455+
456+
```shell
457+
~/codeDir # ab -c 100 -n 1000000 127.0.0.1/
458+
Concurrency Level: 100
459+
Time taken for tests: 85.210 seconds
460+
Complete requests: 1000000
461+
Failed requests: 0
462+
Total transferred: 96000000 bytes
463+
HTML transferred: 13000000 bytes
464+
Requests per second: 11735.71 [#/sec] (mean)
465+
Time per request: 8.521 [ms] (mean)
466+
Time per request: 0.085 [ms] (mean, across all concurrent requests)
467+
Transfer rate: 1100.22 [Kbytes/sec] received
468+
```
469+
470+
可以看出,在百万级别的请求下,服务器也是可以非常稳定的。

0 commit comments

Comments
(0)

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