摘要:原文地址在初探先從一個簡單的服務(wù)器開始中依次講解了三個逐漸進(jìn)步的服務(wù)器只能服務(wù)于一個客戶端的服務(wù)器利用可以服務(wù)于多個客戶端的額服務(wù)器利用預(yù)派生進(jìn)程服務(wù)于多個客戶端的服務(wù)器最后一種服務(wù)器的進(jìn)程模型基本上的大概原理其實(shí)跟我們常用的是非常
[原文地址:https://blog.ti-node.com/blog...]
在<PHP socket初探 --- 先從一個簡單的socket服務(wù)器開始>中依次講解了三個逐漸進(jìn)步的服務(wù)器:
只能服務(wù)于一個客戶端的服務(wù)器
利用fork可以服務(wù)于多個客戶端的額服務(wù)器
利用預(yù)fork派生進(jìn)程服務(wù)于多個客戶端的服務(wù)器
最后一種服務(wù)器的進(jìn)程模型基本上的大概原理其實(shí)跟我們常用的apache是非常相似的.
其實(shí)這種模型最大的問題在于需要根據(jù)實(shí)際業(yè)務(wù)預(yù)估進(jìn)程數(shù)量,依舊是需要大量進(jìn)程來解決問題,可能會出現(xiàn)CPU浪費(fèi)在進(jìn)程間切換上,還有可能會出現(xiàn)驚群現(xiàn)象(簡單理解就是100個進(jìn)程在等帶客戶端連接,來了一個客戶端但是所有進(jìn)程都被喚醒了,但最終只有一個進(jìn)程為這個客戶端服務(wù),其余99個白白折騰),那么,有沒有一種解決方案可以使得少量進(jìn)程服務(wù)于多個客戶端呢?
答案就是在<PHP socket初探 --- 關(guān)于IO的一些枯燥理論>中提到的"IO多路復(fù)用".多路是指多個客戶端連接socket,復(fù)用就是指復(fù)用少數(shù)幾個進(jìn)程,多路復(fù)用本身依然隸屬于同步通信方式,只是表現(xiàn)出的結(jié)果看起來像異步,這點(diǎn)值得注意.目前多路復(fù)用有三種常用的方案,依次是:
select,最早的解決方案
poll,算是select的升級版
epoll,目前的最終解決版,解決c10k問題的功臣
今天說的是select,這個東西本身是個Linux系統(tǒng)調(diào)用.在Linux中一切皆為文件,socket也不例外,每當(dāng)Linux打開一個文件系統(tǒng)都會返回一個對應(yīng)該文件的標(biāo)記叫做文件描述符.文件描述符是一個非負(fù)整數(shù),當(dāng)文件描述數(shù)達(dá)到最大的時候,會重新回到小數(shù)重新開始(題外話:按照傳統(tǒng),一般情況下標(biāo)準(zhǔn)輸入是0,標(biāo)準(zhǔn)輸出是1,標(biāo)準(zhǔn)錯誤是2).對文件的讀寫操作就是利用對文件描述符的讀寫操作.一個進(jìn)程可以操作的文件描述符的數(shù)量是有限制的,不同系統(tǒng)有不同的數(shù)量,在linux中,可以通過調(diào)整ulimit來調(diào)整控制.
先通過一個簡單的例子說明下select的作用和功能.雙11到了,你給少林足球隊(duì)買了很多很多球鞋,分別有10個快遞給你運(yùn)送,然后你就不斷地電話詢問這10個快遞員,你覺得有點(diǎn)兒累.阿梅很心疼你,于是阿梅就說:"這事兒你不用管了,你去專心練大力金剛腿吧,等任何一個快遞到了,我告訴你".當(dāng)其中一個快遞來了后,阿梅就喊你:"下來啦,有快遞?。?,但是,這個阿梅比較缺心眼,她不告訴你是具體哪雙鞋子的快遞,只告訴你有快遞到了.所以,你只能依次查詢一遍所有快遞單的狀態(tài)才能確認(rèn)是哪個簽收了.
上面這個例子通過結(jié)合術(shù)語演繹一遍就是,你就是服務(wù)器軟件,阿梅就是select,10個快遞就是10個客戶端(也就是10個連接socket fd).阿梅負(fù)責(zé)替你管理著這10個連接socket fd,當(dāng)其中任何一個fd有反應(yīng)了也就是可以讀數(shù)據(jù)或可以發(fā)送數(shù)據(jù)了,阿梅(select)就會告訴你有可以讀寫的fd了,但是阿梅(select)不會告訴你是哪個fd可讀寫,所以你必須輪循所有fd來看看是哪個fd,是可讀還是可寫.
是時候機(jī)械記憶一波兒了:
當(dāng)你啟動select后,需要將三組不同的socket fd加入到作為select的參數(shù),傳統(tǒng)意義上這種fd的集合就叫做fd_set,三組fd_set依次是可讀集合,可寫集合,異常集合.三組fd_set由系統(tǒng)內(nèi)核來維護(hù),每當(dāng)select監(jiān)控管理的三個fd_set中有可讀或者可寫或者異常出現(xiàn)的時候,就會通知調(diào)用方.調(diào)用方調(diào)用select后,調(diào)用方就會被select阻塞,等待可讀可寫等事件的發(fā)生.一旦有了可讀可寫或者異常發(fā)生,需要將三個fd_set從內(nèi)核態(tài)全部copy到用戶態(tài)中,然后調(diào)用方通過輪詢的方式遍歷所有fd,從中取出可讀可寫或者異常的fd并作出相應(yīng)操作.如果某次調(diào)用方?jīng)]有理會某個可操作的fd,那么下一次其余fd可操作時,也會再次將上次調(diào)用方未處理的fd繼續(xù)返回給調(diào)用方,也就是說去遍歷fd的時候,未理會的fd依然是可讀可寫等狀態(tài),一直到調(diào)用方理會.
上面都是我個人的理解和匯總,有錯誤可以指出,希望不會誤人子弟.下面通過php代碼實(shí)例來操作一波兒select系統(tǒng)調(diào)用.在php中,你可以通過stream_select或者socket_select來操作select系統(tǒng)調(diào)用,下面演示socket_select進(jìn)行代碼演示:
0 ){ // 判斷l(xiāng)isten_socket有沒有發(fā)生變化,如果有就是有客戶端發(fā)生連接操作了 if( in_array( $listen_socket, $read ) ){ // 將客戶端socket加入到client數(shù)組中 $client_socket = socket_accept( $listen_socket ); $client[] = $client_socket; // 然后將listen_socket從read中去除掉 $key = array_search( $listen_socket, $read ); unset( $read[ $key ] ); } // 查看去除listen_socket中是否還有client_socket if( count( $read ) > 0 ){ $msg = "hello world"; foreach( $read as $socket_item ){ // 從可讀取的fd中讀取出來數(shù)據(jù)內(nèi)容,然后發(fā)送給其他客戶端 $content = socket_read( $socket_item, 2048 ); // 循環(huán)client數(shù)組,將內(nèi)容發(fā)送給其余所有客戶端 foreach( $client as $client_socket ){ // 因?yàn)閏lient數(shù)組中包含了 listen_socket 以及當(dāng)前發(fā)送者自己socket,所以需要排除二者 if( $client_socket != $listen_socket && $client_socket != $socket_item ){ socket_write( $client_socket, $content, strlen( $content ) ); } } } } } // 當(dāng)select沒有監(jiān)聽到可操作fd的時候,直接continue進(jìn)入下一次循環(huán) else { continue; } }
將文件保存為server.php,然后執(zhí)行php server.php運(yùn)行服務(wù),同時再打開三個終端,執(zhí)行telnet 127.0.0.1 9999,然后在任何一個telnet終端中輸入"I am DOG!",再看其他兩個telnet窗口,是不是感覺很屌?
不完全截圖圖下:
還沒意識到問題嗎?如果我們看到有三個telnet客戶端連接服務(wù)器并且可以彼此之間發(fā)送消息,但是我們只用了一個進(jìn)程就可以服務(wù)三個客戶端,如果你愿意,可以開更多的telnet,但是服務(wù)器只需要一個進(jìn)程就可以搞定,這就是IO多路復(fù)用diao的地方!
最后,我們重點(diǎn)解析一些socket_select函數(shù),我們看下這個函數(shù)的原型:
int socket_select ( array &$read , array &$write , array &$except , int $tv_sec [, int $tv_usec = 0 ] )
值得注意的是$read,$write,$except三個參數(shù)前面都有一個&,也就是說這三個參數(shù)是引用類型的,是可以被改寫內(nèi)容的.在上面代碼案例中,服務(wù)器代碼第一次執(zhí)行的時候,我們要把需要監(jiān)聽的所有fd全部放到了read數(shù)組中,然而在當(dāng)系統(tǒng)經(jīng)歷了select后,這個數(shù)組的內(nèi)容就會發(fā)生改變,由原來的全部read fds變成了只包含可讀的read fds,這也就是為什么聲明了一個client數(shù)組,然后又聲明了一個read數(shù)組,然后read = client.如果我們直接將client當(dāng)作socket_select的參數(shù),那么client數(shù)組內(nèi)容就被修改.假如有5個用戶保存在client數(shù)組中,只有1個可讀,在經(jīng)過socket_select后client中就只剩下那個可讀的fd了,其余4個客戶端將會丟失,此時客戶端的表現(xiàn)就是連接莫名其妙發(fā)生丟失了.
[原文地址:https://blog.ti-node.com/blog...]
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/29385.html
摘要:原文地址正如標(biāo)題所言,顫顫抖抖開篇。于是只能是你自己,把單子上的個快遞逐次和收到的對比一遍,然后對比完畢后再把這個單子給了阿梅,然后阿梅繼續(xù)等。剃光頭前的阿梅,就是,不敢正眼看老板娘一眼。剃光頭后的阿梅,就是,可徒手接魔鬼隊(duì)的死亡之球。 [原文地址:https://blog.ti-node.com/blog...] 正如標(biāo)題所言,顫顫抖抖開篇epoll。顫顫抖抖的原因大概也就是以前幾乎...
摘要:原文前面可以說是弄了一系列的和多進(jìn)程的一大坨內(nèi)容,知識淺顯代碼粗暴風(fēng)格簡陋,總的說來,還是差了一些細(xì)節(jié)。今天,就一些漏掉的細(xì)節(jié)補(bǔ)充一下。最后,我補(bǔ)充一句是同步的,而不是異步。 原文:https://t.ti-node.com/thread/... 前面可以說是弄了一系列的php socket和多進(jìn)程的一大坨內(nèi)容,知識淺顯、代碼粗暴、風(fēng)格簡陋,總的說來,還是差了一些細(xì)節(jié)。今天,就一些漏...
摘要:一閱前熱身為了更加形象的說明同步異步阻塞非阻塞,我們以小明去買奶茶為例。等奶茶做好了,店員喊一聲小明,奶茶好了,然后小明去取奶茶。將響應(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)在消息通知的方式上...
閱讀 3122·2021-11-25 09:43
閱讀 1094·2021-11-24 10:22
閱讀 1437·2021-09-22 15:26
閱讀 749·2019-08-30 15:44
閱讀 2543·2019-08-29 16:33
閱讀 3803·2019-08-26 18:42
閱讀 971·2019-08-23 18:07
閱讀 1900·2019-08-23 17:55