因业务需要用到了pcntl_fork 处理多客户端连接处理数据的需求
但测试下来出现一个问题:
fork 之后, 若等待子进程返回, 那么程序就会阻塞, 不等待子进程返回, 则会出现僵尸进程
$obj = new service('127.0.0.1', 50000);
$obj->run();
class service {
private $socket_id;
private $socket_cid;
private $pid;
function __construct($host, $port) {
$this->socket_id = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$this->socket_id) {
$this->logs("create socket error");
return false;
}
if (!socket_bind($this->socket_id, $host, $port)) {
$this->logs(socket_strerror(socket_last_error()));
socket_close($this->socket_id);
return false;
}
if (!socket_listen($this->socket_id)) {
$this->logs(socket_strerror(socket_last_error()));
socket_close($this->socket_id);
return false;
}
}
public function run() {
if (!$this->socket_id) {
$this->logs("create socket error");
return false;
}
$this->logs("Starting...");
while (true) {
$this->socket_cid = socket_accept($this->socket_id);
$ip = "";
socket_getpeername($this->socket_cid, $ip);
$this->logs("Client IP: {$ip}");
if (!is_resource($this->socket_cid)) {
$this->logs(socket_strerror(socket_last_error()));
socket_close($this->socket_id);
return false;
}
$this->pid = pcntl_fork();
if ($this->pid == -1) {
$this->logs(socket_strerror(socket_last_error()));
socket_close($this->socket_cid);
return false;
} else if ($this->pid) {
$status = 0;
$s = pcntl_wait($status, WNOHANG); //等待子进程中断,防止子进程成为僵尸进程, 但这样会阻塞, 意思是只能处理一个客户端连接, 多个进入排队
//$s = pcntl_waitpid(0, $status, WUNTRACED); //如果不等待, 则子进程处理完之后, 没有对其进行处理, 会成为僵尸进程
$this->logs("Status: " . $status . ", pcntl_waitpid: " . $s);
} elseif ($this->pid == 0) { //child process
$k = 0;
while (true) {
if ($data = socket_read($this->socket_cid, 1024)) {
$this->logs("Read Data: {$data}");
$flag = socket_write($this->socket_cid, date('Y-m-d H:i:s') . '|已收到');
$this->logs("Write Result: {$flag}");
}
sleep(1);
$k++;
$this->logs("Wait...\t" . date('Y-m-d H:i:s'));
if ($k > 30) {
break;
}
}
socket_close($this->socket_cid);
exit(1);
}
}
socket_close($this->socket_id);
}
/**
* 打日志
* @param string $msg
* @return int
*/
private function logs($msg) {
$logs_filename = LOGS_PATH;
if (!file_exists($logs_filename)) {
@mkdir($logs_filename, 0777, true);
}
$logs_filename .= date('j') . '.log';
$logs_data = date('[H:i:s]') . " {$msg}\n";
return file_put_contents($logs_filename, $logs_data, FILE_APPEND);
}
function __destruct() {
if (is_resource($this->socket_id)) {
socket_close($this->socket_id);
}
if (is_resource($this->socket_cid)) {
socket_close($this->socket_cid);
}
}
}
在网上搜了半天, 这位仁兄的解决办法是对的(http://blog.youkuaiyun.com/e421083458/article/details/22186475), 就是增加信号处理,
在socket accept之前加上 pcntl_signal(SIGCHLD, SIG_IGN); //如果父进程不关心子进程什么时候结束,子进程结束后,内核会回收。
也就是在run方法开始处 :)
public function run() {
if (!$this->socket_id) {
$this->logs("create socket error");
return false;
}
pcntl_signal(SIGCHLD, SIG_IGN);
$this->logs("Starting....");
...
}