[摘要]在windows下搭建nginx+php环境时,php-cgi.exe会经常性的自动关闭退出,本文介绍通过使用xxfpm进程管理器管理php-cgi.exe。
php-cgi.exe在windows+nginx平台下经常自动退出,网上搜到的大部分解决方法都是类似上面的批处理(代码如下)文件临时解决一下,但如果用户在网站登录的话,用户就会突然挂掉。
@echo off :main set jinchengshuliang=0 set jinchengshuliangxiaxian=2 for /f %%i in ('tasklist /nh^|findstr /i /s /c:"php-cgi.exe"') do set /a jinchengshuliang+=1 if %jinchengshuliang% lss %jinchengshuliangxiaxian% ( goto youwenti ) else ( goto meiwenti ) :youwenti echo 进程丢失,现在添加5个进程 RunHiddenConsole.exe phpphp-cgi.exe -b 127.0.0.1:9000 -c phpphp.ini RunHiddenConsole.exe phpphp-cgi.exe -b 127.0.0.1:9000 -c phpphp.ini RunHiddenConsole.exe phpphp-cgi.exe -b 127.0.0.1:9000 -c phpphp.ini RunHiddenConsole.exe phpphp-cgi.exe -b 127.0.0.1:9000 -c phpphp.ini RunHiddenConsole.exe phpphp-cgi.exe -b 127.0.0.1:9000 -c phpphp.ini ping 127.1 -n 8 goto main :meiwenti echo 正常运行中! ping 127.1 -n 8 goto main
最好的解决办法是用windows下的php-cgi进程管理器,该进程管理器需要用到pthreadGC2.dll。源码和编译文件在本文结尾提供下载。经测试,支持Win32和Linux-x86平台。对于用php的人,有了这个东西来维护一定数量的进程,就能制服经常崩溃退出的php-cgi啦!
下载地址:http://down.chinaz.com/soft/31157.htm
以下是xxfpm进程管理器的操作参数:
1 Usage: xxfpm path [-n number] [-i ip] [-p port] 2 Manage FastCGI processes. 3 4 -n, --number number of processes to keep 5 -i, --ip ip address to bind 6 -p, --port port to bind, default is 8000 7 -u, --user start processes using specified linux user 8 -g, --group start processes using specified linux group 9 -r, --root change root direcotry for the processes 10 -h, --help output usage information and exit 11 -v, --version output version information and exit
第一个写得比较标准的终端应用程序,我是看了cygwin的里的一些源代码,然后学会了如何使用getopt,算是写得比较标准的,但是代码也不短。
使用例子:
xxfpm z:/php5/php-cgi.exe -n 5 -p 8080
有人问,如何给程序加入参数?这个不难,使用双引号即可,路径要用"/"而不用""。例如要指定php.ini的路径,可以用下面例子:
xxfpm "z:/php5/php-cgi.exe -c z:/php5/php.ini" -n 5 -i 127.0.0.1 -p 8080
维护进程原理:
Windows上使用CreateProcess创建进程,使用Wait For Single Object等待进程结束;Linux上使用fork和execl创建进程,使用waitpid等待进程结束。Linux的版本多了在创建子进程的时候可以设置进程限制,能够以受限用户方式来运行。
当进程管理器被关闭的时候,它所创建的所有子进程也必须被关闭。Windows上使用JobObject这个东西来把子进程与管理器的进程产生关联,感谢iceboy提供的资料!Linux上通过捕捉关闭信号,然后给所有子进程发送SIGTERM来结束子进程。详见源代码:
1 #ifdef __WIN32__ 2 3 #ifndef _WIN32_WINNT 4 #define _WIN32_WINNT 0x0500 5 #endif //_WIN32_WINNT 6 7 #include <windows.h> 8 #include <winsock.h> 9 #include <wininet.h> 10 #define SHUT_RDWR SD_BOTH 11 12 #ifndef JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE 13 #define JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE (0x2000) 14 #endif 15 HANDLE FcpJobObject; 16 17 #else 18 19 #include <sys/socket.h> 20 #include <sys/wait.h> 21 #include <fcntl.h> 22 #include <arpa/inet.h> 23 #include <grp.h> 24 #include <pwd.h> 25 #include <unistd.h> 26 #define closesocket close 27 28 #endif //__WIN32__ 29 30 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <getopt.h> 34 #include <string.h> 35 #include <pthread.h> 36 #include <errno.h> 37 38 #define MAX_PROCESSES 1024 39 static const char version[] = "$Revision: 0.01 $"; 40 static char* prog_name; 41 int number = 1; 42 int port = 8000; 43 char *ip = "127.0.0.1"; 44 char *user = ""; 45 char *root = ""; 46 char *path = ""; 47 char *group = ""; 48 int listen_fd; 49 struct sockaddr_in listen_addr; 50 int process_fp[MAX_PROCESSES]; 51 int process_idx = 0; 52 pthread_t threads[MAX_PROCESSES]; 53 54 static struct option longopts[] = 55 { 56 {"help", no_argument, NULL, 'h'}, 57 {"version", no_argument, NULL, 'v'}, 58 {"number", required_argument, NULL, 'n'}, 59 {"ip", required_argument, NULL, 'i'}, 60 {"port", required_argument, NULL, 'p'}, 61 {"user", required_argument, NULL, 'u'}, 62 {"group", required_argument, NULL, 'g'}, 63 {"root", required_argument, NULL, 'r'}, 64 {NULL, 0, NULL, 0} 65 }; 66 67 static char opts[] = "hvnipugr"; 68 69 static void usage(FILE* where) 70 { 71 fprintf(where, "" 72 "Usage: %s path [-n number] [-i ip] [-p port] " 73 "Manage FastCGI processes. " 74 " " 75 " -n, --number number of processes to keep " 76 " -i, --ip ip address to bind " 77 " -p, --port port to bind, default is 8000 " 78 " -u, --user start processes using specified linux user " 79 " -g, --group start processes using specified linux group " 80 " -r, --root change root direcotry for the processes " 81 " -h, --help output usage information and exit " 82 " -v, --version output version information and exit " 83 "", prog_name); 84 exit(where == stderr ? 1:0); 85 } 86 87 static void print_version() 88 { 89 printf("%s %s 90 FastCGI Process Manager 91 Copyright 2010 Xiaoxia.org 92 Compiled on %s 93 ", prog_name, version, __DATE__); 94 exit(0); 95 } 96 97 static int try_to_bind() 98 { 99 listen_addr.sin_family = PF_INET; 100 listen_addr.sin_addr.s_addr = inet_addr( ip ); 101 listen_addr.sin_port = htons( port ); 102 listen_fd = socket(AF_INET, SOCK_STREAM, 0); 103 104 if (-1 == bind(listen_fd, (struct sockaddr*)&listen_addr, sizeof(struct sockaddr_in)) ) { 105 fprintf(stderr, "failed to bind %s:%d ", ip, port ); 106 return -1; 107 } 108 109 listen(listen_fd, MAX_PROCESSES); 110 return 0; 111 } 112 113 static void* spawn_process(void* arg) 114 { 115 int idx = process_idx ++, ret; 116 while(1){ 117 #ifdef __WIN32__ 118 STARTUPINFO si={0}; 119 PROCESS_INFORMATION pi={0}; 120 ZeroMemory(&si,sizeof(STARTUPINFO)); 121 si.cb = sizeof(STARTUPINFO); 122 si.dwFlags = STARTF_USESTDHANDLES; 123 si.hStdInput = (HANDLE)listen_fd; 124 si.hStdOutput = INVALID_HANDLE_VALUE; 125 si.hStdError = INVALID_HANDLE_VALUE; 126 if(0 == (ret=CreateProcess(NULL, path, 127 NULL,NULL, 128 TRUE, CREATE_NO_WINDOW | CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB, 129 NULL,NULL, 130 &si,&pi)) ){ 131 fprintf(stderr, "failed to create process %s, ret=%d ", path, ret); 132 return NULL; 133 } 134 135 /* Use Job Control System */ 136 if(!AssignProcessToJobObject(FcpJobObject, pi.hProcess)){ 137 TerminateProcess(pi.hProcess, 1); 138 CloseHandle(pi.hProcess); 139 CloseHandle(pi.hThread); 140 return NULL; 141 } 142 143 if(!ResumeThread(pi.hThread)){ 144 TerminateProcess(pi.hProcess, 1); 145 CloseHandle(pi.hProcess); 146 CloseHandle(pi.hThread); 147 return NULL; 148 } 149 150 process_fp[idx] = (int)pi.hProcess; 151 WaitForSingleObject(pi.hProcess, INFINITE); 152 process_fp[idx] = 0; 153 CloseHandle(pi.hThread); 154 #else 155 ret = fork(); 156 switch(ret){ 157 case 0:{ //child 158 /* change uid from root to other user */ 159 if(getuid()==0){ 160 struct group *grp = NULL; 161 struct passwd *pwd = NULL; 162 if (*user) { 163 if (NULL == (pwd = getpwnam(user))) { 164 fprintf(stderr, "[fcgi] %s %s ", "can't find username", user); 165 exit(-1); 166 } 167 168 if (pwd->pw_uid == 0) { 169 fprintf(stderr, "[fcgi] %s ", "what? dest uid == 0?" ); 170 exit(-1); 171 } 172 } 173 174 if (*group) { 175 if (NULL == (grp = getgrnam(group))) { 176 fprintf(stderr, "[fcgi] %s %s ", "can't find groupname", group); 177 exit(1); 178 } 179 180 if (grp->gr_gid == 0) { 181 fprintf(stderr, "[fcgi] %s ", "what? dest gid == 0?" ); 182 exit(1); 183 } 184 /* do the change before we do the chroot() */ 185 setgid(grp->gr_gid); 186 setgroups(0, NULL); 187 188 if (user) { 189 initgroups(user, grp->gr_gid); 190 } 191 } 192 if (*root) { 193 if (-1 == chroot(root)) { 194 fprintf(stderr, "[fcgi] %s %s ", "can't change root", root); 195 exit(1); 196 } 197 if (-1 == chdir("/")) { 198 fprintf(stderr, "[fcgi] %s %s ", "can't change dir to", root); 199 exit(1); 200 } 201 } 202 203 /* drop root privs */ 204 if (*user) { 205 setuid(pwd->pw_uid); 206 } 207 } 208 209 int max_fd = 0, i=0; 210 // Set stdin to listen_fd 211 close(STDIN_FILENO); 212 dup2(listen_fd, STDIN_FILENO); 213 close(listen_fd); 214 // Set stdout and stderr to dummy fd 215 max_fd = open("/dev/null", O_RDWR); 216 close(STDERR_FILENO); 217 dup2(max_fd, STDERR_FILENO); 218 close(max_fd); 219 max_fd = open("/dev/null", O_RDWR); 220 close(STDOUT_FILENO); 221 dup2(max_fd, STDOUT_FILENO); 222 close(max_fd); 223 // close other handles 224 for(i=3; i<max_fd; i++) 225 close(i); 226 char *b = malloc(strlen("exec ") + strlen(path) + 1); 227 strcpy(b, "exec "); 228 strcat(b, path); 229 230 /* exec the cgi */ 231 execl("/bin/sh", "sh", "-c", b, (char *)NULL); 232 exit(errno); 233 break; 234 } 235 case -1: 236 fprintf(stderr, "[fcgi] fork failed "); 237 return NULL; 238 default:{ 239 struct timeval tv = { 0, 100 * 1000 }; 240 int status; 241 select(0, NULL, NULL, NULL, &tv); 242 switch(waitpid(ret, &status, WNOHANG)){ 243 case 0: 244 printf("[fcg] spawned process %s: %d ", path, ret); 245 break; 246 case -1: 247 fprintf(stderr, "[fcgi] waitpid failed "); 248 return NULL; 249 default: 250 if (WIFEXITED(status)) { 251 fprintf(stderr, "[fcgi] child exited with: %d ", WEXITSTATUS(status)); 252 } else if (WIFSIGNALED(status)) { 253 fprintf(stderr, "[fcgi] child signaled: %d ", WTERMSIG(status)); 254 } else { 255 fprintf(stderr, "[fcgi] child died somehow: %d ", status); 256 } 257 return NULL; 258 } 259 //wait for child process to exit 260 process_fp[idx] = ret; 261 waitpid(ret, &status, 0); 262 process_fp[idx] = 0; 263 } 264 } 265 #endif 266 } 267 } 268 269 static int start_processes() 270 { 271 int i; 272 pthread_attr_t attr; 273 pthread_attr_init(&attr); 274 pthread_attr_setstacksize(&attr, 64*1024); //64KB 275 for(i=0; i<number; i++){ 276 if( pthread_create( &threads, &attr, spawn_process, NULL ) == -1 ){ 277 fprintf(stderr, "failed to create thread %d ", i); 278 } 279 } 280 281 for(i=0; i<number; i++){ 282 pthread_join(threads, NULL); 283 } 284 return 0; 285 } 286 287 #ifdef __WIN32__ 288 void init_win32() 289 { 290 /* init win32 socket */ 291 static WSADATA wsa_data; 292 if(WSAStartup((WORD)(1<<8|1), &wsa_data) != 0) 293 exit(1); 294 JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit; 295 FcpJobObject = (HANDLE)CreateJobObject(NULL, NULL); 296 if(FcpJobObject == NULL) 297 exit(1); 298 299 /* let all processes assigned to this job object 300 * being killed when the job object closed */ 301 if (!QueryInformationJobObject(FcpJobObject, JobObjectExtendedLimitInformation, &limit, sizeof(limit), NULL)) { 302 CloseHandle(FcpJobObject); 303 exit(1); 304 } 305 306 limit.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; 307 308 if (!SetInformationJobObject(FcpJobObject, JobObjectExtendedLimitInformation, &limit, sizeof(limit))) { 309 CloseHandle(FcpJobObject); 310 exit(1); 311 } 312 } 313 #endif //__WIN32__ 314 315 #ifndef __WIN32__ 316 void before_exit(int sig) 317 { 318 signal(SIGTERM, SIG_DFL); 319 /* call child processes to exit */ 320 kill(0, SIGTERM); 321 } 322 #endif 323 324 int main(int argc, char **argv) 325 { 326 prog_name = strrchr(argv[0], '/'); 327 if(prog_name == NULL) 328 prog_name = strrchr(argv[0], '\'); 329 if(prog_name == NULL) 330 prog_name = argv[0]; 331 else 332 prog_name++; 333 334 if(argc == 1) 335 usage(stderr); 336 337 path = argv[1]; 338 339 opterr = 0; 340 341 char* p; 342 343 for(;;){ 344 int ch; 345 if((ch = getopt_long(argc, argv, opts, longopts, NULL)) == EOF) 346 break; 347 char *av = argv[optind]; 348 switch(ch){ 349 case 'h': 350 usage(stdout); 351 break; 352 case 'v': 353 print_version(); 354 break; 355 case 'n': 356 number = atoi(av); 357 if(number > MAX_PROCESSES){ 358 fprintf(stderr, "exceeds MAX_PROCESSES! "); 359 number = MAX_PROCESSES; 360 } 361 break; 362 case 'u': 363 user = av; 364 break; 365 case 'r': 366 root = av; 367 break; 368 case 'g': 369 group = av; 370 break; 371 case 'i': 372 ip = av; 373 break; 374 case 'p': 375 port = atoi(av); 376 break; 377 default: 378 usage(stderr); 379 break; 380 } 381 } 382 383 #ifdef __WIN32__ 384 init_win32(); 385 #else 386 /* call child processes to exit */ 387 signal(SIGTERM, before_exit); 388 signal(SIGINT, before_exit); 389 signal(SIGABRT, before_exit); 390 #endif 391 392 int ret; 393 ret = try_to_bind(); 394 if(ret != 0) 395 return ret; 396 ret = start_processes(); 397 if(ret !=0) 398 return ret; 399 400 401 #ifdef __WIN32__ 402 CloseHandle(FcpJobObject); 403 WSACleanup(); 404 #endif 405 return 0; 406 }
来源:http://down.chinaz.com/server/201111/1334_1.htm