ThinkPHP6 集成TCP长连接 GatewayWorker

概述

ThinkPHP是一个免费开源的,快速、简单的面向对象的轻量级PHP开发框架,是为了敏捷WEB应用开发和简化企业应用开发而诞生的。

GatewayWorker是基于Workerman开发的一套TCP长连接的应用框架,实现了单发、群发、广播等接口,内置了mysql类库,GatewayWorker分为Gateway进程和Worker进程,天然支持分布式部署。

Workerman 是一款纯 PHP 开发的开源高性能的 PHP socket 服务器框架。而think-worker则是 ThinkPHP 官方发布的一个workerman扩展,从2.0+版本完善了对Workerman的支持。

安装

Step.1 安装 Think-worker

首先通过 composer 安装

复制代码
composer require topthink/think-worker

事实上在安装think-worker扩展的时候会自动安装workerman依赖包,所以直接在你的项目根目录下运行下面的命令安装扩展,如果你还没有安装workerman的话也会自动安装。

如果你是第一次使用ThinkPHP6.0,那么可以先创建一个初始项目,然后再安装扩展,依次执行下面的命令即可。

复制代码
composer create-project topthink/think tp6
cd tp6
composer require topthink/think-worker

Step.2 安装 GatewayWorker

首先确保你已经安装了GatewayWorker,如果还没有,可以使用下面的命令安装

复制代码
composer require workerman/gateway-worker

接下来,可以直接在命令行运行

复制代码
php think worker:gateway

会显示下面的信息,表示启动成功。

复制代码
Starting GatewayWorker server...
Workerman[think] start in DEBUG mode
-------------------------------------------- WORKERMAN ---------------------------------------------
Workerman version:3.5.35          PHP version:7.4.8           Event-Loop:\Workerman\Events\Select
--------------------------------------------- WORKERS ----------------------------------------------
proto   user            worker            listen                      processes    status           
tcp     root            Register          text://127.0.0.1:1236       1             [OK]            
tcp     root            BusinessWorker    none                        1             [OK]            
tcp     root            thinkphp          websocket://0.0.0.0:8888    1             [OK]            
----------------------------------------------------------------------------------------------------
Press Ctrl+C to stop. Start success.

Step.3 守护进程模式

如果需要使用守护进程模式,可以使用

复制代码
php think worker:gateway -d

同样支持在命令行指定地址和端口

复制代码
php think worker:gateway -H tinywan.com -p 2800

配置文件

如果需要调整配置,可以修改配置目录下面的config/gateway_worker.php文件,内容如下:

复制代码
<?php

return [
    // 扩展自身需要的配置
    'protocol' => 'websocket', // 协议 支持 tcp udp unix http websocket text
    'host' => '0.0.0.0', // 监听地址
    'port' => 8888, // 监听端口
    'socket' => '', // 完整监听地址
    'context' => [], // socket 上下文选项
    'register_deploy' => true, // 是否需要部署register
    'businessWorker_deploy' => true, // 是否需要部署businessWorker
    'gateway_deploy' => true, // 是否需要部署gateway

    // Register配置
    'registerAddress' => '127.0.0.1:1236',

    // Gateway配置
    'name' => 'thinkphp',
    'count' => 1,
    'lanIp' => '127.0.0.1',
    'startPort' => 2000,
    'daemonize' => false,
    'pingInterval' => 30,
    'pingNotResponseLimit' => 0,
    'pingData' => '{"type":"ping"}',

    // BusinsessWorker配置
    'businessWorker' => [
        'name' => 'BusinessWorker',
        'count' => 1,
        'eventHandler' => \app\common\Events::class
    ],

];

默认配置参数如果需要更改,可以直接修改。

注:GatewayWorker开发过程中首先要配置BusinessWorker下面的eventHandler参数。系统默认提供了一个think\worker\Events类作为参考,实际请根据需要进行调整。详细用法请参考 GatewayWorker手册。

业务 Events 文件

复制代码
<?php
/**
 * @desc 业务开发只需要关注 Events.php 一个文件即可
 * @author Tinywan(ShaoBo Wan)
 */
declare(strict_types=1);

namespace app\common;

use GatewayWorker\Lib\Gateway;
use Workerman\Worker;

class Events
{
    /**
     * onWorkerStart 事件回调
     * 当businessWorker进程启动时触发。每个进程生命周期内都只会触发一次
     * @access public
     * @param Worker $businessWorker
     * @return void
     */
    publicstaticfunction onWorkerStart(Worker $businessWorker)
    {
    }

    /**
     * onConnect 事件回调
     * 当客户端连接上gateway进程时(TCP三次握手完毕时)触发
     * @access public
     * @param string $client_id
     * @return void
     */
    publicstaticfunction onConnect(string $client_id)
    {
        Gateway::sendToCurrentClient("你的客户端ID是: $client_id");
    }

    /**
     * onWebSocketConnect 事件回调
     * 当客户端连接上gateway完成websocket握手时触发
     * @param string $client_id 断开连接的客户端client_id
     * @param mixed $data
     * @return void
     */
    publicstaticfunction onWebSocketConnect(string $client_id, $data)
    {
        var_export($data);
    }

    /**
     * onMessage 事件回调
     * 当客户端发来数据(Gateway进程收到数据)后触发
     * @access public
     * @param string $client_id
     * @param mixed $data
     * @return void
     * @throws \Exception
     */
    publicstaticfunction onMessage(string$client_id, $data)
    {
        Gateway::sendToAll('开源技术小栈:'.$data);
    }

    /**
     * onClose 事件回调 当用户断开连接时触发的方法
     * @param string $client_id 断开连接的客户端client_id
     * @return void
     * @throws \Exception
     */
    publicstaticfunction onClose(string $client_id)
    {
        GateWay::sendToAll("client[$client_id] logout\n");
    }

    /**
     * onWorkerStop 事件回调
     * 当businessWorker进程退出时触发。每个进程生命周期内都只会触发一次。
     * @param Worker $businessWorker
     * @return void
     */
    publicstaticfunction onWorkerStop(Worker $businessWorker)
    {
        echo"WorkerStop\n";
    }
}

本地调式

以上就把服务启动好了,接下来在本地调式,在桌面创建一个html文件,名字随意,我这里命名为websocket.html,打开文件复制以下代码替换:

复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>开源技术小栈</title>
    <style>
        body {
            font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
            font-size: 22px;
        }
        .btn-group {
            display: inline-block;
        }
    </style>
</head>
<body>
<div>
    <input type='text' value='通信地址, ws://开头..' class="form-control" style='width:390px;display:inline' id='wsaddr'/>
    <div class="btn-group">
        <button type="button" class="btn btn-default" onclick='addsocket();'>连接</button>
        <button type="button" class="btn btn-default" onclick='closesocket();'>断开</button>
        <button type="button" class="btn btn-default" onclick='$("#wsaddr").val("")'>清空</button>
    </div>
    <div class="row">
        <div id="output" style="border:1px solid #ccc;height:385px;overflow: auto;margin: 20px 0;"></div>
        <input type="text" id='message' class="form-control" style='width:365px' placeholder="待发信息"
               onkeydown="en(event);">
        <span class="input-group-btn">
        <button class="btn btn-default" type="button" onclick="doSend();">发送</button>
      </span>
    </div>
</div>
</body>

<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script language="javascript" type="text/javascript">
    function formatDate(now) {
        var year = now.getFullYear();
        var month = now.getMonth() + 1;
        var date = now.getDate();
        var hour = now.getHours();
        var minute = now.getMinutes();
        var second = now.getSeconds();
        return year + "-" + (month = month < 10 ? ("0" + month) : month) + "-" + (date = date < 10 ? ("0" + date) : date) +
            " " + (hour = hour < 10 ? ("0" + hour) : hour) + ":" + (minute = minute < 10 ? ("0" + minute) : minute) + ":" + (
                second = second < 10 ? ("0" + second) : second);
    }

    var output;
    var websocket;

    function init() {
        output = document.getElementById("output");
        testWebSocket();
    }

    function addsocket() {
        var wsaddr = $("#wsaddr").val();
        if (wsaddr == '') {
            alert("请填写websocket的地址");
            returnfalse;
        }
        StartWebSocket(wsaddr);
    }

    function closesocket() {
        websocket.close();
    }

    function StartWebSocket(wsUri) {
        websocket = new WebSocket(wsUri);
        websocket.onopen = function (evt) {
            onOpen(evt)
        };
        websocket.onclose = function (evt) {
            onClose(evt)
        };
        websocket.onmessage = function (evt) {
            onMessage(evt)
        };
        websocket.onerror = function (evt) {
            onError(evt)
        };
    }

    function onOpen(evt) {
        writeToScreen("<span style='color:red'>连接成功,现在你可以发送信息啦!!!</span>");
    }

    function onClose(evt) {
        writeToScreen("<span style='color:red'>websocket连接已断开!!!</span>");
        websocket.close();
    }

    function onMessage(evt) {
        writeToScreen('<span style="color:blue">服务端回应&nbsp;' + formatDate(newDate()) + '</span><br/><span class="bubble">' +
            evt.data + '</span>');
    }

    function onError(evt) {
        writeToScreen('<span style="color: red;">发生错误:</span> ' + evt.data);
    }

    function doSend() {
        var message = $("#message").val();
        if (message == '') {
            alert("请先填写发送信息");
            $("#message").focus();
            returnfalse;
        }
        if (typeof websocket === "undefined") {
            alert("websocket还没有连接,或者连接失败,请检测");
            returnfalse;
        }
        if (websocket.readyState == 3) {
            alert("websocket已经关闭,请重新连接");
            returnfalse;
        }
        console.log(websocket);
        $("#message").val('');
        writeToScreen('<span style="color:green">你发送的信息&nbsp;' + formatDate(newDate()) + '</span><br/>' + message);
        websocket.send(message);
    }

    function writeToScreen(message) {
        var div = "<div class='newmessage'>" + message + "</div>";
        var d = $("#output");
        var d = d[0];
        var doScroll = d.scrollTop == d.scrollHeight - d.clientHeight;
        $("#output").append(div);
        if (doScroll) {
            d.scrollTop = d.scrollHeight - d.clientHeight;
        }
    }


    function en(event) {
        var evt = evt ? evt : (window.event ? window.event : null);
        if (evt.keyCode == 13) {
            doSend()
        }
    }
</script>
</html>

如果服务器有回调信息,证明整个服务搭建完毕

相关推荐
国科安芯3 小时前
国产MCU芯片在船舶压力传感器中的应用探索与实践
网络·单片机·嵌入式硬件·fpga开发·车载系统
sanzk4 小时前
S7-PLCSIM Advanced V3.0下载PLC显示红色IP
服务器·网络·tcp/ip
❀͜͡傀儡师4 小时前
网络嗅探抓包工具 Wireshark v4.6.0
网络·测试工具·wireshark
海外住宅ip供应商-luck4 小时前
Smartproxy API 代理 IP 提取指南——JSON-first 架构与参数化最佳实践
tcp/ip·架构·json
white-persist4 小时前
Linux中,vi(vim)编辑器大部分快捷键
linux·运维·服务器·网络·安全·编辑器·vim
21号 14 小时前
C++ 从零实现Json-Rpc 框架
网络协议·rpc·json
黑马金牌编程4 小时前
tcpdump 常用命令及参数解析
linux·网络·tcpdump·网络抓包
戮戮5 小时前
一次深入排查:Spring Cloud Gateway TCP 连接复用导致 K8s 负载均衡失效
tcp/ip·spring cloud·kubernetes·gateway·负载均衡·netty
せいしゅん青春之我5 小时前
【JavaEE初阶】网络原理——TCP处理先发后至问题
java·网络·笔记·网络协议·tcp/ip·java-ee