websocket借助Redis实现实时双工通信

最近在梳理一些知识点,已脱敏并去除公司实现,做一些自己理解上的实践。

结构

本次打算模拟下一个实时双工交互的业务实践,先来张图。

模式结构图

可以看出,实时双工通信的基础在于Redis部分,核心就在于Pub/Sub模型,其余部分在此基础上丰富了交互内容。

  • Server端 ,用于模拟平时业务机器,对来自客户端的Request给予Response
  • WebSocket Server端,比如直播业务中在直播间内聊天,肯定要用websocket来维系链接状态,这里可以做到语言无关,既可以用Java,也可以用golang。原理都是类似的。根据双工的特征,websocket服务器与客户端发生信息交互的场景无非主动和被动,场景如下:
    • 主动触发, 指的是来自另一个客户端的action,触发了websocket服务器的push行为。
    • 被动触发,比如定时器触发,状态检查等行为,都属于被动触发
  • APP端,一般团队都会以APP形式落地到终端用户,web网页也是类似。在直播场景中,主播、观众实际上可以统一虚化为客户端

实现

从图示上来看,每一个模块都不难,一点点去实现就好了。因为不想涉及公司业务上的东西,所以会有一些改动,如下:

  • APP端我这里会用JavaScript来作为客户端,简单模拟下就。
  • WebSocket服务器端,不打算用公司里Java版本,想试试golang版本。
  • Server端就用PHP简单模拟下。

websocket服务器端实现

// 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 服务器端测试

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)
            }
            // 对所有客户端进行订阅消息的推送
            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端实现

// 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);
};
// 发送消息
websocket.send("hello world!")

server端实现

<?php

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

测试

按照图例,打算对主动触发和被动触发进行下测试。

主动触发

主动触发其实就是对websocket基本功能的测试,一般都是开俩客户端,一段发消息,看看另一端是否能收到就可以了,流程是

1.启动websocket服务器

→ go run server.go

2.客户端连接websocket服务器

Chrome 打开调试器console输入上面的JavaScript代码即可。

3.交互测试


主动触发测试

被动触发

被动触发一般都是定时任务,如定时器timer来触发的。在上面golang代码中,有这么一段调用。

go tick()
// 内部调用了consume方法,来实现对subscribe消息的消费

1.启动websocket服务器

➜ go run server.go
checking ping

2.客户端js链接websocket服务器


javascripe链接websocket服务器

3.PHP去publish消息

➜ php publish.php
int(1)

4.查看客户端是否可以收到websocket消费到的数据


查看客户端的确收到了对应publish的消息

整理

至此,基本上双工实时通信的demo就结束了。相比于公司Java实现的版本,大体框架是类似的,无非是有没有业务的支撑。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348

推荐阅读更多精彩内容