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