主要思路:
1.封裝*websocket.conn,用client結(jié)構(gòu)表示一個(gè)客戶(hù)端。
2.維持一個(gè)map[client]bool,表示有效的客戶(hù)端映射,用于廣播消息
3.除了處理websocket連接外,還要開(kāi)啟一個(gè)廣播協(xié)程,監(jiān)聽(tīng)客戶(hù)端連接,斷開(kāi),發(fā)彈幕事件。
推薦:《go語(yǔ)言教程》
主要的結(jié)構(gòu):
type Client struct{
wsConnect *websocket.Conn
inChan chan []byte
outChan chan []byte
closeChan chan byte
Name string //客戶(hù)的名稱(chēng)
Id string //客戶(hù)id,唯一
mutex sync.Mutex // 對(duì)closeChan關(guān)閉上鎖
IsClosed bool // 防止closeChan被關(guān)閉多次
}
type Message struct {
EventType byte `json:"type"` // 0表示用戶(hù)發(fā)布消息;1表示用戶(hù)進(jìn)入;2表示用戶(hù)退出
Name string `json:"name"` // 用戶(hù)名稱(chēng)
Message string `json:"message"` // 消息內(nèi)容
}
clients = make(map [*util.Client] bool) // 用戶(hù)組映射
join = make(chan *util.Client, 10) // 用戶(hù)加入通道
leave = make(chan *util.Client, 10) // 用戶(hù)退出通道
message = make(chan Message, 10) // 消息通道
server端代碼
package main
import (
"encoding/json"
"fmt"
"github.com/gorilla/websocket"
"goGin/server/util"
"net/http"
)
var(
upgrader = websocket.Upgrader{
// 允許跨域
CheckOrigin:func(r *http.Request) bool{
return true
},
}
clients = make(map [*util.Client] bool) // 用戶(hù)組映射
join = make(chan *util.Client, 10) // 用戶(hù)加入通道
leave = make(chan *util.Client, 10) // 用戶(hù)退出通道
message = make(chan Message, 10) // 消息通道
)
type Message struct {
EventType byte `json:"type"` // 0表示用戶(hù)發(fā)布消息;1表示用戶(hù)進(jìn)入;2表示用戶(hù)退出
Name string `json:"name"` // 用戶(hù)名稱(chēng)
Message string `json:"message"` // 消息內(nèi)容
}
func wsHandler(w http.ResponseWriter , r *http.Request){
var(
wsConn *websocket.Conn
err error
client *util.Client
data []byte
)
r.ParseForm() //返回一個(gè)map,并且賦值給r.Form
name := r.Form["name"][0]
id := r.Form["id"][0]
if wsConn , err = upgrader.Upgrade(w,r,nil); err != nil{
return
}
if client , err = util.InitConnection(wsConn); err != nil{
goto ERR
}
client.Id = id
client.Name = name
// 如果用戶(hù)列表中沒(méi)有該用戶(hù)
if !clients[client] {
join <- client
}
for {
if data , err = client.ReadMessage();err != nil{ //一直讀消息,沒(méi)有消息就阻塞
goto ERR
}
var msg Message
msg.EventType = 0
msg.Name = client.Name
msg.Message = string(data)
message <- msg
}
ERR:
leave<-client//這個(gè)客戶(hù)斷開(kāi)
client.Close()
}
func broadcaster() {
for {
select {
// 消息通道中有消息則執(zhí)行,否則堵塞
case msg := <-message:
// 將數(shù)據(jù)編碼成json形式,data是[]byte類(lèi)型
// json.Marshal()只會(huì)編碼結(jié)構(gòu)體中公開(kāi)的屬性(即大寫(xiě)字母開(kāi)頭的屬性)
data, err := json.Marshal(msg)
if err != nil {
return
}
for client := range clients {
if client.IsClosed == true {
leave<-client//這個(gè)客戶(hù)斷開(kāi)
continue
}
// fmt.Println("=======the json message is", string(data)) // 轉(zhuǎn)換成字符串類(lèi)型便于查看
if client.WriteMessage(data) != nil {
continue //發(fā)送失敗就跳過(guò)
}
}
// 有用戶(hù)加入
case client := <-join:
clients[client] = true // 將用戶(hù)加入映射
// 將用戶(hù)加入消息放入消息通道
var msg Message
msg.Name = client.Name
msg.EventType = 1
msg.Message = fmt.Sprintf("%s join in, there are %d preson in room", client.Name, len(clients))
message <- msg
// 有用戶(hù)退出
case client := <-leave:
// 如果該用戶(hù)已經(jīng)被刪除
if !clients[client] {
break
}
delete(clients, client) // 將用戶(hù)從映射中刪除
// 將用戶(hù)退出消息放入消息通道
var msg Message
msg.Name = client.Name
msg.EventType = 2
msg.Message = fmt.Sprintf("%s leave, there are %d preson in room", client.Name, len(clients))
message <- msg
}
}
}
func main(){
go broadcaster()
http.HandleFunc("/ws",wsHandler)
http.ListenAndServe("0.0.0.0:7777",nil)
}
封裝client
package util
import (
"github.com/gorilla/websocket"
"sync"
"errors"
)
type Client struct{
wsConnect *websocket.Conn
inChan chan []byte
outChan chan []byte
closeChan chan byte
Name string //客戶(hù)的名稱(chēng)
Id string //客戶(hù)id,唯一
mutex sync.Mutex // 對(duì)closeChan關(guān)閉上鎖
IsClosed bool // 防止closeChan被關(guān)閉多次
}
func InitConnection(wsConn *websocket.Conn)(conn *Client ,err error){
conn = &Client{
wsConnect:wsConn,
inChan: make(chan []byte,1000),
outChan: make(chan []byte,1000),
closeChan: make(chan byte,1),
IsClosed:false,
}
// 啟動(dòng)讀協(xié)程
go conn.readLoop();
// 啟動(dòng)寫(xiě)協(xié)程
go conn.writeLoop();
return
}
func (conn *Client)ReadMessage()(data []byte , err error){
select{
case data = <- conn.inChan:
case <- conn.closeChan:
err = errors.New("connection is closeed")
}
return
}
func (conn *Client)WriteMessage(data []byte)(err error){
select{
case conn.outChan <- data:
case <- conn.closeChan:
err = errors.New("connection is closeed")
}
return
}
func (conn *Client)Close(){
// 線程安全,可多次調(diào)用
conn.wsConnect.Close()
// 利用標(biāo)記,讓closeChan只關(guān)閉一次
conn.mutex.Lock()
if !conn.IsClosed {
close(conn.closeChan)
conn.IsClosed = true
}
conn.mutex.Unlock()
}
func (conn *Client)readLoop(){
var(
data []byte
err error
)
for{
if _, data , err = conn.wsConnect.ReadMessage(); err != nil{
goto ERR
}
//阻塞在這里,等待inChan有空閑位置
select{
case conn.inChan <- data:
case <- conn.closeChan: // closeChan 感知 conn斷開(kāi)
goto ERR
}
}
ERR:
conn.Close()
}
func (conn *Client)writeLoop(){
var(
data []byte
err error
)
for{
select{
case data= <- conn.outChan:
case <- conn.closeChan:
goto ERR
}
if err = conn.wsConnect.WriteMessage(websocket.TextMessage , data); err != nil{
goto ERR
}
}
ERR:
conn.Close()
}
客戶(hù)端代碼
<!DOCTYPE html>
<html>
<head>
<title>go websocket</title>
<meta charset="utf-8" />
</head>
<body>
<script type="text/javascript">
var wsUri ="ws://127.0.0.1:7777/ws?name=aaa&id=112";
var output;
function init() {
output = document.getElementById("output");
testWebSocket();
}
function testWebSocket() {
websocket = new WebSocket(wsUri);
websocket.onopen = function(evt) {
onOpen(evt)
};
websocket.onclose = function(evt) {
onClose(evt)
};
websocket.onmessage = function(evt) {
onMessage(evt)
};
websocket.onerror = function(evt) {
onError(evt)
};
}
function onOpen(evt) {
writeToScreen("CONNECTED");
// doSend("WebSocket rocks");
}
function onClose(evt) {
writeToScreen("DISCONNECTED");
}
function onMessage(evt) {
writeToScreen('<span style="color: blue;">RESPONSE: ' evt.data '</span>');
// websocket.close();
}
function onError(evt) {
writeToScreen('<span style="color: red;">ERROR:</span> ' evt.data);
}
function doSend(message) {
// writeToScreen("SENT: " message);
websocket.send(message);
}
function writeToScreen(message) {
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}
window.addEventListener("load", init, false);
function sendBtnClick(){
var msg = document.getElementById("input").value;
doSend(msg);
document.getElementById("input").value = '';
}
function closeBtnClick(){
websocket.close();
}
</script>
<h2>WebSocket Test</h2>
<input type="text" id="input"></input>
<button onclick="sendBtnClick()" >send</button>
<button onclick="closeBtnClick()" >close</button>
<div id="output"></div>
</body>
</html>
更多關(guān)于云服務(wù)器,域名注冊(cè),虛擬主機(jī)的問(wèn)題,請(qǐng)?jiān)L問(wèn)西部數(shù)碼官網(wǎng):m.ps-sw.cn