Go搭建Websocket訊息推送服務
0x01。Package
Http 框架:gin-gonic/gin
Websocket 框架:olahol/melody (在 gorilla/websocket 上封裝的一個庫,更易使用)
0x02。Getting Start
#chat {
text-align: left;
background: #f1f1f1;
width: 500px;
min-height: 300px;
padding: 20px;
}
Chat
var url = “ws://” + window。location。host + “/ws?room=CATCH_100024”;
var ws = new WebSocket(url);
var name = “Guest” + Math。floor(Math。random() * 1000);
var chat = document。getElementById(“chat”);
var text = document。getElementById(“text”);
var now = function () {
var iso = new Date()。toISOString();
return iso。split(“T”)[1]。split(“。”)[0];
};
ws。onmessage = function (msg) {
var line = now() + “ ” + msg。data + “\n”;
chat。innerText += line;
};
text。onkeydown = function (e) {
if (e。keyCode === 13 && text。value !== “”) {
ws。send(“<” + name + “> ” + text。value);
text。value = “”;
}
};
package
main
import
(
“github。com/gin-gonic/gin”
“gopkg。in/olahol/melody。v1”
“fmt”
“sync”
)
//連線數
var
counter
int
=
0
//讀寫鎖
var
mutex
sync
。
RWMutex
//房間和連線的對應關係
var
sm
map
[
*
melody
。
Session
]
string
=
make
(
map
[
*
melody
。
Session
]
string
)
//發生錯誤時的channel
var
CloseChan
chan
*
melody
。
Session
=
make
(
chan
*
melody
。
Session
,
100
)
func
main
()
{
//建立gin例項和melody例項
r
:=
gin
。
Default
()
m
:=
melody
。
New
()
//返回客戶端頁面
r
。
GET
(
“/”
,
func
(
c
*
gin
。
Context
)
{
c
。
File
(
“index。html”
)
})
//ws握手鍊接
r
。
GET
(
“/ws”
,
func
(
c
*
gin
。
Context
)
{
//這裡進行協議升級升級
m
。
HandleRequest
(
c
。
Writer
,
c
。
Request
)
})
//訪問這個網頁傳入一個room的query 就會向room房間的所有連結傳送訊息
//這裡可以理解為一個rpc呼叫之類的,這裡簡單實用http進行觸發
r
。
GET
(
“/call”
,
func
(
c
*
gin
。
Context
)
{
room
:=
c
。
Query
(
“room”
)
// 這個房間所有的連結切片
targets
:=
make
([]
*
melody
。
Session
,
0
)
//讀取資料使用讀鎖
mutex
。
RLock
()
for
s
,
m
:=
range
sm
{
if
m
==
room
{
targets
=
append
(
targets
,
s
)
}
}
mutex
。
RUnlock
()
for
_
,
s
:=
range
targets
{
go
func
(
s
*
melody
。
Session
)
{
//
if
err
:=
s
。
Write
([]
byte
(
“123”
));
err
!=
nil
{
//發生錯誤就往CloseChan扔資料
CloseChan
<-
s
}
}(
s
)
}
c
。
JSON
(
200
,
gin
。
H
{
“ret”
:
0
,
“msg”
:
“success”
,
})
})
//websocket連線狀態的路由
r
。
GET
(
“/health”
,
func
(
c
*
gin
。
Context
)
{
list
:=
make
([]
string
,
0
)
mutex
。
Lock
()
for
_
,
room
:=
range
sm
{
list
=
append
(
list
,
room
)
}
mutex
。
Unlock
()
c
。
JSON
(
200
,
gin
。
H
{
“list”
:
list
,
“counter”
:
counter
,
//自己記錄的連結數
“len”
:
m
。
Len
(),
//melody記錄的連線數
})
})
//處理websocket連線進入時的操作
//s *melody。Session s就是服務端和客戶端的連線物件
m
。
HandleConnect
(
func
(
s
*
melody
。
Session
)
{
fmt
。
(
“Connect”
)
//在index。html的握手地址傳入的query,用來區分房間資訊,這樣就可以將session透過room進行分組
//s。Request就是握手是傳入的Request
var
room
string
=
s
。
Request
。
FormValue
(
“room”
)
// 因為這個是併發的操作所以需要讀寫鎖
mutex
。
Lock
()
counter
++
sm
[
s
]
=
room
mutex
。
Unlock
()
})
//收到訊息回撥的事件
m
。
HandleMessage
(
func
(
s
*
melody
。
Session
,
msg
[]
byte
)
{
fmt
。
Println
(
“Message”
)
//將收到的資料廣播給所有連結
//m有很多廣播相關的方法,如廣播給出了自己的其他人
m
。
Broadcast
(
msg
)
})
m
。
HandleDisconnect
(
func
(
s
*
melody
。
Session
)
{
fmt
。
Println
(
“Disconnect”
)
CloseChan
<-
s
})
//這裡啟動一個協程 進行房間和連結對應關心的維護
go
func
()
{
for
{
select
{
case
s
:=
<-
CloseChan
:
go
func
()
{
if
!
s
。
IsClosed
()
{
s
。
Close
()
}
mutex
。
Lock
()
counter
——
delete
(
sm
,
s
)
mutex
。
Unlock
()
}()
}
}
}()
r
。
Run
(
“:5000”
)
}
可以透過訪問health得到連線資訊
0x03。Summary
透過上述例子可以,可以簡單的瞭解 websocket 的使用。透過維護連線 session 和 room的對印關係就實現了一個組聊的功能,這裡是維護的是一個map。當然也可以封裝成Struct維護房間和連線的關係,來實現組聊,私聊的功能。
curl
http://
localhost:5000/call?
room=CATCH_100024
來實現想房間中的人傳送訊息,這裡就可以理解為一個http的rpc呼叫,當然也可以透過其他的方式觸發這個事件。