用 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 " );
?>