美文网首页
PHP 长连接的猜测和验证

PHP 长连接的猜测和验证

作者: 马六甲的笔记 | 来源:发表于2019-03-25 11:38 被阅读0次

一、什么是长连接,长连接的意义


  • php 作为 server 对外提供服务, 每次处理新的请求都会重头运行一次代码
  • 在运行的代码中, php 可能会作为客户端从另外一个远程服务器获取数据(如:mysql,redis,memcached)
  • 在处理每一次请求的过程中, PHP都会经历连接远程服务器->获取数据->断开连接的过程
  • 可不可以只连接一次远程服务器, 此后处理请求的时候,直接用已连接的通道获取数据呢?
  • php作为server有多种运行方式, cgi/fastcgi/php-fpm/cli,不同运行方式有什么不同?

以上便是本文要了解的问题

二、支持长连接的常见pecl扩展


1. PDO

https://www.php.net/manual/zh/pdo.construct.php
https://www.php.net/manual/zh/pdo.connections.php

2.memcache

https://www.php.net/manual/zh/class.memcache.php
https://www.php.net/manual/zh/memcache.pconnect.php

3.memcached

https://www.php.net/manual/zh/book.memcached.php
https://www.php.net/manual/zh/memcached.construct.php

4.reids

https://pecl.php.net/package/redis
https://github.com/phpredis/phpredis/#connection

5.kafka

https://pecl.php.net/package/rdkafka
https://github.com/arnaud-lb/php-rdkafka
https://github.com/arnaud-lb/php-rdkafka/issues/42
写本文时还未支持,但看issue应该是快了

6.omq

https://www.php.net/manual/zh/zmqcontext.construct.php

以上是各种服务的 client 端,可以看到基本都支持长连接,或未来也要支持;
想必以后有什么其他客户端扩展的话,应该也是这种思路

三、php socket 连接


https://www.php.net/manual/zh/book.sockets.php
https://www.php.net/manual/zh/book.stream.php
https://www.php.net/manual/zh/function.fsockopen.php
https://www.php.net/manual/zh/function.pfsockopen.php

对以上几个做个简单说明

  1. sockets 库是默认是关闭的,编译php时需要--enable-sockets 才能打开,更为底层,需要自己封装各种协议,目前看来并不支持持久连接

  2. stream 库从 php4.3 之后是在内核中了,所以无需担心是否可用的问题了,相比 sockets,是更高一层的实现,封装了一些 常见协议 ,支持 持久连接, 通过 flags 参数设置

  3. fsockopen / pfsockopen 是更高层级的封装,也是直接在php内核中的,无需额外配置;两者只有一个差别,就是 pfsockopen 打开的是持久连接

  4. 所以选择起来,一般就不使用 sockets 了,毕竟不一定支持,且太底层了,要自己去实现传输器;剩下两个呢,推荐 stream,更加灵活

四、验证长连接


php 可以以 cgi/fastcgi/cli 方式运行,所以这里就针对这三种模式来验证长连接的表现,若对 PHP 运行模式疑问,可以看看这篇 PHP运行方式

没有精力去验证各 pecl 扩展,这里仅用 pfsockopen 来做验证,因为本地环境安装了 php swoole 扩展,所以直接使用 swoole 创建一个服务端来测试

1、创建一个 tcp 服务端

<?php
//tcp.php
$serv = new Swoole\Server('0.0.0.0', 9501, SWOOLE_BASE, SWOOLE_SOCK_TCP);
$serv->set([
   'worker_num' => 1,
]);
$serv->on('start', function () {
    echo "server start\n";
});
$serv->on('Connect', function(swoole_server $server, int $fd, int $reactorId) {
    echo "connect:$fd\n";
});
$serv->on('Receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) {
    echo "receive[$fd]: $data\n";
    $serv->send($fd, "Server: $data\n");
});
$serv->on('Close', function(swoole_server $server, int $fd, int $reactorId) {
    echo "close:$fd\n";
});
$serv->start();

运行服务端

$ php tcp.php

2、创建一个测试函数

//client.php
<?php
function connect($callback, $try = 0)
{
    if ($try > 3) {
        $callback('connect error');
    } else {
        // 可测试 fsockopen  或 pfsockopen
        $fp = pfsockopen("tcp://127.0.0.1", 9501, $errno, $errstr);
        if (!$fp) {
            $callback("ERROR: $errno - $errstr<br />\n");
        } else {
            if (!fwrite($fp, "message")) {
                // 对于长连接,若 tcp 服务端重启了, $fp 不会自动重连, 这里判断一下
                fclose($fp);
                connect($callback, ++$try);
            } else {
                $callback(fread($fp, 1024));
                // 不去手动关闭
                // fsockopen 打开的连接会自动关闭
                // pfsockopen 打开的连接不会自动关闭,
                // 若手工关闭, 那就是自愿放弃长连接的持久特性了
                //fclose($fp);
            }
        }
    }
}

3、cgi / fastcgi 测试文件

<?php
require __DIR__.'/client.php';
connect(function ($str) {
    echo $str;
});

4、swoole cli 测试文件

<?php
require __DIR__.'/client.php';
$http = new Swoole\Http\Server("127.0.0.1", 8888);
$http->set([
    'worker_num' => 1,
]);
$http->on('request', function ($request, $response) {
    connect(function ($str) use ($response) {
        $response->end($str);
    });
});
$http->start();

5、workerman cli 测试文件

<?php
require __DIR__.'/../library/workerman/Autoloader.php';
require __DIR__.'/client.php';
use Workerman\Worker;

$http_worker = new Worker("http://0.0.0.0:6666");
$http_worker->count = 1;
$http_worker->onMessage = function($connection, $data) {
    connect(function ($str) use ($connection) {
        $connection->send($str);
    });
};
Worker::runAll();

五、测试结果


1、cgi模式

//没环境,暂未测试,想必是无法使用长连接的,等测试了再补充

2、fastcgi 模式

符合预期
使用 fsockopen : tcp 客户端会在每次处理完自动关闭
使用 pfsockopen:tcp 客户端处理后不会关闭,下次会复用通道

3、cli 模式

swoole / workerman 的表现与 fastcgi 模式下一致,fsockopen自动关闭,pfsockopen持久连接;这就需要思考两个问题了

第一个问题:fsockopen 并没有手工去关闭,php 是守护进程运行的,为什么处理完,通道会关闭呢,猜测是因为 swoole 、workerman 中的处理请求的闭包函数在每次运行完之后,都会清理内存,释放变量,试一下把 连接通道 放到闭包之外进行测试。

swoole

<?php
class Client
{
    protected $fp;

    public function getFp()
    {
        if (!$this->fp) {
            $this->fp = fsockopen("tcp://127.0.0.1", 9501, $errno, $errstr);
        }
        return $this->fp;
    }
}
$client = new Client();
$http = new Swoole\Http\Server("127.0.0.1", 8888);
$http->set([
    'worker_num' => 1,
]);
$http->on('request', function ($request, $response) use ($client) {
    $fp = $client->getFp();
    fwrite($fp, "message");
    $response->end(fread($fp, 1024));
});
$http->start();

workerman

require __DIR__.'/../library/workerman/Autoloader.php';
require __DIR__.'/client.php';
use Workerman\Worker;

class Client
{
    protected $fp;

    public function getFp()
    {
        if (!$this->fp) {
            $this->fp = fsockopen("tcp://127.0.0.1", 9501, $errno, $errstr);
        }
        return $this->fp;
    }
}
$client = new Client();
$http_worker = new Worker("http://0.0.0.0:6666");
$http_worker->count = 1;
$http_worker->onMessage = function($connection, $data) use ($client) {
    $fp = $client->getFp();
    fwrite($fp, "message");
    $connection->send(fread($fp, 1024));
};
Worker::runAll();

再次测试,就会发现,fsockopen 打开的通道,在处理完请求之后也不会关闭,这就比较符合直觉了。

第二个问题:闭包函数内使用 pfsockopen 打开的连接为什么没有被释放呢?

看一下 php 的源码,这里这里,这就好理解了,释放的仅仅是变量,而通道被 php 内部的内存管理缓存起来了,cli 也好,php-fpm 也罢,都还是运行在 php 内核之上的,所以二者都符合 php 的处理机制:释放变量,保持连接。只是 cli 多了一个自己写代码缓存连接通道、保持连接的功能。

相关文章

  • PHP 长连接的猜测和验证

    一、什么是长连接,长连接的意义 php 作为 server 对外提供服务, 每次处理新的请求都会重头运行一次代码 ...

  • 为什么说php长连接 连接mysql是鸡肋

    大家都知道php 是没有进程池和连接池。 但是mysql是支持长连接的,我们php可不可以用长连接的方式来连接数据...

  • php+kafka+zookeeper+logstash

    本书主要实现的目标是php连接kafka并且成功发送消息给kafka。为了验证这个连接和发送,另外配置了log...

  • php连接检查

    长连接可以减少建立连接的过程, 使用长连接可以提高服务的性能。php 很多扩展都支持长连接,如 redis, me...

  • 前端,php和mysql联合使用入门

    声明 本文只是简单的介绍前端和php服务端的交互,以及php连接mysql数据库进行简单的操作,不具备数据正则验证...

  • 架构成长之路:MySQL基础架构和事务

    一、 数据库结构: 1.1 Server层: 连接器 权限验证 尽量使用长连接,但是长连接会消耗内存,可以定时清理...

  • MySQL基础架构和事务

    一、 数据库结构: 1.1 Server层: 连接器 权限验证 尽量使用长连接,但是长连接会消耗内存,可以定时清理...

  • PHP 过滤器(Filter) -- 学习笔记

    1、什么是 PHP 过滤器? PHP 过滤器 :用于验证和过滤来自非安全来源的数据。 验证和过滤 [用户输入信息]...

  • docker-compose.yml构建php开发环境

    docker-compose.yml nginx 配置连接php php 连接mysql php连接redis

  • 网络编程--socket

    php内置函数 只能够主动连接,无法进行监听等活动。 函数包或fsockopen和pfsockopen,后者是长连...

网友评论

      本文标题:PHP 长连接的猜测和验证

      本文链接:https://www.haomeiwen.com/subject/kfvmvqtx.html