首頁 > 軟體

詳解PHP實現HTTP伺服器過程

2023-02-16 06:00:35

PHP並非不能實現HTTP服務,一般來講,這叫網路程式設計或Socket程式設計。在學習到其他語言的這部分的時候,一般的思路就是如何監聽TCP實現一個伺服器,並處理HTTP協定。

PHP也可以這樣做,同時一般伴隨著高效能這樣的關鍵字出現。

原生Socket程式設計

我們可以通過PHP的Socket函數,很簡單的實現出HTTP服務。

function run()
{
    //建立伺服器端的socket套接流,net協定為IPv4,protocol協定為TCP
    $socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
    /*繫結接收的套接流主機和埠,與使用者端相對應*/
    if(socket_bind($socket,"0.0.0.0", 9502) == false){
        echo 'server bind fail:'.socket_strerror(socket_last_error());exit();
    }
    //監聽套接流
    if(socket_listen($socket,4)==false){
        echo 'server listen fail:'.socket_strerror(socket_last_error());exit();
    }
    //非阻塞
    socket_set_nonblock($socket);
    call_user_func('onAccept',$socket);
}
run();

然後通過Socket處理收到的資料以及作出響應:

function onMessage($connection)
{
    //拼裝返回的html內容
    $content = '<html><title>hello,world</title><body>hello,world,http</body></html>';
    //拼裝頭資訊
    $header = '';
    $header .= "HTTP/1.1 200 OKrn";
    $header .= "Date: ".gmdate('D, d M Y H:i:s T')."rn";
    $header .= "Content-Type: text/html;charset=utf-8rn";
    $header .= "Content-Length: ".strlen($content)."rnrn";//必須2個rn表示頭部資訊結束
    $header .= $content;
    socket_write($connection,$header,strlen($header));
}
function onAccept($socket)
{
    //接收使用者端傳遞過來的資訊
    while(true)
    {
        $accept_resource = socket_accept($socket);
        if($accept_resource !== false)
        {
            $string = socket_read($accept_resource,1024);
            echo 'server receive is :'.$string.PHP_EOL;
            if($string != false)
            {
                call_user_func('onMessage',$accept_resource);
            }
        }
    }
}

流行專案

實際上,PHP有很多在專案都在實現HTTP伺服器,而且他們一般也都宣稱是高效能的。

Workerman系

Workerman是一款純PHP開發的開源高效能的PHP 應用容器。幾乎能夠實現任何型別的網路程式設計,並且內建了一個HTTP協定。

$worker = new Worker('http://0.0.0.0:1221');

Workerman的官方在21年出品了Webman,一個基於Workerman實現的高效能HTTP服務架構。替代傳統PHP-FPM架構,提供高效能的HTTP服務。可以用來開發網站、介面、微服務。

Webman實際上是一個開發框架,專案的目錄結構都已經設定好了,按照檔案開發就行,最後只要通過命令就能執行起來。

php start.php start

Webman支援是一個MVC框架,支援名稱空間自動載入,所以程式碼像這樣:

<?php
namespace appcontroller;
use supportRequest;
class UserController
{
    public function hello(Request $request)
    {
        $default_name = 'webman';
        // 從get請求裡獲得name引數,如果沒有傳遞name引數則返回$default_name
        $name = $request->get('name', $default_name);
        // 向瀏覽器返回字串
        return response('hello ' . $name);
    }
}

除了高效能等特點,他的上手難度很低,並且風格與現代的MVC風格一致,支援PSR標準,程式碼精簡高效。如果你是ThinkPHP的開發者,你會發現很容易上手Webman。

Swoole系

說道高效能HTTP服務,總是繞不開swoole的,他也是國內最早火熱起來的PHP高效能解決方案。

使用swoole實現HTTP服務的程式碼也很簡單:

$http = new SwooleHttpServer('0.0.0.0', 9501);
$http->on('Request', function ($request, $response) {
    $response->header('Content-Type', 'text/html; charset=utf-8');
    $response->end('<h1>Hello Swoole. #' . rand(1000, 9999) . '</h1>');
});
$http->start();

swoole實際上是一個PHP的擴充套件,近幾年基於他發展起了很多的高效能框架,比如easyswoole、Hyperf、Swoft、MixPHP等等。它們都基於Swoole實現框架,可以很容易的建立完整度很成熟的系統。

ReactPHP系

ReactPHP 是用於 PHP 事件驅動程式設計的底層庫。也可以用來實現各類網路程式設計,包括HTTP服務。用它實現HTTP服務也很簡單:

require __DIR__ . '/vendor/autoload.php';
$http = new ReactHttpHttpServer(function (PsrHttpMessageServerRequestInterface $request) {
    return ReactHttpMessageResponse::plaintext(
        "Hello World!n"
    );
});
$socket = new ReactSocketSocketServer('127.0.0.1:8080');
$http->listen($socket);
echo "Server running at http://127.0.0.1:8080" . PHP_EOL;

它是一個底層庫,一般而言,所有PSR的框架都可以基於他執行,替換PHP-FPM。所以他也提供了各個流行框架的接入方案,包括laravel、symfony等,基於ReactPHP,開發了一個PHP-PM專案。

PHP-PM 是 PHP 應用程式的程序管理器、增壓器和負載平衡器。

可以直接通過命令執行:

ppm start --bootstrap=laravel --app-env=prod --debug=0 --logging=0 --workers=20

實際上ReactPHP是個很有趣的專案,比如IP電視伺服器、終端shell、Mqtt的server、PHP版的Redis、一個GUI框架、位元幣P2P網路等等,以後有機會給大家介紹介紹。

AMPHP系

AMPHP 是 PHP 的高質量、事件驅動庫的集合,在設計時考慮了纖維和並行性。

基於AMPHP實現的HTTP服務架構叫amphp/http-server。使用它也可以快速實現一個穩定高效能的HTTP服務。

use AmpHttpServerRequestHandlerClosureRequestHandler;
use AmpHttpServerSocketHttpServer;
use AmpHttpServerRequest;
use AmpHttpServerResponse;
use AmpHttpStatus;
use AmpSocketServer;
use PsrLogNullLogger;
// Run this script, then visit http://localhost:1337/ in your browser.
AmpLoop::run(function () {
    $sockets = [
        Server::listen("0.0.0.0:1337"),
        Server::listen("[::]:1337"),
    ];
    $server = new SocketHttpServer($sockets, new ClosureRequestHandler(function (Request $request) {
        return new Response(Status::OK, [
            "content-type" => "text/plain; charset=utf-8"
        ], "Hello, World!");
    }), new NullLogger);
    yield $server->start();
    // Stop the server gracefully when SIGINT is received.
    // This is technically optional, but it is best to call Server::stop().
    AmpLoop::onSignal(SIGINT, function (string $watcherId) use ($server) {
        AmpLoop::cancel($watcherId);
        yield $server->stop();
    });
});

AMPHP也實現了很多有趣的專案,比如Mysql的使用者端,能夠實現連線池等特性。

swow

swow是一個基於協程的跨平臺並行I/O引擎,關注並行IO。

官方給出的HTTP例子程式碼行數比較多,主要是展示了HTTP請求支援的每個階段的操作方法,程式碼也是很簡潔的。

declare(strict_types=1);
use SwowBuffer;
use SwowCoroutine;
use SwowHttpParser;
use SwowHttpParserException;
use SwowSocket;
use SwowSocketException;
$host = getenv('SERVER_HOST') ?: '127.0.0.1';
$port = (int) (getenv('SERVER_PORT') ?: 9764);
$backlog = (int) (getenv('SERVER_BACKLOG') ?: 8192);
$multi = (bool) (getenv('SERVER_MULTI') ?: false);
$bindFlag = Socket::BIND_FLAG_NONE;
$server = new Socket(Socket::TYPE_TCP);
if ($multi) {
    $server->setTcpAcceptBalance(true);
    $bindFlag |= Socket::BIND_FLAG_REUSEPORT;
}
$server->bind($host, $port, $bindFlag)->listen($backlog);
while (true) {
    try {
        $connection = $server->accept();
    } catch (SocketException $exception) {
        break;
    }
    Coroutine::run(static function () use ($connection): void {
        $buffer = new Buffer(Buffer::COMMON_SIZE);
        $parser = (new Parser())->setType(Parser::TYPE_REQUEST)->setEvents(Parser::EVENT_BODY);
        $parsedOffset = 0;
        $body = null;
        try {
            while (true) {
                $length = $connection->recv($buffer, $buffer->getLength());
                if ($length === 0) {
                    break;
                }
                while (true) {
                    $parsedOffset += $parser->execute($buffer, $parsedOffset);
                    if ($parser->getEvent() === $parser::EVENT_NONE) {
                        $buffer->truncateFrom($parsedOffset);
                        $parsedOffset = 0;
                        break; /* goto recv more data */
                    }
                    if ($parser->getEvent() === Parser::EVENT_BODY) {
                        $body ??= new Buffer(Buffer::COMMON_SIZE);
                        $body->write(0, $buffer, $parser->getDataOffset(), $parser->getDataLength());
                    }
                    if ($parser->isCompleted()) {
                        $response = sprintf(
                            "HTTP/1.1 200 OKrn" .
                            "Connection: %srn" .
                            "Content-Length: %drnrn" .
                            '%s',
                            $parser->shouldKeepAlive() ? 'Keep-Alive' : 'Closed',
                            $body ? $body->getLength() : 0,
                            $body ?: ''
                        );
                        $connection->send($response);
                        $body?->clear();
                        break; /* goto recv more data */
                    }
                }
                if (!$parser->shouldKeepAlive()) {
                    break;
                }
            }
        } catch (SocketException $exception) {
            echo "No.{$connection->getFd()} goaway! {$exception->getMessage()}" . PHP_EOL;
        } catch (ParserException $exception) {
            echo "No.{$connection->getFd()} parse error! {$exception->getMessage()}" . PHP_EOL;
        }
        $connection->close();
    });
}

總結

以上是一些非常流行的PHP框架和專案,但還有其他很多實現了高效能HTTP服務的專案。這裡不多做介紹了。雖然我們談到PHP的時候,很少談到網路程式設計,甚至在入門教學中根本就沒有網路程式設計這節課。但是使用PHP做網路程式設計的各項應用已經很火熱了。

在入門其他語言是一定有一節課程是學習網路程式設計的,做PHP教學的也應該考慮考慮增加這部分課程了。

到此這篇關於詳解PHP實現HTTP伺服器過程的文章就介紹到這了,更多相關PHP HTTP伺服器內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com