| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- 项目名称:chat_room 聊天室项目
- 一、项目整体结构
- 这个项目目前分成两个主要部分:
- 1. 服务端
- 路径:D:\Network\chat_room\chat
- 技术栈:Java + Gradle + Java-WebSocket
- 作用:负责接收客户端 WebSocket 连接、管理在线用户、处理登录、群聊、私聊、在线列表等消息路由。
- 2. 客户端
- 路径:D:\Network\chat_room\user
- 技术栈:Android Java + Gradle
- 作用:作为手机 App 连接服务端,发送登录消息、发送聊天消息、接收服务端推送消息,并在界面上展示。
- 二、通信方式
- 整个项目的通信方式是 WebSocket。
- 客户端通过 WebSocket 连接服务端。
- 当前对外使用的地址逻辑是:
- - 服务端 Java 程序实际监听 8080
- - 服务器上的 Nginx 监听 80
- - Nginx 把 80 的 WebSocket 请求反向代理到 127.0.0.1:8080
- 所以客户端默认连接地址是:
- ws://124.220.100.95
- 客户端不需要写 8080,直接走 80 即可。
- 三、消息协议
- 服务端和客户端之间传输的消息统一使用 JSON。
- 核心消息结构是 WsMessage,字段如下:
- - type:消息类型
- - from:发送者用户名
- - to:接收者用户名,私聊时使用
- - content:消息内容
- - timestamp:时间戳
- 示例:
- {
- "type": "ALL",
- "from": "alice",
- "to": null,
- "content": "hello",
- "timestamp": 1710000000000
- }
- 当前支持的消息类型在 MessageType 中定义:
- - LOGIN:登录
- - ALL:群聊消息
- - PRIVATE:私聊消息
- - ONLINE:在线用户列表
- - HISTORY:历史消息
- - QUIT:退出
- - SYSTEM:系统消息
- - ERROR:错误消息
- 四、服务端逻辑
- 1. 启动入口
- 文件:
- chat\src\main\java\com\campus\chat\ChatApplication.java
- 作用:
- - 读取监听地址和端口
- - 默认 host = 0.0.0.0
- - 默认 port = 8080
- - 创建 ChatWebSocketServer
- - 启动后一直阻塞运行
- 2. WebSocket 服务主类
- 文件:
- chat\src\main\java\com\campus\chat\server\ChatWebSocketServer.java
- 作用:
- - 接收客户端连接
- - 接收客户端关闭
- - 接收客户端文本消息
- - 解码 JSON
- - 把消息交给消息路由器处理
- 主要流程如下:
- 1. onOpen
- 有新客户端连上时,先打印连接日志。
- 2. onMessage
- 收到字符串消息后:
- - 用 JsonMessageCodec 把 JSON 转成 WsMessage
- - 根据当前连接查找对应会话
- - 找不到会话时,构造一个临时 SessionInfo(null, conn)
- - 把消息交给 ChatMessageRouter.route() 处理
- - 如果解析失败或处理异常,返回 ERROR 类型消息
- 3. onClose
- 连接断开时:
- - 从在线会话管理器中移除
- - 如果该连接已经登录过用户名,则向所有人广播“某某 disconnected”
- 3. 会话管理
- 文件:
- chat\src\main\java\com\campus\chat\server\ChatRoomManager.java
- 作用:
- - 维护连接和用户之间的映射关系
- 内部维护两张表:
- 1. sessionsBySocket
- key = WebSocket 连接
- value = SessionInfo
- 2. sessionsByUsername
- key = 用户名
- value = SessionInfo
- 这个类负责:
- - register:注册登录用户
- - remove:移除断开连接
- - findBySocket:按连接查会话
- - findByUsername:按用户名查会话
- - listSessions:获取当前在线用户列表
- - sockets:获取所有在线连接,方便广播
- 4. 消息路由
- 文件:
- chat\src\main\java\com\campus\chat\server\ChatMessageRouter.java
- 这是服务端真正的业务核心。
- 收到消息后,route() 会按 type 分发:
- - LOGIN -> handleLogin()
- - ALL -> broadcast()
- - PRIVATE -> privateChat()
- - ONLINE -> sendOnlineList()
- - HISTORY -> sendHistory()
- - QUIT -> handleQuit()
- 5. 登录逻辑
- handleLogin() 的逻辑:
- 1. 读取 from 作为用户名
- 2. 如果用户名为空,返回系统消息:Username is required.
- 3. 如果用户名已被别的连接占用,返回系统消息:Username already in use.
- 4. 如果用户名可用,注册到 ChatRoomManager
- 5. 重新按 socket 取回真正已注册的 SessionInfo
- 6. 给当前用户发送欢迎消息
- 7. 给所有在线用户广播“xxx joined the chat.”
- 8. 给当前用户发送在线用户列表
- 这里的关键修复点是第 5 步。
- 以前 register 完成后仍然拿旧的 sender 对象继续发消息,旧对象里的 username 还是 null,导致登录后的状态不一致。
- 现在改成 register 之后重新从 roomManager.findBySocket() 取回已注册会话,再继续发 Welcome 和 ONLINE。
- 6. 群聊逻辑
- broadcast() 的逻辑:
- 1. 确保当前连接已经先 LOGIN
- 2. 构造一个新的 ALL 消息
- 3. from 使用当前登录用户名
- 4. content 使用用户发送的内容
- 5. 广播给所有在线连接
- 7. 私聊逻辑
- privateChat() 的逻辑:
- 1. 确保当前连接已经登录
- 2. 读取 to 作为目标用户名
- 3. 如果 to 为空,返回系统错误
- 4. 如果目标用户不在线,返回系统错误
- 5. 如果目标在线,则构造 PRIVATE 消息
- 6. 先发给目标用户
- 7. 再发给发送者自己
- 8. 在线列表逻辑
- sendOnlineList() 的逻辑:
- 1. 获取所有 SessionInfo
- 2. 取出用户名
- 3. 拼成一个逗号分隔的字符串
- 4. 以 ONLINE 类型消息发回当前用户
- 目前在线列表的数据格式还是一个字符串,不是结构化 JSON 数组。
- 9. 历史消息逻辑
- sendHistory() 目前还没有真正实现。
- 现在的行为是:
- - 如果客户端发 HISTORY
- - 服务端只返回一条系统消息:History is not implemented yet.
- 五、客户端逻辑
- 1. 主界面入口
- 文件:
- user\app\src\main\java\com\example\user\MainActivity.java
- 这是 Android 客户端的核心控制器。
- 现在的主界面已经不是“调试面板”模式,而是更接近正常聊天页:
- - 顶部只显示标题和连接状态
- - 页面不再显示服务器地址输入框
- - 页面不再显示昵称输入框
- - 提供设置按钮用于修改昵称
- - 提供重连按钮用于手动重连
- - 提供消息列表、输入框和发送按钮
- 2. 客户端启动流程
- App 打开后:
- 1. 初始化状态栏、按钮、消息列表
- 2. 读取本地保存的昵称
- 3. 如果本地没有昵称,则弹窗要求先设置昵称
- 4. 如果已有昵称,则自动连接 ws://124.220.100.95
- 也就是说:
- - 服务器地址已经内置
- - 昵称不再放在主屏幕上
- - 聊天主屏更接近普通 IM 应用
- 3. 客户端连接逻辑
- connectToServer() 的逻辑:
- 1. 如果正在连接、已经连接或昵称为空,则直接返回或弹昵称框
- 2. 关闭旧连接,取消重连任务
- 3. 构造内置服务器 URI
- 4. 设置状态为 connecting
- 5. 创建 ChatWebSocketClient
- 6. onOpen 后自动发送 LOGIN
- 连接过程中:
- - isConnecting = true
- - isConnected = false
- - isLoggedIn = false
- - 发送按钮禁用
- 4. 客户端连接成功后的逻辑
- onOpen() 中会做这些事:
- 1. 设置 isConnected = true
- 2. 状态显示改成“登录中”
- 3. 自动发送 LOGIN 消息
- 客户端现在不会在 WebSocket 刚连上时立刻启用发送按钮,而是要等服务端真正回 Welcome 或 ONLINE 之后才认为登录完成。
- 5. 客户端接收消息逻辑
- handleIncomingMessage() 的逻辑:
- 1. 把服务端返回的 JSON 转成客户端自己的 WsMessage
- 2. 先调用 consumeInternalMessage() 处理登录状态相关消息
- 3. 如果是 SYSTEM 或 ERROR,则作为系统消息显示
- 4. 否则按 from 是否等于当前用户,分成 incoming 和 outgoing 展示
- 6. 客户端发送消息逻辑
- sendCurrentMessage() 的逻辑:
- 1. 如果 WebSocket 未打开,或者还没完成登录,则不给发送
- 2. 读取输入框内容
- 3. 如果内容为空,不发送
- 4. 构造 ALL 类型 WsMessage
- 5. 通过 ChatWebSocketClient.sendMessage() 发给服务端
- 6. 清空输入框
- 这里也做了一处修正:
- 以前客户端本地会先插入一条自己的消息,服务端广播回来后又显示一次,容易造成重复显示。
- 现在改成只依赖服务端真正广播回来的消息来显示。
- 7. 客户端昵称逻辑
- 当前新增了昵称弹窗和本地保存逻辑:
- - 首次进入必须设置昵称
- - 昵称保存到 SharedPreferences
- - 如果服务端返回 Username already in use.
- 客户端会弹窗要求重新换昵称
- 同时默认昵称也不再固定写成 guest,而是根据设备信息自动生成一个更容易区分的默认值。
- 这样能避免两台手机都叫 guest,导致第二台手机表面连上了 WebSocket,但其实登录失败。
- 8. 客户端网络包装类
- 文件:
- user\app\src\main\java\com\example\user\client\ChatWebSocketClient.java
- 作用:
- - 继承 Java-WebSocket 的 WebSocketClient
- - 把 onOpen / onMessage / onClose / onError 回调转给 MainActivity
- - sendMessage() 负责把 WsMessage 转成 JSON 再发送
- 9. 客户端消息展示
- 文件:
- user\app\src\main\java\com\example\user\ui\ChatMessageAdapter.java
- 作用:
- - RecyclerView 适配器
- - 把消息列表渲染成聊天气泡
- 显示规则:
- - system -> 居中
- - incoming -> 靠左
- - outgoing -> 靠右
- 六、部署逻辑
- 当前线上部署方式如下:
- 1. Java 服务端
- - 在 Linux 服务器上运行
- - 部署目录:/opt/chat
- - systemd 服务名:chat
- - 启动命令:/usr/bin/env bash /opt/chat/bin/chat
- - 实际 Java 监听端口:8080
- 2. 反向代理
- - Nginx 监听 80
- - 把 WebSocket 请求转发到 127.0.0.1:8080
- 3. 对外访问地址
- - ws://124.220.100.95
- 七、这次故障修复后的真实链路
- 1. 用户打开 App
- 2. App 读取本地昵称
- 3. 如果没有昵称,先弹窗要求设置
- 4. App 自动连接 ws://124.220.100.95
- 5. Nginx 接收 80 端口 WebSocket 请求
- 6. Nginx 转发到本机 8080
- 7. Java WebSocket 服务收到连接
- 8. 客户端连接成功后自动发送 LOGIN
- 9. 服务端注册用户名
- 10. 服务端正确回 Welcome、joined、ONLINE
- 11. 客户端收到回包后把 isLoggedIn 设为 true
- 12. 发送按钮解锁
- 13. 用户发送消息
- 14. 服务端广播 ALL
- 15. 其他客户端收到消息并展示
- 八、当前已经具备的能力
- 目前这套项目已经具备:
- - Android 客户端连接 WebSocket
- - 自动连接服务器
- - 首次弹窗设置昵称
- - 本地保存昵称
- - 自动发送登录消息
- - 登录成功后才允许发送
- - 群聊消息广播
- - 私聊消息路由
- - 在线会话管理
- - 系统消息显示
- - 用户断开广播
- - Nginx 反代 WebSocket
- - systemd 托管服务端
- 九、当前还没有完成的部分
- 目前还存在这些限制:
- 1. 历史消息未实现
- - 没有数据库
- - 没有文件落盘
- - HISTORY 只有占位回复
- 2. 在线列表只是字符串
- - 还不是结构化数据
- - 客户端也还没做在线用户面板
- 3. 客户端当前默认是群聊
- - 没有私聊选择 UI
- - 虽然服务端支持 PRIVATE,但客户端界面还没接出来
- 4. 用户体系很简单
- - 只有昵称
- - 没有账号密码
- - 没有鉴权
- 5. 没有消息可靠性机制
- - 没有消息 ack
- - 没有重发
- - 没有离线消息
- 6. 没有正式日志系统
- - 主要还是 println
- - 没有统一日志级别和日志文件策略
- 十、后续建议
- 如果要把这个项目继续往真正可用的聊天产品推进,建议按这个顺序做:
- 1. 先做服务端历史消息持久化
- 2. 再做客户端在线用户列表
- 3. 再把私聊 UI 做出来
- 4. 再做登录鉴权
- 5. 再做消息状态、重连、离线消息
- 6. 最后补 HTTPS / WSS 和正式域名
- 十一、总结
- 这个项目现在本质上是一个“WebSocket 即时聊天系统”。
- 结构上:
- - Java 服务端负责连接管理和消息路由
- - Android 客户端负责连接、发送、接收和展示
- - Nginx 负责把外部 80 端口流量转发给 8080 的 Java 服务
- 业务上:
- - 登录靠 LOGIN 消息
- - 群聊靠 ALL
- - 私聊靠 PRIVATE
- - 在线列表靠 ONLINE
- - 系统提示靠 SYSTEM
- 当前它已经能作为一个基础聊天室运行,并且这次“卡在登录中、发送按钮灰色、对方收不到消息”的主链路问题已经修复。
|