├── 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 | --------------------------------------------------------------------------------