用 Socket 和 Pcntl 实现一个多进程服务器(一)
要建立一个简单的服务,如果不考虑性能方面的问题,比如并发100 左右的服务,可以简单的用 Socket + Pcntl。
来实现,我准备写一个系列的教程,让新手就能进行编写socket 服务。
下面要实现的是这样一个服务,就是能进行加减乘除的四则运算。数字可以是任意大的数。可以用下面的命令测试这个服务:
telnet 122.224.124.251 8086
就会进入下面的界面:
输入quit 就可以退出。
下面演示功能:
输入: 11111111111111111111111 * 222222222222222222222222222
# 11111111111111111111111 * 222222222222222222222222222
# result is : 2469135802469135802469111108641975308641975308642.
就能把结果计算出来。
这个演示的服务,可以多个人同时进行 运算。这个或许就是一个基本的多线程服务,比如,web服务器
就是一个多线程服务,但是,它要处理大量线程,进程的并发问题。所以比较复杂。
下面是代码, 具体的解释就在后面的教程中了。
这个类是处理的是进程控制,具体的逻辑处理封装在了 clientHandle 这个回调函数里面。通过修改这个回调
函数的内容,你也能很快的定制一个自己的服务器。
来实现,我准备写一个系列的教程,让新手就能进行编写socket 服务。
下面要实现的是这样一个服务,就是能进行加减乘除的四则运算。数字可以是任意大的数。可以用下面的命令测试这个服务:
telnet 122.224.124.251 8086
就会进入下面的界面:
Welcome to the PHP Test Server.
To quit, type 'quit'.
#
输入quit 就可以退出。
下面演示功能:
输入: 11111111111111111111111 * 222222222222222222222222222
# 11111111111111111111111 * 222222222222222222222222222
# result is : 2469135802469135802469111108641975308641975308642.
就能把结果计算出来。
这个演示的服务,可以多个人同时进行 运算。这个或许就是一个基本的多线程服务,比如,web服务器
就是一个多线程服务,但是,它要处理大量线程,进程的并发问题。所以比较复杂。
下面是代码, 具体的解释就在后面的教程中了。
这个类是处理的是进程控制,具体的逻辑处理封装在了 clientHandle 这个回调函数里面。通过修改这个回调
函数的内容,你也能很快的定制一个自己的服务器。
<?php
class Simple_Server
{
private $sock;
private $csock;
private $isListen = true;
private $callback;
private $user;
private $uid;
private $gid;
private $userHome;
private $scriptName = "simple-server";
/**
* use $user set the user run the script.
* fock a thread, init the socket, and wait user to request.
*
*/
function __construct($callback, $ip = '127.0.0.1', $port = '8086',$user = 'daemon')
{
error_reporting(E_ALL);
ini_set("display_errors", 0);
set_time_limit(0);
ob_implicit_flush();
declare(ticks = 1);
$this->callback = $callback;
$this->user = $user;
$this->getUserInfo();
$this->changeIdentity();
$this->daemon();
pcntl_signal(SIGTERM, array($this, 'sigHandler'));
pcntl_signal(SIGINT, array($this, 'sigHandler'));
pcntl_signal(SIGCHLD, array($this, 'sigHandler'));
$this->run($ip, $port);
}
function run($address, $port)
{
if(($this->sock = socket_create(AF_INET, SOCK_STREAM, 0)) === false)
{
$this->error("failed to create socket: ".socket_strerror($this->sock));
}
$sock = $this->sock;
if(($ret = socket_bind($sock, $address, $port)) === false)
{
$this->error("failed to bind socket: ".socket_strerror($ret));
}
if(($ret = socket_listen($sock, 0)) === false)
{
$this->error("failed to listen to socket: ".socket_strerror($ret));
}
socket_set_nonblock($sock);
$this->log("waiting for clients to connect");
while ($this->isListen)
{
$this->csock = @socket_accept($sock);
if ($this->csock === false)
{
usleep(1000); //1ms
} else if ($this->csock > 0) {
$this->client();
} else {
$this->error("error: ".socket_strerror($this->csock));
}
}
}
/**
* Handle a new client connection
*/
function client()
{
$this->log('begin client');
$ssock = $this->sock;
$csock = $this->csock;
$pid = pcntl_fork();
if ($pid == -1)
{
$this->error("fock clinet child error.");
} else if ($pid == 0) {
$pid = posix_getpid();
$this->log("begin client child ($pid).");
/* child process */
$this->isListen = false;
$this->log("close sock in child");
socket_close($ssock);
$this->log("begin handle user logic.");
$callback = $this->callback;
call_user_func($callback, $csock, $this);
$this->log("end handle user logic.");
$this->log("close client sock in child.");
socket_close($csock);
$this->log("end client");
} else {
$this->log("close csock in child");
socket_close($csock);
}
}
function __destruct()
{
@socket_close($this->sock);
@socket_close($this->csock);
$pid = posix_getpid();
$this->log("end daemon in __destruct pid($pid).");
}
function getUserInfo()
{
$uid_name = posix_getpwnam($this->user);
$this->uid = $uid_name['uid'];
$this->gid = $uid_name['gid'];
$this->userHome = $uid_name['dir'];
}
function changeIdentity()
{
if(!posix_setuid($this->uid))
{
$this->error("Unable to setuid to " . $this->uid);
}
}
/**
* Signal handler
*/
function sigHandler($sig)
{
switch($sig)
{
case SIGTERM:
case SIGINT:
exit();
break;
case SIGCHLD:
pcntl_waitpid(-1, $status);
break;
}
}
function error($msg)
{
$str = date("Y-m-d H:i:s") . " " . $msg . "\n";
file_put_contents(dirname(__FILE__) . "/error.log", $str, FILE_APPEND);
exit(0);
}
function log($msg)
{
$str = date("Y-m-d H:i:s") . " " . $msg . "\n";
file_put_contents(dirname(__FILE__) . "/message.log", $str, FILE_APPEND);
}
function daemon()
{
$ppid = posix_getpid();
$this->log("begin parent daemon pid ($ppid)");
$pid = pcntl_fork();
if ($pid == -1)
{
/* fork failed */
$this->error("fork failure!");
} else if ($pid) {
/* close the parent */
$this->log("end parent daemon pid($ppid) exit.");
exit();
} else {
/* child becomes our daemon */
posix_setsid();
chdir($this->userHome);
umask(0);
$pid = posix_getpid();
$this->log("begin child daemon pid($pid).");
}
}
}
function clientHandle($msgsock, $obj)
{
/* Send instructions. */
$br = "\r\n";
$msg = "$br Welcome to the PHP Test Server. $br $br To quit, type 'quit'.$br# ";
$obj->log($msg);
socket_write($msgsock, $msg, strlen($msg));
$nbuf = '';
socket_set_block($msgsock);
bcscale(4); // defalult 4 eg. 1 + 2.00001 = 3
do {
if (false === ($nbuf = socket_read($msgsock, 2048, PHP_NORMAL_READ))) {
$obj->error("socket_read() failed: reason: " . socket_strerror(socket_last_error($msgsock)));
}
if (!$nbuf = trim($nbuf)) {
continue;
}
if ($nbuf == 'quit') {
break;
}
if ($nbuf == 'shutdown') {
break;
}
if (empty($nbuf)) continue;
preg_match("/([\d.]+)[\s]*([+\-*\/x])[\s]*([\d.]+)/i", $nbuf , $matches);
$op = @$matches[2];
$left = @$matches[1];
$right = @$matches[3];
$result = NULL;
if ($op == "+") {
$result = bcadd($left, $right);
} else if ($op == "-") {
$result = bcsub($left, $right);
} else if ($op == "x" || $op == "x" || $op == "*") {
$result = bcmul($left, $right);
} else if ($op == "/") {
$result = bcdiv($left, $right);
} else {
$talkback = "# error: expression \"$nbuf\" error.$br# ";
}
if ($result === NULL) {
socket_write($msgsock, $talkback, strlen($talkback));
} else {
$result = rtrim($result, ".0");
$talkback = "# result is : $result.$br# ";
socket_write($msgsock, $talkback, strlen($talkback));
}
$nbuf = '';
} while (true);
}
$server = new Simple_Server("clientHandle", "122.224.124.251");
?>
class Simple_Server
{
private $sock;
private $csock;
private $isListen = true;
private $callback;
private $user;
private $uid;
private $gid;
private $userHome;
private $scriptName = "simple-server";
/**
* use $user set the user run the script.
* fock a thread, init the socket, and wait user to request.
*
*/
function __construct($callback, $ip = '127.0.0.1', $port = '8086',$user = 'daemon')
{
error_reporting(E_ALL);
ini_set("display_errors", 0);
set_time_limit(0);
ob_implicit_flush();
declare(ticks = 1);
$this->callback = $callback;
$this->user = $user;
$this->getUserInfo();
$this->changeIdentity();
$this->daemon();
pcntl_signal(SIGTERM, array($this, 'sigHandler'));
pcntl_signal(SIGINT, array($this, 'sigHandler'));
pcntl_signal(SIGCHLD, array($this, 'sigHandler'));
$this->run($ip, $port);
}
function run($address, $port)
{
if(($this->sock = socket_create(AF_INET, SOCK_STREAM, 0)) === false)
{
$this->error("failed to create socket: ".socket_strerror($this->sock));
}
$sock = $this->sock;
if(($ret = socket_bind($sock, $address, $port)) === false)
{
$this->error("failed to bind socket: ".socket_strerror($ret));
}
if(($ret = socket_listen($sock, 0)) === false)
{
$this->error("failed to listen to socket: ".socket_strerror($ret));
}
socket_set_nonblock($sock);
$this->log("waiting for clients to connect");
while ($this->isListen)
{
$this->csock = @socket_accept($sock);
if ($this->csock === false)
{
usleep(1000); //1ms
} else if ($this->csock > 0) {
$this->client();
} else {
$this->error("error: ".socket_strerror($this->csock));
}
}
}
/**
* Handle a new client connection
*/
function client()
{
$this->log('begin client');
$ssock = $this->sock;
$csock = $this->csock;
$pid = pcntl_fork();
if ($pid == -1)
{
$this->error("fock clinet child error.");
} else if ($pid == 0) {
$pid = posix_getpid();
$this->log("begin client child ($pid).");
/* child process */
$this->isListen = false;
$this->log("close sock in child");
socket_close($ssock);
$this->log("begin handle user logic.");
$callback = $this->callback;
call_user_func($callback, $csock, $this);
$this->log("end handle user logic.");
$this->log("close client sock in child.");
socket_close($csock);
$this->log("end client");
} else {
$this->log("close csock in child");
socket_close($csock);
}
}
function __destruct()
{
@socket_close($this->sock);
@socket_close($this->csock);
$pid = posix_getpid();
$this->log("end daemon in __destruct pid($pid).");
}
function getUserInfo()
{
$uid_name = posix_getpwnam($this->user);
$this->uid = $uid_name['uid'];
$this->gid = $uid_name['gid'];
$this->userHome = $uid_name['dir'];
}
function changeIdentity()
{
if(!posix_setuid($this->uid))
{
$this->error("Unable to setuid to " . $this->uid);
}
}
/**
* Signal handler
*/
function sigHandler($sig)
{
switch($sig)
{
case SIGTERM:
case SIGINT:
exit();
break;
case SIGCHLD:
pcntl_waitpid(-1, $status);
break;
}
}
function error($msg)
{
$str = date("Y-m-d H:i:s") . " " . $msg . "\n";
file_put_contents(dirname(__FILE__) . "/error.log", $str, FILE_APPEND);
exit(0);
}
function log($msg)
{
$str = date("Y-m-d H:i:s") . " " . $msg . "\n";
file_put_contents(dirname(__FILE__) . "/message.log", $str, FILE_APPEND);
}
function daemon()
{
$ppid = posix_getpid();
$this->log("begin parent daemon pid ($ppid)");
$pid = pcntl_fork();
if ($pid == -1)
{
/* fork failed */
$this->error("fork failure!");
} else if ($pid) {
/* close the parent */
$this->log("end parent daemon pid($ppid) exit.");
exit();
} else {
/* child becomes our daemon */
posix_setsid();
chdir($this->userHome);
umask(0);
$pid = posix_getpid();
$this->log("begin child daemon pid($pid).");
}
}
}
function clientHandle($msgsock, $obj)
{
/* Send instructions. */
$br = "\r\n";
$msg = "$br Welcome to the PHP Test Server. $br $br To quit, type 'quit'.$br# ";
$obj->log($msg);
socket_write($msgsock, $msg, strlen($msg));
$nbuf = '';
socket_set_block($msgsock);
bcscale(4); // defalult 4 eg. 1 + 2.00001 = 3
do {
if (false === ($nbuf = socket_read($msgsock, 2048, PHP_NORMAL_READ))) {
$obj->error("socket_read() failed: reason: " . socket_strerror(socket_last_error($msgsock)));
}
if (!$nbuf = trim($nbuf)) {
continue;
}
if ($nbuf == 'quit') {
break;
}
if ($nbuf == 'shutdown') {
break;
}
if (empty($nbuf)) continue;
preg_match("/([\d.]+)[\s]*([+\-*\/x])[\s]*([\d.]+)/i", $nbuf , $matches);
$op = @$matches[2];
$left = @$matches[1];
$right = @$matches[3];
$result = NULL;
if ($op == "+") {
$result = bcadd($left, $right);
} else if ($op == "-") {
$result = bcsub($left, $right);
} else if ($op == "x" || $op == "x" || $op == "*") {
$result = bcmul($left, $right);
} else if ($op == "/") {
$result = bcdiv($left, $right);
} else {
$talkback = "# error: expression \"$nbuf\" error.$br# ";
}
if ($result === NULL) {
socket_write($msgsock, $talkback, strlen($talkback));
} else {
$result = rtrim($result, ".0");
$talkback = "# result is : $result.$br# ";
socket_write($msgsock, $talkback, strlen($talkback));
}
$nbuf = '';
} while (true);
}
$server = new Simple_Server("clientHandle", "122.224.124.251");
?>