当前位置: 首页 > 文档资料 > Swoole 中文文档 >

Runtime

优质
小牛编辑
136浏览
2023-12-01

相对于Swoole1.xSwoole4+提供了协程这个大杀器,所有业务代码都是同步的,但底层的IO却是异步的,保证并发的同时避免了传统异步回调所带来的离散的代码逻辑和陷入多层回调中导致代码无法维护, 要达到这个效果必须所有的IO请求都是异步IO,而Swoole1.x时代的提供的MysqlRedis等客户端虽然是异步IO,但是是异步回调的编程方式,不是协程方式,所以我们在Swoole4时代移除了这些客户端。

为了解决这些客户端的协程支持问题我们做了大量的工作:

  • 刚开始,我们针对每种类型的客户端都做了一个协程客户端,都在这里,但这样做有3个问题:

    • 实现复杂,每个客户端细枝末节的协议都很复杂,想都完美的支持工作量巨大。
    • 用户需要更改的代码比较多,比如原来查询Mysql是用的PHP原生的PDO,那么现在需要用Swoole\Coroutine\MySQL的方法。
    • 我们很难覆盖到所有的操作,比如proc_open()sleep()函数等等也可能阻塞住导致程序变成同步阻塞的。
  • 针对上述问题,我们换了实现思路,采用Hook原生PHP函数的方式实现协程客户端,通过一行代码就可以让原来的同步IO的代码变成可以协程调度异步IO,即一键协程化

!> 此特性在v4.3版本后开始稳定,能Hook的函数也越来越多,所以有些之前写的协程客户端已经不再推荐使用了,详情查看协程客户端, 例如:在v4.3+支持了文件操作(file_get_contentsfread等)的Hook,如果您使用的是v4.3+就不要再用Swoole提供的协程文件操作了。

函数原型

Co::set(['hook_flags'=> SWOOLE_HOOK_ALL]);//v4.4+版本使用此方法。
或
Swoole\Runtime::enableCoroutine(int $flags = SWOOLE_HOOK_ALL);

通过flags设置要Hook的函数的范围,同时开启多个flags需要|操作,例如Co::set(['hook_flags'=> SWOOLE_HOOK_TCP|SWOOLE_HOOK_SLEEP]);,被Hook的函数就无法在非协程容器中使用。

选项

flags支持的选项有:

SWOOLE_HOOK_ALL

打开下述所有类型的flags (不包括CURL)

Co::set(['hook_flags' => SWOOLE_HOOK_ALL]);//不包括CURL
Co::set(['hook_flags' => SWOOLE_HOOK_ALL | SWOOLE_HOOK_CURL]);//真正的hook所有类型,包括CURL

SWOOLE_HOOK_TCP

v4.1开始支持,TCP Socket类型的stream,包括最常见的RedisPDOMysqli,以及用PHP的streams系列函数操作TCP连接的操作,都可以Hook,示例:

Co::set(['hook_flags' => SWOOLE_HOOK_TCP]);

Co\run(function() {
    for ($c = 100; $c--;) {
        go(function () {//创建100个协程
            $redis = new Redis();
            $redis->connect('127.0.0.1', 6379);//此处产生协程调度,cpu切到下一个协程,不会阻塞进程
            $redis->get('key');//此处产生协程调度,cpu切到下一个协程,不会阻塞进程
        });
    }
});

上述代码使用的就是原生的Redis类,但是其实已经变成了异步IOCo\run()是创建了协程容器go()是创建协程,这两个操作在Swoole提供的Swoole\Server类簇都是自动做好的,不需要手动做,参考enable_coroutine

也就是说传统的PHP程序员用最熟悉的逻辑代码就能写出高并发、高性能的程序,如下:

Co::set(['hook_flags' => SWOOLE_HOOK_TCP]);

$http = new Swoole\Http\Server("0.0.0.0", 9501);
$http->set(['enable_coroutine' => true]);

$http->on('request', function ($request, $response) {
      $redis = new Redis();
      $redis->connect('127.0.0.1', 6379);//此处产生协程调度,cpu切到下一个协程(下一个请求),不会阻塞进程
      $redis->get('key');//此处产生协程调度,cpu切到下一个协程(下一个请求),不会阻塞进程
});

$http->start();

SWOOLE_HOOK_UNIX

v4.2开始支持。Unix Stream Socket类型的stream,示例:

Co::set(['hook_flags' => SWOOLE_HOOK_UNIX]);

Co\run(function () {
    go(function () {
        $socket = stream_socket_server(
            'unix://swoole.sock', $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN
        );
        if (!$socket) {
            echo "$errstr ($errno)" . PHP_EOL;
            exit(1);
        }
        while (stream_socket_accept($socket)) {
        }
    });
    echo "here" . PHP_EOL;//优先执行子协程(参考"父子协程优先级"),但会马上产生协程调度执行此行。
});

SWOOLE_HOOK_UDP

v4.2开始支持。UDP Socket类型的stream,示例:

Co::set(['hook_flags' => SWOOLE_HOOK_UDP]);

Co\run(function () {
    go(function () {
        $socket = stream_socket_server(
            'udp://0.0.0.0:6666',
            $errno, $errstr, STREAM_SERVER_BIND
        );
        if (!$socket) {
            echo "$errstr ($errno)" . PHP_EOL;
            exit(1);
        }
        while (stream_socket_recvfrom($socket, 1, 0)) {
        }
    });
    echo "here" . PHP_EOL;
});

SWOOLE_HOOK_UDG

v4.2开始支持。Unix Dgram Socket类型的stream,示例:

Co::set(['hook_flags' => SWOOLE_HOOK_UDG]);

Co\run(function () {
    go(function () {
        $socket = stream_socket_server(
            'udg://swoole.sock', $errno, $errstr, STREAM_SERVER_BIND
        );
        if (!$socket) {
            echo "$errstr ($errno)" . PHP_EOL;
            exit(1);
        }
        while (stream_socket_recvfrom($socket, 1, 0)) {
        }
    });
    echo "here" . PHP_EOL;
});

SWOOLE_HOOK_SSL

v4.2开始支持。SSL Socket类型的stream,示例:

Co::set(['hook_flags' => SWOOLE_HOOK_SSL]);

Co\run(function () {
    go(function () {
        $host = 'host.domain.tld';
        $port = 1234;
        $timeout = 10;
        $cert = '/path/to/your/certchain/certchain.pem';
            $context = stream_context_create(array('ssl'=>array('local_cert'=> $cert,
        )));
        if ($fp = stream_socket_client('ssl://'.$host.':'.$port, $errno, $errstr, 30,
                STREAM_CLIENT_CONNECT, $context)) {
            echo "connected\n";
        } else {
           echo "ERROR: $errno - $errstr \n";
        }
    });
    echo "here" . PHP_EOL;
});

SWOOLE_HOOK_TLS

v4.2开始支持。TLS Socket类型的stream,参考

示例:

   Co::set(['hook_flags' => SWOOLE_HOOK_TLS]);

SWOOLE_HOOK_SLEEP

v4.2开始支持。sleep函数的Hook,包括了sleepusleeptime_nanosleeptime_sleep_until,由于底层的定时器最小粒度是1ms,因此使用usleep等高精度睡眠函数时,如果设置为低于1ms时,将直接使用sleep系统调用。可能会引起非常短暂的睡眠阻塞。示例:

Co::set(['hook_flags' => SWOOLE_HOOK_SLEEP]);

Co\run(function () {
    go(function () {
        sleep(1);
        echo '1' . PHP_EOL;
    });
    go(function () {
        echo '2' . PHP_EOL;
    });
});
//输出 
2
1

SWOOLE_HOOK_FILE

v4.3开始支持。

  • 文件操作的Hook,支持的函数有:

    • fopen
    • fread/fgets
    • fwrite/fputs
    • file_get_contentsfile_put_contents
    • unlink
    • mkdir
    • rmdir

示例:

Co::set(['hook_flags' => SWOOLE_HOOK_FILE]);

Co\run(function () {
    go(function () {
        $fp = fopen("test.log", "a+");
        fwrite($fp, str_repeat('A', 2048));
        fwrite($fp, str_repeat('B', 2048));
    });
    echo "here" . PHP_EOL;
});

SWOOLE_HOOK_STREAM_FUNCTION

v4.4开始支持。stream_select()Hook,示例:

Co::set(['hook_flags' => SWOOLE_HOOK_STREAM_FUNCTION]);

Co\run(function () {
   go(function () {
    $fp1 = stream_socket_client("tcp://www.baidu.com:80", $errno, $errstr, 30);
    $fp2 = stream_socket_client("tcp://www.qq.com:80", $errno, $errstr, 30);
    if (!$fp1) {
        echo "$errstr ($errno) \n";
    } else {
        fwrite($fp1, "GET / HTTP/1.0\r\nHost: www.baidu.com\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n");
        $r_array = [$fp1, $fp2];
        $w_array = $e_array = null;
        $n = stream_select($r_array, $w_array, $e_array, 10);
        $html = '';
        while (!feof($fp1)) {
            $html .= fgets($fp1, 1024);
        }
        fclose($fp1);
    }
   });
   echo "here" . PHP_EOL;
});

SWOOLE_HOOK_BLOCKING_FUNCTION

v4.4开始支持。这里的blocking function包括了:gethostbynameexecshell_exec,示例:

Co::set(['hook_flags' => SWOOLE_HOOK_BLOCKING_FUNCTION]);

Co\run(function () {
   go(function () {
        while (true) {
            exec("cat");
        }
    });

    echo "here" . PHP_EOL;
});

SWOOLE_HOOK_PROC

v4.4开始支持。Hook proc* 函数,包括了:proc_openproc_closeproc_get_statusproc_terminate

示例:

Co::set(['hook_flags' => SWOOLE_HOOK_PROC]);

Co\run(function () {
    go(function () {
        $descriptorspec = array(
            0 => array("pipe", "r"),  // stdin, child process read from it
            1 => array("pipe", "w"),  // stdout, child process write to it
        );
        $process = proc_open('php', $descriptorspec, $pipes);
        if (is_resource($process)) {
            fwrite($pipes[0], '<?php echo "I am process\n" ?>');
            fclose($pipes[0]);

            while (true) {
                echo fread($pipes[1], 1024);
            }

            fclose($pipes[1]);
            $return_value = proc_close($process);
            echo "command returned $return_value" . PHP_EOL;
        }
    });

    echo "here" . PHP_EOL;
});

SWOOLE_HOOK_CURL

v4.4LTS后或v4.5开始正式支持。

  • CURL的HOOK,支持的函数有:

    • curl_init
    • curl_setopt
    • curl_exec
    • curl_multi_getcontent
    • curl_setopt_array
    • curl_error
    • curl_getinfo
    • curl_errno
    • curl_close
    • curl_reset

示例:

Co::set(['hook_flags' => SWOOLE_HOOK_CURL]);

Co\run(function () {
     go(function () {
        $ch = curl_init();  
        curl_setopt($ch, CURLOPT_URL, "http://www.xinhuanet.com/");  
        curl_setopt($ch, CURLOPT_HEADER, false);  
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $result=curl_exec($ch);  
        curl_close($ch);
        var_dump($result);
    });
    echo "here" . PHP_EOL;
});

!> Swoole\Runtime::enableCoroutine() VS Co::set(['hook_flags])用哪个

  • Swoole\Runtime::enableCoroutine(),可以在服务启动后(运行时)动态设置flags,调用方法后当前进程内全局生效,应该放在整个项目开始以获得100%覆盖的效果。
  • Co::set()可以理解为PHP的ini_set(),需要在Server->start()前或Co\run()前调用,否则设置的hook_flags不会生效,在v4.4+版本应该用此种方式设置flags
  • 无论是Co::set(['hook_flags])还是Swoole\Runtime::enableCoroutine()都应该只调用一次,重复调用会被覆盖。

方法

getHookFlags()

!> Swoole版本 >= v4.4.12

获取当前已Hook内容的flags,可能会与开启Hook时传入的flags不一致(由于未Hook成功的flags将会被清除)

Swoole\Runtime::getHookFlags(): int;

常见的Hook列表

可用列表

  • redis扩展
  • 使用mysqlnd模式的pdo_mysqlmysqli扩展,如果未启用mysqlnd将不支持协程化
  • soap扩展
  • file_get_contentsfopen
  • stream_socket_client (predisphp-amqplib)
  • stream_socket_server
  • stream_select (需要4.3.2以上版本)
  • fsockopen
  • proc_open (需要4.4.0以上版本)
  • curl

不可用列表

  • mysql:底层使用libmysqlclient
  • mongo:底层使用mongo-c-client
  • pdo_pgsql
  • pdo_ori
  • pdo_odbc
  • pdo_firebird

API变更

v4.3及以前版本,enableCoroutine的API需要2个参数。

Swoole\Runtime::enableCoroutine(bool $enable = true, int $flags = SWOOLE_HOOK_ALL);
  • $enable:打开或关闭Hook。
  • $flags:选择要Hook的类型,可以多选,默认为全选。仅在$enable = true时有效。

!> Runtime::enableCoroutine(false)关闭上一次设置的所有选项协程Hook设置。