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