美文网首页
【Java学习】简易QQDemo——实现私聊和高级群聊

【Java学习】简易QQDemo——实现私聊和高级群聊

作者: 榆野铃爱 | 来源:发表于2019-08-22 22:50 被阅读0次

    心得感悟

    一开始说要实现私聊和群聊时并没有想到可以用标识,有的时候还是感觉自己的程序分析能力不是很好,思维不够开阔,希望写多了Demo后有所进步吧。老师布置的实现文件传输、发语音等功能我还是不会做,等以后会做了,再继续完善这个Demo。


    内容简概

    • 一、程序整体结构分析
    • 二、编写ChatProtocol(标识)类
    • 三、编写Server(服务器端)类
    • 四、编写Client(客户端)类
    • 五、编写UserManager(用户管理)类
    • 六、运行效果

    具体内容

    一、程序整体结构分析

    1. 实现的界面效果
    下面模拟三个用户的聊天界面,按照数字序号阅读,你可能并不知道a+和p+是什么,但没关系我下面会讲到。

    2. 程序流程分析
    首先,运行服务器后,客户端需要登录,既然有登录就要判断是否已经登录过。登录成功,则可以选择群聊或者私聊,登录失败则需要重新登录。如何实现判断呢?接着往下面看。

    3. 实现判断登录
    要判断,那就要有对比。首先,我们需要保存已经登录用户的信息,然后拿正在登录的用户的信息和前者相比,如果已经存在,则登录失败(不能重复登录),如果不存在,则登录成功。而且要实现网络通信,就必须保存用户的IP地址,所以我们可以用映射关系来保存用户登录信息:用户名——socket,而多个映射关系又可以用Map集合来保存

    4. 实现判断输入内容
    由于我们是在Android studio模拟QQ,输入框只有一个,无法模拟私聊窗口和群聊窗口,故需要判断用户在聊天框输入的内容究竟是私聊还是群聊。

    是不是可以有什么东西能够当做判断的标识呢?之前我们讲到接口可以制定一套规范,所以我们可以通过接口来制定一套判断的规范,只不过里面没有方法。规范如下:

    判断的内容 标识
    用户登录 u+(u+用户名u+)
    登录成功 1
    登录失败 -1
    分割用户名与消息 ♥(用户名♥聊天内容)
    私聊 p+(p+用户名♥聊天内容p+)
    群聊 a+(a+聊天信息a+)

    还不理解也没有关系,接着往下看。

    二、编写ChatProtocol(标识)类

    public interface ChatProtocol {
        // 登录
        String LOGIN_FLAG = "u+";
        // 私聊
        String PRIVATE_FLAG = "p+";
        // 群聊
        String PUBLIC_FLAG = "a+";
    
        // 分隔符
        String SPLIT_FLAG = "♥";
    
        // 成功/失败的状态
        String SUCCESS = "1";
        String FAILURE = "-1";
    }
    

    三、编写Server(服务器端)类

    创建服务器端的主线程,将终端的输入发送给客户端

    public class Server {
        // 用于保存每一个用户对应的姓名和Socket
        public static UserManager manager = new UserManager();
    
        public static void main(String[] args){
            // 创建ServerSocket
            try(ServerSocket ss = new ServerSocket(8888)) {
                // 监听所有来连接的客户端
                while (true){
                    Socket socket = ss.accept();
    
                    // 让子线程处理这个Socket
                    new ServerThread(socket).start();
                }
            } catch (IOException e) {}
        }
    }
    

    创建一个服务器端的子线程,处理服务器端接收客户端数据

    class ServerThread extends Thread{
        private Socket socket;
    
        public ServerThread(Socket socket){ this.socket = socket; }
    
        @Override
        public void run() {
            BufferedReader br = null;
            PrintStream ps = null;
            try {
                // 得到对应的输入流对象
                br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                // 得到对应的输出流
                ps = new PrintStream(socket.getOutputStream());
    
                String line = null;
                while ((line = br.readLine()) != null){
                    // 登录u+ ... u+
                    if (line.startsWith(ChatProtocol.LOGIN_FLAG) && line.endsWith(ChatProtocol.LOGIN_FLAG)){
                        //u+jacku+
                        // 获取名字
                        int index = line.lastIndexOf(ChatProtocol.LOGIN_FLAG);
                        String name = line.substring(2,index);
                        System.out.println(name);
    
                        // 判断这个用户是否已经登录
                        if (Server.manager.islogined(name)){
                            // 登录过了
                            // 发送结果给客户端
                            ps.println(ChatProtocol.FAILURE);
                        }
                        else {
                            // 没有登录
                            // 保存当前登录的用户信息
                            Server.manager.save(name,socket);
                            // 发送结果给客户端
                            ps.println(ChatProtocol.SUCCESS);
                        }
                    }
                    // 判断是不是私聊
                    else if (line.startsWith(ChatProtocol.PRIVATE_FLAG) && line.endsWith(ChatProtocol.PRIVATE_FLAG)) {
                        // p+jack♥hellop+
                        // 获取信息
                        int index = line.lastIndexOf(ChatProtocol.PRIVATE_FLAG);
                        String msg = line.substring(2,index);
                        // 分割 jack hello
                        String[] items = msg.split(ChatProtocol.SPLIT_FLAG);
                        // 用户名
                        String name = items[0];
                        // 聊天内容
                        String message = items[1];
    
                        // 通过用户名找到对应的socket
                        Socket desSocket = Server.manager.socketByName(name);
                        PrintStream desPs = new PrintStream(desSocket.getOutputStream());
    
                        // 获取当前用户的名称
                        String currentName = Server.manager.nameBySocket(socket);
    
                        // 发送私聊消息
                        desPs.println(currentName+"向你发来私聊:"+ message);
                    }else {
                        // 群聊
                        // 处理数据
                        String msg = line.substring(2,line.length()-2);
    
                        // 获取当前用户的名称
                        String currentName = Server.manager.nameBySocket(socket);
    
                        // 遍历所有的用户信息
                        Collection<Socket> sockets = Server.manager.allUsers();
                        for(Socket s:sockets){
                            PrintStream tempps = new PrintStream(s.getOutputStream());
                            tempps.println(currentName+"发来群聊:"+msg);
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    四、编写Client(客户端)类

    创建客户端的主线程,将终端的输入发送给服务器端

    public class Client {
        public static void main(String[] args){
            BufferedReader br = null;
            PrintStream ps = null;
            BufferedReader brServer = null;
    
            // 连接服务器
            try ( Socket socket = new Socket("127.0.0.1",8888)){
               // 登录
                // 接收终端输入流
                br = new BufferedReader(new InputStreamReader(System.in));
                // 发给服务器端的输入流
                ps = new PrintStream(socket.getOutputStream());
                // 接收服务器端的输入流
                brServer = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                while (true){
                    // 接收终端输入信息
                    String line = JOptionPane.showInputDialog("请输入姓名");
    
                    // 拼接登录格式
                    String loginStr = ChatProtocol.LOGIN_FLAG+line+ChatProtocol.LOGIN_FLAG;
                    // 发送给服务器端
                    ps.println(loginStr);
                    // 接收服务器端返回的结果
                    String result = brServer.readLine();
    
                    // 判断登录结果
                    if (result.equals(ChatProtocol.SUCCESS)){
                        System.out.println("登录成功");
                        break;
                    }else {
                        System.out.println("用户名已存在 请重新登录");
                    }
                }
                // 登陆成功
                // 开启线程处理服务器端的输入
                new ClientThread(socket).start();
    
                // 接收终端输入 发送给服务器端
                String line = null;
                while ((line = br.readLine()) != null){
                    // 发送给服务器
                    System.out.println(line);
                    ps.println(line);
                }
            } catch (IOException e) {}
        }
    }
    

    创建一个客户端的子线程,处理客户端接收服务器端数据

    class ClientThread extends Thread{
        private Socket socket;
        // 保存socket对象
        public ClientThread(Socket socket){
            this.socket = socket;
        }
    
        @Override
        public void run() {
            BufferedReader br = null;
            try {
                // 获取服务器端的输入流对象
                br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                // 读取数据
                String line = null;
                while ((line = br.readLine()) != null){
                    System.out.println(line);
                }
            } catch (IOException e) {
                System.out.println("网络出错");
            }finally {
                try {
                    if (br != null){
                        br.close();
                    }
                    if (socket != null){
                        socket.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    五、编写UserManager(用户管理)类

    创建一个管理用户信息的类,用于管理所有的登录的用户Map<String,Socket>,并判断某个用户是否已经登录

    public class UserManager {
        // 保存所有用户信息
        private Map<String,Socket> users = new HashMap<>();
    
        /**
         * 判断用户是否已经登录
         */
        public boolean islogined(String name){
            // 遍历数组
            for (String key:users.keySet()){
                if (key.equals(name)){
                    return true;
                }
            }
            return false;
        }
        /**
         * 保存当前登录的用户信息
         */
        public void save(String name,Socket socket){
            users.put(name,socket);
    
        }
        /**
         * 通过用户名找到对应的socket
         */
        public Socket socketByName(String name){
            return users.get(name);
        }
    
        /**
         * 通过Socket对象找到对应的名称
         */
        public String nameBySocket(Socket socket){
            for (String key:users.keySet()){
                // 取出这个key对应的Socket
                if (socket == users.get(key)){
                    return key;
                }
            }
            return null;
        }
    
        /**
         * 获取所有人的socket对象
         */
        public synchronized Collection<Socket> allUsers(){
            return users.values();
        }
    }
    

    六、运行效果

    在本机运行时,可以创建多个Client类模拟多个客户端。我这里再创建一个Client1和Client2。运行效果图顺序为:私聊→群聊

    私聊
    群聊

    相关文章

      网友评论

          本文标题:【Java学习】简易QQDemo——实现私聊和高级群聊

          本文链接:https://www.haomeiwen.com/subject/xlunsctx.html