首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

PHP转Go系列 | ThinkPHP与Gin框架之打造基于WebSocket技术的消息推送中心

编程知识
2024年08月15日 08:10

大家好,我是码农先森。

在早些年前客户端想要实时获取到最新消息,都是使用定时长轮询的方式,不断的从服务器上获取数据,这种粗暴的骚操作实属不雅。不过现如今我也还见有人还在一些场景下使用,比如在 PC 端扫描二维码,然后使用长轮询的方式从服务端获取最新的扫码信息,来判断用户是否已经扫码完成,诸如这种场景还有不少。其实大家都知道长轮询的方式不好,那为什么还有人使用呢?

我想最直接的原因就是「开发起来简单明了」,人性决定了人类都是趋易避难的高级物种,那个容易上手就用那个。但是我想表达的是除了长轮询的方式外,WebSocket 技术其实也不难,只不过对于从来没有接触过长连接的人来说,刚开始上手时会有一些思维上的障碍。这次我分享的内容是基于 WebSocket 技术的消息推送中心,看起来很高大上,其实也就是通过一些小的例子来演示,从服务端推送数据到客户端的这个过程,接下来的例子简单明了容易上手,我们赶紧开始吧。

话不多说,开整!我们先来看一下整体的项目目录结构,内容主要分为 PHP 和 Go 两部分。

[manongsen@root php_to_go]$ tree -L 2
.
├── go_websocket
│   ├── app
│   │   ├── controller
│   │   |	|── message.go
│   │   │   └── websocket.go
│   │   └── route.go
│   ├── go.mod
│   ├── go.sum
│   └── main.go
└── php_websocket
│   ├── app
│   │   ├── controller
│   │   |	|── Push.php
│   │   │   └── Worker.php
│   ├── composer.json
│   ├── composer.lock
│   ├── config
│   │   |── worker_server.php
│   │   └── worker.php
│   ├── route
│   │   └── app.php
│   ├── think
│   ├── vendor
│   └── .env

ThinkPHP

使用 composer 创建基于 ThinkPHP 框架的 php_websocket 项目。

## 当前目录
[manongsen@root ~]$ pwd
/home/manongsen/workspace/php_to_go/php_websocket

## 安装 ThinkPHP 框架
[manongsen@root php_websocket]$ composer create-project topthink/think php_websocket
[manongsen@root php_websocket]$ cp .example.env .env

## 安装 Composer 依赖包
[manongsen@root php_websocket]$ composer require topthink/think-worker
[manongsen@root php_websocket]$ composer require predis/predis

使用 php think make:controller Worker 命令创建 Worker.php 控制器。这个控制器中主要实现了 onWorkerStart 这个方法,首先添加了一个 Timer 异步定时器,然后从 Redis 队列中读取消息,最后将消息推送到客户端,这个定时器会每间隔一秒钟调度一次。

// ./php_to_go/php_websocket/app/controller/Worker.php
<?php
declare (strict_types = 1);

namespace app\controller;

use think\Request;
use think\worker\Server;
use Workerman\Lib\Timer;
use think\facade\Cache;
use think\facade\Env;

class Worker extends Server
{
    protected $socket = 'websocket://0.0.0.0:2345';
    protected static $connections = [];

    public function onWorkerStart($worker) {
        // 添加一个异步定时器任务
        Timer::add(1, function () use ($worker) {
            // 从消息中心队列中读取消息
            $redis = Cache::store('redis')->handler();
            $content = $redis->rpop(Env::get("MESSAGE_CENTER_KEY"));

            // 发送消息到客户端
            foreach ($worker->connections as $connection) {
                if (!empty($content)) {
                    $connection->send("PHP语言消息中心: " . $content);
                }
            }
        });
    }

    public function onWorkerReload($worker) {
    }

    public function onConnect($connection) {
    }

	public function onMessage($connection, $data){
	}

    public function onClose($connection) {
    }

    public function onError($connection, $code, $msg) {
    }
}

使用 php think make:controller Push 命令创建 Push.php 控制器。这个控制器的主要作用是接收外部的消息内容,然后推送到 Redis 消息队列中,这里提供的是 API 接口,这个接口可以在外部的后台系统调用。

// ./php_to_go/php_websocket/app/controller/Push.php
<?php

namespace app\controller;

use app\BaseController;
use think\facade\Cache;
use think\facade\Env;

class Push extends BaseController
{
    public function msg()
    {
        // 接收 GET 参数
        $params = $this->request->param();
        if (empty($params["content"])) {
            return json(["code" => -1, "msg" => "内容不能为空"]);
        }
        $content = $params["content"];

        // 推送消息到消息中心队列
        $redis = Cache::store('redis')->handler();
        $redis->lpush(Env::get("MESSAGE_CENTER_KEY"), $content);

        return json(["code" => 0, "msg" => "success"]);
    }
}

先运行 php think worker 启动 HTTP 服务,再运行 php think worker:server 启动 WebSocket 服务,最后来测试一波。

Gin

通过 go mod 初始化 go_websocket 项目。

## 当前目录
[manongsen@root ~]$ pwd
/home/manongsen/workspace/php_to_go/go_websocket

## 初始化项目
[manongsen@root go_websocket]$ go mod init go_websocket

## 安装第三方依赖库
[manongsen@root go_websocket]$ go get github.com/gin-gonic/gin
[manongsen@root go_websocket]$ go get github.com/gorilla/websocket

在 go_websocket 项目中创建 websocket 控制器。这个控制器会将客户端连接存储到指定的 Map 数据结构中,其次还提供了 WaitMessage 等待消息的方法,如果从 MsgQueue 通道中读取到了消息,则把消息推送给所有的客户端。

// ./php_to_go/go_websocket/app/controller/websocket.php
package controller

import (
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
)

// 定义一个消息传输通道
var MsgQueue = make(chan string, 10)

// 定义一个存储客户端连接的 Map
var Clients = make(map[*websocket.Conn]bool)

// 将 HTTP 协议升级至 WebSocket 协议
var upgrader = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool {
		return true // 允许所有来源
	},
}

// 将客户端连接存储到 Map
func HandleConnection(c *gin.Context) {
	conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		fmt.Printf("客户端连接协议升级失败: %v\n", err)
		return
	}
	Clients[conn] = true
}

// 等待消息中
func WaitMessage() {
	go func() {
		for {
			select {
			case msg, ok := <-MsgQueue:
				if ok {
					for client := range Clients {
						err := client.WriteMessage(websocket.TextMessage, []byte("Go语言消息中心: "+string(msg)))
						if err != nil {
							fmt.Printf("消息推送失败: %v\n", err)
						}
					}
				}
			default:
				// 避免忙等
				time.Sleep(500 * time.Millisecond)
			}
		}
	}()
}

在 go_websocket 项目中创建 message 控制器。这个控制器的主要作用是接收外部的消息内容,然后推送到 MsgQueue 通道中,这里提供的是 API 接口,这个接口可以在外部的后台系统调用。这里和 PHP 中有一点不同的是,在 Go 中无需引入像 Redis 一样的第三方组件,而是利用自身的 Channel 特性即可实现消息的传递。

// ./php_to_go/go_websocket/app/controller/message.php
package controller

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func PushMsg(c *gin.Context) {
	// 接收 GET 参数
	content := c.Query("content")
	if len(content) == 0 {
		c.JSON(http.StatusOK, gin.H{
			"msg":  "内容不能为空",
			"code": -1,
		})
		return
	}

	// 往通道推送消息
	MsgQueue <- content

	c.JSON(http.StatusOK, gin.H{
		"msg":  "ok",
		"code": 0,
	})
}

运行 go run main.go 启动服务,然后进行消息推送测试。

通过这两个简单的例子,我相信大家已经对 WebSocket 技术已经有所了解吧。从例子中也可以看出来,其实在 PHP 和 Go 中实现上有所区别,PHP 中需要启动两个服务,一个是 HTTP 服务,一个是 WebSocket 服务,而且两者服务直接都是单独的进程,不能相互通信,需要额外借助第三方中间件 Redis 来实现数据的传输。反观 Go 中直接一个服务涵盖了 HTTP 服务和 WebSocket 服务,共享一个进程的数据资源,通过使用 Channel 通道传递消息。

此外,在 PHP 中需要使用 Timer 异步定时器来读取 Redis 消息队列中的数据,不能用 for 循环或者 Redis 的阻塞队列,因为它会阻塞整个进程的执行。而在 Go 中直接开启一个协程,在协程中等待通道中的消息即可,会一直阻塞到消息的到来,而且它不会阻塞整个进程的执行,由此可见在这个例子中 Go 相较于 PHP 的优势显著。最后可能有些从来没有使用过 WebSocket 技术的朋友,可能看完这篇文章之后也依然会云里雾里,所以建议这些朋友可以自己亲自实践一下文中的案例,实践过后我相信你会别有一番技术体验。如果有想要获取完整案例代码的朋友,可以在公众号内回复「2463」即可,希望对大家能有所帮助。

感谢大家阅读,个人观点仅供参考,欢迎在评论区发表不同观点。

欢迎关注、分享、点赞、收藏、在看,我是微信公众号「码农先森」作者。

From:https://www.cnblogs.com/yxhblogs/p/18348070
本文地址: http://www.shuzixingkong.net/article/1118
0评论
提交 加载更多评论
其他文章 Deformable DETR:商汤提出可变型 DETR,提点又加速 | ICLR 2021 Oral
DETR能够消除物体检测中许多手工设计组件的需求,同时展示良好的性能。但由于注意力模块在处理图像特征图方面的限制,DETR存在收敛速度慢和特征分辨率有限的问题。为了缓解这些问题,论文提出了Deformable DETR,其注意力模块仅关注参考点周围的一小组关键采样点,通过更少的训练次数实现比DETR
Deformable DETR:商汤提出可变型 DETR,提点又加速 | ICLR 2021 Oral Deformable DETR:商汤提出可变型 DETR,提点又加速 | ICLR 2021 Oral Deformable DETR:商汤提出可变型 DETR,提点又加速 | ICLR 2021 Oral
神经网络之卷积篇:详解三维卷积(Convolutions over volumes)
详解三维卷积 从一个例子开始,假如说不仅想检测灰度图像的特征,也想检测RGB彩色图像的特征。彩色图像如果是6&#215;6&#215;3,这里的3指的是三个颜色通道,可以把它想象成三个6&#215;6图像的堆叠。为了检测图像的边缘或者其他的特征,不是把它跟原来的3&#215;3的过滤器做卷积,而是跟
神经网络之卷积篇:详解三维卷积(Convolutions over volumes) 神经网络之卷积篇:详解三维卷积(Convolutions over volumes) 神经网络之卷积篇:详解三维卷积(Convolutions over volumes)
canvas实现手动绘制矩形
开场白 虽然在实际的开发中我们很少去绘制流程图 就算需要,我们也会通过第3方插件去实现 下面我们来简单实现流程图中很小的一部分 手动绘制矩形 绘制一个矩形的思路 我们这里绘制矩形 会使用到canvas.strokeRect(x,y, w, h)方法绘制一个描边矩形 x:矩形起点的 x 轴坐标。 y:
canvas实现手动绘制矩形 canvas实现手动绘制矩形 canvas实现手动绘制矩形
《痞子衡嵌入式半月刊》 第 106 期
痞子衡嵌入式半月刊: 第 106 期 这里分享嵌入式领域有用有趣的项目/工具以及一些热点新闻,农历年分二十四节气,希望在每个交节之日准时发布一期。 本期刊是开源项目(GitHub: JayHeng/pzh-mcu-bi-weekly),欢迎提交 issue,投稿或推荐你知道的嵌入式那些事儿。 上期回
《痞子衡嵌入式半月刊》 第 106 期 《痞子衡嵌入式半月刊》 第 106 期 《痞子衡嵌入式半月刊》 第 106 期
优化系统性能:深入探讨Web层缓存与Redis应用的挑战与对策
Web层缓存显著提高了应用性能,通过减少重复的数据处理和数据库查询来加快响应时间。Redis作为高效的内存数据结构存储系统,在实现缓存层中发挥了重要作用,它支持各种数据结构,能够迅速存取数据,从而减少数据库负担,提升用户体验。然而,缓存机制也面临挑战,如缓存穿透、缓存击穿和缓存雪崩等问题。缓存穿透通
优化系统性能:深入探讨Web层缓存与Redis应用的挑战与对策
程序员失业日记3:工作真不好找
自从上个月写了失业连载之后,一个月都挺忙的,前几天有小伙伴催更,赶紧再续上一篇。 裁员后一两周,在家基本都是睡到自然醒,悠闲的做个午饭或者晚饭。计划是有了赔偿金之后休息两个月再找工作。开始在深圳周边玩,去了周边的塘朗山、盐田港海边和福田博览园。国庆之后就去了香港玩,2019年去过一次香港,后面疫情就
程序员失业日记3:工作真不好找 程序员失业日记3:工作真不好找
推荐一款Python开源移动应用安全测试分析工具!!!
今天给大家推荐一个安全测试相关的开源项目:nccgroup/house 1、介绍 它是一个由 NCC Group 开发的,一个基于Frida和Python编写的动态运行时移动应用分析工具包,提供了基于 Frida 的 Web GUI 界面,旨在简化动态函数挂钩的过程,让研究人员能够更轻松地评估 An
推荐一款Python开源移动应用安全测试分析工具!!! 推荐一款Python开源移动应用安全测试分析工具!!! 推荐一款Python开源移动应用安全测试分析工具!!!
.NET 7 + Vue 权限管理系统 小白快速上手
前言 今天给大家推荐一个超实用的开源项目《.NET 7 + Vue 权限管理系统 小白快速上手》,DncZeus的愿景就是做一个.NET 领域小白也能上手的简易、通用的后台权限管理模板系统基础框架。 不管你是技术小白还是技术大佬或者是不懂前端Vue 的新手,这个项目可以快速上手让我们从0到1,搭建自
.NET 7 + Vue 权限管理系统 小白快速上手 .NET 7 + Vue 权限管理系统 小白快速上手 .NET 7 + Vue 权限管理系统 小白快速上手