资讯专栏INFORMATION COLUMN

一个简单混合协议通讯列子,物联网和互联网通讯。

王军 / 1761人阅读

摘要:初始化发送消息判断用户是否登录如果没有登录拒绝连接断开清除信息处理协议主要是方法,轮训获取消息。

这个列子主要讨论TcpWebSockethttp之间的通讯。长连接和长连接通讯,长连接和短连接通讯。其他协议同理可得

Tcp: 代表硬件设备
WebSocket: 代表客户端 
http: 代表网页  

本列子是基于one框架 (https://github.com/lizhichao/one) 开发.

配置协议 监听端口

由于swoole的模型 WebSocket server 包含 http server , http server 包含 tcp server 。

所以我们配置主服务为 WebSocket server ,添加两个http 和 tcp 监听。配置文件如下:

return [
    "server" => [
        "server_type" => OneSwooleOneServer::SWOOLE_WEBSOCKET_SERVER,
        "port" => 8082,
        "action" => AppTestMixProWs::class,
        "mode" => SWOOLE_PROCESS,
        "sock_type" => SWOOLE_SOCK_TCP,
        "ip" => "0.0.0.0",
        "set" => [
            "worker_num" => 5
        ]
    ],
    "add_listener" => [
        // http 监听
        [
            "port" => 8081,
            "action" => AppServerAppHttpPort::class, 
            "type" => SWOOLE_SOCK_TCP,
            "ip" => "0.0.0.0",
            "set" => [
                "open_http_protocol" => true,
                "open_websocket_protocol" => false
            ]
        ],
        // tcp 监听
        [
            "port" => 8083,
            "pack_protocol" => OneProtocolText::class, // tcp 打包,解包协议,方便在终端调试 我们使用 text 协议. 换行符 表示一个包的结束
            "action" => AppTestMixProTcpPort::class,
            "type" => SWOOLE_SOCK_TCP,
            "ip" => "0.0.0.0",
            "set" => [
                "open_http_protocol" => false,
                "open_websocket_protocol" => false
            ]
        ]
    ]
];

接下来去 AppTestMixProWsAppTestMixProTcpPort 实现各种事件处理。
AppServerAppHttpPort 是框架内置的,通过路由处理http请求的,配置路由即可。

配置路由
// 首页
Router::get("/mix", [
    "use"    => HttpController::class . "@index",
    "middle" => [AppTestMixProTestMiddle::class . "@isLogin"] // 中间件 如果用户登录了 直接跳转到相应的页面
]);

Router::group([
        "middle" => [AppTestMixProTestMiddle::class . "@checkSession"] // 中间件 让用户登录后 才能进入聊天页面 http websocket 都能获取到这个 session
    ], function () {

    // websocket 页面
    Router::get("/mix/ws", HttpController::class . "@ws");
    
    // http 页面
    Router::get("/mix/http", HttpController::class . "@http");
    
    // http 轮训消息接口
    Router::post("/mix/http/loop", HttpController::class . "@httpLoop");
    
    // http 发送消息接口
    Router::post("/mix/http/send", HttpController::class . "@httpSend");

});

配置的都是 http 协议路由。 websocket和tpc我们直接在回调action处理。如果你的项目复杂也可以配置相应的路由。one框架的路由支持任何协议,使用方法也是统一的。

处理tcp协议

其中__construct,onConnect,onClose 不是必须的。
如果你想在服务器运行开始时最一些事情就写到 __construct里面。
onConnect 当有客户端连接时触发,每个客户端触发一次
onClose 当有客户端连接断开时触发,每个客户端触发一次

class TcpPort extends Tcp
{
    use Funs;

    private $users = [];

    /**
     * @var Ws
     */
    protected $server;

    /**
     * @var Client
     */
    protected $global_data;

    public function __construct($server, $conf)
    {
        parent::__construct($server, $conf);
        $this->global_data = $this->server->global_data;
    }

    // 终端连接上服务器时
    public function onConnect(swoole_server $server, $fd, $reactor_id)
    {
        $name             = uuid();
        $this->users[$fd] = $name;
        $this->sendTo("all", json_encode(["v" => 1, "n" => $name]));
        $this->sendToTcp($fd, json_encode(["v" => 4, "n" => $this->getAllName()]));
        $this->global_data->bindId($fd, $name);
        $this->send($fd, "你的名字是:" . $name);
    }

    // 消息处理 像某个name 发送消息
    public function onReceive(swoole_server $server, $fd, $reactor_id, $data)
    {
        $arr = explode(" ", $data);
        if (count($arr) !== 3 || $arr[0] !== "send") {
            $this->send($fd, "格式不正确");
            return false;
        }
        $n = $arr[1];
        $d = $arr[2];
        $this->sendTo($n, json_encode(["v" => 3, "n" => $d]));
    }

    // 下线 通知所有其他终端,解除与fd的关系绑定。
    public function onClose(swoole_server $server, $fd, $reactor_id)
    {
        echo "tcp close {$fd} 
";
        $this->global_data->unBindFd($fd);
        $this->sendTo("all", json_encode(["v" => 2, "n" => $this->users[$fd]]));
        unset($this->users[$fd]);
    }

}

定义了一个公共的traitFuns主要实现两个方法,获取所有的终端(tcp,ws,http),和向某个用户发送消息 。在ws、http都会用到这个
在构造函数我们初始化了一个 global_data 用来保存,名称和fd的关系。你也可以使用方式储存。因为fd没次连接都不同。global_data是one框架内置的。
终端连接上服务器时触发事件 onConnect ,我们给这个终端取个名字,并把关系保存在 global_data。 通知所有终端有个新终端加入,并告诉刚加入的终端当前有哪些终端在线。

处理 websocket 协议

其中__construct,onHandShake,onOpenonClose 不是必须的。

onHandShake,onOpen 是配合使用的,如果onOpen返回false服务器会拒绝连接。
onOpenonMessageonClose可以拿到当前用户的session信息和http是相通的。

class Ws extends WsServer
{
    use Funs;

    private $users = [];

    /**
     * @var Client
     */
    public $global_data = null;

    public function __construct(swoole_server $server, array $conf)
    {
        parent::__construct($server, $conf);
        $this->global_data = new Client();
    }
    
    // 初始化session
    public function onHandShake(swoole_http_request $request, swoole_http_response $response)
    {
        return parent::onHandShake($request, $response);
    }

    // ws 发送消息
    public function onMessage(swoole_websocket_server $server, swoole_websocket_frame $frame)
    {
        $data = $frame->data;
        $arr  = json_decode($data, true);
        $n    = $arr["n"];
        $d    = $arr["d"];
        $this->sendTo($n, json_encode(["v" => 3, "n" => $d]));

    }

    // 判断用户是否登录 如果没有登录拒绝连接
    public function onOpen(swoole_websocket_server $server, swoole_http_request $request)
    {
        $name = $this->session[$request->fd]->get("name");
        if ($name) {
            $this->users[$request->fd] = $name;
            $this->sendTo("all", json_encode(["v" => 1, "n" => $name]));
            $this->global_data->bindId($request->fd, $name);
            return true;
        } else {
            return false;
        }
    }

    // ws 断开清除信息
    public function onClose(swoole_server $server, $fd, $reactor_id)
    {
        echo "ws close {$fd} 
";
        $this->global_data->unBindFd($fd);
        $this->sendTo("all", json_encode(["v" => 2, "n" => $this->users[$fd]]));
        unset($this->users[$fd]);
    }
}
处理 http 协议

主要是 httpLoop 方法,轮训获取消息。因为http是短连接,发给http的信息我们是先存放在$global_data,然后直接这里读取。防止连接间隙丢信息。

class HttpController extends Controller
{

    use Funs;

    /**
     * @var Ws
     */
    protected $server;

    /**
     * @var Client
     */
    protected $global_data;


    public function __construct($request, $response, $server = null)
    {
        parent::__construct($request, $response, $server);
        $this->global_data = $this->server->global_data;
    }

    /**
     * 首页
     */
    public function index()
    {
        $code = sha1(uuid());
        $this->session()->set("code", $code);
        return $this->display("index", ["code" => $code]);
    }

    /**
     * ws页面
     */
    public function ws()
    {
        $name = $this->session()->get("name");
        if (!$name) {
            $name = uuid();
            $this->session()->set("name", $name);
        }
        return $this->display("ws",["users" => $this->getAllName(),"name" => $name]);
    }

    /**
     * http 页面
     */
    public function http()
    {
        $name = $this->session()->get("name");
        if (!$name) {
            $name = uuid();
            $this->session()->set("name", $name);
        }
        $this->global_data->set("http.{$name}", 1, time() + 60);
        $this->sendTo("all", json_encode(["v" => 1, "n" => $name]));
        return $this->display("http", ["list" => $this->getAllName(), "name" => $name]);
    }

    /**
     * http轮训
     */
    public function httpLoop()
    {
        $name = $this->session()->get("name");
        $this->global_data->set("http.{$name}", 1, time() + 60);
        $i = 0;
        do {
            $data = $this->global_data->getAndDel("data.{$name}");
            $i++;
            co::sleep(0.1);
        } while ($data === null && $i < 300);
        if ($data) {
            foreach ($data as &$v) {
                $v = json_decode($v, true);
            }
        } else {
            $data = [];
        }
        return $this->json($data);
    }

    /**
     * http发送消息
     */
    public function httpSend()
    {
        $n = $this->request->post("n");
        $d = $this->request->post("d");
        if ($n && $d) {
            $this->sendTo($n, json_encode(["v" => 3, "n" => $d]));
            return "1";
        }
        return "0";
    }

    public function __destruct()
    {

    }

    public function __call($name, $arguments)
    {
        return $this->server->$name(...$arguments);
    }

}

到此基本就完成了。你可以去看完整的代码 : 点这里

其他的一些列子 : https://github.com/lizhichao/...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/31219.html

相关文章

  • 以小窥大,从一盏路灯看亿万联网之路

    摘要:而要实现物物相连,一共有个阶段性任务,而这个阶段性任务,也伴随着巨大的挑战本文分享自华为云社区云驻共创以小窥大,从一盏路灯看亿万物联网之路云驻共创以小窥大,从一盏路灯看亿万物联网之路,作者启明。 摘要:IoT, Internet of Things,物联网,顾名思义,是物物相连。而要实现物...

    appetizerio 评论0 收藏0
  • 人工智能融入“云”端

    摘要:去年月,阿里云宣布将设立阿里云广东研发中心,招募名云计算和人工智能工程师,推动前沿技术与广东产业融合。吴维刚表示,人工智能与云计算,两者不是同一事物,但是相互发展。近年来,随着互联网和移动互联网的蓬勃发展,大数据、云计算、人工智能、物联网等新技术也迎来了广阔的发展空间。去年,阿里云工业互联网全国总部正式在广州揭牌成立,阿里云将联合广东本地合作伙伴,共同打造服务全国的工业大脑。去年9月,华为与...

    yacheng 评论0 收藏0
  • 无线网络技术学习总结

    摘要:通过通信线路连入通信子网终端是用户访问网络的界面网络操作系统是相对于主机操作系统而言的。接收方使用同一扩频码进行扩解。 目录 一、计算机网络 1.计算机网络技术概述 2.计算机网络分类 3.无线网络分类 二、无线通信和网络仿真技术基础 1.基本概念 2.调制 (1)、概述 (2)、常用方式 ...

    animabear 评论0 收藏0
  • 如何做一个自己的开源聊天项目?(仿微信)

    摘要:一个轻量级高效率的支持聊天与物联网的通讯框架从月初到现在已经大约已经三个月了,由于一直没有时间与精力很好的维护这个项目,心里一直有所歉意。希望本项目对你有所帮助,我的目标暂定,一个小众加物联网的开源通讯项目。 篇幅较长,感谢阅读。 万事开头难 在我决定做开源是因为自身工作接触到大多数的项目都是基于开源大佬写的框架,自觉惭愧,工作以来一直忙于业务与功能实现,多多少少做过的几个项目也没能抽...

    Zachary 评论0 收藏0

发表评论

0条评论

王军

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<