PHP socket初探 --- 含着淚也要磕完libevent(三)

原文地址:https://t.ti-node.com/thread/...php

這段時間相比你們也看到了,本人離職了,一是在家偷懶實在懶得動手,二是好不容易想寫點兒時間所有砸到數據結構和算法那裏了。node

今兒回過頭來,繼續這裏的文章。那句話是怎麼說的:算法

本身選擇的課題,含着淚也得磕完!」(圖文無關,詳情點擊這裏)。數組

其實在上一篇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方式去請求服務器,效果以下:curl

這是一個很是很是簡單地不能再簡單的http demo了,對於一個完整的http服務器而言,他還差比較完整的http協議的實現、多核CPU的利用等等。這些,咱們會放到後面繼續深刻的文章中開始細化豐富。socket

還記得咱們使用select系統調用實現了一個粗暴的在線聊天室,select這種業餘的都敢出來混個聊天室,專業的絕對不能慫。數據結構和算法

無數個專業???????????????送給libevent!tcp

啦啦啦啦,開始碼:

<?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系列的大致核心三把斧就算是掄完了,弄完這些,你在遇到這些代碼的時候,就應該不會像下面這個樣子了:

相關文章
相關標籤/搜索