最近在梳理一些知識(shí)點(diǎn),已脫敏并去除公司實(shí)現(xiàn)粉洼,做一些自己理解上的實(shí)踐节预。
結(jié)構(gòu)
本次打算模擬下一個(gè)實(shí)時(shí)雙工交互的業(yè)務(wù)實(shí)踐叶摄,先來張圖。
可以看出安拟,實(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ā)
被動(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ù)器
3.PHP去publish消息
? php publish.php
int(1)
4.查看客戶端是否可以收到websocket消費(fèi)到的數(shù)據(jù)
整理
至此,基本上雙工實(shí)時(shí)通信的demo就結(jié)束了佛南。相比于公司Java實(shí)現(xiàn)的版本梗掰,大體框架是類似的,無非是有沒有業(yè)務(wù)的支撐嗅回。