1.文档阅读
Swoole4 文档 - start_tcp_server
Swoole4 文档 - start_udp_server
Swoole系列(1) - 基于Linux[centOS]/Docker/Mac安装
2.整理输出 前言
只要修改了文件,都要重启服务「不管手动还是后期实现自动」
2.1 TCP服务器 服务端 tcp_server.php
<?php
//创建Server对象,监听 127.0.0.1:9501 端口
$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, "Server: {$data}");
});
//监听连接关闭事件
$server->on('Close', function ($server, $fd) {
echo "Client: Close.\n";
});
//启动服务器
$server->start();
CLI下 启动服务:
php tcp_server.php
查看是否启动9501端口,监听服务
netstat -an | grep 9501

CLI输出「所有的输出都是在CLI下」

客户端 「使用 telnet/netcat 工具连接服务器」

排查故障
- 在
Linux 下,使用 netstat -an | grep 端口 ,查看端口是否已经被打开处于 Listening 状态「如:netstat -an | grep 9501 」 - 上一步确认后,再检查防火墙问题
- 注意服务器所使用的 IP 地址,如果是
127.0.0.1 回环地址,则客户端只能使用 127.0.0.1 才能连接上 - 用的阿里云服务或者腾讯服务,需要在安全权限组进行设置开发的端口
参考 TCP 数据包边界问题
2.2 UDP服务器 服务端 udp_server.php
<?php
$server = new Swoole\Server('127.0.0.1', 9502, SWOOLE_PROCESS, SWOOLE_SOCK_UDP);
//监听数据接收事件
$server->on('Packet', function ($server, $data, $clientInfo) {
var_dump($clientInfo);
$server->sendto($clientInfo['address'], $clientInfo['port'], "Server:{$data}");
});
//启动服务器
$server->start();
CLI下 启动服务:
php udp_server.php
查看是否启动9502端口,监听服务
netstat -an | grep 9502

UDP 服务器与 TCP 服务器不同,UDP 没有连接的概念。「即UDP面向无连接」
启动 Server 后,客户端无需 Connect,直接可以向 Server 监听的 9502 端口发送数据包。
对应的事件为 onPacket。
$clientInfo 是客户端的相关信息,是一个数组,有客户端的 IP 和端口等内容- 调用
$server->sendto 方法向客户端发送数据
客户端 UDP 服务器可以使用 netcat -u 来连接测试
netcat -u 127.0.0.1 9502 hello Server: hello
 服务端CLI下输出  2.3 HTTP服务器 服务端 http_server.php
<?php
$http = new Swoole\Http\Server('0.0.0.0', 9501);
$http->on('Request', function ($request, $response) {
$response->header('Content-Type', 'text/html; charset=utf-8');
$response->end('<h1>Hello Swoole. #' . rand(1000, 9999) . '</h1>');
});
$http->start();
HTTP 服务器只需要关注请求响应即可,所以只需要监听一个 onRequest 事件。
当有新的 HTTP 请求进入就会触发此事件。
事件回调函数有 2 个参数,一个是 $request 对象,包含了请求的相关信息,如 GET/POST 请求的数据。
另外一个是 response 对象,对 request 的响应可以通过操作 response 对象来完成。
$response->end() 方法表示输出一段 HTML 内容,并结束此请求。
0.0.0.0 表示监听所有 IP 地址,一台服务器可能同时有多个 IP ,如 127.0.0.1 本地回环 IP、192.168.1.100 局域网 IP、210.127.20.2 外网 IP,这里也可以单独指定监听一个 IP9501 监听的端口,如果被占用程序会抛出致命错误,中断执行。
启动服务
php http_server.php
客户端
- 可以打开浏览器,访问
http://127.0.0.1:9501 查看程序的结果。 - 也可以使用 Apache
ab 工具对服务器进行压力测试
Chrome 请求两次问题
使用 Chrome 浏览器访问服务器,会产生额外的一次请求,/favicon.ico ,可以在代码中响应 404 错误。「这是浏览器自己的默认行为,解决办法就是可以过滤掉,或者,在web root目录下,增加一个avicon.ico的文件」

服务端cli下这时是没有输出内容的,因为没有echo,var_dump等语句输出。

修改代码:「记得重启服务」
$http->on('Request', function ($request, $response) { if ($request->server['path_info'] == '/favicon.ico' || $request->server['request_uri'] == '/favicon.ico') { $response->end(); return; } var_dump($request->get, $request->post); $response->header('Content-Type', 'text/html; charset=utf-8'); $response->end('<h1>Hello Swoole. #' . rand(1000, 9999) . '</h1>'); });
需要重启服务

浏览器刷新

服务端cli输出
➜ learning_of_swoole git:(main) ✗ php server/http_server.php array(1) { ["name"]=> string(7) "william" } NULL
URL 路由
应用程序可以根据 $request->server['request_uri'] 实现路由。
「经常使用的web mvc框架都是这样做的,而且也只能这样做,不然怎么实现请求对应到具体的控制和动作中,可以看下个人写的mvc 框架:PHP - 从零开始编写自己的PHP框架」
如:http://127.0.0.1:9501/test/index/?a=1,代码中可以这样实现 URL 路由。
$http->on('Request', function ($request, $response) { list($controller, $action) = explode('/', trim($request->server['request_uri'], '/')); //根据 $controller, $action 映射到不同的控制器类和方法 (new $controller)->$action($request, $response); });
2.4 WebSocket服务器 服务端 ws_server.php
<?php
//创建WebSocket Server对象,监听0.0.0.0:9502端口 $ws = new Swoole\WebSocket\Server('0.0.0.0', 9502);
//监听WebSocket连接打开事件 $ws->on('Open', function ($ws, $request) { $ws->push($request->fd, "hello, welcome\n"); });
//监听WebSocket消息事件 $ws->on('Message', function ($ws, $frame) { echo "Message: {$frame->data}\n"; $ws->push($frame->fd, "server: {$frame->data}"); });
//监听WebSocket连接关闭事件 $ws->on('Close', function ($ws, $fd) { echo "client-{$fd} is closed\n"; });
$ws->start();
- 客户端向服务器端发送信息时,服务器端触发
onMessage 事件回调 - 服务器端可以调用
$server->push() 向某个客户端(使用 $fd 标识符)发送消息
运行服务端程序
php ws_server.php
客户端 可以使用 Chrome 浏览器进行测试,JS 代码为:
var wsServer = 'ws://127.0.0.1:9502';
var websocket = new WebSocket(wsServer);
websocket.onopen = function (evt) { console.log("Connected to WebSocket server."); };
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); };
Comet WebSocket 服务器除了提供 WebSocket 功能之外,实际上也可以处理 HTTP 长连接。只需要增加 onRequest 事件监听即可实现 Comet 方案 HTTP 长轮询。
详细使用方法参考 Swoole\WebSocket
截图
浏览器输出


服务端输出
无输出,因为客户端与服务端此刻并没有通信
具体的通信见后面
通过设置 open_mqtt_protocol 选项,启用后会解析 MQTT 包头,Worker 进程的 onReceive 事件每次会返回一个完整的 MQTT 数据包。 可以使用 Swoole 作为 MQTT 服务端或客户端,实现一套完整物联网(IOT)解决方案。
完整的 MQTT 协议解析和协程客户端可以使用 simps/mqtt
服务端 mqtt_server.php
<?php
// https://wiki.swoole.com/#/start/start_mqtt
function decodeValue($data) { return 256 * ord($data[0]) + ord($data[1]); }
function decodeString($data) { $length = decodeValue($data); return substr($data, 2, $length); }
function mqttGetHeader($data) { $byte = ord($data[0]);
$header['type'] = ($byte & 0xF0) >> 4; $header['dup'] = ($byte & 0x08) >> 3; $header['qos'] = ($byte & 0x06) >> 1; $header['retain'] = $byte & 0x01;
return $header; }
function eventConnect($header, $data) { $connect_info['protocol_name'] = decodeString($data); $offset = strlen($connect_info['protocol_name']) + 2;
$connect_info['version'] = ord(substr($data, $offset, 1)); $offset += 1;
$byte = ord($data[$offset]); $connect_info['willRetain'] = ($byte & 0x20 == 0x20); $connect_info['willQos'] = ($byte & 0x18 >> 3); $connect_info['willFlag'] = ($byte & 0x04 == 0x04); $connect_info['cleanStart'] = ($byte & 0x02 == 0x02); $offset += 1;
$connect_info['keepalive'] = decodeValue(substr($data, $offset, 2)); $offset += 2; $connect_info['clientId'] = decodeString(substr($data, $offset)); return $connect_info; }
$server = new Swoole\Server('127.0.0.1', 9501, SWOOLE_BASE);
$server->set([ 'open_mqtt_protocol' => true, // 启用 MQTT 协议 'worker_num' => 1, ]);
$server->on('Connect', function ($server, $fd) { echo "Client:Connect.\n"; });
$server->on('Receive', function ($server, $fd, $reactor_id, $data) { $header = mqttGetHeader($data); var_dump($header);
if ($header['type'] == 1) { $resp = chr(32) . chr(2) . chr(0) . chr(0); eventConnect($header, substr($data, 2)); $server->send($fd, $resp); } elseif ($header['type'] == 3) { $offset = 2; $topic = decodeString(substr($data, $offset)); $offset += strlen($topic) + 2; $msg = substr($data, $offset); echo "client msg: {$topic}\n----------\n{$msg}\n"; //file_put_contents(__DIR__.'/data.log', $data); }
echo "received length=" . strlen($data) . "\n"; });
$server->on('Close', function ($server, $fd) { echo "Client: Close.\n"; });
$server->start();
2.6 执行异步任务 (Task) 在 Server 程序中如果需要执行很耗时的操作,比如一个聊天服务器发送广播,Web 服务器中发送邮件。如果直接去执行这些函数就会阻塞当前进程,导致服务器响应变慢。 Swoole 提供了异步任务处理的功能,可以投递一个异步任务到 TaskWorker 进程池中执行,不影响当前请求的处理速度。 基于第一个 TCP 服务器,只需要增加 onTask 和 onFinish 2 个事件回调函数即可。另外需要设置 task 进程数量,可以根据任务的耗时和任务量配置适量的 task 进程。 服务端 tcp_server_with_async_task.php
<?php
// 创建Server对象,监听 127.0.0.1:9501 端口 $server = new Swoole\Server('127.0.0.1', 9509);
// 设置异步任务的工作进程数量 $server->set([
// 未设置 worker_num,底层会启动与 CPU 数量一致的 Worker 进程「这里mac cpu就是8核」 // 'worker_num' => 8, 'task_worker_num' => 4 ]);
// 监听连接进入事件 $server->on('Connect', function ($server, $fd) { echo "Client: Connect.\n"; });
// 监听数据接收事件,此回调函数在worker进程中执行 $server->on('Receive', function ($server, $fd, $reactor_id, $data) { // 投递异步任务 $task_id = $server->task($data); echo "Dispatch AsyncTask: id={$task_id}\n"; });
// 处理异步任务(此回调函数在task进程中执行) $server->on('Task', function ($server, $task_id, $reactor_id, $data) { echo "New AsyncTask[id={$task_id}]".PHP_EOL; //返回任务执行的结果 [finish 操作是可选的,也可以不返回任何结果] $server->finish("{$data} -> OK"); });
// 处理异步任务的结果(此回调函数在worker进程中执行) $server->on('Finish', function ($server, $task_id, $data) { echo "AsyncTask[{$task_id}] Finish: {$data}".PHP_EOL; });
// 监听连接关闭事件 $server->on('Close', function ($server, $fd) { echo "Client: Close.\n"; });
// 启动服务器 $server->start();
客户端
还是使用telnet进行进行测试即可
测试结果  当关闭服务端服务时,客户端的连接关闭「所有的客户端」  同时结合异步任务,分析swoole的进程树,进程模型 Swoole Server支持两种进程模型运行,默认是 SWOOLE_PROCESS
Swoole的进程模型分析:
➜ learning_of_swoole git:(main) ✗ ps -ef | grep 2-快速启动/server/tcp_server_with_async_task.php 501 23702 1 0 4:44下午 ?? 0:00.12 php 2-快速启动/server/tcp_server_with_async_task.php 501 23703 23702 0 4:44下午 ?? 0:00.02 php 2-快速启动/server/tcp_server_with_async_task.php 501 23708 23703 0 4:44下午 ?? 0:00.00 php 2-快速启动/server/tcp_server_with_async_task.php 501 23709 23703 0 4:44下午 ?? 0:00.00 php 2-快速启动/server/tcp_server_with_async_task.php 501 23711 23703 0 4:44下午 ?? 0:00.00 php 2-快速启动/server/tcp_server_with_async_task.php 501 23712 23703 0 4:44下午 ?? 0:00.00 php 2-快速启动/server/tcp_server_with_async_task.php 501 23713 23703 0 4:44下午 ?? 0:00.00 php 2-快速启动/server/tcp_server_with_async_task.php 501 23714 23703 0 4:44下午 ?? 0:00.00 php 2-快速启动/server/tcp_server_with_async_task.php 501 23715 23703 0 4:44下午 ?? 0:00.00 php 2-快速启动/server/tcp_server_with_async_task.php 501 25403 23703 0 4:55下午 ?? 0:00.00 php 2-快速启动/server/tcp_server_with_async_task.php 501 25407 23703 0 4:55下午 ?? 0:00.00 php 2-快速启动/server/tcp_server_with_async_task.php 501 25409 23703 0 4:55下午 ?? 0:00.00 php 2-快速启动/server/tcp_server_with_async_task.php 501 25417 23703 0 4:55下午 ?? 0:00.00 php 2-快速启动/server/tcp_server_with_async_task.php 501 25430 23703 0 4:56下午 ?? 0:00.00 php 2-快速启动/server/tcp_server_with_async_task.php 501 27660 25496 0 5:13下午 ttys001 0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox 2-快速启动/server/tcp_server_with_async_task.php
// 查看进程树 ➜ learning_of_swoole git:(main) ✗ pstree -p 23702 -+= 00001 root /sbin/launchd \-+= 23702 huangbaoyin php 2-快速启动/server/tcp_server_with_async_task.php \-+- 23703 huangbaoyin php 2-快速启动/server/tcp_server_with_async_task.php |--- 23708 huangbaoyin php 2-快速启动/server/tcp_server_with_async_task.php |--- 23709 huangbaoyin php 2-快速启动/server/tcp_server_with_async_task.php |--- 23711 huangbaoyin php 2-快速启动/server/tcp_server_with_async_task.php |--- 23712 huangbaoyin php 2-快速启动/server/tcp_server_with_async_task.php |--- 23713 huangbaoyin php 2-快速启动/server/tcp_server_with_async_task.php |--- 23714 huangbaoyin php 2-快速启动/server/tcp_server_with_async_task.php |--- 23715 huangbaoyin php 2-快速启动/server/tcp_server_with_async_task.php |--- 25403 huangbaoyin php 2-快速启动/server/tcp_server_with_async_task.php |--- 25407 huangbaoyin php 2-快速启动/server/tcp_server_with_async_task.php |--- 25409 huangbaoyin php 2-快速启动/server/tcp_server_with_async_task.php |--- 25417 huangbaoyin php 2-快速启动/server/tcp_server_with_async_task.php \--- 25430 huangbaoyin php 2-快速启动/server/tcp_server_with_async_task.php ➜ learning_of_swoole git:(main) ✗
Swoole进程模型: -+= 00001 // 操作系统真正的祖宗进程 \-+= 23702 // Swoole的主进程「Master 进程」swoole的祖宗进程 \-+- 23703 // Manager进程,父进程为23702,下面的进程都是它的子进程,可以看到12个,8{worker进程}+4{task进程} |--- 23708 |--- 23709 |--- 23711 |--- 23712 |--- 23713 |--- 23714 |--- 23715 |--- 25403 |--- 25407 |--- 25409 |--- 25417 \--- 25430
关于Swoole的进程 / 线程结构图,参见 Swoole4 文档 - 服务端 (异步风格) https://blog.csdn.net/william_n/article/details/127598014 后续补充 ... |