💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、豆包、星火、月之暗面及文生图、文生视频 广告
# 协程处理案例 ## 项目开发场景 终端设备通过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();       } ​   } } ~~~