Go使用WebSocket實(shí)現(xiàn)一個(gè)公域聊天室
什么是「公域聊天室」?
所有連接到服務(wù)端的用戶,都在同一個(gè)公共房間里:
- 任意用戶發(fā)消息,所有人都能看到
- 用戶進(jìn)入 / 離開,系統(tǒng)會(huì)全員通知
- 支持多人同時(shí)在線,互不阻塞
核心原理
- WebSocket 長(zhǎng)連接:客戶端和服務(wù)端永久連通,隨時(shí)收發(fā)消息
- Go goroutine 并發(fā):每個(gè)用戶獨(dú)占一個(gè)協(xié)程,多人連接不阻塞
- 全局客戶端列表 + 廣播:服務(wù)端存儲(chǔ)所有在線用戶,一人發(fā)消息,全員推送
- sync.Mutex 互斥鎖:多人同時(shí)操作客戶端列表,保證線程安全
代碼具體實(shí)現(xiàn)
定義全局變量(存儲(chǔ)用戶 + 保證并發(fā)安全)
var (
// 存儲(chǔ)所有在線的WebSocket連接(核心:全員廣播的基礎(chǔ))
clients = make(map[*websocket.Conn]bool)
// 存儲(chǔ)連接對(duì)應(yīng)的用戶名(區(qū)分誰發(fā)的消息)
clientNames = make(map[*websocket.Conn]string)
// 互斥鎖:多個(gè)協(xié)程同時(shí)修改map,必須加鎖,否則程序崩潰
clientsMutex sync.Mutex
// HTTP 升級(jí)為 WebSocket 的工具
upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // 允許跨域(瀏覽器訪問必備)
},
}
)
clients:記錄誰在線,用于廣播消息clientNames:給連接綁定用戶名clientsMutex:多人并發(fā)操作共享數(shù)據(jù)的安全保障upgrader:把普通 HTTP 請(qǐng)求 → 長(zhǎng)連接 WebSocket
啟動(dòng)服務(wù),注冊(cè)路由
func main() {
// 注冊(cè)路由:訪問 /ws 就觸發(fā)聊天處理邏輯
http.HandleFunc("/ws", wsHandler)
fmt.Println("WebSocket 服務(wù)啟動(dòng):ws://127.0.0.1:8081/ws")
// 核心:?jiǎn)?dòng)HTTP服務(wù),自動(dòng)為每個(gè)客戶端開goroutine(不阻塞)
if err := http.ListenAndServe(":8081", nil); err != nil {
fmt.Println("服務(wù)啟動(dòng)失敗:", err)
}
}
wsHandler(用戶連接全流程)
這是聊天室最核心的函數(shù),處理用戶從「連接→登錄→聊天→斷開」的全生命周期:
升級(jí) HTTP 為 WebSocket 連接
// 把普通HTTP請(qǐng)求,升級(jí)為長(zhǎng)連接WebSocket conn, err := upgrader.Upgrade(w, r, nil) // 函數(shù)結(jié)束自動(dòng)關(guān)閉連接(用戶斷開時(shí)觸發(fā)) defer conn.Close()
業(yè)務(wù)交互:讓用戶輸入用戶名
// 服務(wù)端主動(dòng)發(fā)消息:歡迎語
conn.WriteMessage(websocket.TextMessage, []byte("歡迎連接!請(qǐng)輸入你的名字:"))
// 讀取用戶輸入的名字
_, msg, err := conn.ReadMessage()
name := string(msg)
業(yè)務(wù)交互:注冊(cè)用戶 + 廣播進(jìn)入通知
// 把當(dāng)前用戶加入在線列表
registerClient(conn, name)
// 廣播:XXX 進(jìn)入聊天室(全員可見)
broadcast("系統(tǒng): " + name + " 進(jìn)入了聊天室")
用戶斷開時(shí)自動(dòng)清理
// defer:函數(shù)最后執(zhí)行(用戶關(guān)閉網(wǎng)頁時(shí)觸發(fā))
defer func() {
unregisterClient(conn) // 從在線列表刪除
broadcast("系統(tǒng): " + name + " 離開了聊天室") // 廣播離開消息
}()
主要業(yè)務(wù):循環(huán)讀取消息 + 全員廣播
// 死循環(huán):持續(xù)監(jiān)聽用戶發(fā)的消息
for {
_, msg, err := conn.ReadMessage()
if err != nil {
break // 出錯(cuò)/斷開,退出循環(huán)
}
// 拼接消息:用戶名: 內(nèi)容
chatMsg := fmt.Sprintf("%s: %s", name, string(msg))
// 廣播給所有人
broadcast(chatMsg)
}
線程安全的用戶注冊(cè) / 注銷
因?yàn)?strong>多個(gè)協(xié)程同時(shí)修改全局 map,必須加鎖,否則程序崩潰:
// 注冊(cè)用戶:加鎖 → 修改 → 解鎖
func registerClient(conn *websocket.Conn, name string) {
clientsMutex.Lock() // 加鎖
defer clientsMutex.Unlock() // 自動(dòng)解鎖
clients[conn] = true
clientNames[conn] = name
}
// 注銷用戶:從map刪除
func unregisterClient(conn *websocket.Conn) {
clientsMutex.Lock()
defer clientsMutex.Unlock()
delete(clients, conn)
delete(clientNames, conn)
}
廣播函數(shù)(一人發(fā)消息,全員收到)
func broadcast(message string) {
clientsMutex.Lock()
defer clientsMutex.Unlock()
// 遍歷所有在線用戶
for client := range clients {
// 給每個(gè)用戶發(fā)送消息
client.WriteMessage(websocket.TextMessage, []byte(message))
}
}
給你返回全部代碼(doge)??
package main
import (
"fmt"
"net/http"
"sync"
"github.com/gorilla/websocket" // go get github.com/gorilla/websocket
)
var (
// clients 存儲(chǔ)所有在線的 WebSocket 連接
clients = make(map[*websocket.Conn]bool)
// clientNames 存儲(chǔ)連接對(duì)應(yīng)的用戶名
clientNames = make(map[*websocket.Conn]string)
// clientsMutex 保護(hù) clients 和 clientNames 的并發(fā)讀寫
clientsMutex sync.Mutex
// upgrader 用于將 HTTP 請(qǐng)求升級(jí)為 WebSocket 請(qǐng)求
upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // 允許跨域
},
}
)
func main() {
// 注冊(cè) WebSocket 路由
http.HandleFunc("/ws", wsHandler)
fmt.Println("WebSocket 服務(wù)啟動(dòng):ws://127.0.0.1:8081/ws")
if err := http.ListenAndServe(":8081", nil); err != nil {
fmt.Println("服務(wù)啟動(dòng)失敗:", err)
}
}
// wsHandler 處理 WebSocket 連接請(qǐng)求
func wsHandler(w http.ResponseWriter, r *http.Request) {
// 1. 升級(jí) HTTP 連接為 WebSocket 連接
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
fmt.Println("升級(jí)失?。?, err)
return
}
// 確保連接最終被關(guān)閉
defer conn.Close()
// 2. 詢問并讀取用戶名
if err := conn.WriteMessage(websocket.TextMessage, []byte("歡迎連接!請(qǐng)輸入你的名字:")); err != nil {
return
}
_, msg, err := conn.ReadMessage()
if err != nil {
return
}
name := string(msg)
// 3. 注冊(cè)新用戶并廣播進(jìn)入消息
registerClient(conn, name)
broadcast("系統(tǒng): " + name + " 進(jìn)入了聊天室")
fmt.Printf("用戶 [%s] 已加入\n", name)
// 4. 設(shè)置斷開連接時(shí)的清理操作
defer func() {
unregisterClient(conn)
broadcast("系統(tǒng): " + name + " 離開了聊天室")
fmt.Printf("用戶 [%s] 已斷開\n", name)
}()
// 5. 循環(huán)讀取消息并廣播
for {
_, msg, err := conn.ReadMessage()
if err != nil {
break // 讀取錯(cuò)誤或連接關(guān)閉
}
// 構(gòu)造并廣播消息
chatMsg := fmt.Sprintf("%s: %s", name, string(msg))
fmt.Println(chatMsg) // 服務(wù)端日志
broadcast(chatMsg)
}
}
// registerClient 線程安全地注冊(cè)新連接
func registerClient(conn *websocket.Conn, name string) {
clientsMutex.Lock()
defer clientsMutex.Unlock()
clients[conn] = true
clientNames[conn] = name
}
// unregisterClient 線程安全地注銷連接
func unregisterClient(conn *websocket.Conn) {
clientsMutex.Lock()
defer clientsMutex.Unlock()
delete(clients, conn)
delete(clientNames, conn)
}
// broadcast 向所有在線客戶端廣播消息
func broadcast(message string) {
clientsMutex.Lock()
defer clientsMutex.Unlock()
msgBytes := []byte(message)
for client := range clients {
// 注意:如果某個(gè)客戶端網(wǎng)絡(luò)阻塞,這里可能會(huì)阻塞較長(zhǎng)時(shí)間
// 實(shí)際生產(chǎn)中應(yīng)將發(fā)送邏輯放入獨(dú)立的 goroutine 或使用 channel
err := client.WriteMessage(websocket.TextMessage, msgBytes)
if err != nil {
fmt.Printf("廣播消息失敗: %v\n", err)
client.Close()
delete(clients, client)
delete(clientNames, client)
}
}
}
到此這篇關(guān)于Go使用WebSocket實(shí)現(xiàn)一個(gè)公域聊天室的文章就介紹到這了,更多相關(guān)Go WebSocket公域聊天室內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
利用go-kit組件進(jìn)行服務(wù)注冊(cè)與發(fā)現(xiàn)和健康檢查的操作
這篇文章主要介紹了利用go-kit組件進(jìn)行服務(wù)注冊(cè)與發(fā)現(xiàn)和健康檢查的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-04-04
Go語言Gin框架獲取請(qǐng)求參數(shù)的兩種方式
在添加路由處理函數(shù)之后,就可以在路由處理函數(shù)中編寫業(yè)務(wù)處理代碼了,而編寫業(yè)務(wù)代碼第一件事一般就是獲取HTTP請(qǐng)求的參數(shù)吧,Gin框架在net/http包的基礎(chǔ)上封裝了獲取參數(shù)的方式,本文小編給大家介紹了獲取參數(shù)的兩種方式,需要的朋友可以參考下2024-01-01
詳解golang consul-grpc 服務(wù)注冊(cè)與發(fā)現(xiàn)
這篇文章主要介紹了詳解golang consul-grpc 服務(wù)注冊(cè)與發(fā)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-06-06
Golang設(shè)計(jì)模式之適配器模式詳細(xì)講解
這篇文章主要介紹了使用go實(shí)現(xiàn)適配器模式,這個(gè)模式就是用來做適配的,它將不兼容的接口轉(zhuǎn)換為可兼容的接口,讓原本由于接口不兼容而不能一起工作的類可以一起工作,需要的朋友可以參考下2023-01-01
go簡(jiǎn)介及國(guó)內(nèi)鏡像源配置全過程
本文介紹了Go語言的基本概念和環(huán)境配置,包括GOROOT、GOPATH和GOMODULE的設(shè)置,還展示了如何在IDEA中配置Go語言的開發(fā)環(huán)境,并通過一個(gè)簡(jiǎn)單的“HelloWorld”項(xiàng)目來熟悉Go語言的基本語法和開發(fā)流程2025-01-01
Go和RabbitMQ構(gòu)建高效的消息隊(duì)列系統(tǒng)
本文主要介紹了使用Go語言和RabbitMQ搭建一個(gè)簡(jiǎn)單的消息隊(duì)列系統(tǒng),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-01-01
Golang TCP網(wǎng)絡(luò)編程的具體實(shí)現(xiàn)
go語言是一門功能強(qiáng)大的編程語言,它提供了眾多的網(wǎng)絡(luò)編程庫,其中包括tcp/ip,本文主要介紹了Golang TCP網(wǎng)絡(luò)編程的具體實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以來了解一下2024-06-06

