# 协程处理案例
## 项目开发场景
终端设备通过TCP连接服务端,并维持了 TCP连接。
终端设备发送数据,服务端处理数据,或者响应请求。此过程,服务端没有主动发送数据。
如果服务端主动发送数据到终端设备,且需要等待终端设备的响应。此过程需要进程间通信`IPC`和协程 `Coroutine`。
## 特定项目场景
> 1. 用户发送了HTTP请求,要求终端执行关机指令;
>
> 2. 关机成功后,需要响应HTTP请求。
>
> 3. 如果超时【20秒】未关机,响应HTTP请求 关机指令执行失败。
>
## 简化演示,测试用例场景
> 1. Telnet 连接 TCP服务端,充当终端设备。
>
> 2. 发送标识 heartbeat-110(项目中数据经常为16进制或者JSON),服务端收到数据,做好标识。heartbeart 表示 终端设备行为,110表示 终端设备标识。
>
> 3. 终端客户发送HTTP发送请求,请求数据如下:
>
> ~~~
> action=shutdown&device_id=110
> ~~~
>
> 4. ESD 的 HTTP服务端收到请求数据,生成一个【等待关机回执事件】,协程发送到 TCP数据终端设备,数据内容为:
>
> ~~~
> shutdown-110
> ~~~
>
> 然后协程等待【20秒】。
>
> 5. 终端设备收到shutdown-110,回执shutdown\_receipt-110。(设备处理关机过程略。)
>
> 6. TCP服务端收到回执数据,进程间通讯,触发之前的【等待关机回执事件】,响应HTTP请求【关机成功】。如果【20秒】内未等到回执,回复HTTP请求【关机指令执行失败】。
>
## 代码如下:
App\\Controller\\StreamController.php
~~~
<?php
namespace App\Controller;
use ESD\Core\Server\Server;
use ESD\Go\GoController;
use ESD\Plugins\EasyRoute\Annotation\RequestMapping;
use ESD\Plugins\EasyRoute\Annotation\TcpController;
use ESD\Plugins\Pack\GetBoostSend;
/**
* @TcpController()
* Class StreamController
* @package App\Controller
*/
class StreamController extends GoController
{
/**
* @RequestMapping("onReceive")
*/
public function actionOnReceive()
{
$fd = $this->clientData->getFd();
$receiveData = $this->clientData->getData();
//数据验证功能过程略
list($action, $deviceId) = explode("-", $receiveData);
switch ($action) {
//心跳 保存设备标识和连接fd的关系
case 'heartbeat':
$deviceKey = sprintf("device_%s", $deviceId);
$this->redis()->hMSet($deviceKey, [
'fd' => $fd,
'device_id' => $deviceId,
]);
break;
//关机回执
case 'shutdown_receipt':
//事件名称
$eventName = sprintf("shutdown_receipt_%s", $deviceId);
//事件派发器
$eventDispatcher = Server::$instance->getEventDispatcher();
//事件
$event = new Event($eventName, [
'device_id' => $deviceId,
]);
//向ESD全进程派发事件
$eventDispatcher->dispatchProcessEvent($event, ...Server::$instance->getProcessManager()->getProcesses());
break;
}
}
}
~~~
App\\Controller\\DeviceController.php
~~~
<?php
use ESD\Core\Server\Server;
use ESD\Go\GoController;
use ESD\Plugins\EasyRoute\Annotation\GetMapping;
use ESD\Plugins\EasyRoute\Annotation\RestController;
use ESD\Plugins\Pack\GetBoostSend;
use ESD\Plugins\Redis\GetRedis;
use ESD\Yii\Helpers\Json;
/**
* @RestController("device")
*
* Class DeviceController
* @package App\Controller
*/
class DeviceController extends GoController
{
use GetRedis;
use GetBoostSend;
/**
* @GetMapping("control")
*/
public function actionControl()
{
//验证数据合法性略
$deviceId = $this->request->input('device_id');
$action = $this->request->input('action');
$deviceKey = sprintf("device_%s", $deviceId);
//设备ID和连接FD的关系
$deviceConnInfo = $this->redis()->hGetAll($deviceKey);
//FD标识
$fd = $deviceConnInfo['fd'];
//发送指令到设备
$cmd = sprintf("%s_%s", $action, $deviceId);
$this->autoBoostSend($fd, $cmd);
//事件名称
$eventName = sprintf("shutdown_receipt_%s", $deviceId);
//事件派发器
$eventDispatcher = Server::$instance->getEventDispatcher();
//等待事件回执
$call = $eventDispatcher->listen($eventName)->wait(20);
if (empty($call) || empty($call->getData())) {
$responseData = [
'code' => 4400,
'message' => sprintf("设备%s关机指令执行失败", $deviceId)
];
$this->response
->setHeaders([
'Content-Type' => 'application/json;charset=utf8'
])
->withContent(Json::encode($responseData))->end();
return true;
}
$data = $call->getData();
if (!empty($data)) {
$responseData = [
'code' => 200,
'message' => sprintf("设备%s关机成功", $deviceId)
];
$this->response
->setHeaders([
'Content-Type' => 'application/json;charset=utf8'
])
->withContent(Json::encode($responseData))->end();
}
}
}
~~~
- 1 介绍
- 2 安装
- 2.1 环境
- 2.2 安装
- 3 配置
- 3.1 Server配置
- 3.2 端口配置
- 3.3 项目结构
- 3.4 内核优化
- 4 服务
- 4.1 HTTP服务
- 4.1.1 路由
- 4.1.1.1 静态路由
- 4.1.1.2 路由定义
- 4.1.1.3 路由方法
- 4.1.1.4 路由分组
- 4.1.1.5 资源路由
- 4.1.1.6 端口作用域
- 4.1.1.7 异常处理
- 4.1.1.8 跨域请求
- 4.1.1.9 路由缓存
- 4.1.2 控制器
- 4.1.2.1 控制器初始化
- 4.1.2.2 前置后置操作
- 4.1.2.3 跳转与重定向
- 4.1.2.4 异常处理
- 4.1.3 请求
- 4.1.3.1 请求对象
- 4.1.3.2 请求信息
- 4.1.3.3 REQUEST消息
- 4.1.3.4 RESPONSE消息
- 4.1.3.5 STREAM消息
- 4.1.3.6 URI信息
- 4.1.3.7 处理上传文件
- 4.1.3.8 验证器
- 4.2 Websocket服务
- 4.2.1 Websocket配置
- 4.2.2 Websocket路由
- 4.3 TCP服务
- 4.3.1 TCP配置
- 4.3.2 TCP路由
- 4.3.3 协程处理案例
- 5 插件
- 5.15 Yii-PDO插件
- 5.15.1 PDO 连接MySQL Mariadb
- 5.15.2 PDO连接PostgreSQL
- 5.15.3 PDO连接GreenPlum
- 5.15.4 PDO连接Oracle
- 5.15.5 PDO连接Cubrid
- 5.15.6 PDO连接SQL Server
- 6 概念
- 7. Yii- I18N国际化
- 8. 模型 Yii-Model
- 8.1 快速创建模型
- 8.2 快速创建多个模型
- 8.3 核心验证器 Core Validators
- 9. 配合数据库工作
- 9.1.数据库访问对象 Database Access Objects
- 9.2 查询构造器 Query Builder
- 9.3 活动记录 Active Record
