使用Go語言實(shí)現(xiàn)簡單聊天系統(tǒng)
在互聯(lián)網(wǎng)時代,聊天系統(tǒng)是常見的應(yīng)用場景之一。無論是即時通訊、在線客服還是多人游戲中的消息系統(tǒng),聊天功能的實(shí)現(xiàn)都是必不可少的。本文將使用 Go 語言,結(jié)合 WebSocket 來構(gòu)建一個簡單的多人聊天室系統(tǒng)。
一、項(xiàng)目結(jié)構(gòu)
首先,我們設(shè)計一個簡單的項(xiàng)目結(jié)構(gòu),文件結(jié)構(gòu)如下:
go-chat/ │ ├── main.go // 主程序 ├── client.go // 處理客戶端連接 └── hub.go // 消息管理
WebSocket 簡介
WebSocket 是一種基于 TCP 的網(wǎng)絡(luò)協(xié)議,允許客戶端和服務(wù)端建立持久的全雙工通信連接。相比于傳統(tǒng)的 HTTP 請求-響應(yīng)模型,WebSocket 更加適合實(shí)時通信場景,因此它是實(shí)現(xiàn)聊天系統(tǒng)的理想選擇。
二、實(shí)現(xiàn)思路
- 客戶端連接管理:每個客戶端通過 WebSocket 連接到服務(wù)器,服務(wù)器會為每個連接的客戶端分配一個唯一的
connection。 - 消息廣播:當(dāng)某個客戶端發(fā)送消息時,服務(wù)器將該消息廣播給所有連接的客戶端。
- 并發(fā)處理:Go 原生支持并發(fā)編程,通過 Goroutine 和 Channel 可以輕松處理并發(fā)消息傳遞。
三、詳細(xì)實(shí)現(xiàn)
1. main.go - 啟動 WebSocket 服務(wù)
package main
import (
"log"
"net/http"
)
func main() {
hub := newHub()
go hub.run()
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
serveWs(hub, w, r)
})
log.Println("服務(wù)器啟動,監(jiān)聽端口 8080...")
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("監(jiān)聽失敗:", err)
}
}
main.go 文件的作用是啟動 HTTP 服務(wù)器,并在 /ws 路徑上處理 WebSocket 連接請求。
2. hub.go - 消息管理中心
Hub 負(fù)責(zé)管理所有的客戶端連接,以及消息的廣播。
package main
// Hub 負(fù)責(zé)管理所有客戶端的注冊、注銷及消息廣播
type Hub struct {
clients map[*Client]bool // 已連接的客戶端
broadcast chan []byte // 從客戶端接收的廣播消息
register chan *Client // 注冊請求
unregister chan *Client // 注銷請求
}
func newHub() *Hub {
return &Hub{
clients: make(map[*Client]bool),
broadcast: make(chan []byte),
register: make(chan *Client),
unregister: make(chan *Client),
}
}
func (h *Hub) run() {
for {
select {
case client := <-h.register:
h.clients[client] = true
case client := <-h.unregister:
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
}
case message := <-h.broadcast:
for client := range h.clients {
select {
case client.send <- message:
default:
close(client.send)
delete(h.clients, client)
}
}
}
}
}
- clients:保存當(dāng)前連接的所有客戶端。
- broadcast:一個通道,用于廣播消息給所有客戶端。
- register/unregister:用于客戶端連接和斷開的注冊和注銷。
3. client.go - 處理客戶端連接
每個客戶端的連接由 Client 結(jié)構(gòu)體表示,并包含了 WebSocket 連接和發(fā)送消息的通道。
package main
import (
"github.com/gorilla/websocket"
"log"
"net/http"
"time"
)
const (
writeWait = 10 * time.Second
pongWait = 60 * time.Second
pingPeriod = (pongWait * 9) / 10
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
type Client struct {
hub *Hub
conn *websocket.Conn
send chan []byte
}
func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("升級到 WebSocket 失敗:", err)
return
}
client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)}
client.hub.register <- client
go client.writePump()
go client.readPump()
}
func (c *Client) readPump() {
defer func() {
c.hub.unregister <- c
c.conn.Close()
}()
c.conn.SetReadLimit(512)
c.conn.SetReadDeadline(time.Now().Add(pongWait))
c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
for {
_, message, err := c.conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("讀取錯誤: %v", err)
}
break
}
c.hub.broadcast <- message
}
}
func (c *Client) writePump() {
ticker := time.NewTicker(pingPeriod)
defer func() {
ticker.Stop()
c.conn.Close()
}()
for {
select {
case message, ok := <-c.send:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if !ok {
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
w, err := c.conn.NextWriter(websocket.TextMessage)
if err != nil {
return
}
w.Write(message)
n := len(c.send)
for i := 0; i < n; i++ {
w.Write(<-c.send)
}
if err := w.Close(); err != nil {
return
}
case <-ticker.C:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
}
}
}
serveWs:處理每個 WebSocket 連接請求,并為每個連接創(chuàng)建一個客戶端實(shí)例。readPump:從 WebSocket 連接中讀取消息,并將消息廣播到所有客戶端。writePump:負(fù)責(zé)將消息發(fā)送給客戶端,并定期發(fā)送心跳檢測(ping)消息以保持連接。
四、運(yùn)行項(xiàng)目
首先,安裝 WebSocket 依賴:
go get github.com/gorilla/websocket
編寫前端 HTML 頁面(可用于測試),例如 index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Go 聊天室</title>
</head>
<body>
<div id="chatbox"></div>
<input id="msg" type="text" />
<button onclick="sendMessage()">發(fā)送</button>
<script>
var ws = new WebSocket("ws://localhost:8080/ws");
ws.onmessage = function(event) {
var chatbox = document.getElementById('chatbox');
chatbox.innerHTML += event.data + "<br/>";
};
function sendMessage() {
var msg = document.getElementById("msg").value;
ws.send(msg);
}
</script>
</body>
</html>
運(yùn)行 Go 服務(wù):
go run main.go
打開瀏覽器,訪問 index.html,即可體驗(yàn)多人聊天室的功能。
到此這篇關(guān)于使用Go語言實(shí)現(xiàn)簡單聊天系統(tǒng)的文章就介紹到這了,更多相關(guān)Go語言聊天系統(tǒng)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go語言區(qū)塊鏈實(shí)戰(zhàn)實(shí)現(xiàn)簡單的區(qū)塊與區(qū)塊鏈
這篇文章主要為大家介紹了go語言區(qū)塊鏈的實(shí)戰(zhàn)學(xué)習(xí),來實(shí)現(xiàn)簡單的區(qū)塊與區(qū)塊鏈?zhǔn)纠^程,有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-10-10
golang?gin框架實(shí)現(xiàn)大文件的流式上傳功能
這篇文章主要介紹了golang?gin框架中實(shí)現(xiàn)大文件的流式上傳,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07
Go簡單實(shí)現(xiàn)協(xié)程池的實(shí)現(xiàn)示例
本文主要介紹了Go簡單實(shí)現(xiàn)協(xié)程池的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06

