網站首頁 編程語言 正文
服務端代碼 main.go
package main import ( "errors" "fmt" "net/http" "sync" "time" "github.com/gorilla/websocket" ) // http升級websocket協議的配置 var wsUpgrader = websocket.Upgrader{ // 允許所有CORS跨域請求 CheckOrigin: func(r *http.Request) bool { return true }, } // 客戶端讀寫消息 type wsMessage struct { messageType int data []byte } // 客戶端連接 type wsConnection struct { wsSocket *websocket.Conn // 底層websocket inChan chan *wsMessage // 讀隊列 outChan chan *wsMessage // 寫隊列 mutex sync.Mutex // 避免重復關閉管道 isClosed bool closeChan chan byte // 關閉通知 } func (wsConn *wsConnection) wsReadLoop() { for { // 讀一個message msgType, data, err := wsConn.wsSocket.ReadMessage() if err != nil { goto error } req := &wsMessage{ msgType, data, } // 放入請求隊列 select { case wsConn.inChan <- req: case <-wsConn.closeChan: goto closed } } error: wsConn.wsClose() closed: } func (wsConn *wsConnection) wsWriteLoop() { for { select { // 取一個應答 case msg := <-wsConn.outChan: // 寫給websocket if err := wsConn.wsSocket.WriteMessage(msg.messageType, msg.data); err != nil { goto error } case <-wsConn.closeChan: goto closed } } error: wsConn.wsClose() closed: } func (wsConn *wsConnection) procLoop() { // 啟動一個gouroutine發送心跳 go func() { for { //不斷向客戶端寫數據,其實沒有它也是一樣的,客戶端可以檢測到斷開 time.Sleep(2 * time.Second) if err := wsConn.wsWrite(websocket.TextMessage, []byte("heartbeat from server")); err != nil { fmt.Println("heartbeat fail") wsConn.wsClose() break } } }() // 這是一個同步處理模型(只是一個例子),如果希望并行處理可以每個請求一個gorutine,注意控制并發goroutine的數量!!! for { msg, err := wsConn.wsRead() if err != nil { fmt.Println("read fail") break } fmt.Println(string(msg.data)) err = wsConn.wsWrite(msg.messageType, msg.data) // 讀到數據后,同步的去寫數據。應該寫成異步 if err != nil { fmt.Println("write fail") break } } } func wsHandler(resp http.ResponseWriter, req *http.Request) { // 應答客戶端告知升級連接為websocket wsSocket, err := wsUpgrader.Upgrade(resp, req, nil) if err != nil { return } wsConn := &wsConnection{ wsSocket: wsSocket, inChan: make(chan *wsMessage, 1000), outChan: make(chan *wsMessage, 1000), closeChan: make(chan byte), isClosed: false, } // 處理器 go wsConn.procLoop() // 讀協程 go wsConn.wsReadLoop() // 寫協程 go wsConn.wsWriteLoop() } func (wsConn *wsConnection) wsWrite(messageType int, data []byte) error { select { case wsConn.outChan <- &wsMessage{messageType, data}: case <-wsConn.closeChan: return errors.New("websocket closed") } return nil } func (wsConn *wsConnection) wsRead() (*wsMessage, error) { select { case msg := <-wsConn.inChan: return msg, nil case <-wsConn.closeChan: } return nil, errors.New("websocket closed") } func (wsConn *wsConnection) wsClose() { wsConn.wsSocket.Close() wsConn.mutex.Lock() defer wsConn.mutex.Unlock() if !wsConn.isClosed { wsConn.isClosed = true close(wsConn.closeChan) } } func main() { http.HandleFunc("/ws", wsHandler) http.ListenAndServe("0.0.0.0:7777", nil) }
前端代碼 client.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
window.addEventListener("load", function(evt) {
const output = document.getElementById("output");
const input = document.getElementById("input");
let ws;
const print = function(message) {
const d = document.createElement("div");
d.innerHTML = message;
output.appendChild(d);
};
document.getElementById("open").onclick = function(evt) {
if (ws) {
return false;
}
ws = new WebSocket("ws://localhost:7777/ws");
ws.onopen = function(evt) {
print("OPEN");
}
ws.onclose = function(evt) {
print("CLOSE");
ws = null;
}
ws.onmessage = function(evt) {
print("RESPONSE: " + evt.data);
}
ws.onerror = function(evt) {
print("ERROR: " + evt.data);
}
return false;
};
document.getElementById("send").onclick = function(evt) {
if (!ws) {
return false;
}
print("SEND: " + input.value);
ws.send(input.value);
return false;
};
document.getElementById("close").onclick = function(evt) {
if (!ws) {
return false;
}
ws.close();
return false;
};
});
</script>
</head>
<body>
<table>
<tr><td valign="top" width="50%">
<p>Click "Open" to create a connection to the server,
"Send" to send a message to the server and "Close" to close the connection.
You can change the message and send multiple times.
</p>
<form>
<button id="open">Open</button>
<button id="close">Close</button>
<input id="input" type="text" value="Hello world!">
<button id="send">Send</button>
</form>
</td><td valign="top" width="50%">
<div id="output"></div>
</td></tr></table>
</body>
</html>
原文鏈接:https://juejin.cn/post/7181779790611873852
相關推薦
- 2022-03-12 一文搞懂MemoryCache?清除全部緩存的方法_實用技巧
- 2022-02-24 將antd中的Tree組件放在Form表單里面
- 2022-05-10 Element-ui 中 Table 表格的設置表頭/去除下標線/設置行間距等屬性的使用及 slot
- 2022-04-27 Python線程之線程安全的隊列Queue_python
- 2022-07-23 C++深入淺出探索數據結構的原理_C 語言
- 2022-05-08 利用Pandas讀取某列某行數據之loc和iloc用法總結_python
- 2022-04-09 SpringBoot 項目在Linux 環境下,日志文件logback撐爆云服務器
- 2022-06-17 Go語言使用Request,Response處理web頁面請求_Golang
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支