PHP如何实现HTTP服务器

寻技术 PHP编程 2023年11月05日 61

这篇文章主要介绍“PHP如何实现HTTP服务器”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“PHP如何实现HTTP服务器”文章能帮助大家解决问题。

    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 OK
    ";
        $header .= "Date: ".gmdate('D, d M Y H:i:s T')."
    ";
        $header .= "Content-Type: text/html;charset=utf-8
    ";
        $header .= "Content-Length: ".strlen($content)."
    
    ";//必须2个
    表示头部信息结束
        $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('<h2>Hello Swoole. #' . rand(1000, 9999) . '</h2>');
    });
    $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!
    "
        );
    });
    $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 OK
    " .
                                "Connection: %s
    " .
                                "Content-Length: %d
    
    " .
                                '%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();
        });
    }
    关闭

    用微信“扫一扫”