swoole 学习入门


swool 分为异步和协程两种风格。

异步风格:实例化服务器对象,设置参数,绑定事件,最后 start() 启动服务开始监听。

Swoole\Server: 所有服务器的基类
Swoole\Server::__construct(string $host = '0.0.0.0', int $port = 0, int $mode = SWOOLE_BASE, int $sockType = SWOOLE_SOCK_TCP)

$mode: 分为 SWOOLE_BASE:基本模式,  SWOOLE_PROCESS 多进程模式
$sockType: 连接类型,主要有 SWOOLE_TCP, SWOOLE_UDP, SWOOLE_TCP6, SWOOLE_UDP6, SWOOLE_UNIX_DGRAM, SWOOLE_UNIX_STREAM


Swoole\Http\Server: HTTP 服务器,继承 Swoole\Server
Swoole\Http\Server::__construct(string $host = '0.0.0.0', int $port = 0, int $mode = SWOOLE_BASE, int $sockType = SWOOLE_SOCK_TCP)


Swoole\WebSocket\Server : WebSocket服务器,继承 Swoole\Http\Server
Swoole\WebSocket\Server::__construct(string $host = '0.0.0.0', int $port = 0, int $mode = SWOOLE_BASE, int $sockType = SWOOLE_SOCK_TCP)

它们构造方法都是一样的,只是有些限定了有些参数可选择性,比如 Swoole\Http\Swoole\Server 的 $sockType 不能使用 SWOOLE_UDP, 另外就是能够绑定的事件有所不同。

 

Server 服务器:

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

$server->on('connect', function ($server, $fd){
    echo "Client:Connect.\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 "Client: Close.\n";
});
$server->start();

HTTP 服务器: 同 Swoole\Server 相比, 不接受 onConnect/onReceive 回调设置

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

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

WebSocket 服务器:

$ws = new Swoole\WebSocket\Server('0.0.0.0', 9502);

$ws->on('Open', function ($ws, $request) {
    $ws->push($request->fd, "hello, welcome\n");
});

$ws->on('Message', function ($ws, $frame) {
    echo "Message: {$frame->data}\n";
    $ws->push($frame->fd, "server: {$frame->data}");
});

$ws->on('Close', function ($ws, $fd) {
    echo "client-{$fd} is closed\n";
});

$ws->start();

总结: 可见这种风格基本上非常好理解, 就像写js 绑定事件的一样。

协程风格

直接手写协程,大概如下:

// 短命名风格,swoole 配置 swoole.use_shortname='On' 时,有效。 先创建协程容器,然后在容器内部创建子协程
Co\run(function(){
    go(function(){  }); //创建子协程
    go(function(){  });//创建子协程
});

// 正常写法
Swoole\Coroutine\run(function(){
    Swoole\Coroutine::create(function(){  });
    Swoole\Coroutine::create(function(){  });
});

创建协程风格服务器:大概就是代码写在协程容器中。

构造方法:

Swoole\Coroutine\Server::__construct(string $host, int $port = 0, bool $ssl = false, bool $reuse_port = false)
$reuse_port 是否可以重复启动监听同一个端口。

Swoole\Coroutine\Http\Server::__construct($host, $port = null, $ssl = null, $reuse_port = null)

创建协程风格 HTTP 服务器

Swoole\Coroutine\run(function () {
    $server = new \Swoole\Coroutine\Http\Server('127.0.0.1', 9502, false);
    $server->handle('/', function ($request, $response) {
        $response->end("<h1>Index</h1>");
    });
    $server->handle('/test', function ($request, $response) {
        $response->end("<h1>Test</h1>");
    });
    $server->handle('/stop', function ($request, $response) use ($server) {
        $response->end("<h1>Stop</h1>");
        $server->shutdown();
    });
    $server->start();
});

创建协程风格 HTTP 服务器,带有 websocket 功能

use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\WebSocket\CloseFrame;
use function Swoole\Coroutine\run;

run(function () {

    $server = new \Swoole\Coroutine\Http\Server('127.0.0.1', 9502, false);

    $server->handle('/', function (Request $request, Response $response) {
        $response->end(
            <<<HTML
                <h1>Swoole WebSocket Server</h1>
                <script>
                    var wsServer = 'ws://127.0.0.1:9502/websocket';
                    var websocket = new WebSocket(wsServer);
                    websocket.onopen = function (evt) {
                        console.log("Connected to WebSocket server.");
                        websocket.send('hello');
                    };

                    websocket.onclose = function (evt) {
                        console.log("Disconnected");
                    };

                    websocket.onmessage = function (evt) {
                        console.log('Retrieved data from server: ' + evt.data);
                    };

                    websocket.onerror = function (evt, e) {
                        console.log('Error occured: ' + evt.data);
                    };
                </script>
            HTML
        );
    });

    $server->handle('/websocket', function (Request $request, Response $response) {

        $response->upgrade(); // 发送握手成功信息
        while (true) {
            $frame = $response->recv($timeout = 10); // 接收 WebSocket 消息, 返回: Swoole\WebSocket\Frame | false | string

            if ($frame === '') {
                $response->close();
                break;

            } else if ($frame === false) { // recv(10) 设置了10秒过期所以10秒没收到消息,走到这里
                echo 'errorCode: ' . swoole_last_error() . "\n";
                $response->close();
                break;

            }else if ($frame->data == 'close' || get_class($frame) === CloseFrame::class) {
                $response->close();
                break;
            }

            $response->push("Hello {$frame->data}!");
            $response->push("How are you, {$frame->data}?");
        }
    });

    $server->start();
});

 

测试效果:

 

创建协程风格多进程的 Server 服务器

<?php

use Swoole\Process;
use Swoole\Coroutine;
use Swoole\Coroutine\Server\Connection;

$pool = new Process\Pool(2); //多进程管理模块, 启动两个进程
$pool->set(['enable_coroutine' => true]); //启用携程, 每个进程的 OnWorkerStart 回调都自动创建一个协程

$pool->on('workerStart', function ($pool, $id) {

   echo "worker pid: ".posix_getpid()." \n";
   //收到15信号关闭服务, 这里得向父进程发送信号才能停止
    Process::signal(SIGTERM, function () use ($server) {
        $server->shutdown();
    });
    
   $server = new \Swoole\Coroutine\Server('127.0.0.1', 9501, false, true);//每个进程都监听9501端口

    //设置连接处理函数. 根据配置, 每当接收到新的连接自动动创建一个协程
    $server->handle(function (Connection $conn) {

        while (true) {

            $data = $conn->recv($timeout = 5); // 超过5秒,没收到消息,就断开连接
            if ($data === '' || $data === false) {

                $errCode = swoole_last_error();
                $errMsg = socket_strerror($errCode);

                echo "errCode: {$errCode}, errMsg: {$errMsg}\n";

                $conn->close();
                break;
            }

            //发送数据
            $conn->send('hello ' . $data);
            Coroutine::sleep(1);
        }
    });

    //开始监听端口
    $server->start();

});
echo "main pid: " . posix_getpid() . "\n";
$pool->start();


 

 

一键协程化:所有业务代码都是同步的,但底层的 IO 却是异步的。

比如:当程序协程中出现 sleep 时,必定是阻塞在哪里等待。这时可以选择用 Swoole\Coroutine\System::sleep() 或 一键协程化。

有两种启用方式:
Swoole\Coroutine::set(['hook_flags'=> SWOOLE_HOOK_ALL]); // 需要在 Server->start() 前或 Co\run() 前调用。
Swoole\Runtime::enableCoroutine($flags = SWOOLE_HOOK_ALL); // 在服务启动后 (运行时) 动态设置 flags,调用方法后当前进程内全局生效。

同时开启多个 flags 需要使用 | 操作,更多启用单个的有:
SWOOLE_HOOK_SLEEP | SWOOLE_HOOK_TCP | SWOOLE_HOOK_UNIX ...

 

测试 sleep

Swoole\Coroutine::set(['hook_flags'=> SWOOLE_HOOK_ALL]); // 会先输出 2,然后 3秒后输出 1,我测试时,这个默认已经开启了。
//Swoole\Coroutine::set(['hook_flags'=> false]);// 等3秒后输出 1 和 2

Swoole\Coroutine\run(function(){

    Swoole\Coroutine::create(function(){
        sleep(3);
        echo 1 . "\n"; // 必须要带换行
    });
    Swoole\Coroutine::create(function(){
        echo 2 . "\n";// 这里必须要带换行,我测试时,若不带换行时,2 和 1 是一起输出的,非常奇怪。
    });

});

 

posted @ 2023-07-16 22:04  心随所遇  阅读(139)  评论(0编辑  收藏  举报