国产+高潮+在线,国产 av 仑乱内谢,www国产亚洲精品久久,51国产偷自视频区视频,成人午夜精品网站在线观看

這才是 PHP 高性能框架 Workerman 的立命之本

碼農(nóng)先森

大家好,我是碼農(nóng)先森。

在這個大家都崇尚高性能的時代,程序員的談笑間句句都離不開高性能,仿佛嘴角邊不掛著「高性能」三個字都會顯得自己很 Low,其中眾所皆知的 Nginx 就是高性能的代表。有些朋友可能連什么是高性能都不一定理解,其實高性能就是單位時間內(nèi)能處理更多的客戶端請求,如果要問具體能處理多少請求,這個就要結(jié)合軟硬件條件來評估了,感興趣的朋友可以在定性的條件下使用壓力測試工具對自己的程序進(jìn)行測試。

大家都知道 PHP-FPM 是 PHP 的進(jìn)程管理器,每一次來自 Ngixn 轉(zhuǎn)發(fā)過來的客戶端請求,都會交由一個 PHP-FPM 子進(jìn)程進(jìn)行處理,在同一時刻一個子進(jìn)程只能處理一個客戶端請求,如果想要同一時刻能處理多個請求,那么就需要啟動多個子進(jìn)程,當(dāng)遇到秒殺搶購這種瞬間大量請求的場景時,PHP-FPM 對請求處理的模式顯然無法滿足需求。在這種情況下,我們只能使用 Workerman 或 Swoole 這種 PHP 的高性能通信框架,來解決類似特殊場景下的并發(fā)問題,不過這次我分享的內(nèi)容主要是 Workerman。

如標(biāo)題所提到的 Workerman 立命之本,那什么是其立命之本呢?我認(rèn)為是 IO 多路復(fù)用的 epoll 利器,epoll 是高性能程序的根基,解決 C10K 問題的尚方寶劍。接下來我會剖析 epoll 在 Workerman 源碼中的使用,不過在這之前我們需要先學(xué)習(xí)下 PHP 中如何將 Socket 與 Event 結(jié)合使用的案例。這里的 Event 可以理解為是對 epoll 的高度封裝,底層采用的就是 epoll 利器。

看了這段代碼,有助于你理解 Workerman 源碼,因為這段代碼就是提煉了 Workerman 對事件循環(huán)的實現(xiàn)原理。stream_socket_server 函數(shù)把創(chuàng)建、綁定、監(jiān)聽一并實現(xiàn)了,讓代碼顯得更加簡潔,不像之前的 socket_create、socket_bind、socket_listen 搞了三個步驟略顯繁瑣。因為使用了事件循環(huán),所以需要對 Socket 設(shè)置成非阻塞模式,只有當(dāng)有讀或?qū)懙耐ㄖ獣r才會調(diào)用相應(yīng)的回調(diào)函數(shù)。還有一點需要額外注意的,需要針對客戶端 Socket 創(chuàng)建的 Event 需要定義成靜態(tài)變量或全局變量,不然無法持久化連接到內(nèi)存,會造成客戶端無法建立連接傳輸數(shù)據(jù),我看到網(wǎng)上很多人都踩到了這個坑上。最后啟動事件循環(huán) EventLoop 自此開啟了 Socket 監(jiān)聽和事件循環(huán)雙操作。

<?php

// 創(chuàng)建 TCP 服務(wù)器套接字
$server = stream_socket_server("tcp://0.0.0.0:8080", $errno, $error);
echo "正在監(jiān)聽 8080 端口...". PHP_EOL; 

// 設(shè)置為非阻塞,在 $server 對象沒有數(shù)據(jù)可以讀取或?qū)懭霑r不會阻塞其執(zhí)行
stream_set_blocking($server, 0);

// 創(chuàng)建事件基礎(chǔ)對象
$event_base = new EventBase();

// 建立事件監(jiān)聽服務(wù)端 Socket 可讀事件
$event = new Event($event_base, $server, Event::READ | Event::PERSIST, function ($server) use ($event_base) {
    // 獲取新的連接,由于設(shè)置了非阻塞模式,那么這里即使沒有新的連接,也不會一直阻塞在這
    $client = @stream_socket_accept($server, 0);
    if ($client) {
        echo "客戶端(" . $client . ")連接建立". PHP_EOL; 

        // 針對客戶端過來的連接,也要設(shè)置成非阻塞模式
        stream_set_blocking($client, 0);

        // 客戶端連接創(chuàng)建監(jiān)聽可讀事件
        // 這里需要特別注意:客戶端事件需要定義成靜態(tài)變量或全局變量
        static $client_event;
        $client_event = new Event($event_base, $client, Event::READ | Event::PERSIST, function ($client) {
            // 從客戶端連接中讀取數(shù)據(jù),每次只讀取 1024 字節(jié)數(shù)據(jù)
            $buffer = fread($client, 1024);

            // 如果沒有讀取到數(shù)據(jù)或者客戶端已經(jīng)不是資源句柄,則關(guān)閉客戶端連接
            if ($buffer == false || !is_resource($client)) {
                // 關(guān)閉客戶端連接
                fclose($client);
                echo "客戶端(" . $client . ")連接關(guān)閉" . PHP_EOL; 
                return;
            }
            echo "收到客戶端(" . $client . ")數(shù)據(jù): $buffer" . PHP_EOL;

            // 回寫數(shù)據(jù)給客戶端
            $msg = "HTTP/1.0 200 OK\r\nContent-Length: 10\r\n\r\nServerOK\r\n";
            fwrite($client, $msg);
        }, $client);
        $client_event->add();
    }
}, $server);

// 添加事件
$event->add();

// 執(zhí)行事件循環(huán)
$event_base->loop();

使用 CURL 工具訪問 http://127.0.0.1:8080 便能正確返回結(jié)果 ServerOK 這表明事件循環(huán)可以進(jìn)入正常運(yùn)行狀態(tài)。

[manongsen@root php_event]$ curl -i http://127.0.0.1:8080
HTTP/1.0 200 OK
Content-Length: 10

ServerOK

看懂了上面那段代碼之后,接下來的內(nèi)容就會更順利了。下面這段代碼是引至 Workerman 的示例,通過 Worker 類構(gòu)造了一個 HTTP 服務(wù)。onMessage 參數(shù)定義了一個回調(diào)函數(shù),當(dāng)有事件通知時,會回調(diào)到此處,之后就是用戶自行實現(xiàn)后續(xù)的處理邏輯了。runAll 函數(shù)會整體啟動整個服務(wù),其中包括進(jìn)程的創(chuàng)建、事件的循環(huán)等。

<?php

// 引用 Worker 類
use Workerman\Worker;

// 自動加載 Composer
require_once __DIR__ . '/vendor/autoload.php';

// 定義 HTTP 服務(wù)并監(jiān)聽 8081 端口
$http_worker = new Worker('http://0.0.0.0:8081');

// 定義回調(diào)函數(shù)
$http_worker->onMessage = function ($connection, $request) {
    //$request->get();
    //$request->post();
    //$request->header();
    //$request->cookie();
    //$request->session();
    //$request->uri();
    //$request->path();
    //$request->method();

    // Send data to client
    $connection->send("Hello World");
};

// 啟動服務(wù)
Worker::runAll();

在 Worker.php 文件的 2367 行,使用 stream_socket_server 函數(shù)創(chuàng)建了服務(wù)端 Socket 并且綁定、監(jiān)聽了 8081 端口。

// workerman/Worker.php:2367
$this->_mainSocket = \stream_socket_server($local_socket, $errno, $errmsg, $flags, $this->_context);

在 Worker.php 文件的 2394 行,使用 stream_set_blocking 函數(shù)將 服務(wù)端 Socket 設(shè)置成非阻塞模式。

// workerman/Worker.php:2394
\stream_set_blocking($this->_mainSocket, false);

在 Worker.php 文件的 2417 行,將服務(wù)端的 _mainSocket 添加到事件循序中,并且設(shè)置回調(diào)函數(shù)為 acceptConnection 。

// workerman/Worker.php:2417
static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection'));

在 Worker.php 文件的 2561 行,使用 stream_socket_accept 接收到來自客戶端的連接 $new_socket ,其中這個操作是在 acceptConnection 回到函數(shù)中所進(jìn)行的。

// workerman/Worker.php:2561
$new_socket = \stream_socket_accept($socket, 0, $remote_address);

在 TcpConnection.php 文件的 285 行,使用 stream_set_blocking 函數(shù)將客戶端的 _socket 設(shè)置成非阻塞模式,這里的 _socket 和上面的 new_socket 是同一個。

// workerman/Connection/TcpConnection.php:285
\stream_set_blocking($this->_socket, 0);

在 TcpConnection.php 文件的 290 行,將客戶端的 _socket 添加到事件循環(huán)中,并且設(shè)置其的回調(diào)函數(shù)為 baseRead 。

// workerman/Connection/TcpConnection.php:290
Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead'));

在 Worker.php 文件的 1638 行,啟動事件循環(huán)。

// workerman/Worker.php:1638
static::$globalEvent->loop();

啟動事件循環(huán)后,當(dāng)有客戶端連接時便可以讀取數(shù)據(jù)了。因此在 TcpConnection.php 文件的 583 行,使用 fread 函數(shù)讀取客戶端 $socket 的數(shù)據(jù)。

// workerman/Connection/TcpConnection.php:583
$buffer = @\fread($socket, self::READ_BUFFER_SIZE);

在 TcpConnection.php 文件的 647 行,使用 parser::decode 函數(shù)將上面讀取到的 buffer 數(shù)據(jù)解析成 $request 對象,還有 $this 表示的是 $connection 對象,這個 $this->onMessage 是最開始用戶自定義的回調(diào)函數(shù)。最終通過 call_user_func 函數(shù),將 $connection、$request 參數(shù)回調(diào)到 onMessage 方法。

// workerman/Connection/TcpConnection.php:647
\call_user_func($this->onMessage, $this, $parser::decode($one_request_buffer, $this));

最后我們使用 CURL 工具調(diào)用一下 http://127.0.0.1:8081 通過返回的數(shù)據(jù),可以看出正確的回調(diào)到了 onMessage 函數(shù)。

[manongsen@root workerman]$ curl -i http://127.0.0.1:8081
HTTP/1.1 200 OK
Server: workerman
Connection: keep-alive
Content-Type: text/html;charset=utf-8
Content-Length: 13

Hello World

看到這里相信你已經(jīng)對 Workerman 源碼中的事件循環(huán)有些了解了,如果有時間最好能夠?qū)嵺`下最開始的那段案例代碼,然后再結(jié)合著看 Workerman 的源代碼會頗有收獲。Workerman 的高性能是站在了巨人 epoll 的肩膀上來實現(xiàn),沒有了 epoll 則啥也不是。這里再重申一下 PHP 中的 Event 是對 epoll 的封裝,epoll 是 Linux 的底層技術(shù)。我們在日常的編程中是不會直接接觸到 epoll 的,最后回歸一下主題 epoll 技術(shù)才是 Workerman 的立命之本。

感謝大家閱讀,個人觀點僅供參考,歡迎在評論區(qū)發(fā)表不同觀點。


歡迎關(guān)注、分享、點贊、收藏、在看,我是微信公眾號「碼農(nóng)先森」作者。

2786 9 4
9個評論

artisan

??

  • 暫無評論
tanhongbin

詳細(xì)

shanjian

??

  • 暫無評論
banro512

不錯,寫的很棒,雖然看困了

  • 暫無評論
dangpengsong

寫的非常好,很用心,肯定不是ai生成的,我最近剛用c++手搓了一個基于epoll復(fù)用和線程池的tcp服務(wù)器,看這個文章覺得很清晰很用心

  • 暫無評論
mumuxiaoxiao

業(yè)務(wù)才是立命之本吧

  • tanhongbin 2024-12-13

    那是對公司,對開發(fā)人員來說,技術(shù)才是根本,業(yè)務(wù)好不好和你有鳥關(guān)系?老板給你分紅嘛?

彭彭

請教一下,TcpConnection類中public function baseRead($socket, $check_eof = true)該方法傳入兩個參數(shù)是Event類傳入的,但是Event類回調(diào)https://www.php.net/manual/zh/event.callbacks.php callback( mixed $fd = null , int $what = ?, mixed $arg = null ): void 是這樣三個參數(shù),這里怎么理解呢,謝謝!

  • 暫無評論
ybbhui90

大概看懂了,但其實不是特別明白,我自己也翻了下它的源碼,有個問題請教下,我這邊創(chuàng)建websocket,鏈接的時候在header頭里加了個token,如何在服務(wù)端獲取hender里的這個token,我斷點下源碼好像并沒獲取請求頭的代碼。請問有解決辦法嗎

  • 暫無評論
liziyu

??B,一項偉大的發(fā)現(xiàn),你說的很對:“Workerman 的高性能是站在了巨人 epoll 的肩膀上來實現(xiàn),沒有了 epoll 則啥也不是?!?

你的發(fā)現(xiàn)填補(bǔ)了歷史的空白。

  • 暫無評論

碼農(nóng)先森

420
積分
0
獲贊數(shù)
0
粉絲數(shù)
2024-07-29 加入
??