@@ -75,3 +75,396 @@ php_study_fci_fcc* get_handler();
75
75
```
76
76
77
77
分别用来设置回调函数以及获得回调函数。
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