用 Socket 和 Pcntl 实现一个多进程服务器(一)

    要建立一个简单的服务,如果不考虑性能方面的问题,比如并发100 左右的服务,可以简单的用 Socket + Pcntl。
 来实现,我准备写一个系列的教程,让新手就能进行编写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");
?>
posted @ 2009-09-27 15:01  暮夏  阅读(6761)  评论(1编辑  收藏  举报