摘要:基本概念文章開篇先腦補一些知識有助于閱讀,本篇文章主要以為住,介紹實現原理,并利用來實現一個單進程阻塞復用的網絡服務器。函數監視的文件描述符分類,分別是和。
基本概念
文章開篇先腦補一些知識,有助于閱讀,本篇文章主要以select為住,介紹select實現原理,并利用select來實現一個單進程阻塞復用的網絡服務器。
IO多路復用是指內核一旦發現進程指定的一個或者多個IO條件準備讀取,它就通知該進程,目前支持I/O多路復用有?select,poll,epoll,I/O多路復用就是通過一種機制,一個進程可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作,IO多路復用適用如下場合:
當客戶處理多個描述字時(一般是交互式輸入和網絡套接口),必須使用I/O復用。
當一個客戶同時處理多個套接口時,而這種情況是可能的,但很少出現。
如果一個TCP服務器既要處理監聽套接口,又要處理已連接套接口,一般也要用到I/O復用。
如果一個服務器即要處理TCP,又要處理UDP,一般要使用I/O復用。
如果一個服務器要處理多個服務或多個協議,一般要使用I/O復用。
與多進程和多線程技術相比,I/O多路復用技術的最大優勢是系統開銷小,系統不必創建進程/線程,也不必維護這些進程/線程,從而大大減小了系統的開銷。
select 描述監視并等待多個文件描述符的屬性變化(可讀、可寫或錯誤異常)。
select函數監視的文件描述符分 3 類,分別是writefds、readfds、和 exceptfds。
調用后 select會阻塞,直到有描述符就緒(有數據可讀、可寫、或者有錯誤異常),或者超時( timeout 指定等待時間),函數才返回。
當 select()函數返回后,可以通過遍歷 fdset,來找到就緒的描述符,并且描述符最大不能超過1024
poll的機制與select類似,與select在本質上沒有多大差別,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,但是poll沒有最大文件描述符數量的限制。poll和select同樣存在一個缺點就是,包含大量文件描述符的數組被整體復制于用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨著文件描述符數量的增加而線性增大。
select 與 pollselect/poll問題很明顯,它們需要循環檢測連接是否有事件。如果服務器有上百萬個連接,在某一時間只有一個連接向服務器發送了數據,select/poll需要做循環100萬次,其中只有1次是命中的,剩下的99萬9999次都是無效的,白白浪費了CPU資源。
epoll 描述epoll是在2.6內核中提出的,是之前的select和poll的增強版本。相對于select和poll來說,epoll更加靈活,沒有描述符限制,無需輪詢。epoll使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中。
簡單點來說就是當連接有I/O流事件產生的時候,epoll就會去告訴進程哪個連接有I/O流事件產生,然后進程就去處理這個事件。
單進程阻塞復用的網絡服務器 ,如下圖所示
服務監聽流程如上
1、保存所有的socket,通過select系統調用,監聽socket描述符的可讀事件
2、select會在內核空間監聽一旦發現socket可讀,會從內核空間傳遞至用戶空間,在用戶空間通過邏輯判斷是服務端socket可讀,還是客戶端的socket可讀
3、如果是服務端的socket可讀,說明有新的客戶端建立,將socket保留到監聽數組當中
4、如果是客戶端的socket可讀,說明當前已經可以去讀取客戶端發送過來的內容了,讀取內容,然后響應給客戶端。
缺點:
1、select模式本身的缺點(1、循環遍歷處理事件、2、內核空間傳遞數據的消耗)
2、單進程對于大量任務處理乏力
class Worker{ //監聽socket protected $socket = NULL; //連接事件回調 public $onConnect = NULL; //接收消息事件回調 public $onMessage = NULL; public $workerNum=4; //子進程個數 public $allSocket; //存放所有socket public function __construct($socket_address) { //監聽地址+端口 $this->socket=stream_socket_server($socket_address); stream_set_blocking($this->socket,0); //設置非阻塞 $this->allSocket[(int)$this->socket]=$this->socket; } public function start() { //獲取配置文件 $this->fork(); } public function fork(){ $this->accept();//子進程負責接收客戶端請求 } public function accept(){ //創建多個子進程阻塞接收服務端socket while (true){ $write=$except=[]; //需要監聽socket $read=$this->allSocket; //狀態誰改變 stream_select($read,$write,$except,60); //怎么區分服務端跟客戶端 foreach ($read as $index=>$val){ //當前發生改變的是服務端,有連接進入 if($val === $this->socket){ $clientSocket=stream_socket_accept($this->socket); //阻塞監聽 //觸發事件的連接的回調 if(!empty($clientSocket) && is_callable($this->onConnect)){ call_user_func($this->onConnect,$clientSocket); } $this->allSocket[(int)$clientSocket]=$clientSocket; }else{ //從連接當中讀取客戶端的內容 $buffer=fread($val,1024); //如果數據為空,或者為false,不是資源類型 if(empty($buffer)){ if(feof($val) || !is_resource($val)){ //觸發關閉事件 fclose($val); unset($this->allSocket[(int)$val]); continue; } } //正常讀取到數據,觸發消息接收事件,響應內容 if(!empty($buffer) && is_callable($this->onMessage)){ call_user_func($this->onMessage,$val,$buffer); } } } } } } $worker = new Worker("tcp://0.0.0.0:9805"); //連接事件 $worker->onConnect = function ($fd) { //echo "連接事件觸發",(int)$fd,PHP_EOL; }; //消息接收 $worker->onMessage = function ($conn, $message) { //事件回調當中寫業務邏輯 $content="回復的消息"; $http_resonse = "HTTP/1.1 200 OK "; $http_resonse .= "Content-Type: text/html;charset=UTF-8 "; $http_resonse .= "Connection: keep-alive "; //連接保持 $http_resonse .= "Server: php socket server "; $http_resonse .= "Content-length: ".strlen($content)." "; $http_resonse .= $content; fwrite($conn, $http_resonse); }; $worker->start(); //啟動函數
stream_socket_server
在PHP中提供了一個非常方便的函數一次性創建、綁定端口、監聽端口
stream_set_blocking ( resource $stream , int $mode ) : bool
為資源流設置阻塞或者阻塞模式,$mode 0非阻塞,1阻塞
stream_socket_accept ( resource $server_socket [, float $timeout = ini_get("default_socket_timeout") [, string &$peername ]] ) : resource
接受由 stream_socket_server() 創建的套接字連接
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/31285.html
摘要:一閱前熱身為了更加形象的說明同步異步阻塞非阻塞,我們以小明去買奶茶為例。等奶茶做好了,店員喊一聲小明,奶茶好了,然后小明去取奶茶。將響應結果發給相應的連接請求處理完成因為基于,所以每個可以處理無數個連接請求。如此,就輕松的處理了高并發。 一、閱前熱身 為了更加形象的說明同步異步、阻塞非阻塞,我們以小明去買奶茶為例。 1、同步與異步 ①同步與異步的理解 同步與異步的重點在消息通知的方式上...
摘要:一閱前熱身為了更加形象的說明同步異步阻塞非阻塞,我們以小明去買奶茶為例。等奶茶做好了,店員喊一聲小明,奶茶好了,然后小明去取奶茶。將響應結果發給相應的連接請求處理完成因為基于,所以每個可以處理無數個連接請求。如此,就輕松的處理了高并發。 一、閱前熱身 為了更加形象的說明同步異步、阻塞非阻塞,我們以小明去買奶茶為例。 1、同步與異步 ①同步與異步的理解 同步與異步的重點在消息通知的方式上...
摘要:因為是單一線程,所以同一時刻只有一個操作在進行,所以,耗時的命令會導致并發的下降,不只是讀并發,寫并發也會下降。是一個重要的影響因素,由于是單線程模型,更喜歡大緩存快速,而不是多核。 近乎所有與Java相關的面試都會問到緩存的問題,基礎一點的會問到什么是二八定律、什么是熱數據和冷數據,復雜一點的會問到緩存雪崩、緩存穿透、緩存預熱、緩存更新、緩存降級等問題,這些看似不常見的概念,都與我們...
閱讀 2784·2023-04-25 18:06
閱讀 2576·2021-11-22 09:34
閱讀 1684·2021-11-08 13:16
閱讀 1302·2021-09-24 09:47
閱讀 3049·2019-08-30 15:44
閱讀 2773·2019-08-29 17:24
閱讀 2584·2019-08-23 18:37
閱讀 2433·2019-08-23 16:55