JavaSE---多用户网络通信系统

news2024/11/16 19:32:19

目录

项目开发流程

多用户网络通信系统的架构设计

客户端

界面层

服务层

管理层

服务端

服务层

功能层

管理层

总结


项目开发流程

多用户网络通信系统的架构设计

  • 整体
    作为一个可供多个用户使用的通信系统,那么每个用户和其他用户之间的连接必定不是直接的单通道方式,这样形成的项目会杂乱无章并且需要实时的进行通道的建立和检验。为了解决这种麻烦,分别建立一个客户端和服务端,用户所在的一方为客户端,客户端与其他客户端之间的联系方式为,客户端先连接到服务端,然后由服务端进行消息的转发到发送消息的客户端指定的另一方客户端。
  • 分布
    • 客户端
      客户端进行用户的登录,服务器返回账号和密码的合法与否并返回客户端;如果登录成功,则在客户端启动一个新线程接收消息,保持和服务端之间的实时通讯,并且在相应的功能模块进行消息的编辑发送到服务器端,如果登录失败,则提示用户重新登陆或者退出系统。
    • 服务端
      服务端新开一个端口,循环实时监听客户端的连接请求;如果监听到客户端的请求,并且客户端正常登录后,则创建一个新线程,用于实时接收客户端发送的信息,主线程则继续进行循环,保持相应连接端口的监听。客户端在建立消息对象发送到相应的和服务端之间的通道时,服务端进行消息对象的接收,并且对该消息的类型进行分析做出相应的处理(回复或者转发)。
  • 对象成员设计
    消息对象Message,客户端用户对象User,消息类型接口MessageType
    //Message对象
    
    package com.shuai.qqcommon;
    
    import java.io.Serializable;
    
    public class Message implements Serializable {
        private static final long serialVersionUID = 1L;
        private String sender;  //发送者
        private String getter;  //接受者
        private String content; //发送的内容
        private String sendTime;    //消息的发送时间
        private String mesType; //消息类型
        private byte[] fileBytes;   //存放文件信息
    
        private int fileLength; //文件的长度
    
        private String src;
        private String dest;
    
        public byte[] getFileBytes() {
            return fileBytes;
        }
    
        public void setFileBytes(byte[] fileBytes) {
            this.fileBytes = fileBytes;
        }
    
        public int getFileLength() {
            return fileLength;
        }
    
        public void setFileLength(int fileLength) {
            this.fileLength = fileLength;
        }
    
        public String getSrc() {
            return src;
        }
    
        public void setSrc(String src) {
            this.src = src;
        }
    
        public String getDest() {
            return dest;
        }
    
        public void setDest(String dest) {
            this.dest = dest;
        }
    
        public String getMesType() {
            return mesType;
        }
    
        public void setMesType(String mesType) {
            this.mesType = mesType;
        }
    
        public String getSender() {
            return sender;
        }
    
        public void setSender(String sender) {
            this.sender = sender;
        }
    
        public String getGetter() {
            return getter;
        }
    
        public void setGetter(String getter) {
            this.getter = getter;
        }
    
        public String getContent() {
            return content;
        }
    
        public void setContent(String content) {
            this.content = content;
        }
    
        public String getSendTime() {
            return sendTime;
        }
    
        public void setSendTime(String sendTime) {
            this.sendTime = sendTime;
        }
    }
    //消息类型接口
    
    package com.shuai.qqcommon;
    
    public interface MessageType {
        String MESSAGE_LOGIN_SUCCESS = "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";//客户端请求退出
        String MESSAGE_ALL_MES = "7";   //群发类型
        String MESSAGE_FILE_MES = "8";  //文件类型
    }
    //客户端用户对象
    
    package com.shuai.qqcommon;
    
    import java.io.Serializable;
    public class User implements Serializable {
        //保证兼容性
        private static final long serialVersionUID = 1L;
        private String userId;//用户名
        private String password;//用户密码
    
        public User() {}
        public User(String userId, String password) {
            this.userId = userId;
            this.password = password;
        }
    
        public String getUserId() {
            return userId;
        }
    
        public void setUserId(String userId) {
            this.userId = userId;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    }

客户端

界面层

  • 登录界面
  • 交互界面
      

服务层

服务用户登录推出和请求在线用户列表的UserClientService类:
        将用户输入的账号和密码包装成一个登录类型的信息发送到服务器进行核验,服务器返回核验的结果。如果登录成功,则启动一个新线程和服务端建立实时的socket连接,并且将该线程加入到线程管理类的集合中;登录失败则退出。

public class UserClientService {
    private User user = new User();
    private Socket socket;
    /**
    * 检验用户登录信息
    */
    public boolean checkUser(String userId,String pass) {
        boolean b = false;
        user.setUserId(userId);
        user.setPassword(pass);
        try {
            socket = new Socket(InetAddress.getLocalHost(),9999);
            //发送user
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(user);
            ObjectInputStream ois =  new ObjectInputStream(socket.getInputStream());
            Message mess = (Message)ois.readObject();
            if(MessageType.MESSAGE_LOGIN_SUCCESS.equals(mess.getMesType())) {   //登录成功
                //创建一个线程始终和服务端保持连接
                ClientConnectServerThread clientConnectServerThread = new ClientConnectServerThread(socket);
                clientConnectServerThread.start();
                //为了客户端的扩展,将鲜橙放入到一个集合中进行管理
                ManageClientConnectServerThread.addClientConnectServerThread(userId,clientConnectServerThread);
                b = true;
            } else {
                socket.close();
            }
        } catch(ClassNotFoundException e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
        return b;
    }

    /**
     * 显示在线用户列表
     */
    public void printOnlineUser() {
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
        message.setSender(user.getUserId());
        //将Message对象发送给服务器
        try {
            ObjectOutputStream oos = new ObjectOutputStream
                    (ManageClientConnectServerThread.getClientConnectServerThread(user.getUserId()).getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 退出系统
     */
    public void exitSystem() {
        Message mess = new Message();
        mess.setSender(user.getUserId());
        mess.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
        try {
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(mess);
            System.exit(0);//结束进程,进程内包含的线程也都结束
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

服务用户登录后用户和服务器保持实时连接的线程类ClientConnectServerThread:
        用户登录成功后,启动该线程类对象,在run方法中创建该客户端连接服务端的socket对象,并获得相应的输入流,实时接收来自服务器端的消息。

package com.shuai.qqclient.service;

import com.shuai.qqcommon.Message;
import com.shuai.qqcommon.MessageType;

import java.io.*;
import java.net.Socket;

public class ClientConnectServerThread extends Thread {
    private Socket socket;
    private boolean loop = true;

    public ClientConnectServerThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        while (loop) {
            try {
                ObjectInputStream oos = new ObjectInputStream(socket.getInputStream());
                Message mess = (Message) oos.readObject();
                if (mess.getMesType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)) {
                    System.out.println("== 在线用户列表 ==");
                    String[] s = mess.getContent().split(" ");//得到包含在线用户的数组
                    for (int i = 0; i < s.length; i++) {
                        System.out.println("在线用户: " + s[i]);
                    }
                    System.out.println("\n");
                } else if (mess.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {
                    System.out.println(mess.getSender() + " 对你说: " + mess.getContent()
                            + "(" + mess.getSendTime() + ")");
                } else if (mess.getMesType().equals(MessageType.MESSAGE_ALL_MES)) {
                    System.out.println(mess.getSender() + " 群发消息说: " + mess.getContent()
                            + " (" + mess.getSendTime() + ")");
                } else if (mess.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {   //文件类型
                    FileOutputStream fos = null;
                    System.out.println(mess.getSender() + " 给你发送了文件保存在" + mess.getDest());
                    try {
                        fos = new FileOutputStream(mess.getDest());
                        fos.write(mess.getFileBytes(),0,mess.getFileLength());  //将sender发送过来的文件保存到硬盘
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            fos.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                } else {
                    System.out.println("其他类型的message,正在开发...");
                }
            } catch (IOException | ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public Socket getSocket() {
        return socket;
    }
}

服务客户端发送信息的MessageService类:
        界面层接收用户的特定输入并且调用服务层MessageService类中的对应方法发送给服务器指定消息类型的Message消息对象。

package com.shuai.qqclient.service;

import com.shuai.qqcommon.Message;
import com.shuai.qqcommon.MessageType;

import java.io.*;
import java.util.Date;

public class MessageService {
    /**
     * 发送普通消息
     * @param sender    消息的发送者
     * @param getter    消息的接收者
     * @param content   消息的内容
     */
    public static void sendMessage(String sender, String getter, String content) {
        //根据客户端用户的输入创建爱你相应的消息对象,指定消息类型
        Message message = new Message();
        message.setSender(sender);
        message.setGetter(getter);
        message.setContent(content);
        message.setMesType(MessageType.MESSAGE_COMM_MES);
        message.setSendTime(new Date().toString());
        try {    //得到socket通道的对象输出流
            ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(sender).getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 群发消息
     * @param sender    消息的发送者
     * @param content   消息的内容
     */
    public static void sendMessageToUsers(String sender, String content) {
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_ALL_MES);
        message.setContent(content);
        message.setSender(sender);
        message.setSendTime(new Date().toString());
        try {
            ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(sender).getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 发送文件
     * @param sender    消息的发送者
     * @param getter    消息的接收者
     * @param src       待发送文件的路径
     * @param dest      对方接收文件后的存储路径
     */
    public static void sendFileToUser(String sender, String getter, String src, String dest) {
        Message message = new Message();
        message.setSendTime(new Date().toString());
        message.setMesType(MessageType.MESSAGE_FILE_MES);
        message.setSrc(src);
        message.setDest(dest);
        message.setGetter(getter);
        message.setSender(sender);
        //读取文件到程序
        FileInputStream fis = null;
        byte[] fileBytes = new byte[(int) new File(src).length()];
        try {
            fis = new FileInputStream(src);
            int read = fis.read(fileBytes);
            message.setFileBytes(fileBytes);
            message.setFileLength(read);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println("你给 " + getter + " 发送了文件: " + src + " 到对方的: " + dest);
        try {
            ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(sender).getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

管理层

管理用户用于实时接收服务端信息的ManageClientConnectServerThread类:
        将所有登陆成功的客户端的ClientConnectServerThread类放在一个集合中集中管理,并且提供相应的方法供外界进行集合账户的添加和获得。方便了客户端群体账户的统一管理和获得。

package com.shuai.qqclient.service;

import java.util.HashMap;

public class ManageClientConnectServerThread {
    private static HashMap<String, ClientConnectServerThread> hm = new HashMap<>();

    public static void addClientConnectServerThread(String userId, ClientConnectServerThread clientConnectServerThread) {
        hm.put(userId, clientConnectServerThread);
    }

    public static ClientConnectServerThread getClientConnectServerThread(String userId) {
        return hm.get(userId);
    }
}

服务端

服务层

服务于客户端登录程序的QQServer类:
        在该类的构造方法中循环监听创建的端口,发现有来自客户端的登录请求时,就获得相应端口的输入流,接收登录类型的消息,并且对接收的Message对象进行解体拿到相应的密码进行核验,并获得相应的输出流返回返回包含核验结果的消息对象。如果核验成功,揪心启动一个线程并且获得与该客户端进行通信的socket接口的输入流,实时接收来自客户端的消息,并且将该线程加入到服务端线程管理类ManageServerConnectClientThread中的线程管理集合中。

package qqserver.service;

import com.shuai.qqcommon.Message;
import com.shuai.qqcommon.MessageType;
import com.shuai.qqcommon.User;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;

public class QQServer {
    private ServerSocket serverSocket;
    //保存用户信息
    //使用concurrentHashMap解决线程安全问题
    private static ConcurrentHashMap<String,User> validUsers = new ConcurrentHashMap<>();
    static {    //初始化validUsers
        validUsers.put("100",new User("100","123456"));
        validUsers.put("200",new User("200","123456"));
        validUsers.put("300",new User("300","123456"));
        validUsers.put("白小帅",new User("白小帅","123456"));
        validUsers.put("admin",new User("admin","123456"));
    }

    /**
     * 检查用户信息
     * @param userId    用户id
     * @param pass  用户密码
     * @return  boolean
     */
    private boolean checkUserInfo(String userId,String pass) {
        User user = validUsers.get(userId);
        if(user == null) {
            return false;
        }
        if(!(user.getPassword().equals(pass))) {
            return false;
        }
        return true;
    }
    public QQServer() {

        try {
            System.out.println("服务端监听客户端连接(9999端口)...");
            new Thread(new sendNewsService()).start();
            serverSocket = new ServerSocket(9999);
            while (true) {
                Socket socket = serverSocket.accept();
                //验证客户端发送的user对象
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                User user = (User) ois.readObject();
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                //构建一个Message对象返回
                Message message = new Message();
                //验证用户
                if (checkUserInfo(user.getUserId(), user.getPassword())) {
                    System.out.print("用户: " + user.getUserId() + " 登录... === ");
                    message.setMesType(MessageType.MESSAGE_LOGIN_SUCCESS);
                    oos.writeObject(message);
                    //启动一个新线程与客户端保持实时连接
                    ServerConnectClientThread serverConnectClientThread = new ServerConnectClientThread(socket, user.getUserId());
                    serverConnectClientThread.start();
                    //将对应的连接客户端的线程放入到线程管理集合中
                    ManageServerConnectClientThread.addServerConnectClientThread(user.getUserId(), serverConnectClientThread);
                    //判断用户是否存在离线消息并进行推送
                    UnLineMessageService.putSelfUnLineMessage(user.getUserId());
                } else {
                    //验证不成功
                    message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
                    oos.writeObject(message);
                    socket.close();
                }
            }
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

实时连接客户端的ServerConnectClientThread类:
        循环接收来自客户端的消息,并且根据接收的Message对象的MessageType属性分别进行不同的消息处理。

package qqserver.service;

import com.shuai.qqcommon.Message;
import com.shuai.qqcommon.MessageType;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Iterator;
import java.util.Set;

public class ServerConnectClientThread extends Thread {
    private Socket socket;
    private String userId;


    public ServerConnectClientThread(Socket socket, String userId) {
        this.socket = socket;
        this.userId = userId;
    }

    @Override
    public void run() {
        System.out.println("服务端保持着和 " + userId + "客户端的实时通讯...");
        while (true) {
            try {
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                Message mess = (Message) ois.readObject();   //客户端发送的信息
                if (mess.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)) {    //客户端请求在线用户列表
                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                    System.out.println(mess.getSender() + " 需要在线用户列表");
                    Message message = new Message();
                    message.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
                    //遍历线程集合,获取在线用户
                    message.setGetter(mess.getSender());
                    message.setContent(ManageServerConnectClientThread.getOnlineUserList());
                    oos.writeObject(message);
                } else if (mess.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)) {
                    System.out.println("客户端 " + mess.getSender() + " 退出");
                    ManageServerConnectClientThread.dropUserThread(userId);
                    socket.close();
                    break;                                  //退出run方法
                } else if (mess.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {        //普通消息
                    System.out.println("客户端 " + mess.getSender() + " 给客户端 " + mess.getGetter() + " 发送消息");
                    if (ManageServerConnectClientThread.getServerConnectClientThread(mess.getGetter()) != null) {    //用户在线
                        //拿到getter客户端的线程对应的输出流
                        ObjectOutputStream oosToGetter = new ObjectOutputStream(ManageServerConnectClientThread.getServerConnectClientThread(mess.getGetter()).getSocket().getOutputStream());
                        oosToGetter.writeObject(mess);
                    } else {    //用户不在线
                        UnLineMessageService.addUnLineMessage(mess.getGetter(), mess);
                    }
                } else if (mess.getMesType().equals(MessageType.MESSAGE_ALL_MES)) {     //处理群发消息
                    System.out.println("客户端 " + mess.getSender() + " 群发消息");
                    //遍历在线用户,发送消息
                    Set<String> sets = ManageServerConnectClientThread.getHm().keySet();
                    Iterator<String> iterator = sets.iterator();
                    while (iterator.hasNext()) {
                        String next = iterator.next();
                        if (next.equals(mess.getSender())) {
                            continue;
                        }
                        ObjectOutputStream oos = new ObjectOutputStream(
                                ManageServerConnectClientThread.getServerConnectClientThread(next).getSocket().getOutputStream());
                        oos.writeObject(mess);
                    }
                } else if (mess.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {    //客户端发送文件
                    System.out.println("客户端 " + mess.getSender() + " 发送了文件给客户端 " + mess.getGetter());
                    if (ManageServerConnectClientThread.getServerConnectClientThread(mess.getGetter()) != null) { //用户在线
                        ObjectOutputStream oos = new ObjectOutputStream(ManageServerConnectClientThread.getServerConnectClientThread(mess.getGetter()).getSocket().getOutputStream());
                        oos.writeObject(mess);  //将信息发送给getter客户端
                    } else {
                        UnLineMessageService.addUnLineMessage(mess.getGetter(),mess);
                    }
                } else {
                    System.out.println("其他类型的信息暂不处理,服务器正在升级哦~");
                }

            } catch (IOException | ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }

    }

    public Socket getSocket() {
        return socket;
    }
}

用户转发离线消息的UnLineMessageService类:
        该类的对象持有一个用于保存消息获得者离线状态时的ConcurrentHashMap对象,在用户上线后进行消息的推送。

package qqserver.service;

import com.shuai.qqcommon.Message;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;

public class UnLineMessageService {
    private static ConcurrentHashMap<String, ArrayList<Message>> db = new ConcurrentHashMap<>();//保存离线消息
    public static void addUnLineMessage(String userId,Message message) {
        if(db.get(userId) == null) {
            ArrayList<Message> messages = new ArrayList<>();
            messages.add(message);
            db.put(userId,messages);
        } else {
            ArrayList<Message> messagesExists = db.get(userId);
            messagesExists.add(message);
            db.put(userId,messagesExists);
        }
    }

    /**
     * 在用户上线后,对用户可能存在的离线消息进行推送
     * @param userId    登录成功的用户
     */
    public static void putSelfUnLineMessage(String userId) {
        if(db.get(userId) != null) {
            ArrayList<Message> messages = db.get(userId);
            for(Message mess : messages) {
                //推送给对应的用户
                try {
                    ObjectOutputStream oos = new ObjectOutputStream(ManageServerConnectClientThread.getServerConnectClientThread(userId).getSocket().getOutputStream());
                    oos.writeObject(mess);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            db.remove(userId);
        }
    }
}

功能层

用于发送给客户端公告的线程类SendNewsService:
        在服务端运行时即启动该线程接收服务端的输入流信息并通过遍历线程管理类中的服务端线程集合将该信息循环发送给每一个客户端。

package qqserver.service;

import com.shuai.qqcommon.Message;
import com.shuai.qqcommon.MessageType;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.*;

public class sendNewsService implements Runnable {
    private Scanner scanner = new Scanner(System.in);

    @Override
    public void run() {
        while (true) {
            System.out.println("服务器端推送新闻:[exit退出news推送服务] ");
            String content = scanner.next();
            if ("exit".equals(content)) {
                break;
            }
            Message message = new Message();
            message.setContent(content);
            message.setSendTime(new Date().toString());
            message.setMesType(MessageType.MESSAGE_ALL_MES);
            message.setSender("服务器");
            HashMap<String, ServerConnectClientThread> hm = ManageServerConnectClientThread.getHm();
            Set<String> onLineUserId = hm.keySet();
            Iterator<String> iterator = onLineUserId.iterator();
            while (iterator.hasNext()) {
                String userId = iterator.next();
                try {
                    ObjectOutputStream oos = new ObjectOutputStream(ManageServerConnectClientThread.getServerConnectClientThread(userId).getSocket().getOutputStream());
                    oos.writeObject(message);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

管理层

管理每一个客户端对应的服务端所启动的每一个线程类的ManageServerConnectClientThread类:
        客户端成功上线后,向该类的对象的线程集合中加入对应的线程对象;客户端离线,从集合中删除对应的线程对象。

package qqserver.service;

import java.util.HashMap;
import java.util.Iterator;

public class ManageServerConnectClientThread {
    private static HashMap<String,ServerConnectClientThread> hm = new HashMap<>();

    public static void addServerConnectClientThread(String userId,ServerConnectClientThread serverConnectClientThread) {
        hm.put(userId,serverConnectClientThread);
    }

    public static ServerConnectClientThread getServerConnectClientThread(String userId) {
        return hm.get(userId);
    }

    public static String getOnlineUserList() {
        StringBuffer stringBuffer = new StringBuffer("");
        //遍历集合
        Iterator<String> iterator = hm.keySet().iterator();
        while(iterator.hasNext()) {
            stringBuffer.append(iterator.next()+" ");
        }
        return stringBuffer.toString();
    }

    //删除集合中的对象
    public static void dropUserThread(String userId) {
        hm.remove(userId);
    }

    public static HashMap<String, ServerConnectClientThread> getHm() {
        return hm;
    }
}

总结

运用到的主要知识点

  • 集合
  • 多线程编程
  • IO流
  • 网络通信编程

过程中的设计难点

  • 用户的登录和与服务器之间的实时通信
    解决方案:用户信息在验证通过后,启动一个新线程,获得服务器端口的socket对象,获得输入流,实时接收服务器的回复信息。
  • 无异常退出
    解决方案:在用户输入退出请求时,客户端向服务器发送一个退出类型的Message对象后结束进程。服务端在拿到该消息对象后从线程管理集合中删除与该用户保持通信的线程对象并且退出该线程。
  • 离线信息和文件的发送
    创建一个Map集合(键:userId,值:ArrayList<Message>)用于临时保存离线用户需要接收的消息,得到用户发送的Message对象后拆解得到消息的获得者,判断消息的获得方是否在线,如果不在线,将消息发送者和发送的消息保存在离线消息集合中。在用户登录时检查该用户是否有离线消息,如果有,则进行推送。
  • 服务器推送公告
    在服务器启动时创建一个新线程,接收输入流的消息,遍历服务端线程管理集合中的每一个线程对象,得到相应socket的输出流,进行内容的推送。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/136686.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电脑宽带连接提示错误代码769怎么办?

有用户反映&#xff0c;在使用宽带连接网络时&#xff0c;出现错误代码769&#xff0c;无法连接到指定目标怎么办&#xff1f;这里整理了错误代码769的可能原因和修复方法&#xff0c;带大家顺利连接网络。错误代码769的原因&#xff1a;连接线松动或损坏网卡被禁用网卡驱动过时…

【信息论与编码 沈连丰】第四章:离散信源的信源编码

【信息论与编码 沈连丰】第四章&#xff1a;离散信源的信源编码第四章 离散信源的信源编码4.1 信源编码的模型4.2 信息传输速率和编码效率4.3 单义可译定理4.4 无失真信源编码定理4.5 几种典型的信源编码方法4.6 汉字编码方法及其讨论4.7 图像的信源编码4.8 误码对信源译码的影…

openFeign远程调用返回页面404 ,对应配置文件不生效,排除数据源等问题

在使用上架商品功能时&#xff0c;在debug时候&#xff0c;发现在将数据发送给ES保存时&#xff0c;无法远程调用es的服务&#xff0c;报错404找不到接口&#xff0c;如下图&#xff1a; 一开始以为是openFeign的问题&#xff0c;经过检查&#xff0c;各种接口、注解都没问题&…

2022尚硅谷SSM框架跟学(一)MyBatis基础一

2022尚硅谷SSM框架跟学 一MyBatisSSM框架整合课程优势课程体系框架图MyBatis1、MyBatis简介1.1MyBatis历史1.2MyBatis特性1.3MyBatis下载1.4和其它持久化层技术对比JDBCHibernate 和 JPAMyBatis2.搭建MyBatis2.1开发环境2.2创建maven工程(1)打包方式&#xff1a;jar(2)引入依赖…

【UE4 第一人称射击游戏】20-添加瞄准十字线

上一篇&#xff1a;【UE4 第一人称射击游戏】19-修复冲刺或换弹时可以进行射击的bug本篇效果&#xff1a;步骤&#xff1a;先下载一个瞄准的十字线图片&#xff0c;可以从阿里巴巴矢量图库下载&#xff1a;https://www.iconfont.cn/search/index?searchTypeicon&q%E7%9E%8…

反射Reflection

目录1. 反射快速入门1. 需求2. 运用反射2. 反射原理图2.1 反射相关的主要类2.1 反射优点和缺点2.1.1 反射调用优化-关闭访问检查4. Class类分析4.1 Class类常用方法4.2 获取Class类对象【六种】4.3 哪些类型有class对象4.4 动态和静态加载4.5 类加载流程图5. 获取类结构信息5.1…

RabbitMQ、Kafka、RocketMQ三种消息中间件对比总结

文章目录前言侧重点架构模型消息通讯其他对比总结参考文档前言 不论Kafka还是RabbitMQ和RocketMQ&#xff0c;作为消息中间件&#xff0c;其作用为应用解耦、异步通讯、流量削峰填谷等。 拿我之前参加的一个电商项目来说&#xff0c;订单消息通过MQ从订单系统到支付系统、库存…

【国科大模式识别】第一次作业

【题目一】设 ωmax⁡\omega_{\max }ωmax​ 为类别状态, 此时对所有的 i(i1,…,c)i(i1, \ldots, c)i(i1,…,c), 有 P(ωmax⁡∣x)≥P\left(\omega_{\max } \mid \boldsymbol{x}\right) \geqP(ωmax​∣x)≥ P(ωi∣x)P\left(\omega_i \mid \boldsymbol{x}\right)P(ωi​∣x) …

理解 mysql 之 count(*)的性能问题

一、 count(*) 为什么性能差 在Mysql中&#xff0c;count()的作用是统计表中记录的总行数。而count()的性能跟存储引擎有直接关系&#xff0c;并非所有的存储引擎&#xff0c;count(*)的性能都很差。在Mysql中使用最多的存储引擎是&#xff1a;innodb 和 myisam 。 在 myisam…

手写RPC框架-整合注册中心模块设计与实现

源码地址&#xff1a;https://github.com/lhj502819/IRpc/tree/v2 思考 如果同一个服务有10台不同的机器进行提供&#xff0c;那么客户端该从哪获取这10台目标机器的ip地址信息呢&#xff1f;随着调用方的增加&#xff0c;如何对服务调用者的数据进行监控呢&#xff1f;服务提…

十五、类加载器、反射、xml

类加载器 1类加载器【理解】 作用 负责将.class文件&#xff08;存储的物理文件&#xff09;加载在到内存中 2类加载的过程【理解】 类加载时机 创建类的实例&#xff08;对象&#xff09;调用类的类方法访问类或者接口的类变量&#xff0c;或者为该类变量赋值使用反射方式来…

【C++编程调试秘籍】| 总结归纳要点

文章目录一、编译器是捕捉缺陷的最好场合1 如何使用编译器捕捉缺陷二、在运行时遇见错误该如何处理1 该输出哪些错误信息2 执行安全检查则会减低程序效率&#xff0c;该如何处理呢3 当运行时遇到错误时&#xff0c;该如何处理四、索引越界1 动态数组2 静态数组3 多维数组5 指针…

uboot驱动和Linux内核驱动有什么区别?

一、前言 uboot启动后&#xff0c;一些外设如DDR、EMMC、网口、串口、音频、显示等等已经被初始化&#xff0c;为什么Linux内核中还需要写Linux驱动呢&#xff1f; 二、uboot驱动和Linux驱动的区别 1、直观理解 驱动&#xff0c;不仅仅是为了初始化&#xff0c;还实现了一组…

《Linux》1.权限

1.用户 首先介绍一下Linux中的用户概念。Linux下有两种用户&#xff1a;超级用户&#xff08;root&#xff09;&#xff0c;普通用户。 超级用户&#xff1a;可以再linux系统下做任何事情&#xff0c;不受限制 普通用户&#xff1a;在linux下做有限的事情。 超级用户的命令提示…

FTP回复码

FTP回复码由3个数字和对应文本组成 恢复定义为3个数字编码&#xff0c;紧跟一个空格 sp&#xff0c;然后是一行文本&#xff0c;以telnet的换行符表是结束 但是&#xff0c;某些情况下&#xff0c;文本内容太长超过一行&#xff0c;这就需要在第一行和最后一行特殊处理。处理格…

C++模拟实现栈(stack)和队列 (queue)

目录 一、栈&#xff08;stack) 的介绍 二、队列&#xff08;queue) 的介绍 三、容器适配器 二、deque的简单介绍 三、模拟实现stack 3.1 stack.h 3.2 test.cpp 四、模拟实现queue 4.1 queue.h 4.2 test.cpp 一、栈&#xff08;stack) 的介绍 1. stack是一种容器适配…

字符串、内存函数的介绍(13)

目录 1、字符串函数 1、strlen 模拟实现&#xff1a; 2、strcpy 模拟实现&#xff1a; 3、strcat 模拟实现&#xff1a; 4、strcmp 模拟实现&#xff1a; 5、strncpy 6、strncat 7、strncmp 8、strstr 模拟实现&#xff1a; 9、strtok 10、strerror 11、其他字…

回顾2022年5月IB全球统考成绩,这些学校IB成绩非常亮眼

IB大考成绩放榜&#xff0c;全球17&#xff0c;3878名学生在2022年5月的考试中获得文凭课程(DP)和职业课程(CP)的成绩。今年全球640位考生获得满分45分&#xff0c;全球平均分31.98分。以下是部分公布公布成绩的学校&#xff1a; 成都树德中学国际部&#xff1a;在2022年的全球…

电商维权控价方法论

电商经济繁荣发展&#xff0c;品牌销售渠道多样化&#xff0c;带来流量的同时&#xff0c;各种渠道问题也暴露出来&#xff0c;如&#xff0c;低价、侵权……渠道秩序面临着严峻挑战&#xff0c;品牌生命周期也受到了威胁。所以&#xff0c;越来越多的品牌选择维权控价&#xf…

2022年终总结与2023新年展望

前言 时间过得太快了&#xff0c;虽然写博客已经很多年了&#xff0c;但是年终总结一直由于种种原因没有写过&#xff0c;2022年确实是魔幻的一年&#xff0c;不知不觉自己也已经研二了&#xff0c;因为疫情的原因突然放开&#xff0c;提前放假回家&#xff0c;借此机会写一下…