简易聊天系统的实现

本文介绍了一个简易聊天系统的实现,包括客户端和服务端的设计。客户端通过UserClientService校验登录,使用MessageClientService发送信息,并管理连接线程。服务端则负责监听连接,处理登录验证,管理用户线程并实现群发消息功能。系统支持用户登录、显示在线用户列表、私聊和群发消息。
摘要由CSDN通过智能技术生成

概述

无论在面试还是自己写代码的时候,对网络编程和多线程总是卡壳,自己写也写不明白,背八股也懵懵懂懂,最近系统学了一下网络编程,融合多线程,实现了一个简易聊天系统,分为客户端和服务端。

客户端

 

 

  •  Message:消息类,用于封装消息发送者、接收者、内容、发送时间、消息类型。
  • MessageType:用来标识不同的消息类型。
  • User:封装用户信息。
  • QQview:用于模拟用户操作页面。
  • Utility:读取用户输入工具类。
  • UserClientService:校验用户登录,开启线程与服务端建立连接,退出登录等功能。
  • MessageClientService:实现用户发送信息,群发信息方法。
  • ClientConnectServerThread:每建立一个连接,都需要开启一个新线程。
  • ManageClientConnectServerThread:管理用户连接线程,存放在一个Hashmap中。

服务端

  • Message/MessageType/User/Utility:与客户端一致。
  • QQServer:开启服务器。
  • ServerConnectClientThread:每接收到客户端连接,开启一个线程保持连接。
  • ManageClientThread:管理用户连接线程。
  • SendNewsToAllService:群发消息功能实现。

具体实现流程

本案例实现了用户登录、显示登录用户列表、私聊消息、群发消息。

用户登录

开启服务端

开启客户端

客户端提示输入用户名密码;

调用userClientService.checkUser,checkUser中做了与服务端建立socket连接,并创建线程的操作

            socket = new Socket(InetAddress.getByName("127.0.0.1"),9999);
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(u);

            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            Message ms = (Message)ois.readObject();
            if(ms.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCEED)){
                b = true;
                //创建一个和服务器端保持通讯的线程
                ClientConnectServerThread ccst = new ClientConnectServerThread(socket);
                ccst.start();
                //为了后面客户端扩展,方便管理
                ManageClientConnectServerThread.addClientConnectServerThread(userId, ccst);
                b = true;
            }else{
                //登录失败,就不能启动和服务器通讯的线程,关闭socket
                socket.close();
            }

与此同时,服务端一直在监听连接请求,每得到一个请求,都要开启一个新线程,同时放入集合进行管理

System.out.println("服务端在9999端口监听...");
            new Thread(new SendNewsToAllService()).start();
            ss = new ServerSocket(9999);
            while(true){
                Socket socket = ss.accept();
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                User u = (User) ois.readObject();
                Message message = new Message();
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                if(checkUser(u.getUserId(), u.getPassword())){
                    message.setMesType(MessageType.MESSAGE_LOGIN_SUCCEED);
                    oos.writeObject(message);
                    //创建一个线程,和客户端保持通信,该线程持有socket对象
                    ServerConnectClientThread serverConnectClientThread = new ServerConnectClientThread(socket, u.getUserId());
                    serverConnectClientThread.start();
                    //放入集合中进行管理
                    ManageClientThread.addClientThread(u.getUserId(), serverConnectClientThread);
                }else{
                    System.out.println("用户id = "+ u.getUserId() +"密码 = "+ u.getPassword() +"验证失败");
                    message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
                    oos.writeObject(message);
                    socket.close();
                }
            }

注意代码中,登录成功后,封装Message信息返回给客户端,客户端接收到后进入二级页面

if(userClientService.checkUser(userId, pwd)) {
                        System.out.println("欢迎用户" + userId + "登录成功");
                        while (loop) {
                            System.out.println("网络通讯二级系统"+ userId + "登录");
                            System.out.println("\t\t 1 显示在线用户列表");
                            System.out.println("\t\t 2 群发消息");
                            System.out.println("\t\t 3 私聊消息");
                            System.out.println("\t\t 4 发送文件");
                            System.out.println("\t\t 9 退出系统");

                            System.out.println("请输入你的选择");

                            key = Utility.readString(1);

                            switch (key) {
                                case "1":
                                    System.out.println("显示在线用户列表");
                                    userClientService.getOnlineFriendList();
                                    break;
                                case "2":
                                    System.out.println("请输入你想对大家说的话");
                                    String s = Utility.readString(100);
                                    messageClientService.sendMessageToAll(s, userId);
                                    break;
                                case "3":
                                    System.out.println("请输入想要聊天的用户号(在线)");
                                    String getterId = Utility.readString(50);
                                    System.out.println("请输入想说的话:");
                                    String content = Utility.readString(100);
                                    messageClientService.sendMessage(content, userId, getterId);
                                    break;
                                case "4":
                                    System.out.println("发送文件");
                                    break;
                                case "9":
                                    userClientService.logout();
                                    loop = false;
                                    break;

                            }
                        }
                    }else{
                            System.out.println("登录失败");

                    }

显示在线用户列表

选择1后,向服务端发送请求,获取当前在线用户列表

case "1":
  System.out.println("显示在线用户列表");
  userClientService.getOnlineFriendList();
  break;

getOnlineFriengList方法封装消息,并通过已有的线程,获取socket,获取输出流,将该请求对象输出到服务器端

public void getOnlineFriendList(){
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
        message.setSender(u.getUserId());

        try {
            ClientConnectServerThread clientConnectServerThread = ManageClientConnectServerThread.getClientConnectServerThread(u.getUserId());
            Socket socket = clientConnectServerThread.getSocket();
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(message);//发送一个meesage对象,向服务器要在线用户列表
        }catch (Exception e){
            e.printStackTrace();
        }
    }

服务端收到消息后,判定MessageType为MESSAGE_GET_ONLINE_FRIEND,调用管理线程的方法,获取当前所有用户名称,同时创建一个message,设置内容,接收者、消息类型等信息,获取socket输出流,输出给客户端。

if(message.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)){
                    System.out.println(message.getSender()+"要求在线用户列表");
                    String onlineUser = ManageClientThread.getOnlineUser();
                    Message message2 = new Message();
                    message2.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
                    message2.setContent(onlineUser);
                    message2.setGetter(message.getSender());
                    //返回给客户端
                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                    oos.writeObject(message2);
                }

因为hashmap中存储的key为用户名、value为线程对象,所以遍历得到key就可以了

public static String getOnlineUser(){
        Iterator<String> iterator = map.keySet().iterator();
        String onLineUserlist = "";
        while(iterator.hasNext()){
            onLineUserlist += iterator.next().toString() + " ";
        }
        return onLineUserlist;
    }

客户端判断消息类型为MESSAGE_RET_ONLINE_FRIEND,将得到的Message遍历显示在控制台

if(ms.getMesType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)){
                    String[] onLineUser = ms.getContent().split(" ");
                    System.out.println("=======当前在线用户列表=======");
                    for (int i = 0; i < onLineUser.length; i++){
                        System.out.println("用户:" + onLineUser[i]);
                    }
                }

私聊消息

控制台输入3,进行私聊功能,选定聊天对象,输入想说的话,调用sendMessage方法

case "3":
     System.out.println("请输入想要聊天的用户号(在线)");
     String getterId = Utility.readString(50);
    System.out.println("请输入想说的话:");
    String content = Utility.readString(100);
    messageClientService.sendMessage(content, userId, getterId);
    break;

sendMessage封装发送者接收者,发送内容,消息类型,同时获取socket与输出流,将message发送至服务器端

public void sendMessage(String content, String senderId, String getterId){
        Message message = new Message();
        message.setSender(senderId);
        message.setGetter(getterId);
        message.setContent(content);
        message.setMesType(MessageType.MESSAGE_COMM_MES);
        message.setSendTime(new Date().toString());
        System.out.println(senderId + "要对" + getterId + "发消息");
        try {
            ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
            oos.writeObject(message);
        }catch (Exception e){
            e.printStackTrace();
        }

    }

服务端收到消息后,判别消息类型为MESSAGE_COMM_MES,得到message中的getter,也就是接收者,在通过管理线程的方法中,获取该getter的线程,进而获取其socket,继而获取输出流,将该message转发至该客户端。也就是说在私聊功能中,客户端没有对Message做任何处理,只是得到了该消息的接收者,获取其通道,将消息转发给该接收者。主要起转发作用。

else if(message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {
                    ServerConnectClientThread serverConnectClientThread = ManageClientThread.getServerConnectClientThread(message.getGetter());
                    ObjectOutputStream oos = new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
                    oos.writeObject(message);
                }

客户端收到message后,判断其类型为MESSAGE_COMM_MES,在控制台输出谁想对谁说啥

else if(ms.getMesType().equals(MessageType.MESSAGE_COMM_MES)){
                    System.out.println("\n" + ms.getSender() + "对" + ms.getGetter() + "说:" + ms.getContent());
                }

群发消息

与私聊类似,区别在于,服务端转发时,不仅仅是给getter一个人转了,而是要遍历所有用户,都转发,也就是在管理集合中,获取到所有除发送者之外的线程,进行转发

else if(message.getMesType().equals(MessageType.MESSAGE_TO_ALL)){
                    HashMap<String, ServerConnectClientThread> map = ManageClientThread.getMap();
                    Iterator<String> iterator = map.keySet().iterator();
                    while(iterator.hasNext()){
                        String onLineUserId = iterator.next().toString();
                        if(!onLineUserId.equals(message.getSender())){
                            ObjectOutputStream oos = new ObjectOutputStream(map.get(onLineUserId).getSocket().getOutputStream());
                            oos.writeObject(message);
                        }
                    }
                }

客户端也类似

else if(ms.getMesType().equals(MessageType.MESSAGE_TO_ALL)){
                    System.out.println("\n" + ms.getSender() + "对大家说:" + ms.getContent());
                }

服务器群发消息

不同于客户端群发消息,客户端群发是发送消息至服务器,服务器转发给其他客户端。

而服务器群发是,服务器在启动时,也开启了一个线程,主动接受在控制台输入的内容,并已群发的形式,遍历所有用户发送至客户端(此行为与用户群发类似)

public class SendNewsToAllService extends Thread {

    private Message message = new Message();

    @Override
    public void run() {
        while (true) {
            message.setMesType(MessageType.MESSAGE_TO_ALL);
            message.setSender("服务器");
            message.setSendTime(new Date().toString());
            System.out.println("请输入你想推送的全体消息:(输入exit表示退出!)");
            String s = Utility.readString(100);
            if(s.equals("exit")) break;
            message.setContent(s);
            HashMap<String, ServerConnectClientThread> map = ManageClientThread.getMap();
            Iterator<String> iterator = map.keySet().iterator();
            while(iterator.hasNext()){
                String user = iterator.next();
                try {
                    ObjectOutputStream oos = new ObjectOutputStream(ManageClientThread.getServerConnectClientThread(user).getSocket().getOutputStream());
                    oos.writeObject(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }
}

客户端与群发消息的代码完全一致,因为消息类型都为MessageType.MESSAGE_TO_ALL

else if(ms.getMesType().equals(MessageType.MESSAGE_TO_ALL)){
                    System.out.println("\n" + ms.getSender() + "对大家说:" + ms.getContent());
                }

总结

待定!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值