├── README.md
├── config.m4
├── debug.php
├── php_php_server.h
├── php_server.c
├── server.php
└── tests
└── 001.phpt
/README.md:
--------------------------------------------------------------------------------
1 | # PHP SERVER #
2 |
3 | 这是一个可以作为TCP服务器的PHP扩展(适用7.0.0RC1),性能跟Apache不相上下,在单机4G内存4核上rqs都在3万以上。
4 |
5 | 1. [配置选项](https://github.com/Yaoguais/php-server#配置选项)
6 | 2. [类与函数](https://github.com/Yaoguais/php-server#类与函数)
7 | 3. [安装扩展](https://github.com/Yaoguais/php-server#安装扩展)
8 | 4. [信号处理](https://github.com/Yaoguais/php-server#信号处理)
9 | 5. [调试程序](https://github.com/Yaoguais/php-server#调试程序)
10 | 6. [压力测试](https://github.com/Yaoguais/php-server#压力测试)
11 |
12 |
13 | #配置选项#
14 |
15 | 1. 进程的名称master\_name
16 | 2. 是否开启调试模式debug
17 | 3. 是否开启调试输出文件debug_file
18 |
19 | 详细说明
20 |
21 | master_name:
22 | 字符串,如 php_server.master_name = "php server"
23 |
24 | debug:
25 | 布尔值,如 php_server.debug = On
26 | php_server.debug = Off
27 |
28 | debug_file:
29 | 字符串,如 php_server.debug_file = "/home/yaoguai/php-server-%d.txt"
30 | 其中%d是进程号的占位符
31 |
32 | #类与函数#
33 |
34 | 扩展提供的类与全局函数:
35 |
36 | class php_server{
37 |
38 | /**
39 | * @var array 当前的配置
40 | */
41 | private $_settings = array();
42 |
43 | /**
44 | * 架构函数
45 | * @param string $ip IP地址
46 | * @param int $port 端口
47 | * function php_server_create
48 | */
49 | public function __construct($ip,$port){
50 |
51 | $this->_settings['ip'] = $ip;
52 | $this->_settings['port'] = $port;
53 | }
54 |
55 | /**
56 | * 事件回调函数
57 | * @param string $event
58 | * @param function $callback
59 | * @return bool
60 | * function php_server_bind
61 | */
62 | public function bind($event,$callback){
63 |
64 | return true;
65 | }
66 |
67 | /**
68 | * 设置当前的配置
69 | * @param string $key
70 | * @param mixed $value
71 | * function php_server_set
72 | */
73 | public function set($key,$value){
74 |
75 | $this->_settings[$key] = $value;
76 | }
77 |
78 | /**
79 | * 获取当前的配置
80 | * @param string|null $key
81 | * @return string
82 | * function php_server_get
83 | */
84 | public function get($key=null){
85 |
86 | return is_string($key) ? $this->_settings[$key] : $this->_settings;
87 | }
88 |
89 |
90 | /**
91 | * 启动服务器
92 | * @return bool
93 | * function php_server_run
94 | */
95 | public function run(){
96 |
97 | return true;
98 | }
99 | }
100 |
101 |
102 | 事件绑定的回调函数:
103 |
104 | /**
105 | * 接受连接时的回调
106 | * @param int $fd
107 | * @param string $ip
108 | * @param int $port
109 | * @return void
110 | */
111 | function bind_accept($fd,$ip,$port){
112 |
113 | }
114 |
115 |
116 | /**
117 | * 接受消息时的回调
118 | * @param int $fd
119 | * @param string $message
120 | * @param string $ip
121 | * @param int $port
122 | */
123 | function bind_receive($fd,$message,$ip,$port){
124 |
125 | }
126 |
127 | /**
128 | * 当连接关闭时的回调
129 | * @param int $fd
130 | * @param string $ip
131 | * @param int $port
132 | */
133 | function bind_close($fd,$ip,$port){
134 |
135 | }
136 |
137 | 全局函数:
138 |
139 | /**
140 | * 主动向客户端发送消息
141 | * @param int $fd
142 | * @param string $message
143 | * @param bool $flush
144 | * @return int (成功发送的字节数)
145 | */
146 | function php_server_send($fd,$message,$flush=true){
147 |
148 | }
149 |
150 | /**
151 | * 主动关闭客户端连接
152 | * @param int $fd
153 | */
154 | function php_server_close($fd){
155 |
156 | }
157 |
158 |
159 | #安装扩展#
160 |
161 | phpize
162 | ./configure
163 | make && make install
164 |
165 | 编辑php.ini并添加
166 | [php_server]
167 | extension = php_server.so
168 | php_server.master_name = "php server"
169 | php_server.debug = Off
170 | php_server.debug_file = ""
171 |
172 | 执行php server.php即可启动服务器
173 |
174 | #信号处理#
175 |
176 | 可以向进程发送SIGTERM或SIGINT信号终止该进程,这样可以让应用处理完所有的业务才自动终止。
177 |
178 | 也可以直接CRTL+C直接结束进程池。
179 |
180 |
181 | #调试程序#
182 |
183 | 多进程多线程程序本来就很难调试,再加上高并发,单步只能调试一般的错误,而有些错误只在高并发下才会有,
184 | 所以我这里采用的是通过调试文件记录关键字来调试程序。其实现为debug.php。
185 |
186 | 用法:
187 |
188 | ;修改php.ini
189 | ;添加
190 | php_server.debug = On
191 | php_server.debug_file = "/home/yaoguai/php-server-%d.txt"
192 |
193 | #运行服务器程序
194 | /root/php7d/bin/php server.php
195 | #打开另一个终端
196 | ab -c 100 -n 1000 http://127.0.0.1:9009/
197 | #在第一个的终端CTRL+C结束server.php
198 | #运行结果收集程序
199 | /root/php7d/bin/php debug.php
200 | #屏幕会显示关于接收连接、接收数据、发送数据、关闭连接的统计
201 |
202 | #在脚本中收集调试信息(上面是扩展中的内部收集数据)
203 | /root/php7d/bin/php server.php enable-debug
204 | #ctrl+C 结束进程屏幕会显示accept,receive,close的统计数量
205 |
206 | 在开启调试模式的时候,rqs一直在8000左右,只有关闭调试的四分之一。
207 |
208 | #压力测试#
209 |
210 | 压力测试采用Apache的ab工具进行,100的并发,1000的连接。
211 |
212 | ApacheBench:
213 | ab -c 100 -n 1000 http://127.0.0.1:9009/
214 |
215 | php server测试server.php
216 | apache测试index.html
217 | PHP Version:7.0.0-dev
218 | Linux version:Linux version 3.13.0-34-generic (buildd@allspice) (gcc version 4.8.2 (Ubuntu 4.8.2-19ubuntu1) )
219 | 内存 :3.8 GiB
220 | 处理器:Intel® Core™ i5-4430 CPU @ 3.00GHz × 4
221 | http content:
PHP SERVER 1.0
222 | content length:24 bytes
223 | php server version:1.0
224 | Apache2 version:Apache/2.4.7 (Ubuntu)
225 |
226 | 经测试,PHP-SERVER rps一直在30,000以上,apache2-server rps也在30,000以上。
--------------------------------------------------------------------------------
/config.m4:
--------------------------------------------------------------------------------
1 | dnl $Id$
2 | dnl config.m4 for extension php_server
3 |
4 | dnl Comments in this file start with the string 'dnl'.
5 | dnl Remove where necessary. This file will not work
6 | dnl without editing.
7 |
8 | dnl If your extension references something external, use with:
9 |
10 | PHP_ARG_WITH(php_server, for php_server support,
11 | Make sure that the comment is aligned:
12 | [ --with-php_server Include php_server support])
13 |
14 | dnl Otherwise use enable:
15 |
16 | dnl PHP_ARG_ENABLE(php_server, whether to enable php_server support,
17 | dnl Make sure that the comment is aligned:
18 | dnl [ --enable-php_server Enable php_server support])
19 |
20 | if test "$PHP_PHP_SERVER" != "no"; then
21 | dnl Write more examples of tests here...
22 |
23 | dnl # --with-php_server -> check with-path
24 | dnl SEARCH_PATH="/usr/local /usr" # you might want to change this
25 | dnl SEARCH_FOR="/include/php_server.h" # you most likely want to change this
26 | dnl if test -r $PHP_PHP_SERVER/$SEARCH_FOR; then # path given as parameter
27 | dnl PHP_SERVER_DIR=$PHP_PHP_SERVER
28 | dnl else # search default path list
29 | dnl AC_MSG_CHECKING([for php_server files in default path])
30 | dnl for i in $SEARCH_PATH ; do
31 | dnl if test -r $i/$SEARCH_FOR; then
32 | dnl PHP_SERVER_DIR=$i
33 | dnl AC_MSG_RESULT(found in $i)
34 | dnl fi
35 | dnl done
36 | dnl fi
37 | dnl
38 | dnl if test -z "$PHP_SERVER_DIR"; then
39 | dnl AC_MSG_RESULT([not found])
40 | dnl AC_MSG_ERROR([Please reinstall the php_server distribution])
41 | dnl fi
42 |
43 | dnl # --with-php_server -> add include path
44 | dnl PHP_ADD_INCLUDE($PHP_SERVER_DIR/include)
45 |
46 | dnl # --with-php_server -> check for lib and symbol presence
47 | dnl LIBNAME=php_server # you may want to change this
48 | dnl LIBSYMBOL=php_server # you most likely want to change this
49 |
50 | dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
51 | dnl [
52 | dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $PHP_SERVER_DIR/$PHP_LIBDIR, PHP_SERVER_SHARED_LIBADD)
53 | dnl AC_DEFINE(HAVE_PHP_SERVERLIB,1,[ ])
54 | dnl ],[
55 | dnl AC_MSG_ERROR([wrong php_server lib version or lib not found])
56 | dnl ],[
57 | dnl -L$PHP_SERVER_DIR/$PHP_LIBDIR -lm
58 | dnl ])
59 | dnl
60 | dnl PHP_SUBST(PHP_SERVER_SHARED_LIBADD)
61 |
62 | PHP_NEW_EXTENSION(php_server, php_server.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
63 | fi
64 |
--------------------------------------------------------------------------------
/debug.php:
--------------------------------------------------------------------------------
1 | $file){
24 | $pid = intval(str_replace(array('php-server-'),array(''),pathinfo($file,PATHINFO_FILENAME)));
25 | if($pid<$min){
26 | $min = $pid;
27 | $fileKey = $key;
28 | }
29 | }
30 | $ret = $files[$fileKey];
31 | unset($files[$fileKey]);
32 | return $ret;
33 | }
34 |
35 | //$firstFile = get_first_file($files);
36 | //$keywords = array('__will_accept','epoll come in master:0','epoll come in master:1','master_break:0','master_break:1');
37 |
38 | function debug_file($file,$keywords){
39 | echo pathinfo($file,PATHINFO_FILENAME),"\n";
40 | $string = file_get_contents($file);
41 | foreach ($keywords as $keyword){
42 | echo $keyword,': ',substr_count($string,$keyword),"\n";
43 | }
44 | echo "\n";
45 | }
46 |
47 | //debug_file($firstFile,$keywords);
48 |
49 | $keywords = array('__accepted','__accept_error','__recv_error','__recv_from','__recv_once','__send','__close','__client_close');
50 |
51 | foreach ($files as $file){
52 | debug_file($file,$keywords);
53 | }
--------------------------------------------------------------------------------
/php_php_server.h:
--------------------------------------------------------------------------------
1 | /*
2 | +----------------------------------------------------------------------+
3 | | PHP Version 7 |
4 | +----------------------------------------------------------------------+
5 | | Copyright (c) 1997-2014 The PHP Group |
6 | +----------------------------------------------------------------------+
7 | | This source file is subject to version 3.01 of the PHP license, |
8 | | that is bundled with this package in the file LICENSE, and is |
9 | | available through the world-wide-web at the following url: |
10 | | http://www.php.net/license/3_01.txt |
11 | | If you did not receive a copy of the PHP license and are unable to |
12 | | obtain it through the world-wide-web, please send a note to |
13 | | license@php.net so we can mail you a copy immediately. |
14 | +----------------------------------------------------------------------+
15 | | Author: Yaoguai (newtopstdio@163.com) |
16 | +----------------------------------------------------------------------+
17 | */
18 |
19 | /* $Id$ */
20 |
21 | #ifndef PHP_PHP_SERVER_H
22 | #define PHP_PHP_SERVER_H
23 |
24 | extern zend_module_entry php_server_module_entry;
25 | #define phpext_php_server_ptr &php_server_module_entry
26 |
27 | // fix 7.0.0RC1
28 | #define PHP_PHP_SERVER_VERSION "0.1.1"
29 |
30 | #ifdef PHP_WIN32
31 | # define PHP_PHP_SERVER_API __declspec(dllexport)
32 | #elif defined(__GNUC__) && __GNUC__ >= 4
33 | # define PHP_PHP_SERVER_API __attribute__ ((visibility("default")))
34 | #else
35 | # define PHP_PHP_SERVER_API
36 | #endif
37 |
38 | #ifdef ZTS
39 | #include "TSRM.h"
40 | #endif
41 |
42 |
43 | ZEND_BEGIN_MODULE_GLOBALS(php_server)
44 | char * master_name;
45 | int debug;
46 | char * debug_file;
47 | ZEND_END_MODULE_GLOBALS(php_server)
48 |
49 |
50 | typedef struct php_server_process{
51 | int epoll_fd;
52 | int socket_fd;
53 | int is_stop;
54 | }server_process;
55 |
56 | /* function declare */
57 | PHP_FUNCTION(php_server_create);
58 | PHP_FUNCTION(php_server_bind);
59 | PHP_FUNCTION(php_server_send);
60 | PHP_FUNCTION(php_server_close);
61 | PHP_FUNCTION(php_server_set);
62 | PHP_FUNCTION(php_server_get);
63 | PHP_FUNCTION(php_server_run);
64 | void php_set_proc_name(char * name);
65 | void php_server_set_debug_file();
66 | int php_server_set_nonblock(int fd);
67 | void php_server_epoll_add_read_fd(int epoll_fd,int fd,uint32_t events);
68 | int php_server_epoll_del_fd(int epoll_fd,int fd);
69 | zend_bool php_server_setup_socket(char * ip,int port);
70 | zend_bool php_server_shutdown_socket();
71 | void php_server_sig_handler(int signal_no);
72 | zend_bool php_server_run_init();
73 | zend_bool php_server_clear_init();
74 | void php_server_epoll_debug(char * tag,uint32_t events);
75 | zend_bool php_server_run_process();
76 | zend_bool php_server_accept_client();
77 | zend_bool php_server_close_client(int sock_fd);
78 | int php_server_recv_from_client(int sock_fd);
79 |
80 | #ifdef ZTS
81 | #define PHP_SERVER_G(v) ZEND_TSRMG(php_server_globals_id, zend_php_server_globals *, v)
82 | #ifdef COMPILE_DL_PHP_SERVER
83 | ZEND_TSRMLS_CACHE_EXTERN;
84 | #endif
85 | #else
86 | #define PHP_SERVER_G(v) (php_server_globals.v)
87 | #endif
88 |
89 | #endif /* PHP_PHP_SERVER_H */
90 |
91 |
92 | /*
93 | * Local variables:
94 | * tab-width: 4
95 | * c-basic-offset: 4
96 | * End:
97 | * vim600: noet sw=4 ts=4 fdm=marker
98 | * vim<600: noet sw=4 ts=4
99 | */
100 |
--------------------------------------------------------------------------------
/php_server.c:
--------------------------------------------------------------------------------
1 | /*
2 | +----------------------------------------------------------------------+
3 | | PHP Version 7 |
4 | +----------------------------------------------------------------------+
5 | | Copyright (c) 1997-2014 The PHP Group |
6 | +----------------------------------------------------------------------+
7 | | This source file is subject to version 3.01 of the PHP license, |
8 | | that is bundled with this package in the file LICENSE, and is |
9 | | available through the world-wide-web at the following url: |
10 | | http://www.php.net/license/3_01.txt |
11 | | If you did not receive a copy of the PHP license and are unable to |
12 | | obtain it through the world-wide-web, please send a note to |
13 | | license@php.net so we can mail you a copy immediately. |
14 | +----------------------------------------------------------------------+
15 | | Author: Yaoguai (newtopstdio@163.com) |
16 | +----------------------------------------------------------------------+
17 | */
18 |
19 | /* $Id$ */
20 |
21 | #ifdef HAVE_CONFIG_H
22 | #include "config.h"
23 | #endif
24 |
25 | #include "php.h"
26 | #include "php_ini.h"
27 | #include "php_globals.h"
28 | #include "php_main.h"
29 | #include "ext/standard/info.h"
30 | #include "php_php_server.h"
31 |
32 | #include
33 | #include
34 | #include
35 | #include
36 | #include
37 | #include
38 | #include
39 | #include
40 | #include
41 | #include
42 | #include
43 | #include
44 | #include
45 | #include
46 | #include
47 | #include
48 | #include
49 |
50 | static zend_php_server_globals php_server_globals;
51 |
52 | PHP_INI_BEGIN()
53 | STD_PHP_INI_ENTRY("php_server.master_name", "php server master", PHP_INI_ALL, OnUpdateString, master_name, zend_php_server_globals, php_server_globals)
54 | STD_PHP_INI_ENTRY("php_server.debug", "0", PHP_INI_ALL, OnUpdateBool, debug, zend_php_server_globals, php_server_globals)
55 | STD_PHP_INI_ENTRY("php_server.debug_file", "", PHP_INI_ALL, OnUpdateString, debug_file, zend_php_server_globals, php_server_globals)
56 | PHP_INI_END()
57 |
58 | #define MAX_EPOLL_NUMBER 10000
59 | #define PHP_SERVER_DEBUG(format,...) do{ \
60 | if(php_server_globals.debug) \
61 | printf(format, ##__VA_ARGS__); \
62 | }while(0);
63 |
64 | // no need for thread safe
65 | #define BUFFER_SIZE 8096
66 | static char recv_buffer[BUFFER_SIZE];
67 | static int socket_fd_global;
68 | static server_process * process_global;
69 | static zend_class_entry * php_server_class_entry;
70 | static HashTable callback_ht;
71 |
72 | ZEND_BEGIN_ARG_INFO_EX(arginfo_php_server_create, 0, 0, 2)
73 | ZEND_ARG_INFO(0,ip)
74 | ZEND_ARG_INFO(0,port)
75 | ZEND_END_ARG_INFO()
76 |
77 | ZEND_BEGIN_ARG_INFO_EX(arginfo_php_server_bind, 0, 0, 2)
78 | ZEND_ARG_INFO(0,event)
79 | ZEND_ARG_INFO(0,callback)
80 | ZEND_END_ARG_INFO()
81 |
82 | ZEND_BEGIN_ARG_INFO_EX(arginfo_php_server_send, 0, 0, 2)
83 | ZEND_ARG_INFO(0,sockfd)
84 | ZEND_ARG_INFO(0,message)
85 | ZEND_ARG_INFO(0,flush)
86 | ZEND_END_ARG_INFO()
87 |
88 | ZEND_BEGIN_ARG_INFO_EX(arginfo_php_server_set, 0, 0, 2)
89 | ZEND_ARG_INFO(0,key)
90 | ZEND_ARG_INFO(0,value)
91 | ZEND_END_ARG_INFO()
92 |
93 | ZEND_BEGIN_ARG_INFO_EX(arginfo_php_server_get, 0, 0, 0)
94 | ZEND_ARG_INFO(0,key)
95 | ZEND_END_ARG_INFO()
96 |
97 | ZEND_BEGIN_ARG_INFO_EX(arginfo_php_server_close, 0, 0, 1)
98 | ZEND_ARG_INFO(0,sockfd)
99 | ZEND_END_ARG_INFO()
100 |
101 |
102 | const zend_function_entry php_server_class_functions[] = {
103 | ZEND_FENTRY(__construct,PHP_FN(php_server_create),arginfo_php_server_create,ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
104 | ZEND_FENTRY(bind,PHP_FN(php_server_bind),arginfo_php_server_bind,ZEND_ACC_PUBLIC)
105 | ZEND_FENTRY(set,PHP_FN(php_server_set),arginfo_php_server_set,ZEND_ACC_PUBLIC)
106 | ZEND_FENTRY(get,PHP_FN(php_server_get),arginfo_php_server_get,ZEND_ACC_PUBLIC)
107 | ZEND_FENTRY(run,PHP_FN(php_server_run),NULL,ZEND_ACC_PUBLIC)
108 | PHP_FE_END
109 | };
110 |
111 | PHP_FUNCTION(php_server_create)
112 | {
113 | char * ip;
114 | size_t ip_len;
115 | zend_long port;
116 |
117 | #ifdef FAST_ZPP
118 | ZEND_PARSE_PARAMETERS_START(2,2)
119 | Z_PARAM_STRING(ip,ip_len)
120 | Z_PARAM_LONG(port)
121 | ZEND_PARSE_PARAMETERS_END();
122 | #else
123 | if(zend_parse_parameters(ZEND_NUM_ARGS(),"sl",&ip,&ip_len,&port) == FAILURE){
124 | return;
125 | }
126 | #endif
127 |
128 | zval * this_settings = zend_read_property(php_server_class_entry,getThis(),"_settings",sizeof("_settings")-1,0,NULL);
129 | array_init(this_settings);
130 | add_assoc_stringl_ex(this_settings,"ip",sizeof("ip")-1,ip,ip_len);
131 | add_assoc_long_ex(this_settings,"port",sizeof("port")-1,(zend_long)port);
132 | zend_update_property(php_server_class_entry,getThis(),"_settings",sizeof("_settings")-1,this_settings);
133 | }
134 |
135 | PHP_FUNCTION(php_server_bind)
136 | {
137 | zval * event , *callback;
138 |
139 | #ifdef FAST_ZPP
140 | ZEND_PARSE_PARAMETERS_START(2,2)
141 | Z_PARAM_ZVAL(event)
142 | Z_PARAM_ZVAL(callback)
143 | ZEND_PARSE_PARAMETERS_END();
144 | #else
145 | if(zend_parse_parameters(ZEND_NUM_ARGS(),"zz",&event,&callback) == FAILURE || Z_TYPE_P(event) != IS_STRING){
146 | return;
147 | }
148 | #endif
149 |
150 | zend_hash_add(&callback_ht,Z_STR_P(event),callback);
151 | zval_ptr_dtor(event);
152 | zval_ptr_dtor(callback);
153 | }
154 |
155 | PHP_FUNCTION(php_server_send) {
156 | char * message;
157 | size_t message_len;
158 | zend_long sockfd;
159 | zend_bool is_flush = 0;
160 | int ret = -1;
161 | FILE * fp;
162 |
163 | #ifdef FAST_ZPP
164 | ZEND_PARSE_PARAMETERS_START(2,3)
165 | Z_PARAM_LONG(sockfd)
166 | Z_PARAM_STRING(message,message_len)
167 | Z_PARAM_OPTIONAL
168 | Z_PARAM_BOOL(is_flush)
169 | ZEND_PARSE_PARAMETERS_END();
170 | #else
171 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "ls|b", &sockfd, &message,
172 | &message_len, &is_flush) != FAILURE) {
173 | return;
174 | }
175 | #endif
176 |
177 | ret = send((int) sockfd, message, message_len, 0);
178 | if (is_flush) {
179 | if (NULL != (fp = fdopen(sockfd, "rw"))) {
180 | fflush(fp);
181 | fp = NULL;
182 | PHP_SERVER_DEBUG("__flush %d size\n", ret);
183 | }
184 | }
185 | PHP_SERVER_DEBUG("__send %d\n", (int )sockfd);
186 |
187 | RETURN_LONG(ret);
188 | }
189 |
190 | PHP_FUNCTION(php_server_close)
191 | {
192 | zend_long sockfd;
193 |
194 | #ifdef FAST_ZPP
195 | ZEND_PARSE_PARAMETERS_START(1,1)
196 | Z_PARAM_LONG(sockfd)
197 | ZEND_PARSE_PARAMETERS_END();
198 | #else
199 | if(zend_parse_parameters(ZEND_NUM_ARGS(),"l",&sockfd) == FAILURE){
200 | return;
201 | }
202 | #endif
203 |
204 | php_server_close_client(sockfd);
205 | PHP_SERVER_DEBUG("__close sockfd %d\n",(int)sockfd);
206 | }
207 |
208 | PHP_FUNCTION(php_server_set)
209 | {
210 | char * key_str;
211 | size_t key_len;
212 | zval *value;
213 |
214 | #ifdef FAST_APP
215 | ZEND_PARSE_PARAMETERS_START(2,2)
216 | Z_PARAM_STRING(key_str,key_len)
217 | Z_PARAM_ZVAL(value)
218 | ZEND_PARSE_PARAMETERS_END();
219 | #else
220 | if(zend_parse_parameters(ZEND_NUM_ARGS(),"sz",&key_str,&key_len,&value) == FAILURE){
221 | return;
222 | }
223 | #endif
224 |
225 | zval * this_settings = zend_read_property(php_server_class_entry,getThis(),"_settings",sizeof("_settings")-1,0,NULL);
226 | zend_hash_str_update(Z_ARRVAL_P(this_settings),key_str,key_len,value);
227 | }
228 |
229 | PHP_FUNCTION(php_server_get)
230 | {
231 | char * key = NULL;
232 | size_t key_len;
233 |
234 | #ifdef FAST_APP
235 | ZEND_PARSE_PARAMETERS_START(0,1)
236 | Z_PARAM_OPTIONAL
237 | Z_PARAM_STRING(key,key_len)
238 | Z_PARAM_ZVAL(value)
239 | ZEND_PARSE_PARAMETERS_END();
240 | #else
241 | if(zend_parse_parameters(ZEND_NUM_ARGS(),"|s",&key,&key_len) == FAILURE){
242 | return;
243 | }
244 | #endif
245 |
246 | zval * this_settings = zend_read_property(php_server_class_entry,getThis(),"_settings",sizeof("_settings")-1,0,NULL);
247 | if(0 == ZEND_NUM_ARGS()){
248 | RETURN_ZVAL(this_settings,1,NULL);
249 | }else{
250 | zval * retval = zend_hash_str_find(Z_ARRVAL_P(this_settings),key,key_len);
251 | if(retval){
252 | RETURN_ZVAL(retval,1,NULL);
253 | }
254 | }
255 | }
256 |
257 | PHP_FUNCTION(php_server_run){
258 | zval * this_settings = zend_read_property(php_server_class_entry,getThis(),"_settings",sizeof("_settings")-1,0,NULL);
259 | zval * ip = zend_hash_str_find(Z_ARRVAL_P(this_settings),"ip",sizeof("ip")-1);
260 | zval * port = zend_hash_str_find(Z_ARRVAL_P(this_settings),"port",sizeof("port")-1);
261 | if(Z_TYPE_P(ip)!= IS_STRING || Z_TYPE_P(port)!=IS_LONG){
262 | php_error_docref(NULL,E_ERROR,"ip must be string and port must be int");
263 | return;
264 | }
265 |
266 | zend_bool ret = php_server_setup_socket(Z_STRVAL_P(ip),(int)Z_LVAL_P(port));
267 | PHP_SERVER_DEBUG("setup_socket:%d\n",ret);
268 | if(ret!=SUCCESS){
269 | php_error_docref(NULL,E_ERROR,"create socket error\n");
270 | return;
271 | }
272 |
273 | process_global = (server_process * ) emalloc(sizeof(server_process));
274 | process_global->socket_fd = socket_fd_global;
275 | php_set_proc_name(PHP_SERVER_G(master_name));
276 | php_server_run_process();
277 | efree(process_global);
278 | php_server_shutdown_socket();
279 | }
280 |
281 | static void php_php_server_init_globals(zend_php_server_globals *php_server_globals)
282 | {
283 | php_server_globals->master_name = NULL;
284 | php_server_globals->debug = 0;
285 | php_server_globals->debug_file = "";
286 | }
287 |
288 | PHP_MINIT_FUNCTION(php_server)
289 | {
290 | REGISTER_INI_ENTRIES();
291 |
292 | zend_class_entry ce;
293 | INIT_CLASS_ENTRY(ce,"php_server",php_server_class_functions);
294 | php_server_class_entry = zend_register_internal_class(&ce);
295 | zend_declare_property_null(php_server_class_entry,"_settings",sizeof("_settings")-1,ZEND_ACC_PUBLIC);
296 | zend_hash_init(&callback_ht, 0, NULL, NULL, 1);
297 | return SUCCESS;
298 | }
299 |
300 | PHP_MSHUTDOWN_FUNCTION(php_server)
301 | {
302 | UNREGISTER_INI_ENTRIES();
303 | zend_hash_destroy(&callback_ht);
304 | return SUCCESS;
305 | }
306 |
307 | PHP_RINIT_FUNCTION(php_server)
308 | {
309 | #if defined(COMPILE_DL_PHP_SERVER) && defined(ZTS)
310 | ZEND_TSRMLS_CACHE_UPDATE;
311 | #endif
312 | return SUCCESS;
313 | }
314 |
315 | PHP_RSHUTDOWN_FUNCTION(php_server)
316 | {
317 | return SUCCESS;
318 | }
319 |
320 | PHP_MINFO_FUNCTION(php_server)
321 | {
322 | php_info_print_table_start();
323 | php_info_print_table_header(2, "php_server support", "enabled");
324 | php_info_print_table_end();
325 |
326 | DISPLAY_INI_ENTRIES();
327 | }
328 |
329 | void php_set_proc_name(char * name){
330 | zval argv[1];
331 | zval retval;
332 | zval function_name;
333 | ZVAL_STRING(&function_name,"cli_set_process_title");
334 | ZVAL_STRING(&argv[0],name);
335 | if(call_user_function_ex(EG(function_table),NULL,&function_name,&retval,1,argv,0,NULL) == FAILURE){
336 | php_error_docref(NULL, E_WARNING, "Could not call the cli_set_process_name");
337 | }
338 | zval_ptr_dtor(&argv[0]);
339 | zval_ptr_dtor(&function_name);
340 | }
341 |
342 | void php_server_set_debug_file(){
343 | int fileno;
344 | char fileBuf[200];
345 | snprintf(fileBuf,199,php_server_globals.debug_file,getpid());
346 | if(strcmp(php_server_globals.debug_file,"")!=0){
347 | fileno = open(fileBuf,O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
348 | if(fileno>=0){
349 | if(dup2(fileno,STDOUT_FILENO)<0){
350 | PHP_SERVER_DEBUG("dup2 error\n");
351 | }
352 | close(fileno);
353 | }else{
354 | PHP_SERVER_DEBUG("out fileno open error\n");
355 | }
356 | }
357 | }
358 |
359 | int php_server_set_nonblock(int fd){
360 | int old_option,new_option;
361 | old_option = fcntl(fd,F_GETFL);
362 | new_option = old_option | O_NONBLOCK;
363 | fcntl(fd,F_SETFL,new_option);
364 | return old_option;
365 | }
366 |
367 | void php_server_epoll_add_read_fd(int epoll_fd,int fd,uint32_t events){
368 | struct epoll_event event;
369 | event.data.fd = fd;
370 | event.events = events;
371 | epoll_ctl(epoll_fd,EPOLL_CTL_ADD,fd, &event);
372 | }
373 |
374 | int php_server_epoll_del_fd(int epoll_fd,int fd){
375 | int ret = epoll_ctl(epoll_fd, EPOLL_CTL_DEL,fd,0);
376 | close(fd);
377 | return ret;
378 | }
379 |
380 | zend_bool php_server_setup_socket(char * ip,int port){
381 | int ret;
382 | struct sockaddr_in address;
383 |
384 | socket_fd_global = socket(PF_INET,SOCK_STREAM,0);
385 | if(socket_fd_global<0){
386 | PHP_SERVER_DEBUG("socket error\n");
387 | return FAILURE;
388 | }
389 | bzero(&address,sizeof(address));
390 | address.sin_family = AF_INET;
391 | inet_pton(AF_INET,ip,&address.sin_addr);
392 | address.sin_port = htons(port);
393 | ret = bind(socket_fd_global,(struct sockaddr * )&address,sizeof(address));
394 | if(-1 == ret){
395 | PHP_SERVER_DEBUG("bind error\n");
396 | return FAILURE;
397 | }
398 | ret = listen(socket_fd_global,SOMAXCONN);
399 | if(-1 == ret){
400 | PHP_SERVER_DEBUG("listen error\n");
401 | return FAILURE;
402 | }
403 | return SUCCESS;
404 | }
405 |
406 | zend_bool php_server_shutdown_socket(){
407 | close(socket_fd_global);
408 | return SUCCESS;
409 | }
410 |
411 | void php_server_sig_handler(int signal_no){
412 | switch(signal_no){
413 | case SIGTERM:
414 | case SIGINT:
415 | process_global->is_stop = 1;
416 | break;
417 | }
418 | }
419 |
420 | zend_bool php_server_run_init(){
421 | php_server_set_debug_file();
422 | process_global->epoll_fd = epoll_create(MAX_EPOLL_NUMBER);
423 | if(process_global->epoll_fd == -1){
424 | return FAILURE;
425 | }
426 | process_global->is_stop = 0;
427 | signal(SIGTERM,php_server_sig_handler);
428 | signal(SIGINT,php_server_sig_handler);
429 | return SUCCESS;
430 | }
431 |
432 | zend_bool php_server_clear_init(){
433 | close(process_global->epoll_fd);
434 | return SUCCESS;
435 | }
436 |
437 | zend_bool php_server_run_process(){
438 | struct epoll_event events[MAX_EPOLL_NUMBER];
439 | int i,number,ret,sock_fd;
440 | php_server_run_init();
441 | php_server_set_nonblock(process_global->socket_fd);
442 | php_server_epoll_add_read_fd(process_global->epoll_fd, process_global->socket_fd,EPOLLIN);
443 | while(!process_global->is_stop){
444 | number = epoll_wait(process_global->epoll_fd,events,MAX_EPOLL_NUMBER,-1);
445 | PHP_SERVER_DEBUG("epoll come in worker:%d worker_break:%d pid(%d)\n",number,number<0 && errno!=EINTR,getpid());
446 | if(number<0 && errno != EINTR){
447 | break;
448 | }
449 | for(i=0;isocket_fd){
453 | php_server_accept_client();
454 | }else{
455 | php_server_recv_from_client(sock_fd);
456 | }
457 | }
458 | }
459 | }
460 | php_server_clear_init();
461 | return SUCCESS;
462 | }
463 |
464 | zend_bool php_server_accept_client(){
465 | int conn_fd;
466 | struct sockaddr_in client;
467 | socklen_t client_len = sizeof(client);
468 | bzero(&client,client_len);
469 | conn_fd = accept(process_global->socket_fd,(struct sockaddr *) & client, &client_len);
470 | if(conn_fd < 0){
471 | PHP_SERVER_DEBUG("worker(%d) __accept_error %d\n",getpid(),conn_fd);
472 | return FAILURE;
473 | }
474 | PHP_SERVER_DEBUG("worker(%d) __accepted %d\n",getpid(),conn_fd);
475 | php_server_set_nonblock(conn_fd);
476 | php_server_epoll_add_read_fd(process_global->epoll_fd,conn_fd,EPOLLIN | EPOLLET);
477 | /* call accept */
478 | zval * accept_cb = zend_hash_str_find(&callback_ht,"accept",sizeof("accept")-1);
479 | char client_ip_str[INET_ADDRSTRLEN];
480 | long port = (long)ntohs(client.sin_port);
481 | if(NULL!=accept_cb && port > 0 && NULL != inet_ntop(AF_INET,(void *)&client.sin_addr.s_addr,client_ip_str,INET_ADDRSTRLEN)){
482 | zval args[3], retval;
483 | ZVAL_LONG(&args[0],conn_fd);
484 | ZVAL_STRING(&args[1],client_ip_str);
485 | ZVAL_LONG(&args[2],port);
486 | if(call_user_function_ex(EG(function_table),NULL,accept_cb,&retval,3,args,0,NULL)){
487 | php_error_docref(NULL,E_WARNING,"accept error.\n");
488 | }
489 | PHP_SERVER_DEBUG("worker(%d) accept %d after call\n",getpid(),conn_fd);
490 | zval_dtor(&args[0]);
491 | zval_dtor(&args[1]);
492 | zval_dtor(&args[2]);
493 | }
494 | /* call accept end */
495 | return SUCCESS;
496 | }
497 |
498 | zend_bool php_server_close_client(int sock_fd){
499 | if(php_server_epoll_del_fd(process_global->epoll_fd,sock_fd)<0){
500 | return FAILURE;
501 | }
502 | /* call close */
503 | struct sockaddr_in client;
504 | size_t client_len = sizeof(client);
505 | getpeername(sock_fd,(struct sockaddr *)&client,(socklen_t *)&client_len);
506 | zval * close_cb = zend_hash_str_find(&callback_ht,"close",sizeof("close")-1);
507 | char client_ip_str[INET_ADDRSTRLEN];
508 | long port = (long)ntohs(client.sin_port);
509 | if(NULL!=close_cb && port > 0 && NULL != inet_ntop(AF_INET,(void *)&client.sin_addr.s_addr,client_ip_str,INET_ADDRSTRLEN)){
510 | zval args[3], retval;
511 | ZVAL_LONG(&args[0],sock_fd);
512 | ZVAL_STRING(&args[1],client_ip_str);
513 | ZVAL_LONG(&args[2],port);
514 | if(call_user_function_ex(EG(function_table),NULL,close_cb,&retval,3,args,0,NULL)){
515 | php_error_docref(NULL,E_WARNING,"close error.\n");
516 | }
517 | zval_dtor(&args[0]);
518 | zval_dtor(&args[1]);
519 | zval_dtor(&args[2]);
520 | }
521 | /* call close end */
522 | return SUCCESS;
523 | }
524 |
525 | int php_server_recv_from_client(int sock_fd){
526 | int ret,length = 0;
527 | for(;;){
528 | bzero(recv_buffer,sizeof(recv_buffer));
529 | ret = recv(sock_fd,recv_buffer,sizeof(recv_buffer),0);
530 | if(ret<0){
531 | if(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN){
532 | PHP_SERVER_DEBUG("worker __recv_once\n");
533 | break;
534 | }else{
535 | if(SUCCESS == php_server_close_client(sock_fd)){
536 | PHP_SERVER_DEBUG("worker __recv_error\n");
537 | }
538 | return -1;
539 | }
540 | }else if(ret == 0){
541 | PHP_SERVER_DEBUG("worker %d __client_close after call\n",sock_fd);
542 | php_server_close_client(sock_fd);
543 | return 0;
544 | }else{
545 | length += ret;
546 | PHP_SERVER_DEBUG("worker(%d) __recv_from %d:\n%s\n",getpid(),sock_fd,recv_buffer);
547 | /* call receive */
548 | struct sockaddr_in client;
549 | size_t client_len = sizeof(client);
550 | getpeername(sock_fd,(struct sockaddr *)&client,(socklen_t *)&client_len);
551 | zval * receive_cb = zend_hash_str_find(&callback_ht,"receive",sizeof("receive")-1);
552 | char client_ip_str[INET_ADDRSTRLEN];
553 | long port = (long)ntohs(client.sin_port);
554 | if(NULL!=receive_cb && port > 0 && NULL != inet_ntop(AF_INET,(void *)&client.sin_addr.s_addr,client_ip_str,INET_ADDRSTRLEN)){
555 | zval args[4], retval;
556 | ZVAL_LONG(&args[0],sock_fd);
557 | ZVAL_STRINGL(&args[1],recv_buffer,ret);
558 | ZVAL_STRING(&args[2],client_ip_str);
559 | ZVAL_LONG(&args[3],port);
560 | if(call_user_function_ex(EG(function_table),NULL,receive_cb,&retval,4,args,0,NULL)){
561 | php_error_docref(NULL,E_WARNING,"receive error.\n");
562 | }
563 | PHP_SERVER_DEBUG("worker(%d) receive %d after call\n",getpid(),sock_fd);
564 | zval_dtor(&args[0]);
565 | zval_dtor(&args[1]);
566 | zval_dtor(&args[2]);
567 | zval_dtor(&args[3]);
568 | }
569 | /* call receive end */
570 | }
571 | }
572 | return length;
573 | }
574 |
575 | const zend_function_entry php_server_functions[] = {
576 | PHP_FE(php_server_send,arginfo_php_server_send)
577 | PHP_FE(php_server_close,arginfo_php_server_close)
578 | PHP_FE_END
579 | };
580 |
581 | zend_module_entry php_server_module_entry = {
582 | STANDARD_MODULE_HEADER,
583 | "php_server",
584 | php_server_functions,
585 | PHP_MINIT(php_server),
586 | PHP_MSHUTDOWN(php_server),
587 | PHP_RINIT(php_server),
588 | PHP_RSHUTDOWN(php_server),
589 | PHP_MINFO(php_server),
590 | PHP_PHP_SERVER_VERSION,
591 | STANDARD_MODULE_PROPERTIES
592 | };
593 |
594 | #ifdef COMPILE_DL_PHP_SERVER
595 | #ifdef ZTS
596 | ZEND_TSRMLS_CACHE_DEFINE;
597 | #endif
598 | ZEND_GET_MODULE(php_server)
599 | #endif
600 |
601 | /*
602 | * Local variables:
603 | * tab-width: 4
604 | * c-basic-offset: 4
605 | * End:
606 | * vim600: noet sw=4 ts=4 fdm=marker
607 | * vim<600: noet sw=4 ts=4
608 | */
609 |
--------------------------------------------------------------------------------
/server.php:
--------------------------------------------------------------------------------
1 | get();
14 | if( empty($settings) || $ip != $settings['ip'] || $port != $settings['port'] ){
15 | exit(__LINE__);
16 | }
17 | if( $ip != $server->get('ip') || $port != $server->get('port') ){
18 | exit(__LINE__);
19 | }
20 | $port2 = 9010;
21 | $server->set('port',$port2);
22 | if( $port2 != $server->get('port') ){
23 | exit(__LINE__);
24 | }
25 | $server->set('port',$port);
26 | if( $port != $server->get('port') ){
27 | exit(__LINE__);
28 | }
29 | /* test end */
30 | function bind_accept($fd,$ip,$port){
31 | user_log(ACCEPT);
32 | }
33 | $server->bind('accept','bind_accept');
34 |
35 | function bind_receive($fd,$message,$ip,$port){
36 | user_log(RECEIVE);
37 | $content = "PHP SERVER 1.0
";
38 | $contentLen = sizeof($content);
39 | $dateStr = date('r');
40 | $response = <<bind('receive','bind_receive');
57 | function bind_close($fd,$ip,$port){
58 | user_log(CLOSE);
59 | }
60 | $server->bind('close','bind_close');
61 | $server->run();
62 | log_message();
63 |
64 | function log_reset(){
65 | if(isset($_SERVER['argv'][1])){
66 | @unlink(__DIR__.'/test.out');
67 | }
68 | }
69 |
70 | function user_log($msg){
71 | if(isset($_SERVER['argv'][1])){
72 | file_put_contents(__DIR__.'/test.out',$msg,FILE_APPEND);
73 | }
74 | }
75 |
76 | function log_message(){
77 | if(isset($_SERVER['argv'][1])){
78 | $content = file_get_contents(__DIR__.'/test.out');
79 | echo ACCEPT,': ',substr_count($content,ACCEPT),"\n";
80 | echo RECEIVE,': ',substr_count($content,RECEIVE),"\n";
81 | echo CLOSE,': ',substr_count($content,CLOSE),"\n";
82 | }
83 | }
--------------------------------------------------------------------------------
/tests/001.phpt:
--------------------------------------------------------------------------------
1 | --TEST--
2 | Check for php_server presence
3 | --SKIPIF--
4 |
5 | --FILE--
6 |
20 | --EXPECT--
21 | php_server extension is available
22 |
--------------------------------------------------------------------------------