Swoole coroutine system
It's finally time for the coroutine. Are you looking forward to it or excited or both looking forward and excited? In any case, coroutines are now the most popular development method, no one. Even the fibers proposed by Java are actually very similar in concept. The characteristics of coroutines, we have already said in the first article related to the advanced Swoole process. I believe that everyone already has a preliminary concept. Next, let's take a look at how to apply coroutine services in Swoole.
Problems with asynchronous services
For asynchrony, we need to monitor events, and the monitored processes are concurrent, so there is a problem that the sequence cannot be guaranteed.
$serv = new Swoole\Server("0.0.0.0", 9501);
//监听连接进入事件
$serv->on('Connect', function ($serv, $fd) {
Swoole\Coroutine\System::sleep(5);//此处sleep模拟connect比较慢的情况,这种sleep()是不阻塞的
echo "onConnect", PHP_EOL;
});
//监听数据接收事件
$serv->on('Receive', function ($serv, $fd, $reactor_id, $data) {
echo "onReceive", PHP_EOL;
});
//监听连接关闭事件
$serv->on('Close', function ($serv, $fd) {
echo "Client: Close.\n";
});
//启动服务器
$serv->start();
In this example, we simulate the slow connection problem of connect by pausing for 5 seconds in the Connect event, and then use telnet to test, and we will find that the Receive event is outputted first.
[root@localhost source]# php 3.3Swoole协程系统.php
onReceive
onConnect
Logically speaking, our Connect should be executed before Receive. After all, the connection must be established to receive data, but due to the execution of parallel multi-processes, this step may be completed in parallel, and the logic in the callback function The code will be blocked due to various factors. At this time, the callback function in Receive is executed before the Connect callback function, which is also a very likely serious problem.
For coroutines, it does not need to listen to events, and the code is executed sequentially. We said this when we talked about the concept of coroutines. It is a function, and it has no parallel nature. It is concurrent. It works on threads and is executed sequentially in the same thread. Naturally, there will be no such things. question. Friends who don't remember the difference between parallelism and concurrency have to go back to make up lessons. [Swoole Series 3.1] Processes, threads, and coroutines. Have you been asked in the interview? https://mp.weixin.qq.com/s/GM_oGeVYOADcsKf43BN38A .
Coroutine Http Service
使用协程来提供 Http 服务非常简单,甚至比异步方式更简单,因为我们不需要去记住那么多的事件名称了。
Swoole\Coroutine\run (function () {
$server = new Swoole\Coroutine\Http\Server('0.0.0.0', 9501, 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();
});
我们需要先建立一个协程容器,也就是这个 Swoole\Coroutine\run() 方法,这是一种开启协程容器的方式,其它的方式我们后面聊到了再说。这个协程容器是什么意思呢?它就像是一个 C 或者 Java 中的 main() 函数,提供程序的入口。
在协程服务中,我们真的不需要去监听事件了,只需要在这个协程容器的回调函数中实例化一个 Swoole\Coroutine\Http\Server 对象,然后通过它的 handle() 方法获得请求路径的内容,并交给回调函数进行处理即可。这里的回调函数中的参数与异步的 onRequest 监听中的回调参数是一样的,一个请求参数,一个响应参数。
协程 TCP 服务
对于 TCP 服务来说实现协程服务端也非常简单方便,和上面的 Http 服务类似,我们还是通过在协程容器中创建 TCP 服务对象并使用 handle() 方法操作连接数据。
Swoole\Coroutine\run (function () {
$server = new Swoole\Coroutine\Server('0.0.0.0', 9501, false);
$server->handle(function(Swoole\Coroutine\Server\Connection $conn){
$data = $conn->recv();
echo $data, PHP_EOL;
$conn->send("协程 TCP :" . $data);
});
$server->start();
});
注意我们这里实例化的 Server 对象是不带 Http 命名空间的。同时,在它的 handle() 方法中,也不用路径参数了,直接就是一个回调函数。这个回调函数的参数,是一个 Swoole\Coroutine\Server\Connection 对象,它返回的实际上就是建立好的 TCP 连接对象,在这个对象中,有 recv() 和 send() 方法,分别就是接收和发送数据的两个方法。
没有 UDP 的?确实没有,如果要实现 UDP 的协程服务端,需要单独使用 Socket 方式。
Swoole\Coroutine\run(function () {
$socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_DGRAM, 0);
$socket->bind('0.0.0.0', 9501);
while (true) {
$peer = null;
$data = $socket->recvfrom($peer);
echo "[Server] recvfrom[{$peer['address']}:{$peer['port']}] : $data\n";
$socket->sendto($peer['address'], $peer['port'], "Swoole: $data");
}
});
这个 Socket 相关的内容,在后面我专门讲 Socket 对象的时候再说。
协程 WebSocket 服务
WebScoket 的代码比较长,但其实还是基于的是 Http 服务。
Swoole\Coroutine\run(function () {
$server = new Swoole\Coroutine\Http\Server('0.0.0.0', 9501, false);
$server->handle('/websocket', function (Swoole\Http\Request $request, Swoole\Http\Response $ws) {
$ws->upgrade();
while (true) {
$frame = $ws->recv();
if ($frame === '') {
$ws->close();
break;
} else if ($frame === false) {
echo 'errorCode: ' . swoole_last_error() . "\n";
$ws->close();
break;
} else {
if ($frame->data == 'close' || get_class($frame) === Swoole\WebSocket\CloseFrame::class) {
$ws->close();
break;
}
$ws->push("Hello {$frame->data}!");
$ws->push("How are you, {$frame->data}?");
}
}
});
$server->handle('/', function (Swoole\Http\Request $request, Swoole\Http\Response $response) {
$response->end(<<<HTML
<h1>Swoole WebSocket Server</h1>
<script>
var wsServer = 'ws://192.168.56.133:9501/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->start();
});
在这个示例代码中,我们其实主要操作的是 Response 对象,通过它的 upgrade() 方法向客户端发送 WebSocket 握手消息,然后循环监听消息的接收和发送。在循环体内部,通过 recv() 和 push() 方法接收和发送信息。
另外一个路径其实就是一个前端页面,为了方便测试。这个例子也是官网上的例子,当然,你也可以拿我们之前在基础阶段的静态测试页面来进行测试。
总结
通过今天的内容,我们简单地了解到了使用协程和异步方式搭起的服务器有什么不同。另外也顺便就搭起了 Http/TCP/WebSocket 的服务端程序,其中 UDP 是个特殊情况,官方并没有直接的 UDP 服务器,需要我们通过 Socket 的方式自己搭建一个。关于 Socket 的内容,我们在后面的学习中还会再讲到。
测试代码:
https://github.com/zhangyue0503/swoole/blob/main/4.Swoole%E5%8D%8F%E7%A8%8B/source/4.1Swoole%E5%8D%8F%E7%A8%8B%E6%9C%8D%E5%8A%A1.php
参考文档:
https://wiki.swoole.com/#/server/co_init