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