世界杯黑哨

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("

Hello Swoole. #".rand(1000, 9999)."

");

});

$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("

Index

");

});

$server->handle('/test', function ($request, $response) {

$response->end("

Test

");

});

$server->handle('/stop', function ($request, $response) use ($server) {

$response->end("

Stop

");

$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(

<<

Swoole WebSocket Server

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 服务器

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 是一起输出的,非常奇怪。

});

});