韩天峰

韩天峰 查看完整档案

上海编辑  |  填写毕业院校  |  填写所在公司/组织 rango.swoole.com 编辑
编辑

Swoole 开源项目创始人

个人动态

韩天峰 赞了文章 · 4月9日

Swoole v4.6.5 版本发布,增加原生curl multi支持

v4.6.5 版本没有向下不兼容改动,主要对原生 curl hook 进行了一些增强,支持了 curl multi

  • 支持原生 curl multi

使用原生 curl hook 的前提是在编译 Swoole 扩展时开启--enable-swoole-curl选项

可以使用以下代码进行测试:

use Swoole\Runtime;
use function Swoole\Coroutine\run;

Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL);
run(function () {
    $ch1 = curl_init();
    $ch2 = curl_init();

    // 设置URL和相应的选项
    curl_setopt($ch1, CURLOPT_URL, "http://www.baidu.com/");
    curl_setopt($ch1, CURLOPT_HEADER, 0);
    curl_setopt($ch1, CURLOPT_RETURNTRANSFER, 1);

    curl_setopt($ch2, CURLOPT_URL, "http://www.gov.cn/");
    curl_setopt($ch2, CURLOPT_HEADER, 0);
    curl_setopt($ch2, CURLOPT_RETURNTRANSFER, 1);

    $mh = curl_multi_init();

    curl_multi_add_handle($mh, $ch1);
    curl_multi_add_handle($mh, $ch2);

    $active = null;
    // 执行批处理句柄
    do {
        $mrc = curl_multi_exec($mh, $active);
    } while ($mrc == CURLM_CALL_MULTI_PERFORM);

    while ($active && $mrc == CURLM_OK) {
        $n = curl_multi_select($mh);
        if ($n != -1) {
            do {
                $mrc = curl_multi_exec($mh, $active);
            } while ($mrc == CURLM_CALL_MULTI_PERFORM);
        }
    }

    $info1 = curl_multi_info_read($mh);
    $info2 = curl_multi_info_read($mh);
    $info3 = curl_multi_info_read($mh);

    assert($info1['msg'] === CURLMSG_DONE);
    assert($info2['msg'] === CURLMSG_DONE);
    assert($info3 === false);

    assert(strpos(curl_multi_getcontent($ch1),'baidu.com') !== false);
    assert(strpos(curl_multi_getcontent($ch2),'中央人民政府门户网站') !== false);

    curl_multi_remove_handle($mh, $ch1);
    curl_multi_remove_handle($mh, $ch2);

    curl_multi_close($mh);
});

支持 curl multi 之后,也就间接的支持了 Guzzle,无需更改任何代码,即可支持。

include __DIR__ . '/vendor/autoload.php';

use Swoole\Coroutine\Barrier;
use Swoole\Runtime;
use GuzzleHttp\Client;
use GuzzleHttp\Promise;

use function Swoole\Coroutine\run;
use function Swoole\Coroutine\go;

Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL);

const N = 4;

run(function () {
    $barrier = Barrier::make();
    $result = [];
    go(function () use ($barrier, &$result) {
        $client = new Client();
        $promises = [
            'baidu' => $client->getAsync('http://www.baidu.com/'),
            'qq' => $client->getAsync('https://www.qq.com/'),
            'gov' => $client->getAsync('http://www.gov.cn/')
        ];
        $responses = Promise\Utils::unwrap($promises);
        assert(strpos($responses['baidu']->getBody(),'百度') !== false);
        assert(strpos(iconv('gbk', 'utf-8', $responses['qq']->getBody()),'腾讯') !== false);
        assert(strpos($responses['gov']->getBody(),'中华人民共和国') !== false);
        $result['task_1'] = 'OK';
    });

    go(function () use ($barrier, &$result) {
        $client = new Client(['base_uri' => 'http://httpbin.org/']);
        $n = N;
        $data = $promises = [];
        while ($n--) {
            $key = 'req_' . $n;
            $data[$key] = uniqid('swoole_test');
            $promises[$key] = $client->getAsync('/base64/' . base64_encode($data[$key]));
        }
        $responses = Promise\Utils::unwrap($promises);

        $n = N;
        while ($n--) {
            $key = 'req_' . $n;
            assert($responses[$key]->getBody() === $data[$key]);
        }
        $result['task_2'] = 'OK';
    });

    Barrier::wait($barrier);
    assert($result['task_1'] === 'OK');
    assert($result['task_2'] === 'OK');
    echo 'Done' . PHP_EOL;
});

同时也还添加了一些 Guzzle 的单元测试。

  • 允许在使用 HTTP/2 的 Response 中使用数组设置 headers

v4.6.0 版本开始 Swoole\Http\Response 支持重复设置相同 $keyHTTP 头,并且 $value 支持多种类型,如 arrayobjectintfloat,底层会进行 toString 转换,并且会移除末尾的空格以及换行。

但是未支持 HTTP/2 的,详情见 issue #4133

在此版本中也进行了支持:

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

$http->on('request', function ($request, $response) {
    $response->header('Test-Value', [
        "a\r\n",
        'd5678',
        "e  \n ",
        null,
        5678,
        3.1415926,
    ]);

    $response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>");
});
$http->start();

可以使用以上代码进行测试,并使用 curl 命令进行测试结果

$ curl --http2-prior-knowledge -v http://localhost:9501
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 9501 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 9501 (#0)
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fe9e9009200)
> GET / HTTP/2
> Host: localhost:9501
> User-Agent: curl/7.64.1
> Accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
< HTTP/2 200
< test-value: a
< test-value: d5678
< test-value: e
< test-value: 5678
< test-value: 3.1415926
< server: swoole-http-server
< date: Fri, 09 Apr 2021 11:04:39 GMT
< content-type: text/html
< content-length: 28
<
* Connection #0 to host localhost left intact
<h1>Hello Swoole. #6944</h1>* Closing connection 0

更新日志

下面是完整的更新日志:

新增 API

  • 在 WaitGroup 中增加 count 方法(swoole/library#100) (@sy-records) (@deminy)

增强

  • 支持原生 curl multi (#4093) (#4099) (#4101) (#4105) (#4113) (#4121) (#4147) (swoole/swoole-src@cd7f51c) (@matyhtf) (@sy-records) (@huanghantao)
  • 允许在使用 HTTP/2 的 Response 中使用数组设置 headers

修复

  • 修复 NetBSD 构建 (#4080) (@devnexen)
  • 修复 OpenBSD 构建 (#4108) (@devnexen)
  • 修复 illumos/solaris 构建,只有成员别名 (#4109) (@devnexen)
  • 修复握手未完成时,SSL 连接的心跳检测不生效 (#4114) (@matyhtf)
  • 修复 Http\Client 使用代理时host中存在host:port产生的错误 (#4124) (@Yurunsoft)
  • 修复 Swoole\Coroutine\Http::request 中 header 和 cookie 的设置 (swoole/library#103) (@leocavalcante) (@deminy)

内核

  • 支持 BSD 上的 asm context (#4082) (@devnexen)
  • 在 FreeBSD 下使用 arc4random\_buf 来实现 getrandom (#4096) (@devnexen)
  • 优化 darwin arm64 context:删除 workaround 使用 label (#4127) (@devnexen)

测试

  • 添加 alpine 的构建脚本 (#4104) (@limingxinleo)

查看原文

赞 5 收藏 1 评论 0

韩天峰 赞了文章 · 2020-12-07

websocket & swoole & swoft

1.Websocket

1.OSI七层与TCP/IP五层模型

image.png

2.socket

Socket实际上是对TCP/IP协议的封装,本身并不是协议,而是一个调用接口(API).

Socket的出现只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,从而形成了我们知道的一些最基本的函数接口.

比如create、listen、connect、accept、send、read和write.

3.简介

WebSocket 是一种网络通信协议.

WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了.

服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,属于服务器推送技术的一种。

`长链接`
# 服务器推送技术
1    Webpush
2    HTTP server push
3    Pushlet
4    Long polling
5    Flash XMLSocket relays
6    Reliable Group Data Delivery (RGDD)
7    Push notification

4.与HTTP的对比

image.png

5.特点


(1)建立在 TCP 协议之上,服务器端的实现比较容易。

(2)与 HTTP 协议有着良好的兼容性。默认端口也是`80`和`443`,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

(3)数据格式比较轻量,性能开销小,通信高效。

(4)可以发送文本,也可以发送二进制数据。

(5)没有`同源限制`,客户端可以与任意服务器通信。

(6)协议标识符是`ws`(如果加密,则为wss),服务器网址就是 URL。   (scheme)
ws://example.com:80/uri
wss://example.com:80/uri

6.示例

# 客户端
let ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) { 
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log("Connection closed.");
};

# 服务端
php -> socket_create(),  new Socket
python -> socket,
go -> gorilla/websocket
node -> socket.io / socket.io -client
# 调试
https://jsbin.com/?js,console,output
# webSocket.readyState
readyState属性返回实例对象的当前状态,共有四种。
CONNECTING:值为0,表示正在连接。
OPEN:值为1,表示连接成功,可以通信了。
CLOSING:值为2,表示连接正在关闭。
CLOSED:值为3,表示连接已经关闭,或者打开连接失败。

2.swoole

1.简介

作者 韩天峰 
pecl开发组成员

php扩展

Swoole 是一个 PHP 的 `协程` `高性能` 网络通信引擎,使用 C/C++ 语言编写,提供了多种通信协议的网络服务器和客户端模块。可以方便快速的实现 TCP/UDP服务、高性能Web、WebSocket服务、物联网、实时通讯、游戏、微服务等,使 PHP 不再局限于传统的 Web 领域。

4.4+之后, 全面协程化, PHP 协程框架 
# 文档
https://www.swoole.com/

php-fpm` 处理请求

sapi---> 初始化http的环境变量

phpcore ---> 初始化php拓展, 初始化上下文环境

image.png

执行php脚本

image.png

2.示例

1.http server

        $http = new Swoole\Http\Server("127.0.0.1", 9501);

    $http->on("start", function ($server) {
        echo "Swoole http server is started at http://127.0.0.1:9501\n";
    });

    $http->on("request", function ($request, $response) {
        $response->header("Content-Type", "text/plain");
        $response->end("Hello World\n");
    });

    $http->start();

2.websocket server

        $server = new Swoole\Websocket\Server("127.0.0.1", 9502);

    $server->on('open', function($server, $req) {
        echo "connection open: {$req->fd}\n";
    });

    $server->on('message', function($server, $frame) {
        echo "received message: {$frame->data}\n";
        $server->push($frame->fd, json_encode(["hello", "world"]));
    });

    $server->on('close', function($server, $fd) {
        echo "connection close: {$fd}\n";
    });

    $server->start();

3.tcp server

        $server = new Swoole\Server("127.0.0.1", 9503);
    $server->on('connect', function ($server, $fd){
        echo "connection open: {$fd}\n";
    });
    $server->on('receive', function ($server, $fd, $reactor_id, $data) {
        $server->send($fd, "Swoole: {$data}");
        $server->close($fd);
    });
    $server->on('close', function ($server, $fd) {
        echo "connection close: {$fd}\n";
    });
    $server->start();

4.udp server

        $serv = new Swoole\Server("127.0.0.1", 9502, SWOOLE_PROCESS, SWOOLE_SOCK_UDP);

    //监听数据接收事件
    $serv->on('Packet', function ($serv, $data, $clientInfo) {
        $serv->sendto($clientInfo['address'], $clientInfo['port'], "Server ".$data);
        var_dump($clientInfo);
    });

    //启动服务器
    $serv->start();

5.task

$server = new Swoole\Server("127.0.0.1", 9502);
    $server->set(array('task_worker_num' => 4));
    $server->on('receive', function($server, $fd, $reactor_id, $data) {
        $task_id = $server->task("Async");
        echo "Dispatch AsyncTask: [id=$task_id]\n";
    });
    $server->on('task', function ($server, $task_id, $reactor_id, $data) {
        echo "New AsyncTask[id=$task_id]\n";
        $server->finish("$data -> OK");
    });
    $server->on('finish', function ($server, $task_id, $data) {
        echo "AsyncTask[$task_id] finished: {$data}\n";
    });
    $server->start();

6.coroutine

//睡眠 1 万次,读取,写入,检查和删除文件 1 万次,使用 PDO 和 MySQLi 与数据库通信 1 万次,创建 TCP 服务器和多个客户端相互通信 1 万次,
//创建 UDP 服务器和多个客户端到相互通信 1 万次...... 一切都在一个进程一秒内完美完成!

   Swoole\Runtime::enableCoroutine();//此行代码后,文件操作,sleep,Mysqli,PDO,streams等都变成异步IO,见文档'一键协程化'章节
   $s = microtime(true);
    //Co/run()见文档'协程容器'章节
   Co\run(function() {
    // i just want to sleep...
    for ($c = 100; $c--;) {
        go(function () {
            for ($n = 100; $n--;) {
                usleep(1000);
            }
        });
    }

    // 10k file read and write
    for ($c = 100; $c--;) {
        go(function () use ($c) {
            $tmp_filename = "/tmp/test-{$c}.php";
            for ($n = 100; $n--;) {
                $self = file_get_contents(__FILE__);
                file_put_contents($tmp_filename, $self);
                assert(file_get_contents($tmp_filename) === $self);
            }
            unlink($tmp_filename);
        });
    }

    // 10k pdo and mysqli read
    for ($c = 50; $c--;) {
        go(function () {
            $pdo = new PDO('mysql:host=127.0.0.1;dbname=test;charset=utf8', 'root', 'root');
            $statement = $pdo->prepare('SELECT * FROM `user`');
            for ($n = 100; $n--;) {
                $statement->execute();
                assert(count($statement->fetchAll()) > 0);
            }
        });
    }
    for ($c = 50; $c--;) {
        go(function () {
            $mysqli = new Mysqli('127.0.0.1', 'root', 'root', 'test');
            $statement = $mysqli->prepare('SELECT `id` FROM `user`');
            for ($n = 100; $n--;) {
                $statement->bind_result($id);
                $statement->execute();
                $statement->fetch();
                assert($id > 0);
            }
        });
    }

    // php_stream tcp server & client with 12.8k requests in single process
    function tcp_pack(string $data): string
    {
        return pack('n', strlen($data)) . $data;
    }

    function tcp_length(string $head): int
    {
        return unpack('n', $head)[1];
    }

    go(function () {
        $ctx = stream_context_create(['socket' => ['so_reuseaddr' => true, 'backlog' => 128]]);
        $socket = stream_socket_server(
            'tcp://0.0.0.0:9502',
            $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctx
        );
        if (!$socket) {
            echo "$errstr ($errno)\n";
        } else {
            $i = 0;
            while ($conn = stream_socket_accept($socket, 1)) {
                stream_set_timeout($conn, 5);
                for ($n = 100; $n--;) {
                    $data = fread($conn, tcp_length(fread($conn, 2)));
                    assert($data === "Hello Swoole Server #{$n}!");
                    fwrite($conn, tcp_pack("Hello Swoole Client #{$n}!"));
                }
                if (++$i === 128) {
                    fclose($socket);
                    break;
                }
            }
        }
    });
    for ($c = 128; $c--;) {
        go(function () {
            $fp = stream_socket_client("tcp://127.0.0.1:9502", $errno, $errstr, 1);
            if (!$fp) {
                echo "$errstr ($errno)\n";
            } else {
                stream_set_timeout($fp, 5);
                for ($n = 100; $n--;) {
                    fwrite($fp, tcp_pack("Hello Swoole Server #{$n}!"));
                    $data = fread($fp, tcp_length(fread($fp, 2)));
                    assert($data === "Hello Swoole Client #{$n}!");
                }
                fclose($fp);
            }
        });
    }

    // udp server & client with 12.8k requests in single process
    go(function () {
        $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_DGRAM, 0);
        $socket->bind('127.0.0.1', 9503);
        $client_map = [];
        for ($c = 128; $c--;) {
            for ($n = 0; $n < 100; $n++) {
                $recv = $socket->recvfrom($peer);
                $client_uid = "{$peer['address']}:{$peer['port']}";
                $id = $client_map[$client_uid] = ($client_map[$client_uid] ?? -1) + 1;
                assert($recv === "Client: Hello #{$id}!");
                $socket->sendto($peer['address'], $peer['port'], "Server: Hello #{$id}!");
            }
        }
        $socket->close();
    });
    for ($c = 128; $c--;) {
        go(function () {
            $fp = stream_socket_client("udp://127.0.0.1:9503", $errno, $errstr, 1);
            if (!$fp) {
                echo "$errstr ($errno)\n";
            } else {
                for ($n = 0; $n < 100; $n++) {
                    fwrite($fp, "Client: Hello #{$n}!");
                    $recv = fread($fp, 1024);
                    list($address, $port) = explode(':', (stream_socket_get_name($fp, true)));
                    assert($address === '127.0.0.1' && (int)$port === 9503);
                    assert($recv === "Server: Hello #{$n}!");
                }
                fclose($fp);
            }
        });
    }
  });
  echo 'use ' . (microtime(true) - $s) . ' s';

7.Channel

 Co\run(function(){
        //使用Channel进行协程间通讯
        $chan = new Swoole\Coroutine\Channel(1);
        Swoole\Coroutine::create(function () use ($chan) {
            for($i = 0; $i < 100000; $i++) {
                co::sleep(1.0);
                $chan->push(['rand' => rand(1000, 9999), 'index' => $i]);
                echo "$i\n";
            }
        });
        Swoole\Coroutine::create(function () use ($chan) {
            while(1) {
                $data = $chan->pop();
                var_dump($data);
            }
        });
  });

3.风格

服务端 + 客户端

1.异步风格

2.协程风格

进程 ---> 线程 ---> 协程

连接池 (server的manager模块)

# 创建
`Coroutine::create` 或 `go` 方法创建协程

支持 `waitgroup`
# 通信问题
     进程
             高性能共享内存 `Table`
       `Process`模块 (代替php自带的 `pcntl` )
   协程
       `Coroutine\Channel`
        并发编程: Coroutine::create() + setdefer()  ->   go() + channel


退出协程`exit`禁用
`异常捕获`不能跨协程
在多个协程间不能`共用`一个连接
禁止使用`静态类`或者`全局变量`保存上下文对象
`sleep`不能用

4.基本须知

1.四种设置回调函数的方式

# 匿名函数
$server->on('Request', function ($req, $resp) use ($a, $b, $c) {
    echo "hello world";
});
Copy to clipboardErrorCopied
可使用 use 向匿名函数传递参数
# 类静态方法
class A
{
    static function test($req, $resp)
    {
        echo "hello world";
    }
}
$server->on('Request', 'A::Test');
$server->on('Request', array('A', 'Test'));
Copy to clipboardErrorCopied
对应的静态方法必须为 public
# 函数
function my_onRequest($req, $resp)
{
    echo "hello world";
}
$server->on('Request', 'my_onRequest');
# 对象方法
class A
{
    function test($req, $resp)
    {
        echo "hello world";
    }
}
$object = new A();
$server->on('Request', array($object, 'test'));
对应的方法必须为 public

2.同步 IO / 异步 IO

# 网络io模型:
同步模型(synchronous IO)
阻塞IO(bloking IO)
非阻塞IO(non-blocking IO)
多路复用IO(multiplexing IO) `poll/select`  -> `epoll`
信号驱动式IO(signal-driven IO)
异步IO(asynchronous IO)

3.EventLoop

所谓 EventLoop,即事件循环,可以简单的理解为 `epoll_wait`,我们会把所有要发生事件的句柄(fd)加入到 epoll_wait 中,这些事件包括可读,可写,出错等。 我们的进程就阻塞在 epoll_wait 这个内核函数上,当发生了事件 (或超时) 后 epoll_wait 这个函数就会结束阻塞返回结果,就可以回调相应的 PHP 函数,例如,收到客户端发来的数据,回调 OnRecieve 回调函数。

当有大量的 fd 放入到了 epoll_wait 中,并且同时产生了大量的事件,epoll_wait 函数返回的时候我们就会挨个调用相应的回调函数,叫做一轮事件循环,即 IO 多路复用,然后再次阻塞调用 epoll_wait 进行下一轮事件循环。

4.TCP粘包问题

tcp 封包 解包 粘包 
数据封包协议规定:整个数据包包含2字节长度信息+数据包体。2字节长度信息包含本身着2字节。
如:数据体是(abcdefg)7个字节,整体封包就是09abcdefg,总共是9个字节的协议
EOF 结束符协议
固定包头 + 包体协议

5.IPC

同一台主机上两个进程间通信 (`Inter-Process Communication`)
在 Swoole 下使用了 2 种方式 :

# Unix Socket :
全名 UNIX Domain Socket, 简称 UDS
SOCK_STREAM : 数据大用 (有粘包问题)
SOCK_DGRAM : 数据小用 (64k)

# sysvmsg :
Linux 提供的消息队列,这种 IPC 方式通过一个文件名来作为 key 进行通讯,这种方式非常的不灵活,实际项目使用的并不多

5.安装

扩展冲突

xdebug
phptrace
aop
molten
xhprof
phalcon(Swoole 协程无法运行在 phalcon 框架中)

必须

php-7.1 或更高版本
gcc-4.8 或更高版本
make
autoconf

1.源码安装

#1. 下载 swoole 源码
https://github.com/swoole/swoole-src/releases
http://pecl.php.net/package/swoole
http://git.oschina.net/swoole/swoole

#2. 从源码编译安装
下载源代码包后,在终端进入源码目录,执行下面的命令进行编译和安装
cd swoole-src && \
phpize && \
./configure && \
--enable-openssl  \
--enable-http2 && \
make && sudo make install

#3. 启用扩展
编译安装到系统成功后,需要在 php.ini 中加入一行 extension=swoole.so 来启用 Swoole 扩展

2.pecl安装

pecl install swoole

3.swoft

version:2.x

1.环境准备

#必要部分
PHP,版本 >=7.1
PHP 包管理器 Composer
PCRE 库
PHP 扩展 Swoole,版本 >=4.3
额外扩展:PDO、Redis

#冲突部分
Xdebug
Xhprof
Blackfire
Zend
trace
Uopz

2.安装方式

1.docker

docker run -p 18306:18306 --name swoft swoft/swoft

2.docker-compose

git clone https://github.com/swoft-cloud/swoft
cd swoft
docker-compose up

# sserver下面有以下文件配置
docker-compose.yml
docker-sync -> rsync , native_osx

3.composer

composer create-project swoft/swoft Swoft

4.手动安装

git clone https://github.com/swoft-cloud/swoft
cd swoft
composer install
cp .env.example .env

5.swoftcli

# 支持从不同模板项目中快速创建一个干净的 Swoft 应用
php swoftcli.phar create:app --type full Swoft-Full
php swoftcli.phar create:app --type ws Swoft-WebSocket
php swoftcli.phar create:app --type tcp Swoft-TCP

# 使用
cp swoftcli.phar /usr/local/bin/swoftcli && chmod a+x swoftcli

3.目录结构

├── app/    ----- 应用代码目录
│   ├── Annotation/        ----- 定义注解相关   (`ReflectionCalss`)
│   ├── Aspect/            ----- AOP 切面      (`Aspect-oriented programming`)
│   ├── Common/            ----- 一些具有独立功能的 class bean
│   ├── Console/           ----- 命令行代码目录
│   ├── Exception/         ----- 定义异常类目录
│   │   └── Handler/           ----- 定义异常处理类目录
│   ├── Http/              ----- HTTP 服务代码目录
│   │   ├── Controller/
│   │   └── Middleware/
│   ├── Helper/            ----- 助手函数
│   ├── Listener/          ----- 事件监听器目录
│   ├── Model/             ----- 模型、逻辑等代码目录(这些层并不限定,根据需要使用)
│   │   ├── Dao/
│   │   ├── Data/
│   │   ├── Logic/
│   │   └── Entity/
│   ├── Rpc/               ----- RPC 服务代码目录
│   │   └── Service/
│   │   └── Middleware/
│   ├── WebSocket/         ----- WebSocket 服务代码目录
│   │   ├── Chat/
│   │   ├── Middleware/
│   │   └── ChatModule.php
│   ├── Tcp/               ----- TCP 服务代码目录
│   │   └── Controller/        ----- TCP 服务处理控制器目录
│   ├── Application.php    ----- 应用类文件继承自swoft核心
│   ├── AutoLoader.php     ----- 项目扫描等信息(应用本身也算是一个组件)
│   └── bean.php
├── bin/
│   ├── bootstrap.php
│   └── swoft              ----- Swoft 入口文件
├── config/                ----- 应用配置目录
│   ├── base.php               ----- 基础配置
│   └── db.php                 ----- 数据库配置
├── public/                ----- 公共目录
├── resource/              ----- 应用资源目录
│   ├── language/              ----- 语言资源目录  
│   └── view/                  ----- 视图资源目录  
├── runtime/               ----- 临时文件目录(日志、上传文件、文件缓存等)
├── test/                  ----- 单元测试目录
│   └── bootstrap.php
├── composer.json
├── phar.build.inc
└── phpunit.xml.dist

4.运行服务

如果在 .env 文件中开启了调试 SWOFT_DEBUG=1 将会在控制台中显示更多详细的信息。

1.http server

# 启动 HTTP 服务
$ php ./bin/swoft http:start

# 以守护进程模式启动
$ php ./bin/swoft http:start -d

# 重启 HTTP 服务
$ php ./bin/swoft http:restart

# 重新加载 HTTP 服务
$ php ./bin/swoft http:reload

# 停止 HTTP 服务
$ php ./bin/swoft http:stop

# swoftcli
swoftcli -h
swoftcli run -c ws:start
swoftcli run -c http:start

2.websocket server

# 启动 WS 服务
$ php ./bin/swoft ws:start

# 以守护进程模式启动
$ php ./bin/swoft ws:start -d

# 重启 WS 服务
$ php ./bin/swoft ws:restart

# 重新加载 WS 服务
$ php ./bin/swoft ws:reload

# 关闭 WS 服务
$ php ./bin/swoft ws:stop

3.rpc server

# 启动 RPC 服务
$ php ./bin/swoft rpc:start

# 以守护进程模式启动
$ php ./bin/swoft rpc:start -d

# 重启 RPC 服务
$ php ./bin/swoft rpc:restart

# 重新加载 RPC 服务
$ php ./bin/swoft rpc:reload

# 关闭 RPC 服务
$ php ./bin/swoft rpc:stop

5.注解

use Swoft\Http\Message\Request;
use Swoft\Http\Server\Annotation\Mapping\Controller;
use Swoft\Http\Server\Annotation\Mapping\RequestMapping;

/**
 * Class Home
 *
 * @Controller(prefix="home")
 */
class Home
{
    /**
     * 该方法路由地址为 /home/index
     *
     * @RequestMapping(route="/index", method="post")
     *
     * @param Request $request
     */
    public function index(Request $request)
    {
        // TODO:
    }
}

6.IoC - DI

IoC: Inversion of Control
DI: Dependency Injection

Bean容器

# 定义
@Bean
命名空间:\Swoft\Bean\Annotation\Bean

name 定义 Bean 别名,缺省默认类名
scope 注入 Bean 类型,默认单例,Scope::SINGLETON/Scope::PROTOTYPE(每次创建)
ref 指定引用 Bean ,用于定义在接口上面,指定使用哪个接口实现。
# 注入
@Inject
命名空间:\Swoft\Bean\Annotation\Inject

name 定义属性注入的bean名称,缺省属性自动类型名称
# 操作
App::getBean("name");
ApplicationContext::getBean('name');
BeanFactory::getBean('name');
BeanFactory::hasBean("name");
查看原文

赞 2 收藏 1 评论 0

韩天峰 赞了文章 · 2020-12-07

另一个Swoole调试器 - Yasd

前段时间说过如何使用 Sdebug(Xdebug)来调试 Swoole,现在 PHP8 和 Xdebug3 都发布了,也有小伙伴在 Swoole 的 ISSUE 中询问 Xdebug 的支持,不过如果还继续兼容 Xdebug 的话肯定不是一件容易的事情。

于是 Swoole 开发组成员 @codinghuang 开发了一个新的调试扩展,名为 Yasd ,另一个 Swoole 调试器。

先简单概述一下这个调试器能干什么:

  1. 调试协程
  2. 断点调试
  3. 断点缓存
  4. 查看调用栈
  5. 单步调试
  6. .....

更多功能敬请期待~包括 PHP8 的支持和集成 IDE 的支持。

下面就来看看如何使用这个调试器:

安装扩展

需要先安装 Yasd 扩展

phpize --clean && \
phpize && \
./configure && \
make clean && \
make && \
make install

设置 php.ini 文件:

zend_extension=yasd.so

查看扩展信息:

php --ri yasd

开始调试

安装成功之后,在需要调试时,增加-e参数,如

php -e test.php

你就会看到这样的输出

$ php -e test.php
[Welcome to yasd, the Swoole debugger]
[You can set breakpoint now]
>

可用命令

接着就可以来使用一些命令进行调试了,所有命令都支持优先级模糊匹配,如lilislist都等于l,表示查看源码。

查看源码 list

l

设置断点

b 文件的绝对路径 需要断点的行号

默认会将断点信息保存在缓存文件 .breakpoints_file.log 中;

你也可以通过修改 php.ini 指定此文件名,如:

yasd.breakpoints_file=yasd.log

如果缓存文件存在,当启动调试时,文件中的断点信息会被自动加载;

删除断点 delete

d 文件的绝对路径 断点所在的行号

如果设置或者删除断点时,不指定文件绝对路径,默认是当前停留的文件。

运行 run

r

下一步 step over

n

遇到函数的时候,不会进入函数内部

下一步 step into

s

遇到函数的时候,会进入函数内部

跳出当前函数 finish

f

查看调用栈

bt

查看所有断点信息 info

i

继续运行 continue

c

退出 quit

q

打印变量 print

p

变量名字不需要带$,例如:

p a
p this
p this->prop

查看当前所在的协程 level

le

一些小细节

  • 打印断点格式filename:lineno

如果你是在 IDE 中调试,可以直接点击跳转到对应的文件

  • 自动缓存断点信息

默认会将断点信息保存在缓存文件 .breakpoints_file.log 中,如果缓存文件存在,当启动调试时,文件中的断点信息会被自动加载。

  • 设置断点和删除断点

如果不指定文件绝对路径,默认是当前停留的文件。

  • 自动重复命令

如果不输入命令直接回车,默认是上一条命令

  • 更多细节等你来发现

Swoole官方公众号

查看原文

赞 12 收藏 6 评论 2

韩天峰 赞了文章 · 2020-11-11

Swoole 如何使用 Xdebug 进行单步调试

在 PHP-FPM 中使用 Xdebug 的人应该不少,而在 Swoole 中使用 Xdebug 的人还是很少的,原因是 Swoole 扩展明确说明了和 Xdebug 扩展冲突

不过好在我们社区成员给力,提供了一个 Sdebug ,在此我们应该感谢 @mabu233@huanghantao 进行了兼容,让 Xdebug 可用于 Swoole 环境进行断点、调试

之前在 Swoole 文档中补充了 Sdebug 的安装,同样的 Sdebug 的 README 也进行了修改介绍如何安装,不过都是简单说明如何成功加载扩展,没有详细说明配置

先说一下如何安装 Sdebug

为了避免 Swoole 的检测 Xdebug 警告,所以扩展注册的名称是 Sdebug
git clone git@github.com:swoole/sdebug.git -b sdebug_2_9 --depth=1
cd sdebug
phpize
./configure
make clean
make
make install

步骤很简单,就是 clone 源码,进入目录然后编译

如果你的 PHP 是通用安装,没有修改默认位置等等,也可以直接运行目录下的脚本:

./rebuild.sh

如果你的 phpize 不是默认路径的话,请使用绝对路径;同样的 php-config 需要使用--with-php-config=加上你的绝对路径
编译成功后需要在 php.ini 加载扩展

zend_extension=xdebug.so
编译完成后生成的 so 文件名还是 xdebug

查看是否加载成功

php --ri sdebug

别走,还没完,还需要一些其他的配置,不然你去断点会发现不起作用
我们还需要在 php.ini 中加入这几个配置项

xdebug.remote_enable=1
xdebug.remote_autostart=1
xdebug.remote_host=localhost
xdebug.remote_port=8000
xdebug.idekey="xdebug"

一个配置难倒英雄汉,很多人在使用 Sdebug 的时候就会遇到需要这样问题,不起作用,就吐槽不好用,实际上是你的姿势不对,配置项没加或者加错了

需要配合 PhpStorm 的话,还需要设置一下 PhpStorm 的配置

Preferences | Languages & Frameworks | PHP | Debug

1 是为了我们不加断点的时候,自动给第一行断点

2 是修改配置的remote_port端口

Preferences | Languages & Frameworks | PHP | Servers
添加一个服务

紧接着在右上角这里添加一个调试,选择 PHP Remote Debug

server 选择我们刚才创建的 server,IDE key 就填我们 php.ini 中配置的xdebug

然后我们来试验一波,看看好不好使

先来一个简单的 TCP Server

//创建Server对象,监听 127.0.0.1:9501 端口
$server = new SwooleServer('127.0.0.1', 9501);
//监听连接进入事件
$server->on('Connect', function ($server, $fd) {
 echo "Client: Connect.n";});
//监听数据接收事件
$server->on('Receive', function ($server, $fd, $from_id, $data) {
 var_dump($data);
 $server->send($fd, "Server: " . $data);
});
//监听连接关闭事件
$server->on('Close', function ($server, $fd) {
 echo "Client: Close.n";});
//启动服务器
$server->start();

点击右上角的绿色虫子进入 Debug 状态,启动我们的服务,会发现自动断在了第 4 行创建 Server 对象的地方

然后下一步下一步...

start 之后我们使用 telnet 进行连接,发送一个消息,断点就进入到 Connect 这里,然后我们再下一步,终端才会输出Connect

紧接着我们会到 var_dump 的地方,就可以看到 $data 的值是11111rn

然后来一个 HTTP Server

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

这里需要在访问时加上一个XDEBUG_SESSION_START参数或者在 Cookie 中添加也可以

浏览器访问http://127.0.0.1:9501/?XDEBUG_SESSION_START=xdebug

也是可以断点调试的

框架的使用也是同理的,至于其他的什么和 docker 一起使用等等自己研究吧...

这里放一张调试 Hyperf 的截图

还有一个 Tips,把右上角的电话图标选择为上图那样,会在命令行启动服务时自动调起 Debug 服务

原文发布于Swoole问答,转载请注明出处。

Swoole官方公众号

查看原文

赞 25 收藏 12 评论 2

韩天峰 发布了文章 · 2020-09-29

Swoole 4.5.5 将对 Server 数据收发时间的优化

Commit: https://github.com/swoole/swoole-src/pull/3708/files

4.5.5 正在开发中,尚未发布版本

在之前的版本中,底层提供了connect_timelast_time两项时间信息,单位为秒,表示:

  • 连接到服务器的时间
  • 最后一次接收数据的时间

在非常复杂实际的项目中,这两项信息是远远不能满足需求的,在最新的版本中我们进行了优化。

时间精度调整

底层的时间全部改为使用 double 类型,精确到了微秒,包括:建立连接与数据接收、投递、数据。

增加发送和投递时间

Server::getClientInfo() 方法的返回值中增加了3个新的时间字段,精度为微秒:

  • last_recv_time:最近一次接收数据的时间
  • last_dispatch_time:最近一次投递数据的时间,当触发onReceive回调时,读取此信息可以得到,当前的$datamaster进程是什么时间dispatch的,通过判断投递时间和当前时间的差值,可以得到任务在管道中等待的耗时
  • last_send_time:最近一次发送到内核Socket缓存区的时间,通过此时间可以判断出客户端是否可以正常接收数据包,是否存在接收延时

通过更精确的时间数据,可以实现更细粒度的通信管理。

查看原文

赞 6 收藏 1 评论 0

韩天峰 回答了问题 · 2020-09-03

文件协程fseek ftell 文件大于2G bug

底层已修复此问题,与 lseeklseek64无关的。

https://github.com/swoole/swo...

关注 4 回答 3

韩天峰 赞了文章 · 2020-08-24

PHP 中文工具包 ChineseUtil v2.0 发布,引入 FFI 提升性能节省内存

ChineseUtil 是 PHP 中文工具包,支持汉字转拼音、拼音分词、简繁互转、数字转换、金额数字转换。

由于中文的博大精深,字有多音字,简体字和繁体字也有多种对应。并且本类库返回的所有结果,均为包含所有组合的数组。

本类库字典数据总共收录 73925 个汉字,包括:3955 个简体字,1761 个繁体字,68209 个其它汉字。

Github:https://github.com/Yurunsoft/...

更新日志

v2.0.0 (2020-08-17)

  • 支持 FFI、Swoole FFI
  • 重构拼音分词算法
  • 拼音分词支持可选获取数组或字符串数组的结果集

本次更新,重磅推出了 FFI 支持。FFI 动态库 C++ 代码:https://github.com/Yurunsoft/...

项目起源

2020 年 7 月份开始想研究 C++,那么研究点什么好呢。想着反哺一下 PHP 生态,那么就为我之前开发的 ChineseUtil 提升一下性能吧。

于是有了一个多月的折腾研究,起初是折腾 C++ 项目的配置。

然后是开发了 FFI C 函数,效果不是很理想。性能甚至都不如 PHP 算法,原因是 FFI 转换字符串数组,甚至是二维数组,效率实在是太低并且麻烦,有内存泄漏风险。

经过一番研究,尝试了一下目前的方案,也就是调用 zend_register_functions() 函数来动态创建 PHP 函数。

类似于 PHP 扩展的开发,但是是通过 FFI 来创建的。

那么问题来了,为啥不直接做成 PHP 扩展?

我的本意呢,是让 ChineseUtil 这个库可以开箱即用,不需要编译、启用扩展啥的,麻烦。

PHP dl() 函数也可以动态加载扩展,但是很多环境中是被禁用的。

那么 FFI 来动态创建 PHP 函数,就成了最佳方案(也许)。

模式

性能模式 (Memory)

使用 SQLite 作为数据载体,一次性加载所有数据到变量,内存占用高(80+ MB),性能最佳。

适合用于运行 Cli 任务。

需要 PDO 和 PDO_SQLITE 扩展支持。

通用模式 (SQLite)

使用 SQLite 作为数据载体,每次查询都通过 SQL 查询,内存占用低(100-200 KB),性能中等。

适合用于大部分场景。

需要 PDO 和 PDO_SQLITE 扩展支持。

兼容模式 (JSON)

使用精简过的 JSON 数据作为数据载体,一次性加载所有数据到变量,内存占用中(30+ MB),性能差。

内存占用量以实际为准,根据版本、扩展等环境的不同,占用的内存容量不一样,上述值为我电脑上的情况,仅供参考。

适合无法使用 PDO 的场景。

由于精简了数据,一些拼音结果需要经过代码计算处理才可以得出,所以性能较差。

FFI 模式 (FFI)

需要 PHP >= 7.4 并且启用 FFI 扩展,代码全部由 C++ 开发,性能和内存占用都比 PHP 实现的要好。

FFI 动态库 C++ 代码:https://github.com/Yurunsoft/...

Swoole FFI 模式 (SwooleFFI)

需要 PHP >= 7.4 并且启用 FFI、Swoole 扩展,代码全部由 C++ 开发,性能和内存占用都比 PHP 实现的要好。

不会阻塞 PHP 代码所在线程。


默认情况下,优先使用通用模式,如果环境不支持 PDO 将采用兼容模式。

你可以在未执行任何初始化或者转换处理之前,设置使用何种模式运行。

// 设为性能模式
Chinese::setMode('Memory');
// 设为通用模式
Chinese::setMode('SQLite');
// 设为兼容模式
Chinese::setMode('JSON');
// 设为 FFI 模式
Chinese::setMode('FFI');
// 设为Swoole FFI 模式
Chinese::setMode('SwooleFFI');

无论何种模式,拼音分词所需数据总是从 JSON 数据中加载。

使用说明

Composer 直接安装

composer require yurunsoft/chinese-util

Composer 项目配置引入

"require": {
    "yurunsoft/chinese-util" : "~1.1"
}

功能

汉字转拼音

use \Yurun\Util\Chinese;
use \Yurun\Util\Chinese\Pinyin;
$string = '恭喜發財!123';
echo $string, PHP_EOL;

echo '全拼:', PHP_EOL;
var_dump(Chinese::toPinyin($string, Pinyin::CONVERT_MODE_PINYIN));

echo '首字母:', PHP_EOL;
var_dump(Chinese::toPinyin($string, Pinyin::CONVERT_MODE_PINYIN_FIRST));

echo '读音:', PHP_EOL;
var_dump(Chinese::toPinyin($string, Pinyin::CONVERT_MODE_PINYIN_SOUND));

echo '读音数字:', PHP_EOL;
var_dump(Chinese::toPinyin($string, Pinyin::CONVERT_MODE_PINYIN_SOUND_NUMBER));

echo '自选返回格式 + 以文本格式返回 + 自定义分隔符:', PHP_EOL;
var_dump(Chinese::toPinyin($string, Pinyin::CONVERT_MODE_PINYIN | Pinyin::CONVERT_MODE_PINYIN_SOUND_NUMBER, ' '));

echo '所有结果:', PHP_EOL;
var_dump(Chinese::toPinyin($string));

echo '不分割无拼音字符:', PHP_EOL;
var_dump(Chinese::toPinyin($string, Pinyin::CONVERT_MODE_PINYIN, ' ', false));

// 结果太长,请自行运行代码查看

拼音分词

结果是字符串:

use \Yurun\Util\Chinese;
$string2 = 'xianggang';
echo '"', $string2, '"的分词结果:', PHP_EOL;
var_dump(Chinese::splitPinyin($string2));

输出结果:

"xianggang"的分词结果:
array(2) {
  [0]=>
  string(11) "xiang gang"
  [1]=>
  string(12) "xi ang gang"
}

结果是数组:

use \Yurun\Util\Chinese;
$string2 = 'xianggang';
echo '"', $string2, '"的分词结果:', PHP_EOL;
var_dump(Chinese::splitPinyinArray($string2));

输出结果:

"xianggang"的分词结果:
array(2) {
  [0]=>
  array(2) {
    [0]=>
    string(5) "xiang"
    [1]=>
    string(4) "gang"
  }
  [1]=>
  array(3) {
    [0]=>
    string(2) "xi"
    [1]=>
    string(3) "ang"
    [2]=>
    string(4) "gang"
  }
}

简繁互转

use \Yurun\Util\Chinese;
$string3 = '中华人民共和国!恭喜發財!';
echo '"', $string3, '"的简体转换:', PHP_EOL;
var_dump(Chinese::toSimplified($string3));
echo '"', $string3, '"的繁体转换:', PHP_EOL;
var_dump(Chinese::toTraditional($string3));

输出结果:

"中华人民共和国!恭喜發財!"的简体转换:
array(1) {
  [0]=>
  string(39) "中华人民共和国!恭喜发财!"
}
"中华人民共和国!恭喜發財!"的繁体转换:
array(1) {
  [0]=>
  string(39) "中華人民共和國!恭喜發財!"
}

数字转换

use Yurun\Util\Chinese\Number;
function test($number)
{
    $chinese = Number::toChinese($number, [
        'tenMin'    =>  true, // “一十二” => “十二”
    ]);
    $afterNumber = Number::toNumber($chinese);
    echo $number, '=>', $chinese, '=>', $afterNumber, '=>', 0 === bccomp($number, $afterNumber, 20) ? 'true' : 'false', PHP_EOL;
}

test(1.234);
test(-1234567890.666);
test(pi());

输出结果:

1.234=>一点二三四=>1.234=>true
-1234567890.666=>负十二亿三千四百五十六万七千八百九十点六六六=>-1234567890.666=>true
3.1415926535898=>三点一四一五九二六五三五八九八=>3.1415926535898=>true

金额数字转换

use Yurun\Util\Chinese\Money;
function test($number)
{
    $chinese = Money::toChinese($number, [
        'tenMin'    =>  true, // “一十二” => “十二”
    ]);
    $afterMoney = Money::toNumber($chinese);
    echo $number, '=>', $chinese, '=>', $afterMoney, '=>', 0 === bccomp($number, $afterMoney) ? 'true' : 'false', PHP_EOL;
}

test(1.234);
test(-1234567890.666);

输出结果:

输出结果:
1.234=>壹圆贰角叁分肆厘=>1.234=>true
-1234567890.666=>负壹拾贰亿叁仟肆佰伍拾陆万柒仟捌佰玖拾圆陆角陆分陆厘=>-1234567890.666=>true
查看原文

赞 13 收藏 7 评论 2

韩天峰 发布了文章 · 2020-08-11

Swoole 协程屏障(CoroutineBarrier)的使用

在最新版本的 Swoole Library 中底层提供了一个更便捷的协程并发管理工具:Coroutine\Barrier 协程屏障,或者叫协程栅栏。基于 PHP 引用计数和 Coroutine API 实现。相比于Coroutine\WaitGroupCoroutine\Barrier使用更简单一些,只需通过参数传递或者闭包的use语法,引入子协程函数上即可。

使用实例

use Swoole\Coroutine\Barrier;
use Swoole\Coroutine\System;
use function Swoole\Coroutine\run;
use Swoole\Coroutine;

run(function () {
    $barrier = Barrier::make();

    $count = 0;
    $N = 4;

    foreach (range(1, $N) as $i) {
        Coroutine::create(function () use ($barrier, &$count) {
            System::sleep(0.5);
            $count++;
        });
    }

    Barrier::wait($barrier);
    
    assert($count == $N);
});

执行流程

  • 先使用Barrier::make()创建了一个新的协程屏障
  • 在子协程用使用use语法传递屏障,增加引用计数
  • 在需要等待的位置加入Barrier::wait($barrier),这时会自动挂起当前协程,等待引用该协程屏障的子协程退出
  • 子协程退出时会减少$barrier对象的引用计数,直到为0
  • 当所有子协程完成了任务处理并退出时,$barrier对象引用计数为0,在$barrier对象析构函数中底层会自动恢复挂起的协程,从Barrier::wait($barrier)函数中返回

Coroutine\Barrier 是一个比 WaitGroupChannel 更易用的并发控制器,大幅提升了 PHP 并发编程的用户体验。

查看原文

赞 28 收藏 3 评论 6

韩天峰 赞了文章 · 2020-07-05

【SWOOLE系列】浅淡SWOOLE协程(二) 一键协程化

前言

是的,我又来了,我带着我的文章表情包回来。

再这感谢swoole大佬们的点赞和转载,让我短暂的感受到了什么要叫高光时刻。

背景

我相信大部分人一开始用swoole的协程的时候都会再协程里写了一大堆堵塞的函数,导致项目崩溃。(是的!不要告诉我!就我一个人!)
image.png
在大家了解上一篇【菜鸟光系列】浅谈SWOOLE协程篇
可以了解到协程的创建、yieldresume的相关流程和代码。
所以我们可以猜到在协程执行IO堵塞的相关的代码段是需要主动去yield并且在reactor监听,那么使用原生的php的函数(例curl、文件操作、sleep....)是不可能会主动触发yield()

<?php

$time = time();
go(function () {
    sleep(2);
    echo "done1" . PHP_EOL;
});

go(function () {
    sleep(2);
    echo "done2" . PHP_EOL;
});

go(function () {
    sleep(2);
    echo "done3" . PHP_EOL;
});

echo "over" . PHP_EOL;
echo time() - $time;

输出内容

done1
done2
done3
over
6

以上就是一个反面例子,下面列举下在协程里那些不能调用的函数

那些传说中的php堵塞函数

*   mysql、mysqli、pdo以及其他DB操作函数
*   sleep、usleep
*   curl_*相关函数
*   stream、socket扩展的函数
*   swoole\_client同步模式
*   memcache、redis扩展函数
来自swoole的官方文档https://wiki.swoole.com/wiki/...

那么肯定有人会说,哇 我用个协程还要拿小本本记住下那么多不用调用的,谁家孩子受得了啊。事实上总有很多人再协程上调用各种IO堵塞的函数

所以swoole那些大佬为了让我们这些孩子能够愉快的使用协程,掉秃噜皮了想到了一键协程化。

一键协程化

那我们来瞅瞅官方说的(一键协程化让我想起了以前的一键环境安装的工具。真的是菜鸟福音,发际线的恩人!)

针对上述问题,我们换了实现思路,采用 Hook 原生 PHP 函数的方式实现协程客户端,通过一行代码就可以让原来的同步 IO 的代码变成可以协程调度异步 IO,即一键协程化

https://wiki.swoole.com/#/run...

又到了划重点提问题的时候了,Hook原生PHP的函数,大家可以换个角度思考,如果是我来实现,我可能要挨个把PHP原生堵塞的函数挨个重写成支持协程的方式,但是这样的工作量成本特别的巨大,所以为了验证自己的猜想来分析下一键协程化的源码实现

源码分析

为了不误导大家 这里使用的swoole版本为最新的4.5.2的源码

一键协程化提供给PHPAPI

Swoole\Runtime::enableCoroutine($flags = SWOOLE_HOOK_ALL);

$flags选项 有

  1. SWOOLE_HOOK_ALL(不包括 CURL 不要被ALL给迷惑了)
  2. SWOOLE_HOOK_TCP
  3. SWOOLE_HOOK_UNIX
  4. SWOOLE_HOOK_UDP
  5. SWOOLE_HOOK_UDG
  6. SWOOLE_HOOK_SSL
  7. SWOOLE_HOOK_TLS
  8. SWOOLE_HOOK_SLEEP
  9. SWOOLE_HOOK_FILE
  10. SWOOLE_HOOK_STREAM_FUNCTION
  11. SWOOLE_HOOK_BLOCKING_FUNCTION
  12. SWOOLE_HOOK_PROC
  13. SWOOLE_HOOK_CURL

对应的flags可以通过我们的塑料英语就能知道基本的对应的意思
我们根据上篇文章提到的函数申明规范,可以在swoole_runtime.cc找到对应的代码

static PHP_METHOD(swoole_runtime, enableCoroutine)
{
    zval *zflags = nullptr;
    /*TODO:[v4.6] enable SW_HOOK_CURL by default after curl handler completed */
    zend_long flags = SW_HOOK_ALL;

    ZEND_PARSE_PARAMETERS_START(0, 2)
        Z_PARAM_OPTIONAL
        Z_PARAM_ZVAL(zflags) // or zenable
        Z_PARAM_LONG(flags)
    ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);

    if (zflags)
    {
        if (Z_TYPE_P(zflags) == IS_LONG)
        {
            flags = SW_MAX(0, Z_LVAL_P(zflags));
        }
        else if (ZVAL_IS_BOOL(zflags))
        {
            if (!Z_BVAL_P(zflags))
            {
                flags = 0;
            }
        }
        else
        {
            const char *space, *class_name = get_active_class_name(&space);
            zend_type_error("%s%s%s() expects parameter %d to be %s, %s given", class_name, space, get_active_function_name(), 1, "bool or long", zend_zval_type_name(zflags));
        }
    }

    RETURN_BOOL(PHPCoroutine::enable_hook(flags));
}

根据PHPCoroutine::enable_hook继续往下追
发现了我们的答案

bool PHPCoroutine::enable_hook(int flags)
{
    if (!hook_init)
    {
        HashTable *xport_hash = php_stream_xport_get_hash();
        // php_stream
        ori_factory.tcp = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("tcp"));
        ori_factory.udp = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("udp"));
        ori_factory._unix = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("unix"));
        ori_factory.udg = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("udg"));
        ori_factory.ssl = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("ssl"));
        ori_factory.tls = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("tls"));

        // file
        memcpy((void*) &ori_php_plain_files_wrapper, &php_plain_files_wrapper, sizeof(php_plain_files_wrapper));

        function_table = (zend_array*) emalloc(sizeof(zend_array));
        zend_hash_init(function_table, 8, NULL, NULL, 0);

        hook_init = true;
    }
    // php_stream
    if (flags & SW_HOOK_TCP)
    {
        if (!(hook_flags & SW_HOOK_TCP))
        {
            if (php_stream_xport_register("tcp", socket_create) != SUCCESS)
            {
                flags ^= SW_HOOK_TCP;
            }
        }
    }
    else
    {
        if (hook_flags & SW_HOOK_TCP)
        {
            php_stream_xport_register("tcp", ori_factory.tcp);
        }
    }
    if (flags & SW_HOOK_UDP)
    {
        if (!(hook_flags & SW_HOOK_UDP))
        {
            if (php_stream_xport_register("udp", socket_create) != SUCCESS)
            {
                flags ^= SW_HOOK_UDP;
            }
        }
    }
    else
    {
        if (hook_flags & SW_HOOK_UDP)
        {
            php_stream_xport_register("udp", ori_factory.udp);
        }
    }
    if (flags & SW_HOOK_UNIX)
    {
        if (!(hook_flags & SW_HOOK_UNIX))
        {
            if (php_stream_xport_register("unix", socket_create) != SUCCESS)
            {
                flags ^= SW_HOOK_UNIX;
            }
        }
    }
    else
    {
        if (hook_flags & SW_HOOK_UNIX)
        {
            php_stream_xport_register("unix", ori_factory._unix);
        }
    }
    if (flags & SW_HOOK_UDG)
    {
        if (!(hook_flags & SW_HOOK_UDG))
        {
            if (php_stream_xport_register("udg", socket_create) != SUCCESS)
            {
                flags ^= SW_HOOK_UDG;
            }
        }
    }
    else
    {
        if (hook_flags & SW_HOOK_UDG)
        {
            php_stream_xport_register("udg", ori_factory.udg);
        }
    }
    if (flags & SW_HOOK_SSL)
    {
        if (!(hook_flags & SW_HOOK_SSL))
        {
            if (php_stream_xport_register("ssl", socket_create) != SUCCESS)
            {
                flags ^= SW_HOOK_SSL;
            }
        }
    }
    else
    {
        if (hook_flags & SW_HOOK_SSL)
        {
            if (ori_factory.ssl != nullptr) {
                php_stream_xport_register("ssl", ori_factory.ssl);
            } else {
                php_stream_xport_unregister("ssl");
            }
        }
    }
    if (flags & SW_HOOK_TLS)
    {
        if (!(hook_flags & SW_HOOK_TLS))
        {
            if (php_stream_xport_register("tls", socket_create) != SUCCESS)
            {
                flags ^= SW_HOOK_TLS;
            }
        }
    }
    else
    {
        if (hook_flags & SW_HOOK_TLS)
        {
            if (ori_factory.tls != nullptr)
            {
                php_stream_xport_register("tls", ori_factory.tls);
            }
            else
            {
                php_stream_xport_unregister("tls");
            }
        }
    }
    if (flags & SW_HOOK_STREAM_FUNCTION)
    {
        if (!(hook_flags & SW_HOOK_STREAM_FUNCTION))
        {
            SW_HOOK_FUNC(stream_select);
            SW_HOOK_FUNC(stream_socket_pair);
        }
    }
    else
    {
        if (hook_flags & SW_HOOK_STREAM_FUNCTION)
        {
            SW_UNHOOK_FUNC(stream_select);
            SW_UNHOOK_FUNC(stream_socket_pair);
        }
    }
    // file
    if (flags & SW_HOOK_FILE)
    {
        if (!(hook_flags & SW_HOOK_FILE))
        {
            memcpy((void*) &php_plain_files_wrapper, &sw_php_plain_files_wrapper, sizeof(php_plain_files_wrapper));
        }
    }
    else
    {
        if (hook_flags & SW_HOOK_FILE)
        {
            memcpy((void*) &php_plain_files_wrapper, &ori_php_plain_files_wrapper, sizeof(php_plain_files_wrapper));
        }
    }
    // sleep
    if (flags & SW_HOOK_SLEEP)
    {
        if (!(hook_flags & SW_HOOK_SLEEP))
        {
            SW_HOOK_FUNC(sleep);
            SW_HOOK_FUNC(usleep);
            SW_HOOK_FUNC(time_nanosleep);
            SW_HOOK_FUNC(time_sleep_until);
        }
    }
    else
    {
        if (hook_flags & SW_HOOK_SLEEP)
        {
            SW_UNHOOK_FUNC(sleep);
            SW_UNHOOK_FUNC(usleep);
            SW_UNHOOK_FUNC(time_nanosleep);
            SW_UNHOOK_FUNC(time_sleep_until);
        }
    }
    // proc_open
    if (flags & SW_HOOK_PROC)
    {
        if (!(hook_flags & SW_HOOK_PROC))
        {
            SW_HOOK_FUNC(proc_open);
            SW_HOOK_FUNC(proc_close);
            SW_HOOK_FUNC(proc_get_status);
            SW_HOOK_FUNC(proc_terminate);
        }
    }
    else
    {
        if (hook_flags & SW_HOOK_PROC)
        {
            SW_UNHOOK_FUNC(proc_open);
            SW_UNHOOK_FUNC(proc_close);
            SW_UNHOOK_FUNC(proc_get_status);
            SW_UNHOOK_FUNC(proc_terminate);
        }
    }
    // blocking function
    if (flags & SW_HOOK_BLOCKING_FUNCTION)
    {
        if (!(hook_flags & SW_HOOK_BLOCKING_FUNCTION))
        {
            hook_func(ZEND_STRL("gethostbyname"), PHP_FN(swoole_coroutine_gethostbyname));
            hook_func(ZEND_STRL("exec"));
            hook_func(ZEND_STRL("shell_exec"));
        }
    }
    else
    {
        if (hook_flags & SW_HOOK_BLOCKING_FUNCTION)
        {
            SW_UNHOOK_FUNC(gethostbyname);
            SW_UNHOOK_FUNC(exec);
            SW_UNHOOK_FUNC(shell_exec);
        }
    }

    if (flags & SW_HOOK_CURL)
    {
        if (!(hook_flags & SW_HOOK_CURL))
        {
            hook_func(ZEND_STRL("curl_init"));
            hook_func(ZEND_STRL("curl_setopt"));
            hook_func(ZEND_STRL("curl_setopt_array"));
            hook_func(ZEND_STRL("curl_exec"));
            hook_func(ZEND_STRL("curl_getinfo"));
            hook_func(ZEND_STRL("curl_errno"));
            hook_func(ZEND_STRL("curl_error"));
            hook_func(ZEND_STRL("curl_reset"));
            hook_func(ZEND_STRL("curl_close"));
            hook_func(ZEND_STRL("curl_multi_getcontent"));
        }
    }
    else
    {
        if (hook_flags & SW_HOOK_CURL)
        {
            SW_UNHOOK_FUNC(curl_init);
            SW_UNHOOK_FUNC(curl_setopt);
            SW_UNHOOK_FUNC(curl_setopt_array);
            SW_UNHOOK_FUNC(curl_exec);
            SW_UNHOOK_FUNC(curl_getinfo);
            SW_UNHOOK_FUNC(curl_errno);
            SW_UNHOOK_FUNC(curl_error);
            SW_UNHOOK_FUNC(curl_reset);
            SW_UNHOOK_FUNC(curl_close);
            SW_UNHOOK_FUNC(curl_multi_getcontent);
        }
    }

    hook_flags = flags;
    return true;
}

不要函数这么长给吓到了,这段函数非常简单明了甚至不用加中文备注,根据传入不同的FLAG进行hook_func对应的模块的函数

我们可以看到关键核心的代码为SW_UNHOOK_FUNC,hook_func
拿我们上一篇聊的sleep的例子来说

  SW_HOOK_FUNC(sleep);
#define SW_HOOK_FUNC(f)       hook_func(ZEND_STRL(#f), PHP_FN(swoole_##f))

那么替换sleep的函数为swoole_sleep

static PHP_FUNCTION(swoole_sleep)
{

   ....
        RETURN_LONG(System::sleep((double ) num) < 0 ? 
   ....
}

呀!这不就我们熟悉的System::sleep了吗
分析到这里与开头的猜想一致,如果方便大家在协程里面放飞的编码和无缝的项目切入swoole,需要巨大的工程对php原生的函数的替换,真的是非常不容易!

写给最后

立个flag今年写完swoole系列,向swoole社区和贡献者比个心心

最后还是那句话

文章纯属自己根据代码和资料理解,如果有错误麻烦提出来,倍感万分,如果因为一些错误的观点被误导我只能说

查看原文

赞 8 收藏 4 评论 2

韩天峰 赞了文章 · 2020-07-01

【SWOOLE系列】浅谈SWOOLE协程篇

阅读本文需要以下知识点

  • 了解进程、线程相关基础
  • 熟练php的hello world输出
  • 会swoole单词拼写

协程的介绍

协程是什么?

A coroutine is a function that can suspend its execution (yield) until the given given YieldInstruction finishes.

简单的说协程是寄宿在线程下程序员实现的一种跟更轻量的并发的协作轻量线程

随着程序员人群的增大,大佬也不断的爆发式增长,当然就开始有人觉得线程不好用了,那怎么办呢?当然是基于线程的理念上再去实现一套更加轻量、更好骗star的一套轻量线程(事实上协程不能完全被认为线程,因为一个线程可以有多个协程)

协程和线程的区别

本质

线程 内核态
协程 用户态

调度方式

线程的调度方式为系统调度,常用的调度策略有分时调度抢占调度。说白就是线程的调度完全不受自己控制

协程的调度方式为协作式调度 不受内核控制由自由策略调度切换

等等

协作式调度?

上述说了协程是用户态的,所以所谓的协作式调度直接可以理解为是程序员写的调度方式,也就是我想怎么调度就怎么调度,而不用通过系统内核被调度。

深。。。。浅入理解swoole的协程

既然打算浅入理解的swoole的协程,我们必须要知道swoole的协程模型。
swoole的协程是基于单线程。可以理解为协程的切换是串行的,再同一个时间点只运行一个协程.

说到这里,肯定就有人问了。go呢,go的协程的是基于多线程。当然各有各的好处,具体可以自行使用搜索引擎了解

我们可以直接copy & paste 下面代码,再本地的环境进行的 demo run

<?php

$func = function ($index, $isCorotunine = true) {
    $isCorotunine && \Swoole\Coroutine::sleep(2);
    echo "index:" . $index . PHP_EOL;
    echo "is corotunine:" . intval($isCorotunine) . PHP_EOL;
};

$func(1, false);
go($func, 2, true);
go($func, 3, true);
go($func, 4, true);
go($func, 5, true);
go($func, 6, true);
$func(7, false);    

会得到以下结果

index:1
is corotunine:0
index:7
is corotunine:0
index:2
is corotunine:1
index:6
is corotunine:1
index:5
is corotunine:1
index:4
is corotunine:1
index:3
is corotunine:1

肯定有人会想,哇塞,尽然2秒都执行完了,一点都不堵塞啊!!

好了,事实上关于2秒执行完的事情可以回过头再去看下协程的概念。
我们可以关注的是执行顺序,1和7是非协程的执行能立马返回结果符合预期。
关于协程的调度顺序
为什么是26543不是65432或者23456有序的返回呢

为了找到我们的答案,我们只能通过源码进行知晓一些东西

分析源码

image

图来自https://segmentfault.com/a/11...

如果没有较强的基础还有啃烂的apue的前提下(当然我也没有!T_T)
我们需要关心的是以下两个
yield 切换协程
resume 恢复协程

协程的创建

<?php
go (function(){
echo "swoole 太棒了";
});

调用的swoole封装给PHPgo函数为创建一个协程

我们根据拓展源码中的

大部分的PHP扩展函数以及扩展方法的参数声明放在swoole_*.ccswoole.cc里面。
PHP_FALIAS(go, swoole_coroutine_create, arginfo_swoole_coroutine_create)

可以知道 go->swoole_coroutine_create

在swoole_coroutine.cc文件里找到

PHP_FUNCTION(swoole_coroutine_create)
{
    ....
    // 划重点 要考
    long cid = PHPCoroutine::create(&fci_cache, fci.param_count, fci.params);
    ....
}

long PHPCoroutine::create(zend_fcall_info_cache *fci_cache, uint32_t argc, zval *argv)
{
    if (sw_unlikely(Coroutine::count() >= config.max_num))
    {
        php_swoole_fatal_error(E_WARNING, "exceed max number of coroutine %zu", (uintmax_t) Coroutine::count());
        return SW_CORO_ERR_LIMIT;
    }

    if (sw_unlikely(!active))
    {
        // 划重点 要考
        activate();
    }

    // 保存回调函数
    php_coro_args php_coro_args;
    //函数信息
    php_coro_args.fci_cache = fci_cache;
    //参数
    php_coro_args.argv = argv;
    php_coro_args.argc = argc;
    // 划重点 要考
    save_task(get_task());

    // 划重点 要考
    return Coroutine::create(main_func, (void*) &php_coro_args);
}
// 保存栈 
void PHPCoroutine::save_task(php_coro_task *task)
{
    save_vm_stack(task);
    save_og(task);
}
// 初始化reactor的事件
inline void PHPCoroutine::activate()
{
    if (sw_unlikely(active))
    {
        return;
    }

    /* init reactor and register event wait */
    php_swoole_check_reactor();

    /* replace interrupt function */
    orig_interrupt_function = zend_interrupt_function;
    zend_interrupt_function = coro_interrupt_function;
    
    /* replace the error function to save execute_data */
    orig_error_function = zend_error_cb;
    zend_error_cb = error;

    if (config.hook_flags)
    {
        enable_hook(config.hook_flags);
    }

    if (SWOOLE_G(enable_preemptive_scheduler) || config.enable_preemptive_scheduler)
    {
        /* create a thread to interrupt the coroutine that takes up too much time */
        interrupt_thread_start();
    }

    if (!coro_global_active)
    {
        if (zend_hash_str_find_ptr(&module_registry, ZEND_STRL("xdebug")))
        {
            php_swoole_fatal_error(E_WARNING, "Using Xdebug in coroutines is extremely dangerous, please notice that it may lead to coredump!");
        }

        /* replace functions that can not work correctly in coroutine */
        inject_function();

        coro_global_active = true;
    }
    /**
     * deactivate when reactor free.
     */
    swReactor_add_destroy_callback(SwooleG.main_reactor, deactivate, nullptr);
    active = true;
}

根据Coroutine::create继续往下跳转

    static inline long create(coroutine_func_t fn, void* args = nullptr)
    {
        return (new Coroutine(fn, args))->run();
    }

在创建完协程后立马执行
我们观察下构造方法

    Coroutine(coroutine_func_t fn, void *private_data) :
            ctx(stack_size, fn, private_data)
    {
        cid = ++last_cid;
        coroutines[cid] = this;
        if (sw_unlikely(count() > peak_num))
        {
            peak_num = count();
        }
    }

上述代码我可以发现还有一个Context的类 这个构造函数我们可以猜到做了3件事情

  1. 分配对应协程id (每个协程都有自己的id)
  2. 保存上下文
  3. 更新当前的协程的数量
swoole使用的协程库为 boost.context 可自行搜索
主要暴露的函数接口为jump_fcontextmake_fcontext
具体的作用保存当前执行状态的上下文暂停当前的执行状态够跳转到其他位置继续执行

创建完协程立马执行

inline long run()
    {
        long cid = this->cid;
        origin = current;
        current = this;
        // 依赖boost.context 切栈
        ctx.swap_in();
        // 判断是否执行结束
        check_end();
        return cid;
    }

判断是否结束

inline void check_end()
    {
        if (ctx.is_end())
        {
            close();
        }
        else if (sw_unlikely(on_bailout))
        {
            SW_ASSERT(current == nullptr);
            on_bailout();
            // expect that never here
            exit(1);
        }
    }

根据ctx.is_end()的函数找到

    inline bool is_end()
    {
        return end_;
    }
bool Context::swap_in()
{
    jump_fcontext(&swap_ctx_, ctx_, (intptr_t) this, true);
    return true;
}

我们可以总结下swoole在创建协程的时候主要做了哪些事情

  1. 检测环境
  2. 解析参数
  3. 保存上下文
  4. 切换C栈
  5. 执行协程

协程的yield

上述的demo我们使用\Swoole\Coroutine::sleep(2)
根据上述说函数申明的我们在swoole_corotunine_system.cc发现对应函数为swoole_coroutine_systemsleep

PHP_METHOD(swoole_coroutine_system, sleep)
{
    double seconds;

    ZEND_PARSE_PARAMETERS_START(1, 1)
        Z_PARAM_DOUBLE(seconds)
    ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);

    if (UNEXPECTED(seconds < SW_TIMER_MIN_SEC))
    {
        php_swoole_fatal_error(E_WARNING, "Timer must be greater than or equal to " ZEND_TOSTR(SW_TIMER_MIN_SEC));
        RETURN_FALSE;
    }
    System::sleep(seconds);
    RETURN_TRUE;
}

调用了sleep函数之后对当前的协程做了三件事
1.增加了timer定时器
2.注册回掉函数再延迟之后resume协程
3.通过yield让出调度

int System::sleep(double sec)
{
// 获取当前的协程
    Coroutine* co = Coroutine::get_current_safe();
   //swTimer_add 注册定时器 sleep_timeout回调的函数
   if (swTimer_add(&SwooleG.timer, (long) (sec * 1000), 0, co, sleep_timeout) == NULL)
    {
        return -1;
    }
    // 让出当前cpu
    co->yield();
    return 0;
}

// 回调函数
static void sleep_timeout(swTimer *timer, swTimer_node *tnode)
{
   // 恢复调度
    ((Coroutine *) tnode->data)->resume();
}
swTimer_node* swTimer_add(swTimer *timer, long _msec, int interval, void *data, swTimerCallback callback)
{
    ....
    // 保存当前上下文和对应过期时间
    tnode->data = data;
    tnode->type = SW_TIMER_TYPE_KERNEL;
    tnode->exec_msec = now_msec + _msec;
    tnode->interval = interval ? _msec : 0;
    tnode->removed = 0;
    tnode->callback = callback;
    tnode->round = timer->round;
    tnode->dtor = NULL;

    // _next_msec保存最快过期的事件
    if (timer->_next_msec < 0 || timer->_next_msec > _msec)
    {
        timer->set(timer, _msec);
        timer->_next_msec = _msec;
    }

    tnode->id = timer->_next_id++;
    if (sw_unlikely(tnode->id < 0))
    {
        tnode->id = 1;
        timer->_next_id = 2;
    }

    tnode->heap_node = swHeap_push(timer->heap, tnode->exec_msec, tnode);
    ....
    timer->num++;
    return tnode;
}

协程的切换

我们

void Coroutine::resume()
{
    SW_ASSERT(current != this);
    if (sw_unlikely(on_bailout))
    {
        return;
    }
    state = SW_CORO_RUNNING;
    if (sw_likely(on_resume))
    {
        on_resume(task);
    }
    // 将当前的协程保存为origin -> 理解程previous
    origin = current;
    // 需要执行的协程 变成 current
    current = this;
    // 入栈执行
    ctx.swap_in();
    check_end();
}

到这里时候 关于协程调用顺序的答案已经出来了

在创建协程的时候(new Coroutine(fn, args))->run();sleep触发yield都在不断变更的Corotuninecurrentorigin 再执切换的时候和php代码创建协程的时间发生穿插,而不是我们想象中的队列有序执行
比如当创建协程只有2个的时候

<?php

$func = function ($index, $isCorotunine = true) {
    $isCorotunine && \Swoole\Coroutine::sleep(2);
    echo "index:" . $index . PHP_EOL;
    echo "is corotunine:" . intval($isCorotunine) . PHP_EOL;
};

$func(1, false);

go($func, 2, true);
go($func, 3, true);

返回输出 因为连续创建协程的执行时间小没有被打乱

php swoole_go_demo1.php
index:1
is corotunine:0
index:2
is corotunine:1
index:3
is corotunine:1

当连续创建的时候200个协程的时候
返回就变得打乱的index 符合预计猜想

index:1,index:2,index:4,index:8,index:16,index:32,index:64,index:128,index:129,index:65,index:130,index:131,index:33,index:66,index:132,index:133,index:67,index:134,index:135,index:17,index:34,index:68,index:136,index:137,index:69,index:138,index:139,index:35,index:70,index:140,index:141,index:71,index:142,index:143,index:9,index:18,index:36,index:72,index:144,index:145,index:73,index:146,index:147,index:158,index:157,index:156,index:155,index:154,index:153,index:152,index:151,index:37,index:74,index:148,index:149,index:75,index:150,index:19,index:38,index:76,index:77,index:39,index:78,index:79,index:5,index:10,index:20,index:40,index:80,index:81,index:41,index:82,index:83,index:21,index:127,index:126,index:125,index:124,index:123,index:122,index:121,index:120,index:119,index:118,index:117,index:116,index:115,index:114,index:113,index:112,index:111,index:110,index:109,index:108,index:107,index:106,index:105,index:104,index:103,index:102,index:101,index:100,index:99,index:98,index:97,index:96,index:95,index:94,index:93,index:92,index:91,index:90,index:89,index:88,index:87,index:42,index:84,index:85,index:43,index:86,index:11,index:22,index:44,index:45,index:23,index:46,index:47,index:3,index:6,index:12,index:24,index:48,index:49,index:25,index:50,index:51,index:13,index:26,index:63,index:62,index:61,index:60,index:59,index:58,index:57,index:56,index:55,index:52,index:53,index:27,index:54,index:7,index:14,index:28,index:29,index:15,index:30,index:31,index:200,index:199,index:192,index:185,index:175,index:168,index:161,index:163,index:172,index:179,index:187,index:194,index:174,index:160,index:173,index:176,index:198,index:195,index:180,index:167,index:169,index:184,index:197,index:193,index:177,index:162,index:171,index:186,index:182,index:164,index:191,index:183,index:166,index:196,index:178,index:170,index:189,index:188,index:165,index:181,index:190,index:159

最后彩蛋

我们使用GO的协程的来实现上述的demo

package main

import (
    "fmt"
    "time"
)

var count int = 0

func main() {
    output(false, 1)

    go output(true, 2)
    go output(true, 3)
    go output(true, 4)
    go output(true, 5)
    go output(true, 6)

    output(false, 7)

    time.Sleep(time.Second)
}

func output(isCorotunine bool, index int) {
    time.Sleep(time.Second)
    count = count + 1
    fmt.Println(count, isCorotunine, index)
}

猜猜返回结果是如何的 可以根据go的协程基于多线程的方式再去研究下
image.png

写给最后,文章纯属自己根据代码和资料理解,如果有错误麻烦提出来,倍感万分,如果因为一些错误的观点被误导我只能说

查看原文

赞 25 收藏 14 评论 3

认证与成就

  • 认证信息 Swoole 开源项目创始人
  • 获得 2166 次点赞
  • 获得 34 枚徽章 获得 2 枚金徽章, 获得 11 枚银徽章, 获得 21 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2014-05-05
个人主页被 27.7k 人浏览