最近在看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();