(一)如何實現一個單進程阻塞的網絡服務器

圖片描述

概述

想要更好的理解,網絡編程,寫出一個高性能的服務,我們需要花點時間來理解下對於服務器處理客戶端的整個流程並且理解一些關鍵的術語,本來想在本文中補充一些基礎理論知識,擔心篇幅過長不利於閱讀,所以以後補發一些基礎知識,接下來進入正題。

理論

主要介紹下實現一個網絡服務器的基本步驟,代碼會在實踐環節復現一次。

clipboard.png

第一步

我們需要創建一個socket,綁定服務器端口(bind),監聽端口(listen),在PHP中用stream_socket_server一個函數就能完成上面3個步驟。

第二步

進入while循環,阻塞在accept操作上,等待客戶端連接進入。此時程序會進入睡眠狀態,直到有新的客戶端發起connect到服務器,操作系統會喚醒此進程。accept函數返回客戶端連接的socket

第三步

利用fread讀取客戶端socket當中的數據收到數據後服務器程序進行處理然後使用fwrite向客戶端發送響應。長連接的服務會持續與客戶端交互,而短連接服務一般收到響應就會close。

實踐

在這裏我們用代碼來實現下基本一個流程,在開始寫代碼之前介紹介幾個php函數,是我們代碼中可能會用到的,方便大家理解。

函數

stream_socket_server
stream_socket_accept
call_user_func
is_callable
fread

點擊函數了解用法

代碼

廢話少說直接開擼~

<?php
 class Worker{
     //監聽socket
     protected $socket = NULL;
     //連接事件回調
     public $onConnect = NULL;
     //接收消息事件回調
     public $onMessage = NULL;
     public function __construct($socket_address) {

     }

     public function run(){

     }
 }



$worker = new Worker('tcp://0.0.0.0:9810');
//提前註冊了一個連接事件回調
$worker->onConnect = function ($data) {
    echo '新的連接來了', $data, PHP_EOL;
};
//提前註冊了一個接收消息事件回調
$worker->onMessage = function ($conn, $message) {
};
$worker->run();

按照之前的流程我們需要監聽端口+地址

public function __construct($socket_address) {
         //監聽地址+端口
         $this->socket=stream_socket_server($socket_address);
     }

下一步就需要阻塞在accept操作,等待客戶端連接進入。此時程序會進入睡眠狀態,直到有新的客戶端發起connect到服務器,操作系統會喚醒此進程

public function run(){
        while (true) { //循環監聽
         $client = stream_socket_accept($this->socket);//在服務端阻塞監聽
        }
     }

當新的連接進入喚醒進程並且觸發連接事件回調

 public function run(){
        while (true) { //循環監聽
         $client = stream_socket_accept($this->socket);//在服務端阻塞監聽
         if(!empty($client) && is_callable($this->onConnect)){//socket連接成功並且是我們的回調
             //觸發事件的連接的回調
             call_user_func($this->onConnect,$client);
         }
        }
     }

這裏的連接回調實際上觸發的就是之前準備好類庫的這裏下面這段代碼

$worker->onConnect = function ($data) {
    echo '連接事件:', $data, PHP_EOL;
};

當連接成功後利用fread獲取到客戶端的內容,並觸發接收消息事件

     public function run(){
      while (true) { //循環監聽
         $client = stream_socket_accept($this->socket);//在服務端阻塞監聽
         if(!empty($client) && is_callable($this->onConnect)){//socket連接成功並且是我們的回調
             //觸發事件的連接的回調
             call_user_func($this->onConnect,$client);
         }
         //從連接中讀取客戶端內容
         $buffer=fread($client,65535);//參數2:在緩衝區當中讀取的最大字節數
         //正常讀取到數據。觸發消息接收事件,進行響應
         if(!empty($buffer) && is_callable($this->onMessage)){
             //觸發時間的消息接收事件
             call_user_func($this->onMessage,$this,$client,$buffer);//傳遞到接收消息事件》當前對象、當前連接、接收到的消息
         }
       }
     }

到此處基本的一個網絡服務接收基本完成,還需要對請求做出一個響應,以HTTP請求爲例,這裏封裝了一個http響應的方法(http://127.0.0.1:9810)

 class Worker{
    ...
    ...
    ...
     public function  send($conn,$content){
         $http_resonse = "HTTP/1.1 200 OK\r\n";
         $http_resonse .= "Content-Type: text/html;charset=UTF-8\r\n";
         $http_resonse .= "Connection: keep-alive\r\n";
         $http_resonse .= "Server: php socket server\r\n";
         $http_resonse .= "Content-length: ".strlen($content)."\r\n\r\n";
         $http_resonse .= $content;
         fwrite($conn, $http_resonse);
     }
 }

當觸發接收消息事件時對http請求做出響應

$worker->onMessage = function ($server,$conn, $message) {
    echo '來自客戶端消息:',$message,PHP_EOL;
    $server->send($conn,'來自服務端消息');
};

到這就結束了~,完整代碼直通車

缺點

一次只能處理一個連接,不支持多個連接同時處理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章