• day03-拉取在线用户&无异常退出功能


    多用户即时通讯系统03

    4.编码实现02

    4.2功能实现-拉取在线用户

    image-20220922182116066

    4.2.1思路分析

    客户端想要知道在线用户列表,就要向服务器发送请求(Message),因为只有服务器端保持着所有与客户端相连接的socket和uid信息。

    整个流程大致为:对Message的种类进行扩展,然后客户端向服务器发送一个Message,服务端向客户端返回数据,客户端接收信息(Message),在接收的这个Message信息里面包含了在线的用户列表。

    4.2.2代码实现

    1.客户端:
    1.修改:MessageType接口

    拓展了一些Message消息类型

    package qqcommon;
    /**
    * @author
    * @version 1.0
    * 表示消息类型
    */
    public interface MessageType {
    //在接口中定义类一些常量,不同的常量的表示不同的消息类型
    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";//客户端请求退出
    }
    2.修改:UserClientService类

    在该类中添加onlineFriendList()方法,该方法向服务器发送要求,请求在线用户列表

    //先服务器端请求在线用户列表
    public void onlineFriendList() {
    //向服务器发送一个Message,类型MESSAGE_GET_ONLINE_FRIEND,要求返回在线用户列表
    Message message = new Message();
    message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
    message.setSender(u.getUserId());
    //发送给服务器
    try {
    //从管理线程的集合里面,通过userId,得到这个线程对象
    ClientConnectServerThread clientConnectServerThread =
    ManageClientConnectServerThread.getClientConnectServerThread(u.getUserId());
    //通过这个线程中获取关联的socket
    Socket socket = clientConnectServerThread.getSocket();
    //得到当前线程的Socket对应的ObjectOutputStream对象
    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
    oos.writeObject(message);//发送一个Message对象向服务器,要求在线用户列表
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    3.修改:ClientConnectServerThread类

    在该类中的run方法中,增加判断Message的类型,然后做相应的业务处理

    @Override
    public void run() {
    //因为Thread需要在后台和服务器通信,因此我们使用while循环
    while (true) {
    try {
    System.out.println("客户端线程,等待读取从服务端发送的消息");
    ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
    //如果服务器没有发送Message对象,线程会阻塞在这里
    Message message = (Message) ois.readObject();
    //判断Message的类型,然后做相应的业务处理
    //如果读取到的是 服务端返回的在线用户列表(MESSAGE_RET_ONLINE_FRIEND)
    if (message.getMesType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)) {
    //取出在线用户列表信息,并展示
    //这里假定返回的用户列表是用空格隔开的id名(如:100 200 紫霞仙子 至尊宝 唐僧)
    String[] onlineUsers = message.getContent().split(" ");
    System.out.println("\n=======当前在线用户列表=======");
    for (int i = 0; i < onlineUsers.length; i++) {
    System.out.println("用户:" + onlineUsers[i]);
    }
    } else {
    System.out.println("读取到的是其他类型的message,暂时不处理");
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }
    4.修改QQView类

    修改该类中的第54行

    使用上述编写的方法,完成拉取在线用户列表的操作

    //这里写一个拉取用户在线列表的方法
    userClientService.onlineFriendList();
    image-20220922180741819
    2.服务端

    大致思路为:前面我们在服务端的线程的run方法中,使用for循环在不停地请求读取数据,这里我们可以在run方法里面拓展功能,读取客户端发送过来的拉取用户的Message对象,返回在线用户列表

    1.修改:MessageType接口

    和客户端一样,拓展Message消息类型

    package qqcommon;
    /**
    * @author
    * @version 1.0
    * 表示消息类型
    */
    public interface MessageType {
    //在接口中定义类一些常量
    //不同的常量的表示不同的消息类型
    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";//客户端请求退出
    }
    2.修改:ServerConnectClientThread类

    在该类中的run方法添加了客户请求拉取在线用户列表 的业务操作

    package qqserver.server;
    import qqcommon.Message;
    import qqcommon.MessageType;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.net.Socket;
    /**
    * @author
    * @version 1.0
    * 该类的一个对象和某个客户端保持通信
    */
    public class ServerConnectClientThread extends Thread {
    private Socket socket;
    private String userId;//连接到服务端的用户id
    public ServerConnectClientThread(Socket socket, String userId) {
    this.socket = socket;
    this.userId = userId;
    }
    @Override
    public void run() {//这里线程处于run的状态,可以发送/接收消息
    while (true) {
    try {
    System.out.println("服务端和客户端" + userId + "保持通信,读取数据...");
    ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
    Message message = (Message) ois.readObject();
    //后面会使用message,根据message的类型,做相应的业务处理
    //业务-:客户请求拉取在线用户列表
    if (message.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)) {
    //客户请求拉取在线用户列表
    //假定返回的用户列表是用空格隔开的id名(如:100 200 紫霞仙子 至尊宝 唐僧)
    System.out.println(message.getSender()+" 要在线用户列表");
    String onlineUser = ManageClientThreads.getOnlineUser();
    //返回message
    //构建一个Message对象(这个Message对象包含了在线用户列表信息),返回给客户端
    Message message2 = new Message();
    //设置消息类型--返回的在线用户列表类型-客户端会根据返回的消息类型来进行相应的业务处理
    message2.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
    message2.setContent(onlineUser);//返回用户消息列表
    //服务器发送的消息的接收者Getter 就是服务器接收的信息 的发送者Sender
    message2.setGetter(message.getSender());
    //返回给客户端
    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
    oos.writeObject(message2);
    }else{
    System.out.println("其他类型的message,暂时不处理");
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }
    }
    3.修改:ManageClientThreads类

    在该类中增加了getOnlineUser()方法,用来遍历取出userId,并在上面的ServerConnectClientThread的run方法中使用。

    //这里编写方法,可以返回在线用户列表
    public static String getOnlineUser() {
    //遍历集合,遍历 hashmap 的 key
    Iterator iterator = hm.keySet().iterator();//获取hm集合的ketSet集合的迭代器(这里的ketSet就是userId)
    String onlineUserList = "";
    while (iterator.hasNext()) {//遍历
    onlineUserList += iterator.next().toString() + " ";//遍历所有的userId,用空格拼接起来
    }
    return onlineUserList;
    }

    运行:

    1. 运行服务端:
    image-20220922182247168
    1. 分别运行三个客户端,并进行登录,拉取在线用户:

      ​ 用户1:

    image-20220922182621787 image-20220922183010340


    ​ 用户2:

    image-20220922182643944 image-20220922182828114


    ​ 用户3:

    image-20220922182701298 image-20220922182911274

    此时服务端显示:

    image-20220922183325135
  • 相关阅读:
    【java:牛客每日三十题总结-3】
    Bootstrap禁止点击空白处关闭模态框
    Nexus 私服资源的上传下载
    基于HTML仿华为手机网站电商项目的设计与实现
    【Docker】Harbor私有仓库与管理
    Espresso Sequencer:去中心化Rollups
    fastdeploy快速部署yolov5离线模型
    【C++面向对象】2.构造函数、析构函数
    权限维持专题:域控制器权限维持
    界面组件DevExpress WinForms v23.1 - 增强的图表、甘特图功能
  • 原文地址:https://www.cnblogs.com/liyuelian/p/16720760.html