• day03-2无异常退出


    多用户即时通讯系统03

    4.编码实现02

    4.3功能实现-无异常退出系统

    4.3.1思路分析

    image-20220922191529799

    上述代码运行时,在客户端选择退出系统的时候,可以发现程序并没有停止运行,原因是:

    退出时,程序将循环标志loop设为false,退出了内层循环,而外层循环因为也用了loop来作为循环条件,外层循环也同样退出。此时在客户端 类QQView中的主线程已经结束,但是在循环过程中,因为与服务端连接而产生的线程并没有结束,整个进程也就没有结束,因此程序仍在运行中。

    解决方法:

    客户端:在main线程中调用方法,给服务端发送一个退出系统的message对象,然后调用System.exit(0)指令,正常退出。这样整个进程就可以关闭。

    服务器端:在服务器这边,接收到一个退出系统的message对象后,把这个客户端对应的线程所持有的socket关闭,然后退出该线程

    image-20220922193057065

    4.3.2代码实现

    1.客户端:
    1.修改:UserClientService类

    在该类中增加logout()方法

    //编写方法,退出客户端,并给服务器端发送一个退出系统的message对象
    public void logout(){
    Message message = new Message();
    message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
    message.setSender(u.getUserId());//一定要指定是那个客户端,服务端要根据这个userId移除集合中的线程
    //发送message
    try {
    //从管理线程的集合里面,通过userId,得到这个线程对象
    ClientConnectServerThread clientConnectServerThread =
    ManageClientConnectServerThread.getClientConnectServerThread(u.getUserId());
    //通过这个线程中获取关联的socket
    Socket socket = clientConnectServerThread.getSocket();
    //得到当前线程的Socket对应的ObjectOutputStream对象
    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
    oos.writeObject(message);
    System.out.println(u.getUserId()+"退出系统");
    System.exit(0);
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    2.修改:QQView类

    在该类中的内层循环中,调用logout方法:

    //调用方法,给服务器发送一个退出系统的message
    userClientService.logout();

    image-20220922200240425

    2.服务端:
    1.修改:ManageClientThreads类

    在该类中增加removeServerConnectClientThread()方法

    //增加一个方法,从集合中移除某个对象
    public static void removeServerConnectClientThread(String userId){
    hm.remove(userId);
    }
    2.修改:ServerConnectClientThread类

    在该类的run方法中增加业务二操作:

    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 if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)) {
    //业务二:客户请求退出系统
    System.out.println(message.getSender() + " 退出");
    //将客户端对应的线程从集合中删除
    ManageClientThreads.removeServerConnectClientThread(message.getSender());
    socket.close();//关闭的是当前的线程持有的socket属性
    //退出线程的循环
    break;
    } else {
    System.out.println("其他类型的message,暂时不处理");
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }

    运行:

    1.运行服务端:

    image-20220922200701524

    2.运行客户端,登录两个用户:

    image-20220922200802600 image-20220922201246653

    3.查看当前用户列表,可以看到有两个用户:

    image-20220922201404318

    3.其中一个用户选择退出系统,可以看到用户正确退出,程序结束运行:

    image-20220922201011864

    4.在另一个用户中查看当前用户列表,可以看到只剩下一个用户,说明服务端已经成功将退出的用户的线程从集合中删除

    image-20220922201649953

    4.服务端这边显示该用户正确退出:

    image-20220922201736838
  • 相关阅读:
    工厂仪表定时拍照智能AI算法识别内网部署方案
    Torch生成类激活图CAM
    【天勤量化】python多进程获取所有期货分钟数据和股票日线数据
    Docker 安装Redis
    从零开始设计一个共识算法——一场没有硝烟的战争
    swift 约束布局
    用DIV+CSS技术设计的美食主题网站(web前端网页制作课作业)美食餐饮网站设计与实现(HTML+CSS+JavaScript)
    网络传输中的重要参数-谈谈带宽
    地铁大数据客流分析系统 设计与实现 计算机竞赛
    Gitee使用用户名密码登录一直错误/IDEA连接gitee仓库密码错误
  • 原文地址:https://www.cnblogs.com/liyuelian/p/16721066.html