多進(jìn)程監(jiān)聽同個端口及單進(jìn)程監(jiān)聽多個端口的php版本實(shí)現(xiàn)

最近在看nginx設(shè)計原理時思考到兩個問題,便是:

  • 多個進(jìn)程能否監(jiān)聽同個端口?
  • 單個進(jìn)程能否監(jiān)聽多個端口?

當(dāng)然隨著學(xué)習(xí)的深入,答案均是肯定的,在這個過程中筆者為了驗(yàn)證,用php寫了兩個例子,在這里分享出來,供有需要的php同學(xué)學(xué)習(xí)跟理解。

在分享例子之前,需要先介紹兩個php在socket編程中常用的擴(kuò)展,pcntl和libevent:

1. pcntl

php本身并不支持多進(jìn)程,但通過擴(kuò)展pcntl便可以實(shí)現(xiàn)fork功能,fork編程的大概原理是,每次調(diào)用fork函數(shù),操作系統(tǒng)就會產(chǎn)生一個子進(jìn)程,兒子進(jìn)程所有的堆棧信息都是原封不動復(fù)制父進(jìn)程的,而在fork之后,父進(jìn)程與子進(jìn)程實(shí)際上是相互獨(dú)立的,父子進(jìn)程不會相互影響。也就是說,fork調(diào)用位置之前的所有變量,父進(jìn)程和子進(jìn)程是一樣的,但fork之后則取決于各自的動作,且數(shù)據(jù)也是獨(dú)立的;因?yàn)閿?shù)據(jù)已經(jīng)完整的復(fù)制給了子進(jìn)程。而唯一能夠區(qū)分父子進(jìn)程的方法就是判斷fork的返回值。如果為0,表示是子進(jìn)程,如果為正數(shù),表示為父進(jìn)程,且該正數(shù)為子進(jìn)程的PID(進(jìn)程號),而如果是-1,表示子進(jìn)程創(chuàng)建失敗。

2. libevent

linux網(wǎng)絡(luò)編程中有三大事件處理,IO(socket)、信號和定時器,理解并處理好這三者,linux網(wǎng)絡(luò)編程就理解了一半,而libevent則是對這三者處理提供了一個很好的封裝,大大簡化了socket編程中事件處理的難度,非常推薦對這塊感興趣的同學(xué)去深入學(xué)習(xí)。

一、多個進(jìn)程監(jiān)聽同個端口

<?php

/**
* 
*/
class Server
{
    protected $ip = '127.0.0.1';
    protected $port = 5000;
    protected $sock = null;

    public function main()
    {
        if(($this->sock = socket_create(AF_INET,SOCK_STREAM,SOL_TCP)) < 0) {
            echo "socket_create() 失敗的原因是:".socket_strerror($sock)."\n";
            return ;
        }

        if(($ret = socket_bind($this->sock,$this->ip,$this->port)) < 0) {
            echo "socket_bind() 失敗的原因是:".socket_strerror($ret)."\n";
            return ;
        }

        if(($ret = socket_listen($this->sock,4)) < 0) {
            echo "socket_listen() 失敗的原因是:".socket_strerror($ret)."\n";
            return ;
        }

        for ($i=0; $i<3; $i++)
        {
            $pid = pcntl_fork();
            if (-1 === $pid) {
                throw new Exception("fork fail");
            } elseif (0 === $pid) {
                echo "fork pid:".getmypid()."\n";
                while (1) {
                    if(($msgsock = socket_accept($this->sock)) < 0) {
                        echo "socket_accept() failed: reason: " . socket_strerror($msgsock) . " ,pid: ".getmypid()."\n";
                        break;
                    }else{
                        $msg ="測試成功 ! \n";
                        echo $msg."pid: ".getmypid()."\n";
                        socket_write($msgsock, $msg, strlen($msg));
                    }
                }
            }   
        }
        while(1)
        {
            $status = 0;
            $pid = pcntl_wait($status,WUNTRACED);   
            if($pid > 0)
            {
                echo "pid:$pid exit,status:$status";
            }       
        }
    
    }

}

$server = new Server();
$server->main();

二、單個進(jìn)程監(jiān)聽多個端口


<?php

/**
* 
*/
class Server
{
    protected $socks = array();
    protected $event_base = null;
    protected $events = array();
    public function __construct()
    {
        $this->event_base = event_base_new();
    }

    protected function acceptConnect($sock)
    {
        echo "acceptConnect pid:".getmypid()."\n";
        //sleep(5);
        if(($msgsock = socket_accept($sock)) < 0) {
            echo "socket_accept() failed: reason: " . socket_strerror($msgsock) . " ,pid: ".getmypid()."\n";
            //break;
        }else{
            $msg ="測試成功,sock:$sock ! \n";
            echo $msg."pid: ".getmypid()."\n";
            socket_write($msgsock, $msg, strlen($msg));
            socket_close($msgsock);
        }
    }

    protected function addEvent($sock,$callback)
    {
        $event = event_new();

        if (!event_set($event, $sock, EV_READ|EV_PERSIST, $callback, null)) {
            echo "event_set faild,pid:".getmypid()."\n";
            return ;
        }

         if (!event_base_set($event,$this->event_base)) {
            echo "event_base_set faild,pid:".getmypid()."\n";
            return ;
        }

        if (!event_add($event)) {
            echo "event_add faild,pid:".getmypid()."\n";
            return ;
        }

        $this->event[] = $event;
    }

    public function listen($ip = '127.0.0.1',$port = '5000')
    {
        if(($sock = socket_create(AF_INET,SOCK_STREAM,SOL_TCP)) < 0) {
            echo "socket_create() 失敗的原因是:".socket_strerror($sock)."\n";
            return ;
        }

        if(($ret = socket_bind($sock,$ip,$port)) < 0) {
            echo "socket_bind() 失敗的原因是:".socket_strerror($ret)."\n";
            return ;
        }

        if(($ret = socket_listen($sock,4)) < 0) {
            echo "socket_listen() 失敗的原因是:".socket_strerror($ret)."\n";
            return ;
        }

        $this->socks[] = $sock;
    }

    public function main()
    {
        if($this->event_base == null)
        {
            echo "event base null";
            return ;
        }
        echo "event base:".$this->event_base."\n";
        foreach ($this->socks as $sock) {
            echo "sock:$sock\n";
            $this->addEvent($sock,array($this,'acceptConnect'));
        }
        echo "libevent success,pid:".getmypid()."\n";
        $result = event_base_loop($this->event_base);       
        echo "event loop result:$result";
    }

}

$server = new Server();
$server->listen('127.0.0.1','5000');
$server->listen('127.0.0.1','5001');
$server->main();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容