× 警告!旧版文档已经暂停维护,请查看新版文档。点击前往新版文档

setDefer 机制

绝大部分协程组件,都支持了setDefer特性。可以将请求响应式的接口拆分为两个步骤,使用此机制可以实现先发送数据, 再并发收取响应结果。

由于大多数情况下, [建立连接和发送数据的耗时] 相比于 [等待响应的耗时] 来说可以忽略不计, 所以可以简单理解为defer模式下, 多个客户端的请求响应是并发的

HttpClient为例,设置setDefer(true)后,发起$http->get()请求,将不再等待服务器返回结果,而是在send request之后,立即返回true。在此之后可以继续发起其他HttpClientMySQLRedis等请求。最后再使用$http->recv()接收响应内容。

相比子协程 + Channel的实现方式,setDefer更简单一些。

需注意的是, defer特性只支持并发收取响应结果, 正如示例代码所示, 创建连接和数据的发送, 仍是串行的

示例代码

<?php
$server = new Swoole\Http\Server("127.0.0.1", 9502, SWOOLE_BASE);

$server->set([
    'worker_num' => 1,
]);

$server->on('Request', function ($request, $response) {

    $tcpclient = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP);
    $tcpclient->connect('127.0.0.1', 9501, 0.5)
    $tcpclient->send("hello world\n");

    $redis = new Swoole\Coroutine\Redis();
    $redis->connect('127.0.0.1', 6379);
    $redis->setDefer();
    $redis->get('key');

    $mysql = new Swoole\Coroutine\MySQL();
    $mysql->connect([
        'host' => '127.0.0.1',
        'user' => 'user',
        'password' => 'pass',
        'database' => 'test',
    ]);
    $mysql->setDefer();
    $mysql->query('select sleep(1)');

    $httpclient = new Swoole\Coroutine\Http\Client('0.0.0.0', 9599);
    $httpclient->setHeaders(['Host' => "api.mp.qq.com"]);
    $httpclient->set([ 'timeout' => 1]);
    $httpclient->setDefer();
    $httpclient->get('/');

    $tcp_res  = $tcpclient->recv();
    $redis_res = $redis->recv();
    $mysql_res = $mysql->recv();
    $http_res  = $httpclient->recv();

    $response->end('Test End');
});
$server->start();

  • 杜飞

    这个例子棒棒哒

  • gouchaoer

    connect的时候没法defer么?

  • copperfield

    既然协程已经实现了在IO操作中自动挂起,获得结果之后自动继续,不明白为什么还需要setDefer和recv。后面协程的例子好像也再没有用到这两个函数了。

  • dana

    下面是代码 $server = new Swoole\Http\Server("0.0.0.0",9502,SWOOLE_BASE);

    $server->set([ 'worker_num' => 1, ]);

    $server->on('Request', function ($request, $response) { $tcpclient = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP);

    // $response->end(json_encode($tcpclient)); if($tcpclient->connect('0.0.0.0', 9501) === false){ $response->end('connect server failed!'); return; }

        $tcpclient->send("hello world\n");
    
        $tcp_res  = $tcpclient->recv(100);
        $tcpclient->close();
    
        if($tcp_res){
            $response->end("swoole response is ok");
        }else{
            $response->end("recv failed error : {$tcp_res->errCode}");
        }
    

    // $response->end('Test End'); });

    $server->start();

    问题 连接不上9501 在网页上直接报connect server failed 开启telnet 0.0.0.0 9501 提示 telnet: connect to address 0.0.0.0: Connection refused

  • 穆白

    当setDefer() 是自运行呢 还是挂起了. 等待recv() 才会再继续运行? 这点没有搞明白.

  • 落落

    @copperfield 如果不手动设置延迟收包,开启一条长时间的 sql 查询,到获取结果的时候仍有可能获得 false。我的理解是,协程本身的运行和主进程已经没有关系了,设置延迟收包的作用就类似于设置一个无缓冲的通道,在获取查询结果的时候因为 pop 不出数据而被阻塞了,也就实现了『等待』查询结果的同时去做别的事情。

  • 落落

    @穆白 设置了延迟收包是已经自运行了,当 recv() 结果时,若已经获取到则直接运行,否则就等待直到获取到结果或者超时。