使用 Go 语言实现简单聊天系统
在互联网时代,聊天系统是常见的应用场景之一。无论是即时通讯、在线客服还是多人游戏中的消息系统,聊天功能的实现都是必不可少的。本文将使用 Go 语言,结合 WebSocket 来构建一个简单的多人聊天室系统。
一、项目结构
首先,我们设计一个简单的项目结构,文件结构如下:
go-chat/
│
├── main.go // 主程序
├── client.go // 处理客户端连接
└── hub.go // 消息管理
WebSocket 简介
WebSocket 是一种基于 TCP 的网络协议,允许客户端和服务端建立持久的全双工通信连接。相比于传统的 HTTP 请求-响应模型,WebSocket 更加适合实时通信场景,因此它是实现聊天系统的理想选择。
二、实现思路
- 客户端连接管理:每个客户端通过 WebSocket 连接到服务器,服务器会为每个连接的客户端分配一个唯一的
connection
。 - 消息广播:当某个客户端发送消息时,服务器将该消息广播给所有连接的客户端。
- 并发处理:Go 原生支持并发编程,通过 Goroutine 和 Channel 可以轻松处理并发消息传递。
三、详细实现
1. main.go
- 启动 WebSocket 服务
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("服务器启动,监听端口 8080...")
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("监听失败:", err)
}
}
main.go
文件的作用是启动 HTTP 服务器,并在 /ws
路径上处理 WebSocket 连接请求。
2. hub.go
- 消息管理中心
Hub
负责管理所有的客户端连接,以及消息的广播。
package main
// Hub 负责管理所有客户端的注册、注销及消息广播
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:保存当前连接的所有客户端。
- broadcast:一个通道,用于广播消息给所有客户端。
- register/unregister:用于客户端连接和断开的注册和注销。
3. client.go
- 处理客户端连接
每个客户端的连接由 Client
结构体表示,并包含了 WebSocket 连接和发送消息的通道。
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 连接请求,并为每个连接创建一个客户端实例。readPump
:从 WebSocket 连接中读取消息,并将消息广播到所有客户端。writePump
:负责将消息发送给客户端,并定期发送心跳检测(ping)消息以保持连接。
四、运行项目
-
首先,安装 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()">发送</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>
-
运行 Go 服务:
go run main.go
-
打开浏览器,访问
index.html
,即可体验多人聊天室的功能。