websocket借助Redis實(shí)現(xiàn)實(shí)時(shí)雙工通信

最近在梳理一些知識(shí)點(diǎn),已脫敏并去除公司實(shí)現(xiàn)粉洼,做一些自己理解上的實(shí)踐节预。

結(jié)構(gòu)

本次打算模擬下一個(gè)實(shí)時(shí)雙工交互的業(yè)務(wù)實(shí)踐叶摄,先來張圖。

模式結(jié)構(gòu)圖

可以看出安拟,實(shí)時(shí)雙工通信的基礎(chǔ)在于Redis部分蛤吓,核心就在于Pub/Sub模型,其余部分在此基礎(chǔ)上豐富了交互內(nèi)容去扣。

  • Server端 柱衔,用于模擬平時(shí)業(yè)務(wù)機(jī)器,對(duì)來自客戶端的Request給予Response愉棱。
  • WebSocket Server端唆铐,比如直播業(yè)務(wù)中在直播間內(nèi)聊天,肯定要用websocket來維系鏈接狀態(tài)奔滑,這里可以做到語言無關(guān)艾岂,既可以用Java,也可以用golang朋其。原理都是類似的王浴。根據(jù)雙工的特征,websocket服務(wù)器與客戶端發(fā)生信息交互的場(chǎng)景無非主動(dòng)和被動(dòng)梅猿,場(chǎng)景如下:
    • 主動(dòng)觸發(fā)氓辣, 指的是來自另一個(gè)客戶端的action,觸發(fā)了websocket服務(wù)器的push行為袱蚓。
    • 被動(dòng)觸發(fā)钞啸,比如定時(shí)器觸發(fā),狀態(tài)檢查等行為喇潘,都屬于被動(dòng)觸發(fā)
  • APP端体斩,一般團(tuán)隊(duì)都會(huì)以APP形式落地到終端用戶,web網(wǎng)頁也是類似颖低。在直播場(chǎng)景中絮吵,主播、觀眾實(shí)際上可以統(tǒng)一虛化為客戶端忱屑。

實(shí)現(xiàn)

從圖示上來看蹬敲,每一個(gè)模塊都不難,一點(diǎn)點(diǎn)去實(shí)現(xiàn)就好了莺戒。因?yàn)椴幌肷婕肮緲I(yè)務(wù)上的東西伴嗡,所以會(huì)有一些改動(dòng),如下:

  • APP端我這里會(huì)用JavaScript來作為客戶端脏毯,簡(jiǎn)單模擬下就闹究。
  • WebSocket服務(wù)器端,不打算用公司里Java版本食店,想試試golang版本渣淤。
  • Server端就用PHP簡(jiǎn)單模擬下赏寇。

websocket服務(wù)器端實(shí)現(xiàn)

// server.go
package main

import (
    "fmt"
    "log"
    "net/http"

    "time"

    "github.com/garyburd/redigo/redis"
    "golang.org/x/net/websocket"
)

var clients map[*websocket.Conn]string = make(map[*websocket.Conn]string)

// websocket 服務(wù)器端測(cè)試

func Echo(ws *websocket.Conn) {
    var err error
    if _, ok := clients[ws]; ok != true {
        clients[ws] = "匿名"
    }
    for {
        var reply string
        if err = websocket.Message.Receive(ws, &reply); err != nil {
            fmt.Println("Cannot receive")
            break
        }
        fmt.Println("Current client number: ", len(clients))
        fmt.Println("Received back from client: ", reply)
        msg := "RECEIVED: " + reply
        fmt.Println("Sending to client: " + msg)
        for client, _ := range clients {
            fmt.Println(client)
            if err = websocket.Message.Send(client, msg); err != nil {
                fmt.Println("Sending failed")
                break
            }

        }
    }
}

func tick() {
    for {
        time.Sleep(time.Second * 10)
        fmt.Println("checking ping")
        for key, _ := range clients {
            if key.IsClientConn() == false {
                // delete(clients, key)
            }
            // 對(duì)所有客戶端進(jìn)行訂閱消息的推送
            consume(clients)
        }
    }
}

func consume(clients map[*websocket.Conn]string) {
    client, err := redis.Dial("tcp", "localhost:6379")
    defer client.Close()
    if err != nil {
        fmt.Println(err)
    }
    psc := redis.PubSubConn{Conn: client}
    psc.Subscribe("channel")
    for {
        switch v := psc.Receive().(type) {
        case redis.Message:
            fmt.Printf("%s: message: %s\n", v.Channel, v.Data)
            for client, _ := range clients {
                fmt.Println(client)
                if err = websocket.Message.Send(client, string(v.Data)); err != nil {
                    fmt.Println("Sending failed")
                    break
                }
            }
        case redis.Subscription:
            fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
        }
    }
}

func main() {
    http.Handle("/", websocket.Handler(Echo))
    go tick()
    if err := http.ListenAndServe(":1234", nil); err != nil {
        log.Fatal("ListenAndServe failed: ", err)
    }
}

app端實(shí)現(xiàn)

// client.js
var wsServer = 'ws://localhost:1234';
var websocket = new WebSocket(wsServer);
websocket.onopen = function (evt) {
    console.log("Connected to WebSocket server.");
};

websocket.onclose = function (evt) {
    console.log("Disconnected");
};

websocket.onmessage = function (evt) {
    console.log('Retrieved data from server: ' + evt.data);
};

websocket.onerror = function (evt, e) {
    console.log('Error occured: ' + evt.data);
};
// 發(fā)送消息
websocket.send("hello world!")

server端實(shí)現(xiàn)

<?php

$redis = new Redis();
$redis->pconnect("localhost", 6379);
$ret = $redis->publish("channel", "data from PHP");
var_dump($ret);

測(cè)試

按照?qǐng)D例,打算對(duì)主動(dòng)觸發(fā)和被動(dòng)觸發(fā)進(jìn)行下測(cè)試价认。

主動(dòng)觸發(fā)

主動(dòng)觸發(fā)其實(shí)就是對(duì)websocket基本功能的測(cè)試嗅定,一般都是開倆客戶端,一段發(fā)消息用踩,看看另一端是否能收到就可以了渠退,流程是

1.啟動(dòng)websocket服務(wù)器

→ go run server.go

2.客戶端連接websocket服務(wù)器

Chrome 打開調(diào)試器console輸入上面的JavaScript代碼即可。

3.交互測(cè)試


主動(dòng)觸發(fā)測(cè)試

被動(dòng)觸發(fā)

被動(dòng)觸發(fā)一般都是定時(shí)任務(wù)脐彩,如定時(shí)器timer來觸發(fā)的碎乃。在上面golang代碼中,有這么一段調(diào)用惠奸。

go tick()
// 內(nèi)部調(diào)用了consume方法梅誓,來實(shí)現(xiàn)對(duì)subscribe消息的消費(fèi)

1.啟動(dòng)websocket服務(wù)器

? go run server.go
checking ping

2.客戶端js鏈接websocket服務(wù)器


javascripe鏈接websocket服務(wù)器

3.PHP去publish消息

? php publish.php
int(1)

4.查看客戶端是否可以收到websocket消費(fèi)到的數(shù)據(jù)


查看客戶端的確收到了對(duì)應(yīng)publish的消息

整理

至此,基本上雙工實(shí)時(shí)通信的demo就結(jié)束了佛南。相比于公司Java實(shí)現(xiàn)的版本梗掰,大體框架是類似的,無非是有沒有業(yè)務(wù)的支撐嗅回。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末及穗,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子绵载,更是在濱河造成了極大的恐慌埂陆,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尘分,死亡現(xiàn)場(chǎng)離奇詭異猜惋,居然都是意外死亡丸氛,警方通過查閱死者的電腦和手機(jī)培愁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缓窜,“玉大人定续,你說我怎么就攤上這事『檀福” “怎么了私股?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)恩掷。 經(jīng)常有香客問我倡鲸,道長(zhǎng),這世上最難降的妖魔是什么黄娘? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任峭状,我火速辦了婚禮克滴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘优床。我一直安慰自己劝赔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布胆敞。 她就那樣靜靜地躺著着帽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪移层。 梳的紋絲不亂的頭發(fā)上仍翰,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音观话,去河邊找鬼歉备。 笑死,一個(gè)胖子當(dāng)著我的面吹牛匪燕,可吹牛的內(nèi)容都是我干的蕾羊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼帽驯,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼龟再!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起尼变,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤利凑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后嫌术,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哀澈,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年度气,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了割按。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡磷籍,死狀恐怖适荣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情院领,我是刑警寧澤弛矛,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站比然,受9級(jí)特大地震影響丈氓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一万俗、第九天 我趴在偏房一處隱蔽的房頂上張望鱼鼓。 院中可真熱鬧,春花似錦该编、人聲如沸迄本。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嘉赎。三九已至,卻和暖如春于樟,著一層夾襖步出監(jiān)牢的瞬間公条,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工迂曲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留靶橱,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓路捧,卻偏偏與公主長(zhǎng)得像关霸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子杰扫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容