這篇文章帶大家初探PHP socket,從做一個簡單的socket伺服器來了解socket,希望對大家有幫助!
socket的中文名字叫做套接字,這種東西就是對TCP/IP的「封裝」。現實中的網路其實只有四層而已,從上到下分別是應用層、傳輸層、網路層、資料鏈結層。最常用的http協議則是屬於應用層的協議,而socket,可以簡單粗暴的理解為是傳輸層的一種東西。如果還是很難理解,那再粗暴地點兒tcp://218.221.11.23:9999,看到沒?這就是一個tcp socket。
socket賦予了我們操控傳輸層和網路層的能力,從而得到更強的效能和更高的效率,socket程式設計是解決高並發網路伺服器的最常用解決和成熟的解決方案。任何一名伺服器程式設計師都應掌握socket程式相關技能。
在php中,可以操控socket的函數一共有兩套,一套是socket_系列的函數,另一套是stream_系列的函數。 socket_是php直接將C語言中的socket抄了過來得到的實現,而stream_系則是php使用流的概念將其進行了一層封裝。以下用socket_*系函數簡單為這一系列文章開篇。
先來做個最簡單socket伺服器:
<?php $host = '0.0.0.0'; $port = 9999; // 创建一个tcp socket $listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP ); // 将socket bind到IP:port上 socket_bind( $listen_socket, $host, $port ); // 开始监听socket socket_listen( $listen_socket ); // 进入while循环,不用担心死循环死机,因为程序将会阻塞在下面的socket_accept()函数上 while( true ){ // 此处将会阻塞住,一直到有客户端来连接服务器。阻塞状态的进程是不会占据CPU的 // 所以你不用担心while循环会将机器拖垮,不会的 $connection_socket = socket_accept( $listen_socket ); // 向客户端发送一个helloworld $msg = "helloworld\r\n"; socket_write( $connection_socket, $msg, strlen( $msg ) ); socket_close( $connection_socket ); } socket_close( $listen_socket );
將檔案儲存為server.php,然後執行php server.php運行。客戶端我們使用telnet就可以了,打開另外一個終端機執行telnet 127.0.0.1 9999按下回車即可。運行結果如下:
簡單解析一下上述程式碼來說明一下tcp socket伺服器的流程:
上面這個案例中,有兩個很大的缺陷:
分析了上述問題後,又聯想到了前面說的多進程,那我們可以在accpet到一個請求後就fork一個子進程來處理這個客戶端的請求,這樣當accept了第二個客戶端後再fork一個子程序來處理第二個客戶端的請求,這樣問題不就解決了嗎? OK!擼一把程式碼示範一下:
<?php $host = '0.0.0.0'; $port = 9999; // 创建一个tcp socket $listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP ); // 将socket bind到IP:port上 socket_bind( $listen_socket, $host, $port ); // 开始监听socket socket_listen( $listen_socket ); // 进入while循环,不用担心死循环死机,因为程序将会阻塞在下面的socket_accept()函数上 while( true ){ // 此处将会阻塞住,一直到有客户端来连接服务器。阻塞状态的进程是不会占据CPU的 // 所以你不用担心while循环会将机器拖垮,不会的 $connection_socket = socket_accept( $listen_socket ); // 当accept了新的客户端连接后,就fork出一个子进程专门处理 $pid = pcntl_fork(); // 在子进程中处理当前连接的请求业务 if( 0 == $pid ){ // 向客户端发送一个helloworld $msg = "helloworld\r\n"; socket_write( $connection_socket, $msg, strlen( $msg ) ); // 休眠5秒钟,可以用来观察时候可以同时为多个客户端提供服务 echo time().' : a new client'.PHP_EOL; sleep( 5 ); socket_close( $connection_socket ); exit; } } socket_close( $listen_socket );
將程式碼儲存為server.php,然後執行php server.php,客戶端依然使用telnet 127.0.0.1 9999,只不過這次我們開啟兩個終端機來執行telnet。重點觀察當第一個客戶端連接上去後,第二個客戶端時候也可以連接上去。運行結果如下:
透過接受到客戶端請求的時間戳記可以看到現在伺服器可以同時為N個客戶端服務的。但是,接著想,如果先後有1萬個客戶端來請求呢?這時候伺服器會fork出1萬個子進程來處理每個客戶端連接,這是會死人的。 fork本身就是一個很浪費系統資源的系統調用,1W次fork足以讓系統崩潰,即便當下系統承受住了1W次fork,那麼fork出來的這1W個子進程也夠系統內存喝一壺了,最後是好不容易費勁fork出來的子進程在處理完畢當前客戶端後又被關閉了,下次請求還要重新fork,這本身就是一種浪費,不符合社會主義主流價值觀。如果是有人惡意攻擊,那麼系統fork的數量還會直線上升一直到系統崩潰。
所以,我們就再次提出增進型解決方案。我們可以預估一下業務量,然後在服務啟動的時候就fork出固定數量的子進程,每個子進程處於無限循環中並阻塞在accept上,當有客戶端連接擠出來就處理客戶請求,當處理完成後僅關閉連線但本身並不會銷毀,而是繼續等待下一個客戶端的請求。這樣,不僅避免了進程反覆fork銷毀巨大資源浪費,而且透過固定數量的子進程來保護系統不會因無限fork而崩潰。
<?php $host = '0.0.0.0'; $port = 9999; // 创建一个tcp socket $listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP ); // 将socket bind到IP:port上 socket_bind( $listen_socket, $host, $port ); // 开始监听socket socket_listen( $listen_socket ); // 给主进程换个名字 cli_set_process_title( 'phpserver master process' ); // 按照数量fork出固定个数子进程 for( $i = 1; $i <= 10; $i++ ){ $pid = pcntl_fork(); if( 0 == $pid ){ cli_set_process_title( 'phpserver worker process' ); while( true ){ $conn_socket = socket_accept( $listen_socket ); $msg = "helloworld\r\n"; socket_write( $conn_socket, $msg, strlen( $msg ) ); socket_close( $conn_socket ); } } } // 主进程不可以退出,代码演示比较粗暴,为了不保证退出直接走while循环,休眠一秒钟 // 实际上,主进程真正该做的应该是收集子进程pid,监控各个子进程的状态等等 while( true ){ sleep( 1 ); } socket_close( $connection_socket );
將檔案儲存為server.php後php server.php執行,然後再用ps -ef | grep phpserver | grep -v grep來看下伺服器進程狀態:
#
可以看到master進程存在,除此之外還有10個子進程處於等待服務狀態,再同一個時刻可以同時為10個客戶端提供服務。我們透過telnet 127.0.0.1 9999來嘗試一下,運行結果如下圖:
好啦,php新的旅程系列就先透過一個簡單的入門開始啦!下篇將會講述一些比較深刻的理論基礎知識。
推薦學習:《PHP影片教學》
以上是PHP socket學習:帶你做個簡單的socket伺服器的詳細內容。更多資訊請關注PHP中文網其他相關文章!