使用Golang实现简易聊天室,核心是利用goroutine处理并发连接,通过channel实现消息的流转和广播,结合WebSocket协议完成客户端和服务端的实时通信。整体流程可以分为服务端初始化、客户端连接管理、消息收发处理三个核心部分。

核心组件设计
简易聊天室需要三个核心组件:服务端实例、客户端连接实例、消息广播通道。服务端负责管理所有客户端连接,客户端实例封装单个WebSocket连接的相关信息,消息广播通道用于在不同goroutine之间传递消息。
客户端结构体定义
每个客户端需要包含唯一标识、对应的WebSocket连接、用于接收该客户端消息的通道:
package main
import (
"github.com/gorilla/websocket"
)
// 客户端结构体
type Client struct {
ID string // 客户端唯一标识
Conn *websocket.Conn // WebSocket连接实例
Send chan []byte // 用于向该客户端发送消息的通道
}
服务端结构体定义
服务端需要维护所有在线的客户端,同时提供注册、注销客户端和广播消息的方法:
// 服务端结构体
type Server struct {
Clients map[string]*Client // 存储所有在线客户端,key为客户端ID
Register chan *Client // 客户端注册通道
Unregister chan *Client // 客户端注销通道
Broadcast chan []byte // 全局消息广播通道
}
服务端初始化与运行逻辑
服务端启动后需要初始化各个通道,同时启动一个主goroutine处理客户端的注册、注销和消息广播逻辑。
服务端初始化方法
// 初始化服务端
func NewServer() *Server {
return &Server{
Clients: make(map[string]*Client),
Register: make(chan *Client),
Unregister: make(chan *Client),
Broadcast: make(chan []byte),
}
}
服务端主运行逻辑
主goroutine循环监听三个通道的事件,处理对应的业务逻辑:
// 服务端运行主逻辑
func (s *Server) Run() {
for {
select {
// 处理新客户端注册
case client := <-s.Register:
s.Clients[client.ID] = client
// 给新加入的客户端发送欢迎消息
welcomeMsg := []byte("欢迎加入聊天室,当前在线人数:" + string(len(s.Clients)))
client.Send <- welcomeMsg
// 处理客户端注销
case client := <-s.Unregister:
if _, ok := s.Clients[client.ID]; ok {
delete(s.Clients, client.ID)
close(client.Send)
}
// 处理广播消息
case message := <-s.Broadcast:
// 遍历所有在线客户端,发送消息
for _, client := range s.Clients {
select {
case client.Send <- message:
// 如果发送失败,说明客户端已经断开,执行注销逻辑
default:
delete(s.Clients, client.ID)
close(client.Send)
}
}
}
}
}
WebSocket连接处理
需要使用gorilla/websocket库处理WebSocket连接升级,每个新的WebSocket连接都会对应一个独立的goroutine处理消息读写。
连接升级配置
import (
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
// 允许跨域请求
CheckOrigin: func(r *http.Request) bool {
return true
},
}
处理WebSocket连接的方法
当有新的HTTP请求升级为WebSocket连接时,创建客户端实例,注册到服务端,同时启动读写goroutine:
// 处理WebSocket连接
func (s *Server) HandleWebSocket(w http.ResponseWriter, r *http.Request) {
// 升级HTTP连接为WebSocket连接
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
// 生成客户端唯一ID,这里简单用连接远程地址作为ID
clientID := conn.RemoteAddr().String()
client := &Client{
ID: clientID,
Conn: conn,
Send: make(chan []byte, 256),
}
// 注册客户端到服务端
s.Register <- client
// 启动写goroutine,负责向客户端发送消息
go s.writePump(client)
// 启动读goroutine,负责读取客户端发送的消息
go s.readPump(client)
}
读goroutine实现
读goroutine持续读取客户端发送的消息,将消息发送到服务端的广播通道:
// 读goroutine,读取客户端消息
func (s *Server) readPump(client *Client) {
defer func() {
// 断开连接时注销客户端
s.Unregister <- client
client.Conn.Close()
}()
for {
// 读取消息类型和消息内容
messageType, message, err := client.Conn.ReadMessage()
if err != nil {
break
}
// 只处理文本消息
if messageType == websocket.TextMessage {
// 拼接客户端ID和消息内容,再广播
fullMsg := []byte(client.ID + ":" + string(message))
s.Broadcast <- fullMsg
}
}
}
写goroutine实现
写goroutine持续从客户端的Send通道读取消息,发送给客户端:
// 写goroutine,向客户端发送消息
func (s *Server) writePump(client *Client) {
defer client.Conn.Close()
for {
select {
case message, ok := <-client.Send:
if !ok {
// 通道关闭,说明客户端已经注销
return
}
// 发送文本消息
err := client.Conn.WriteMessage(websocket.TextMessage, message)
if err != nil {
return
}
}
}
}
主函数启动服务
最后在main函数中初始化服务端,启动服务端主逻辑,同时注册HTTP路由处理WebSocket连接:
package main
import (
"log"
"net/http"
)
func main() {
server := NewServer()
// 启动服务端主逻辑goroutine
go server.Run()
// 注册WebSocket处理路由
http.HandleFunc("/ws", server.HandleWebSocket)
// 启动HTTP服务,监听8080端口
log.Println("聊天室服务启动,监听端口8080")
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("服务启动失败:", err)
}
}
项目运行与测试
首先需要安装gorilla/websocket依赖,执行命令:
go get github.com/gorilla/websocket
运行服务端代码后,可以使用支持WebSocket的客户端工具连接ws://127.0.0.1:8080/ws,多个客户端连接后发送消息,就可以看到其他客户端能收到广播的消息,简易聊天室就实现完成了。整个流程中goroutine负责处理每个连接的并发逻辑,channel负责不同组件之间的消息传递,是Golang并发特性的典型应用场景。