|
|
@@ -11,18 +11,15 @@ import (
|
|
|
"github.com/gorilla/websocket"
|
|
|
)
|
|
|
|
|
|
-// message 包含要转发的消息内容和发送者信息
|
|
|
-// 用于在转发时排除发送者自己
|
|
|
-type message struct {
|
|
|
- content []byte // 消息内容
|
|
|
- sender *Client // 发送者
|
|
|
-}
|
|
|
+// 全局房间 map,存储所有创建的房间
|
|
|
+// key: 房间名称,value: 房间对象指针
|
|
|
+var rooms = make(map[string]*Room)
|
|
|
|
|
|
// Room 表示一个聊天房间
|
|
|
type Room struct {
|
|
|
- // holds all current clientCons in the room
|
|
|
+ // holds all current clientConns in the room
|
|
|
// 保存房间中所有当前活跃的客户端连接
|
|
|
- clientCons map[*Client]bool
|
|
|
+ clientConns map[*Client]bool
|
|
|
|
|
|
// join is a channel for all clients wishing to join the room
|
|
|
// 用于接收希望加入房间的客户端。新客户端通过这个 channel 请求加入
|
|
|
@@ -42,23 +39,25 @@ type Room struct {
|
|
|
name string
|
|
|
}
|
|
|
|
|
|
-// 全局房间 map,存储所有创建的房间
|
|
|
-// key: 房间名称,value: 房间对象指针
|
|
|
-var rooms = make(map[string]*Room)
|
|
|
+// message 包含要转发的消息内容和发送者信息
|
|
|
+// 用于在转发时排除发送者自己
|
|
|
+type message struct {
|
|
|
+ content []byte // 消息内容
|
|
|
+ sender *Client // 发送者
|
|
|
+}
|
|
|
+
|
|
|
// 全局互斥锁,用于保护 rooms 字典的并发访问,确保多个 goroutine 同时访问 rooms 时的线程安全
|
|
|
var mu sync.Mutex
|
|
|
|
|
|
-
|
|
|
-
|
|
|
// newRoom 创建并返回一个新的房间实例
|
|
|
// 初始化所有必要的 channel 和 map
|
|
|
func newRoom(name string) *Room {
|
|
|
return &Room{
|
|
|
- clientCons: make(map[*Client]bool),
|
|
|
- join: make(chan *Client),
|
|
|
- leave: make(chan *Client),
|
|
|
- forward: make(chan *message), // 现在传输 message 结构体
|
|
|
- name: name, // 设置房间名称
|
|
|
+ clientConns: make(map[*Client]bool),
|
|
|
+ join: make(chan *Client),
|
|
|
+ leave: make(chan *Client),
|
|
|
+ forward: make(chan *message), // 现在传输 message 结构体
|
|
|
+ name: name, // 设置房间名称
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -67,7 +66,7 @@ func newRoom(name string) *Room {
|
|
|
func GetRoomAndRun(name string) *Room {
|
|
|
mu.Lock()
|
|
|
defer mu.Unlock()
|
|
|
-
|
|
|
+
|
|
|
if r, ok := rooms[name]; ok {
|
|
|
return r
|
|
|
}
|
|
|
@@ -102,15 +101,15 @@ func (r *Room) run() {
|
|
|
// 处理客户端加入事件
|
|
|
case client := <-r.join:
|
|
|
// 将新客户端添加到房间的客户端集合中
|
|
|
- r.clientCons[client] = true // 使用 true 作为值,实际上只使用 key(client 指针)
|
|
|
-
|
|
|
+ r.clientConns[client] = true // 使用 true 作为值,实际上只使用 key(client 指针)
|
|
|
+
|
|
|
// 处理客户端离开事件
|
|
|
case client := <-r.leave:
|
|
|
// 从房间的客户端集合中删除客户端
|
|
|
- delete(r.clientCons, client)
|
|
|
+ delete(r.clientConns, client)
|
|
|
// 关闭客户端的接收 channel,通知客户端的 write() 方法退出
|
|
|
close(client.receive)
|
|
|
-
|
|
|
+
|
|
|
// 处理消息转发事件
|
|
|
case msg := <-r.forward:
|
|
|
// 打印消息内容与发送者信息
|
|
|
@@ -118,10 +117,10 @@ func (r *Room) run() {
|
|
|
|
|
|
// 直接存储原始消息内容到Redis,使用房间名作为键
|
|
|
go StoreRawMessage(r.name, msg.content) // 异步存储,不阻塞消息转发
|
|
|
-
|
|
|
+
|
|
|
// 将消息发送给房间中的所有客户端,但排除发送者自己
|
|
|
// 使用指针比较是最高效的方法,因为每个客户端都有唯一的内存地址
|
|
|
- for client := range r.clientCons {
|
|
|
+ for client := range r.clientConns {
|
|
|
if client != msg.sender { // 高效的指针比较,排除发送者
|
|
|
// 将消息发送到每个客户端的接收 channel
|
|
|
client.receive <- msg.content // 只发送消息内容,不包含发送者信息
|
|
|
@@ -134,7 +133,7 @@ func (r *Room) run() {
|
|
|
// WebSocket 相关常量定义
|
|
|
const (
|
|
|
// WebSocket 连接的读写缓冲区大小(字节)
|
|
|
- socketBufferSize = 1024
|
|
|
+ socketBufferSize = 1024
|
|
|
// 每个客户端消息接收 channel 的缓冲区大小
|
|
|
messageBufferSize = 256
|
|
|
)
|
|
|
@@ -142,8 +141,8 @@ const (
|
|
|
// WebSocket 升级器,用于将 HTTP 连接升级为 WebSocket 连接
|
|
|
// 设置读写缓冲区大小以优化性能
|
|
|
var upgrader = &websocket.Upgrader{
|
|
|
- ReadBufferSize: socketBufferSize, // 读缓冲区大小
|
|
|
- WriteBufferSize: socketBufferSize, // 写缓冲区大小
|
|
|
+ ReadBufferSize: socketBufferSize, // 读缓冲区大小
|
|
|
+ WriteBufferSize: socketBufferSize, // 写缓冲区大小
|
|
|
}
|
|
|
|
|
|
// ServeHTTP 处理 WebSocket 连接请求
|
|
|
@@ -152,12 +151,12 @@ var upgrader = &websocket.Upgrader{
|
|
|
func (r *Room) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
|
// -------- 使用当前房间实例(接收者 r),而不是重新获取房间 -------- 这是才正确的面向对象设计
|
|
|
// 从 URL 查询参数中获取房间名称
|
|
|
- // roomName := req.URL.Query().Get("room")
|
|
|
- // if roomName == "" {
|
|
|
- // http.Error(w, "Room name required", http.StatusBadRequest)
|
|
|
- // return
|
|
|
- // }
|
|
|
- // room := GetRoomAndRun(roomName)
|
|
|
+ // roomName := req.URL.Query().Get("room")
|
|
|
+ // if roomName == "" {
|
|
|
+ // http.Error(w, "Room name required", http.StatusBadRequest)
|
|
|
+ // return
|
|
|
+ // }
|
|
|
+ // room := GetRoomAndRun(roomName)
|
|
|
|
|
|
// 将 HTTP 连接升级为 WebSocket 连接
|
|
|
socket, err := upgrader.Upgrade(w, req, nil)
|
|
|
@@ -168,16 +167,16 @@ func (r *Room) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
|
|
|
|
// 创建新的客户端对象
|
|
|
client := &Client{
|
|
|
- socket: socket, // WebSocket 连接
|
|
|
- receive: make(chan []byte, messageBufferSize), // 接收消息的缓冲 channel
|
|
|
- room: r, // 使用当前房间实例
|
|
|
- name: fmt.Sprintf("user_%d", rand.Intn(1000)), // 生成随机用户名
|
|
|
+ socket: socket, // WebSocket 连接
|
|
|
+ receive: make(chan []byte, messageBufferSize), // 接收消息的缓冲 channel
|
|
|
+ room: r, // 使用当前房间实例
|
|
|
+ name: fmt.Sprintf("user_%d", rand.Intn(1000)), // 生成随机用户名
|
|
|
}
|
|
|
|
|
|
// 将客户端加入当前房间
|
|
|
r.join <- client
|
|
|
defer func() { r.leave <- client }()
|
|
|
-
|
|
|
+
|
|
|
// 启动客户端的写消息 goroutine
|
|
|
go client.write()
|
|
|
// 在当前 goroutine 中处理客户端的读消息(阻塞直到连接断开)
|