基于 TCP + WebSocket 的多用户聊天室,从零逐步构建。
chat-system/
├── client/ # Go 终端客户端
│ └── main.go
├── sever/ # 服务端
│ ├── main.go # TCP 服务端入口、Manager、TCP Client
│ ├── ws.go # WebSocket 客户端、HTTP 接口
│ └── frontend/ # Web 前端(内嵌到二进制)
│ ├── index.html # 聊天主页面
│ ├── style.css # 暗色主题样式
│ └── app.js # WebSocket 前端逻辑
├── go.mod
├── go.sum
├── .gitignore
└── README.md
# 启动服务端
cd sever
go run .
# 打开浏览器
# http://localhost:8080服务端启动后同时监听两个端口:
- TCP :9001 — 给 Go 终端客户端使用
- HTTP :8080 — Web 界面 + WebSocket
浏览器访问 http://localhost:8080,输入昵称进入聊天室。
| 功能 | 操作 |
|---|---|
| 公聊 | 输入框发送消息即可 |
| 私聊 | 点击左侧在线用户,或输入 /私聊 用户名 消息 |
| 查看在线 | 点击左上角刷新按钮,或输入 /在线 |
| 修改昵称 | 点击左下角编辑按钮,或输入 /改名 |
| 退出 | 点击左下角门按钮,或输入 /退出 |
cd client
go run .| 命令 | 功能 |
|---|---|
| 直接输入 | 公聊消息 |
one |
查看在线用户 |
two |
私聊模式 |
three |
下线 |
four |
修改昵称 |
Web 用户和终端用户共享同一聊天室,消息互通。
纯文本,换行分隔。服务端每条消息以 \n 结尾。
登录流程:
- 服务端发送
请输入昵称: \n - 客户端回复昵称 +
\n
广播消息格式:[HH:MM:SS] [发送者]消息内容\n
JSON 消息,双向通信。
客户端 → 服务端:
{"type":"login","name":"张三"}
{"type":"message","data":"大家好"}
{"type":"list_users"}
{"type":"private_chat","target":"李四","data":"你好"}
{"type":"change_name","name":"新名字"}
{"type":"disconnect"}服务端 → 客户端:
{"type":"login_ok"}
{"type":"system","data":"张三 加入了聊天室","time":"15:04:05"}
{"type":"broadcast","sender":"张三","data":"大家好","time":"15:04:05"}
{"type":"private","sender":"张三","data":"你好","time":"15:04:05"}
{"type":"online_users","users":["张三","李四"]}
{"type":"error","data":"昵称不能为空"}整个服务唯一实例,管理所有在线用户。两个 map 共享同一读写锁:
type Manager struct {
onlineUsers map[*Client]struct{} // TCP 用户
wsOnlineUsers map[*WSClient]struct{} // WebSocket 用户
lock sync.RWMutex
}每个终端连接对应一个 Client,两个 goroutine:
| goroutine | 职责 |
|---|---|
readLoop() |
从 TCP 连接读消息,分发命令(one/two/three/four/公聊) |
writeLoop() |
从 sendchan 读消息,加时间戳后写回 TCP 连接 |
每个浏览器连接对应一个 WSClient,两个 goroutine:
| goroutine | 职责 |
|---|---|
readLoop() |
从 WebSocket 读 JSON,分发命令(message/list_users/private_chat/change_name/disconnect) |
writeLoop() |
从 sendchan 读 JSON 字节,写回 WebSocket |
Web 和 TCP 用户在同一聊天室,broadcastedWS 方法负责将 Web 消息转发为 TCP 文本格式,AddConn / RemoveConn 统一管理上线/下线通知。