1. 程式人生 > >PHP 實現簡單的Proxy-Worker併發伺服器

PHP 實現簡單的Proxy-Worker併發伺服器

近來研究php-fpm 對其併發比較感興趣,遂造了一個tcp多程序併發伺服器玩具,拿出來給大家看看(比較簡單)

<?php

//設定worker程序數目
$workerNum=4;

//設定監聽ip
$ip='0.0.0.0';

//設定監聽埠
$port='9501';

//tcp連線池
$pool=[];
//事件池
$event_arr=[];

//程序池
$process=[];


//檢查環境設定
if(PHP_VERSION<7) die('php版本至少7以上'.PHP_EOL);
//檢查擴充套件
if(!extension_loaded('event')) die('未安裝 evenet 擴充套件.'.PHP_EOL);
if(!extension_loaded('pcntl')) die('未安裝 pcntl 擴充套件'.PHP_EOL);
// 只允許在cli下面執行  
if (php_sapi_name()!="cli") die("只允許在cli下面執行".PHP_EOL);  




/*
//守護程序
//由於程序組長無法建立會話,fork一個子程序並讓父程序退出,以便可以建立新會話
switch(pcntl_fork()) 
{
    case -1:
            exit("fork error");
            break;
    case 0: 
            break;
    default:
            exit(0); //父程序退出
}
//建立新會話,脫離原來的控制終端
posix_setsid();  
//再次fork並讓父程序退出, 子程序不再是會話首程序,讓其永遠無法開啟一個控制終端
switch(pcntl_fork()) {
    case -1:
        exit("fork error");
        break;
    case 0:
        break;
    default:
        exit(0); //父程序退出
}
//關閉標準輸入輸出
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
fopen('/dev/null', 'r');
fopen('/dev/null', 'w');
fopen('/dev/null', 'w');
//切換工作目錄
chdir('/');
//清除檔案掩碼
umask(0);
*/




//載入autoload設定
include('./vendor/autoload.php');
spl_autoload_register(['Autoload', 'load']);





//設定訊息佇列組
$queueGroup=[];
for($i=0;$i<$workerNum;$i++)
{
    $queueGroup[$i]=msg_get_queue(ftok(__FILE__,$i));
}



//設定共享記憶體
$share_mem=shmop_open($share_key=ftok(__FILE__,'a'),'c',0644,4098);
//存入程序池
shmop_write($share_mem,serialize($process),0);




//設定程序名稱
cli_set_process_title('tcpserver-master');




//安裝訊號非同步處理
pcntl_async_signals(true);
//pcntl_signal_dispatch();




//啟動worker程序
for ($i=0; $i < $workerNum; $i++) 
{ 
    //複製worker程序
    $worker_pid=pcntl_fork();
    
    if($worker_pid==0)
    {
        //引數
        $is_close=false;
        $is_restart=false;
        //註冊安全重啟
        pcntl_signal(SIGUSR1,function(){
            global $is_restart;
            $is_restart=true;
        });
        //註冊關閉訊號
        pcntl_signal(SIGUSR2,function(){
            global $is_close;
            $is_close=true;
        });

        //設定程序名稱
        //cli_set_process_title('tcpserver-worker');
        echo '子程序:'.getmypid().' 已啟動!'.PHP_EOL;
        
        while(true)
        {
            //接受訊息佇列
            msg_receive($queueGroup[$i],1,$msgType,1024,$message); 
            //to  do ..  虛擬碼 
            $queue_message=json_decode($message,true);

            **********在這裡可以實現自己的OnReceive業務邏輯***********

            msg_send($queueGroup[$i],這裡傳入業務邏輯的結果);
            
            //檢測是否有訊號傳送
            if($is_close) 
            {
                echo '程序已關閉...'.PHP_EOL;
                posix_kill(getmypid(),SIGKILL); 
            }
            //熱重啟
            if($is_restart)
            {
                $pid=pcntl_fork();
                if($pid>0)
                {   
                    //移除程序池
                    $id=sem_get($share_key);
                    if(sem_acquire($id))
                    {
                        $arr=unserialize(shmop_read($share_mem,0,1024));
                        foreach($arr as $k=>$v)
                        {
                            if($v==getmypid())
                            {
                                unset($arr[$k]);
                            }
                        }
                        shmop_write($share_mem,serialize($arr),0);
                        sem_release($id);
                    }
                    //安全退出
                    posix_kill(getmypid(),SIGKILL); 
                }
                if($pid==0)
                {
                    //新程序加入程序池
                    $id=sem_get($share_key);
                    if(sem_acquire($id))
                    {
                        $arr=unserialize(shmop_read($share_mem,0,1024));
                        $arr['worker'][]=getmypid();
                        shmop_write($share_mem,serialize($arr),0);
                        sem_release($id);
                    }
                    echo '新程序:'.getmypid().' 已加入程序池'.PHP_EOL;
                    //恢復狀態
                    $is_restart=false;
                }
            }
        }
        exit;
    }
    if($worker_pid>0)
    {
        global $share_key;
        //記錄子程序池
        $id=sem_get($share_key);
        if(sem_acquire($id))
        {
            $arr=unserialize(shmop_read($share_mem,0,1024));
            $arr['worker'][]=$worker_pid;
            shmop_write($share_mem,serialize($arr),0);
            sem_release($id);
        }
    }
}




//啟動proxy程序
$proxy_pid=pcntl_fork();
if($proxy_pid==0)
{
    //設定程序名稱
    cli_set_process_title('tcpserver-proxy');
    echo 'proxy程序id:'.getmypid().PHP_EOL;
    //載入全域性函式
    global $pool,$event_arr;
    //建立tcp伺服器
    $socket=socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
    //繫結ip 、埠號
    socket_bind($socket,$ip,$port);
    //監聽
    socket_listen($socket);
    //設定非阻塞模式
    socket_set_nonblock($socket);

    //設定事件
    $event_base=new EventBase(new EventConfig());
    //建立socket監聽事件
    $event=new Event($event_base,$socket,Event::READ | Event::PERSIST,function($socket){
        //載入全域性引數
        global $event_arr,$pool,$event_base,$workerNum,$queueGroup,$index;
        if(($conn=socket_accept($socket))!=false)
        {
            //設定連線非阻塞
            socket_set_nonblock($conn);
            //加入tcp連線池
            $pool[(int)$conn]=$conn;
            //訊息列印
            echo (int)$conn.' 已連線'.PHP_EOL;
            $index->onConnect($conn);
            //設定事件非同步
            $event_fd=new Event($event_base,$conn,Event::READ | Event::PERSIST,function($conn) use($workerNum,$queueGroup,$pool,$index){
                global $event_arr;
                //接受來自客戶端的訊息
                $socketStatus=socket_recv($conn,$message,1024,0); 
                //echo $message;
                if(!(bool)$socketStatus){
                    $index->onClose($conn);
                    echo (int)$conn.' 已離線!'.PHP_EOL;
                    //刪除事件監聽
                    $event_arr[(int)$conn]->free();
                    unset($event_arr[(int)$conn]);
                    //刪除連結監聽
                    unset($pool[(int)$conn]);
                    socket_close($conn);
                }
                if($socketStatus)
                {   
                    $return_arr=[
                        'k'=>(int)$conn,
                        'c'=>$message
                    ];
                    msg_send($queueGroup[$return_arr['k']%$workerNum],1,json_encode($return_arr));
                    msg_receive($queueGroup[$return_arr['k']%$workerNum],$return_arr['k'],$msgType,1024,$rmessage);
                    socket_write($conn,$rmessage,strlen($rmessage));
                }
                
            },$conn);
            //加入事件迴圈
            $event_fd->add();  
            //加入事件池
            $event_arr[(int)$conn]=$event_fd;
        }
    },$socket);
    //加入事件迴圈
    $event->add();
    //加入事件連線池
    $event_arr[(int)$socket]=$event;
    //啟動迴圈
    $event_base->loop();
    exit;
}
if($proxy_pid>0)
{
    global $share_key;
    //記錄子程序池
    $id=sem_get($share_key);
    if(sem_acquire($id))
    {
        $arr=unserialize(shmop_read($share_mem,0,1024));
        $arr['proxy']=$proxy_pid;
        shmop_write($share_mem,serialize($arr),0);
        sem_release($id);
    }
}




//註冊平滑重啟訊號
pcntl_signal(SIGUSR1,function(){
    // to do...
    echo '----正在執行安全重啟----'.PHP_EOL;
    global $share_key;
    global $share_mem;
    $id=sem_get($share_key);
    if(sem_acquire($id))
    {
        $process=unserialize(shmop_read($share_mem,0,1024));
        sem_release($id);
    }
    //傳送重啟訊號
    foreach ($process['worker'] as  $v) {
        posix_kill($v,SIGUSR1);
    }
});

//註冊平滑關閉訊號
pcntl_signal(SIGUSR2,function(){
    global $share_key;
    global $share_mem;
    $id=sem_get($share_key);
    if(sem_acquire($id))
    {
        $process=unserialize(shmop_read($share_mem,0,1024));
        sem_release($id);
    }
    echo '---正在執行安全關閉---'.PHP_EOL;
    //傳送關閉訊號
    foreach ($process['worker'] as  $v) {
        posix_kill($v,SIGUSR2);
    }
});


//註冊ctrl c訊號
pcntl_signal(SIGINT,function(){
    global $share_key;
    global $share_mem;
    global $queueGroup;
    $id=sem_get($share_key);
    if(sem_acquire($id))
    {
        $process=unserialize(shmop_read($share_mem,0,1024));
        sem_release($id);
    }
    echo '---正在執行程式安全關閉---'.PHP_EOL;
    //傳送關閉訊號
    foreach ($process['worker'] as  $v) {
        posix_kill($v,SIGUSR2);
    }

    //關閉共享記憶體,訊息佇列
    foreach ($queueGroup as $v) {
        msg_remove_queue($v);
    }
    //關閉共享記憶體
    shmop_close($share_mem);
    shmop_delete($share_mem);
    echo '------已關閉-----'.PHP_EOL;
    posix_kill(SIGKILL,getmypid());
    exit;
});




//主執行緒等待訊號
echo 'master程序id:'.getmypid().PHP_EOL;
while(true)
{
    sleep(1000);
}

 

做一下簡單的效能測試,先給出測試程式碼

<?php
for($i=0;$i<10;$i++)
{
    $pid=pcntl_fork();
    if($pid==0)
    {
        $conn=stream_socket_client('tcp://0.0.0.0:9501');
        $count=0;
        pcntl_async_signals(true);
        //pcntl_signal_dispatch();
        pcntl_signal(SIGALRM,function(){
            global $count;
            echo getmypid().'---'.$count.PHP_EOL;
            posix_kill(getmypid(),SIGKILL);
        });

        pcntl_alarm(1);

        while(true)
        {
            stream_socket_sendto($conn,time());
            stream_socket_recvfrom($conn,1024).PHP_EOL;
            $count++;  
        }
    }
}
while(true)
{
    sleep(1);
}

 

貼上截圖 ,由於測試具有波動性,本次測試只供參考

相關推薦

PHP 實現簡單Proxy-Worker併發伺服器

近來研究php-fpm 對其併發比較感興趣,遂造了一個tcp多程序併發伺服器玩具,拿出來給大家看看(比較簡單) <?php

PHP 實現簡單的樹形列表。

開發 記錄 動態 數據庫設計 在線 最近在為公司開發一個在線瀏覽PDF文檔的小web系統。在構建動態列表的時候犯了愁,很久沒寫代碼了,手有些生了,搞了半天才搞出來,寫篇博文記錄一下。首先是數據庫設計我設計的一個列數為三列的表Treenodes,這三列分別用來存儲當前節點的id、節點名稱、父

PHP 實現簡單的樹形列表 之二

服務器 數據庫 字符串 小項目 function 2017.5.22 北京 大雨來到公司之後, 準備把代碼移植到公司的小項目裏,移植的過程中發現了一處設計有問題的地方,調整一下。第一處錯誤是:構建葉子節點的時候,只保存了節點的名稱,沒有存ID, 如果頁面跟後臺交互的時候,當存在節點名稱一樣

PHP實現簡單的評論與回復功能還有刪除信息

技術分享 mit [0 ech ges get values width 錯誤 我們首先先看一下功能 上面黑色的是評論的下面紅色的字體是回復的 再來看看怎麽實現的 1.發布評論 <form action="pinglunchili.php" method="po

Jquery+PHP實現簡單的前後臺數據交互實現註冊登錄,添加留言功能

.html 獲取系統時間 沒有 username explode 註冊賬號 trap ++ 方法   頁面樣式應用了BootStrap框架。   首先看登錄頁(登錄頁用於賬號登錄,也可以跳轉到註冊賬號頁): <!DOCTYPE html> <html&g

php實現簡單的驗證碼功能

mage isset pat ech ace ring bcd ont es2017 1.根據php中的GD庫對圖片進行處理,繪制出驗證碼的圖片,code.php中2.表單界面,簡單的session保存及與用戶輸入對比,確定是否驗證正確,form.php中<?php

PHP實現簡單的雙色球機選號碼

tex ima 實現簡單 組成 輸出 img 簡單的 push each <?php header(‘Content-Type: text/html; charset=utf-8‘); //PHP實現雙色球機選號碼 $red = range(1, 33);//

PHP實現簡單的socket與異步應用

ins pos 結構 data timeout 撥號 而是 recv tcp 1.socket應用 (1)簡單概念     網絡上的兩個程序通過一個雙向的通信連接實現數據的交換,這個連接的一端稱為一個socket。   建立網絡通信連接至少要一對端口號(socket)。so

初學PHP實現簡單的留言板 -> php+MySQL+Apache

效果圖: 1.在文字框輸入內容後,點擊發表留言,效果如下如所示.  2.資料庫中則將資料記錄了下來.     實現程式碼: gbook.php -> 資料視覺化介面 <

PHP實現簡單雙向連結串列

用PHP實現雙向連結串列,在實現上,與C++的區別,主要在指標與this的使用上的不同。在資料結構方面雙向連結串列要考慮兩個方向四個指向,每次增減節點時注意每個指向都要照顧到。 實現檔案DoubleLinkList.php <?php /** * **雙向連結串列 * @au

使用php實現簡單爬蟲(一種通用的爬蟲思想)

概述 現在爬蟲技術算是一個普遍的技術了,各個語言的爬蟲百家爭鳴,但是根據筆者自己的感覺還是python是主流。爬蟲涉及到太多的東西,筆者並不是專業的爬蟲工程師,只不過個人興趣分享一下。由於筆者是php工作,所以就使用php來進行簡單爬蟲。不過我的方法應該是很通用的,我相信java,

Socket+執行緒池實現簡單http單檔案伺服器

import java.io.*; import java.net.*; import java.nio.charset.Charset; import java.nio.file.*; import java.util.concurrent.*; impor

php實現簡單的搶紅包

/** * 簡單 搶紅包 示例 */ header("Content-Type: text/html;charset=utf-8");//輸出不亂碼,你懂的 $total=10;//紅包總額

php實現簡單記事本功能

首先先做個html網頁表單 程式碼=》 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-

php實現簡單的登入

我用了bootstrap框架,不知為什麼一直表單不能正常排序顯示,不過主要學習php邏輯,先不糾結。 登入頁:test.php <!DOCTYPE html> <html lang="zh-CN"> <head>

php 實現簡單爬蟲

大部分是由他人部落格轉載而來,  只是根據目前自己的情況進行了部分修改. <?php /** * 爬蟲程式 -- 原型 * * 從給定的url獲取html內容 * * @param string $url * @return string */

PHP實現簡單的驗證碼功能機制

        網站的安全性是開發者不可忽視的一個問題,目前使用最多的一種可以提高網站安全性的方法就是使用驗證碼功能機制,有的僅僅使用一個幾位數字字母混亂的驗證碼,有的進行手機發送簡訊進行驗證,有

php實現簡單的留言板

最近學些php的課程還是一本滿足的,畢竟是指令碼語言還是很容易上手的,不吹不黑,哈哈!好了,進入正題,本文在實現留言板的過程講解一些知識點。 如何連線資料庫? 說來尷尬,我的wampserver的my

用IOCP實現個簡易TCP併發伺服器

我們前面接觸過幾個高效的unix/linux的非同步IO模型:select,poll,epoll,kqueue,其實windows也有它的非同步模型,比如windows版本的select,當然最高效的還屬IOCP吧。我也沒有做過多少windows的網路程式設計,但是看到網上

PHP實現簡單計算器小程式

最近剛開始學習一門新的語言 PHP,然後就寫了一個計算器的小程式,大體上的思路就是有一個前臺程式和一個後臺程式,前臺程式就是介面類似下圖這樣的: 然後還有一個後臺程式,就是負責計算的,那麼根據這個