PHP socket初探 --- 含著淚也要磕完libevent(三)
阿新 • • 發佈:2018-12-22
這段時間相比大家也看到了,本人離職了,一是在家偷懶實在懶得動手,二是好不容易想寫點兒時間全部砸到資料結構和演算法那裡了。
今兒回過頭來,繼續這裡的文章。那句話是怎麼說的:
“自己選擇的課題,含著淚也得磕完!”(圖文無關,詳情點選這裡)。
其實在上一篇libevent文章中(《PHP socket初探 --- 硬著頭皮繼續libevent(二)》),如果你總結能力很好的話,可以觀察出來我們嘗試利用libevent做了至少兩件事情:
- 毫秒級別定時器
- 訊號監聽工具
大家都是碼php的,也喜歡把自己說的洋氣點兒:“ 我是寫伺服器的 ”。所以,今天的第一個案例就是拿libevent來構建一個簡單粗暴的http伺服器:
<?php $host = '0.0.0.0'; $port = 9999; $listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP ); socket_bind( $listen_socket, $host, $port ); socket_listen( $listen_socket ); echo PHP_EOL.PHP_EOL."Http Server ON : http://{$host}:{$port}".PHP_EOL; // 將伺服器設定為非阻塞,此處概念可能略拐彎,建議各位查閱一下手冊 socket_set_nonblock( $listen_socket ); // 建立事件基礎體,還記得航空母艦嗎? $event_base = new EventBase(); // 建立一個事件,還記得殲15艦載機嗎?我們將“監聽socket”新增到事件監聽中,觸發條件是read,也就是說,一旦“監聽socket”上有客戶端來連線,就會觸發這裡,我們在回撥函式裡來處理接受到新請求後的反應 $event = new Event( $event_base, $listen_socket, Event::READ | Event::PERSIST, function( $listen_socket ){ // 為什麼寫成這樣比較執拗的方式?因為,“監聽socket”已經被設定成了非阻塞,這種情況下,accept是立即返回的,所以,必須通過判定accept的結果是否為true來執行後面的程式碼。一些實現裡,包括workerman在內,可能是使用@符號來壓制錯誤,個人不太建議這>樣做 if( ( $connect_socket = socket_accept( $listen_socket ) ) != false){ echo "有新的客戶端:".intval( $connect_socket ).PHP_EOL; $msg = "HTTP/1.0 200 OK\r\nContent-Length: 2\r\n\r\nHi"; socket_write( $connect_socket, $msg, strlen( $msg ) ); socket_close( $connect_socket ); } }, $listen_socket ); $event->add(); $event_base->loop();
將程式碼儲存為test.php,然後php http.php執行起來。再開一個終端,使用curl的GET方式去請求伺服器,效果如下:
這是一個非常非常簡單地不能再簡單的http demo了,對於一個完整的http伺服器而言,他還差比較完整的http協議的實現、多核CPU的利用等等。這些,我們會放到後面繼續深入的文章中開始細化豐富。
還記得我們使用select系統呼叫實現了一個粗暴的線上聊天室,select這種業餘的都敢出來混個聊天室,專業的絕對不能慫。
無數個專業???????????????送給libevent!
啦啦啦啦,開始碼:
<?php $host = '0.0.0.0'; $port = 9999; $fd = socket_create( AF_INET, SOCK_STREAM, SOL_TCP ); socket_bind( $fd, $host, $port ); socket_listen( $fd ); // 注意,將“監聽socket”設定為非阻塞模式 socket_set_nonblock( $fd ); // 這裡值得注意,我們宣告兩個陣列用來儲存 事件 和 連線socket $event_arr = []; $conn_arr = []; echo PHP_EOL.PHP_EOL."歡迎來到ti-chat聊天室!發言注意遵守當地法律法規!".PHP_EOL; echo " tcp://{$host}:{$port}".PHP_EOL; $event_base = new EventBase(); $event = new Event( $event_base, $fd, Event::READ | Event::PERSIST, function( $fd ){ // 使用全域性的event_arr 和 conn_arr global $event_arr,$conn_arr,$event_base; // 非阻塞模式下,注意accpet的寫法會稍微特殊一些。如果不想這麼寫,請往前面新增@符號,不過不建議這種寫法 if( ( $conn = socket_accept( $fd ) ) != false ){ echo date('Y-m-d H:i:s').':歡迎'.intval( $conn ).'來到聊天室'.PHP_EOL; // 將連線socket也設定為非阻塞模式 socket_set_nonblock( $conn ); // 此處值得注意,我們需要將連線socket儲存到陣列中去 $conn_arr[ intval( $conn ) ] = $conn; $event = new Event( $event_base, $conn, Event::READ | Event::PERSIST, function( $conn ) use( $event_arr ) { global $conn_arr; $buffer = socket_read( $conn, 65535 ); foreach( $conn_arr as $conn_key => $conn_item ){ if( $conn != $conn_item ){ $msg = intval( $conn ).'說 : '.$buffer; socket_write( $conn_item, $msg, strlen( $msg ) ); } } }, $conn ); $event->add(); // 此處值得注意,我們需要將事件本身儲存到全域性陣列中,如果不儲存,連線會話會丟失,也就是說服務端和客戶端將無法保持持久會話 $event_arr[ intval( $conn ) ] = $event; } }, $fd ); $event->add(); $event_base->loop();
將程式碼儲存為server.php,然後php server.php執行,再開啟其他三個終端使用telnet連線上聊天室,執行效果如下所示:
嘗試放一張動態圖試試,看看行不行,自己製作的gif都特別大,不知道頻寬夠不夠。
截止到這篇為止,死磕Libevent系列的大體核心三把斧就算是掄完了,弄完這些,你在遇到這些程式碼的時候,就應該不會像下面這個樣子了: