<noframes id="bhrfl"><address id="bhrfl"></address>

    <address id="bhrfl"></address>

    <noframes id="bhrfl"><address id="bhrfl"><th id="bhrfl"></th></address>

    <form id="bhrfl"><th id="bhrfl"><progress id="bhrfl"></progress></th></form>

    <em id="bhrfl"><span id="bhrfl"></span></em>

    全部
    常見問題
    產品動態
    精選推薦

    PHP轉Go系列 | ThinkPHP與Gin框架之打造基于WebSocket技術的消息推送中心

    管理 管理 編輯 刪除

    在早些年前客戶端想要實時獲取到最新消息,都是使用定時長輪詢的方式,不斷的從服務器上獲取數據,這種粗暴的騷操作實屬不雅。不過現如今我也還見有人還在一些場景下使用,比如在 PC 端掃描二維碼,然后使用長輪詢的方式從服務端獲取最新的掃碼信息,來判斷用戶是否已經掃碼完成,諸如這種場景還有不少。其實大家都知道長輪詢的方式不好,那為什么還有人使用呢?

    c608c202501231055596899.png


    我想最直接的原因就是「開發起來簡單明了」,人性決定了人類都是趨易避難的高級物種,那個容易上手就用那個。但是我想表達的是除了長輪詢的方式外,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 服務,最后來測試一波。

    31c39202501231058292752.png

    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 啟動服務,然后進行消息推送測試。

    898f4202501231100039645.png

    通過這兩個簡單的例子,我相信大家已經對 WebSocket 技術已經有所了解吧。從例子中也可以看出來,其實在 PHP 和 Go 中實現上有所區別,PHP 中需要啟動兩個服務,一個是 HTTP 服務,一個是 WebSocket 服務,而且兩者服務直接都是單獨的進程,不能相互通信,需要額外借助第三方中間件 Redis 來實現數據的傳輸。反觀 Go 中直接一個服務涵蓋了 HTTP 服務和 WebSocket 服務,共享一個進程的數據資源,通過使用 Channel 通道傳遞消息。

    此外,在 PHP 中需要使用 Timer 異步定時器來讀取 Redis 消息隊列中的數據,不能用 for 循環或者 Redis 的阻塞隊列,因為它會阻塞整個進程的執行。而在 Go 中直接開啟一個協程,在協程中等待通道中的消息即可,會一直阻塞到消息的到來,而且它不會阻塞整個進程的執行,由此可見在這個例子中 Go 相較于 PHP 的優勢顯著。最后可能有些從來沒有使用過 WebSocket 技術的朋友,可能看完這篇文章之后也依然會云里霧里,所以建議這些朋友可以自己親自實踐一下文中的案例,實踐過后我相信你會別有一番技術體驗。

    注:本文轉載自“碼農先森”,如有侵權,請聯系刪除!

    請登錄后查看

    哈哈哈醬 最后編輯于2025-01-23 11:40:04

    快捷回復
    回復
    回復
    回復({{post_count}}) {{!is_user ? '我的回復' :'全部回復'}}
    排序 默認正序 回復倒序 點贊倒序

    {{item.user_info.nickname ? item.user_info.nickname : item.user_name}} LV.{{ item.user_info.bbs_level }}

    作者 管理員 企業

    {{item.floor}}# 同步到gitee 已同步到gitee {{item.is_suggest == 1? '取消推薦': '推薦'}}
    {{item.is_suggest == 1? '取消推薦': '推薦'}}
    沙發 板凳 地板 {{item.floor}}#
    {{item.user_info.title || '暫無簡介'}}
    附件

    {{itemf.name}}

    {{item.created_at}}  {{item.ip_address}}
    打賞
    已打賞¥{{item.reward_price}}
    {{item.like_count}}
    {{item.showReply ? '取消回復' : '回復'}}
    刪除
    回復
    回復

    {{itemc.user_info.nickname}}

    {{itemc.user_name}}

    回復 {{itemc.comment_user_info.nickname}}

    附件

    {{itemf.name}}

    {{itemc.created_at}}
    打賞
    已打賞¥{{itemc.reward_price}}
    {{itemc.like_count}}
    {{itemc.showReply ? '取消回復' : '回復'}}
    刪除
    回復
    回復
    查看更多
    打賞
    已打賞¥{{reward_price}}
    975
    {{like_count}}
    {{collect_count}}
    添加回復 ({{post_count}})

    相關推薦

    快速安全登錄

    使用微信掃碼登錄
    {{item.label}} 加精
    {{item.label}} {{item.label}} 板塊推薦 常見問題 產品動態 精選推薦 首頁頭條 首頁動態 首頁推薦
    取 消 確 定
    回復
    回復
    問題:
    問題自動獲取的帖子內容,不準確時需要手動修改. [獲取答案]
    答案:
    提交
    bug 需求 取 消 確 定
    打賞金額
    當前余額:¥{{rewardUserInfo.reward_price}}
    {{item.price}}元
    請輸入 0.1-{{reward_max_price}} 范圍內的數值
    打賞成功
    ¥{{price}}
    完成 確認打賞

    微信登錄/注冊

    切換手機號登錄

    {{ bind_phone ? '綁定手機' : '手機登錄'}}

    {{codeText}}
    切換微信登錄/注冊
    暫不綁定
    亚洲欧美字幕
    CRMEB客服

    CRMEB咨詢熱線 咨詢熱線

    400-8888-794

    微信掃碼咨詢

    CRMEB開源商城下載 源碼下載 CRMEB幫助文檔 幫助文檔
    返回頂部 返回頂部
    CRMEB客服