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

圖片描述

概述

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

理論

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

clipboard.png

第一步

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

第二步

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

第三步

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

實踐

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

函數

stream_socket_server
stream_socket_accept
call_user_func
is_callable
fread網絡

點擊函數了解用法

代碼

廢話少說直接開擼~socket

<?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();

按照以前的流程咱們須要監聽端口+地址tcp

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,'來自服務端消息');
};

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

缺點

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

相關文章
相關標籤/搜索