Java-Day-30( 多用户即时通信系统 —— 登录 + 获取在线用户列表 )

发布时间 2023-07-10 22:01:43作者: 朱呀朱~

Java-Day-30

多用户即时通信系统

  • 需求分析
    • 用户登录
    • 拉取在线用户列表
    • 无异常退出
    • 私聊
    • 群聊
    • 发文件
    • 服务器推送新闻

用户登录

  • 功能说明

    • 我们暂时人为规定用户名 / id = 100,密码 123456 就可以登录,其他用户不能登录
    • 后面使用 HashMap 模拟数据库,可以多个用户登录
  • 思路分析

    • 为方便,客户端 A 的 socket 输出流发送数据时使用对象方式 ( 即使用对象流来读写 ) ,由服务端的 9999 端口监听,监听 accept 产生一个输入流 socket
      • 一个对象希望用 Object 对象流的方式进行读写的话,此对象所对应的类需要序列化 —— implements Serializable ( IO 流部分 ),并且为保证兼容性需要加上 —— private static final long serialVersionUID = 1L; —— 防止在随机分配时,若类修改,反序列化报错
    • 如果多个客户端访问的话,服务器端就产生多个 socket
      • 服务器端验证 User 对象,验证通过后才能到后面的发送信息等功能
    • 每获取一个 socket 就多一个线程,一个线程包裹着一个 socket,也可以说 socket 就是该线程的属性,只一个 socket 不方便维护
    • 为更好的管理线程,两端都需要使用集合,即多个线程都包裹在一个管理线程的集合中 ( 为了扩展,就算是只有一个 socket 也最好写到线程集合中 )
    • 每对 socket 对应一个通道 ( 两端之间也可能有多条通道 )

image-20230610210633741

  • 注意:客户端的线程中只有 socket,而服务端的线程中不仅要有 socket,还要有用于区分的客户端传来的身份验证的 userId

前提准备

  • 服务端和客户端都共有文件:Message、User 映射实体类,MessageType 内含各种信息类型的常量的接口

    • 信息 Message 包含所有发送的东西,例如客户向服务器发送请求类型是存在 Message 里,服务端将客户请求到的信息也存到 Message 中,即凡是要两端信息交互,就是通过 Message 来存储发送的

    • 接口中:

      //    在接口中定义了一些常量
      //    不同的常量的值,表示不同的消息类型
          String MESSAGE_LOGIN_SUCCEED = "1"; //表示登录成功
          String MESSAGE_LOGIN_FAIL = "2"; // 表示登录失败
      
          String MESSAGE_COMM_MES = "3"; // 普通信息包
          String MESSAGE_GET_ONLINE_FRIEND = "4"; // 要求返回在线用户列表 —— 客户端
          String MESSAGE_RET_ONLINE_FRIEND = "5"; // 返回在线用户列表 —— 服务器端
          String MESSAGE_CLIENT_EXIT = "6"; // 客户端请求退出
      

用户登录 —— 客户端

  • 为了用户信息可发送到服务端
    • QQView 为登录页面简单样式
    • UserClientService 为用户登录验证、用户注册等具体方法所在处
      • 如果登录成功,就将 socket 存入线程,线程启动并存入线程集合,关于线程与集合的具体如下
    • ClientConnectServerThread 为一个持有 Socket 的线程,始终保持着与服务端的连接,时刻处于可接收状态
      • 为了能够管理 socket,否则客户端多个 socket 时会很麻烦,而且只能做一件事情
      • 减缓了反复链接的延迟和资源浪费等
    • ManageClientConnectServerThread 类中,我们将线程放入到集合管理 ( 如上图所画 )
      • 主要是为了后面客户端的扩展,如果以后和服务端只会有一个连接的话,此处就不需要集合管理了
      • 内就仅含一个 HashMap、一个 ( id, 线程 ) 存放方法、一个根据 id 获取线程的方法

用户登录 —— 服务端

  • 为了验证客户端发来的信息,随后服务端发送信息对象给用户以登录结果
    • QQServer 中用 ServerSocket 来监听端口并获取 Socket
      • 端口可以写在配置文件中
      • 注意:监听也同样是一个循环不断的操作,并不是获取到一个 Socket 就结束了
    • 获取输入流得到客户端发送的 User 对象进行验证
    • 再根据 Socket 创建输出流,用来根据验证结果发送 Message 对象给客户端进行回复
    • 登录成功就将 socket 存入线程,线程启动并存入线程集合,关于线程与集合的具体如下
  • ServerConnectClientThread 似过程一的 ClientConnectServerThread 持有 socket 的线程,用于始终和对应用户保持连接
  • ManageClientThreads 管理线程类,似过程一的 ManageClientConnectServerThread
  • 若是没链接数据库想要临时测试,可以在 QQServer 中使用 HashMap / ConcurrentHashMap 集合于静态代码块中存储用户信息

拉取在线用户列表

  • 不管是客户端还是服务端都是有一个线程在不停执行 run 方法,在此方法中,不断接收对方的 Message 对象,根据传来的 Message 对象的类型来执行进一步的操作

  • 在客户端的 UserClientService 中编写向服务端请求在线用户列表的方法,在需要时 QQView 在合适的位置调用即可

  • 在 ClientConnectServerThread 线程中判断服务端发送回来的信息类型,若接收到的是服务端发来的用户列表,就在线程中直接控制台打印出用户列表即可,在需要时直接在 QQView 页面调用即可

    • split 分割为数组,循环输出
  • 服务端获取在线用户列表:所有在线的用户 id 都存放在 socket 中,socket 都包在一个线程中,而每个线程都在一个线程集合中,所以想要获取所有的在线用户,就需要通过管理线程的集合来获取,即在 ManageClientThreads 里编写获取列表的方法

    • HashMap 的遍历,可以用迭代器,
  • 服务端的线程中判断客户传来的 Message 类型,若是,就通过线程集合的方法获取所有的用户信息,拿到后将所需的类型、发送者、内容 ( 即用户信息 ) 等打包,输出流返回给客户端即可