模拟QQ聊天功能通信系统

多用户通信系统

1、用户登录

我们人为规定 用户名 id = 100, 密码123456就可以登录,其他用户不能登录。

后面使用HashMap模拟数据库,可以多个用户登录。

共有类

//表示一个用户信息
public class User implements Serializable {

    private static final long serialVersionUID = 1L;
    private String userId;//用户ID//用户名
    private String password;//用户密码

    //省略构造器和setXxx和getXxx
}
//表示客户端和服务端通信时的消息对象
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 fileLen = 0;
    private String dest;//目的路径
    private String src;//源文件路径

	//省略setXxx和getXxx
}
//表示消息类型
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";//客户端请求退出
    String MESSAGE_TO_ALL_MES = "7";//群发消息
    String MESSAGE_FILE_MES = "8";//文件消息(发送文件)
    
}

客户端

//客户端菜单界面
public class QQView {

    private boolean loop = true;//控制是否显示菜单
    private String key = "";//接收用户的键盘输入
    private UserClientService userClientService = new UserClientService();//用于登录和注册

    public static void main(String[] args) {
        new QQView().mainMenu();
        System.out.println("客户端退出系统......");
    }

    //显示主菜单
    private void mainMenu() {

        while (loop) {

            System.out.println("============欢迎登录网络通信系统============");
            System.out.println("\t\t 1 登录系统");
            System.out.println("\t\t 9 退出系统");
            System.out.print("请输入你的选择:");

            key = Utility.readString(1);

            //根据用户输入,来处理不同的逻辑
            switch (key) {
                case "1":
                    System.out.print("请输入用户名:");
                    String userId = Utility.readString(50);
                    System.out.print("请输入密 码:");
                    String pwd = Utility.readString(50);
                    //需要到服务器去验证用户是否合法
                    //这里编写一个类 UserClientService[用户登录/注册]
                    if (userClientService.checkUser(userId, pwd)) {
                        System.out.println("============欢迎 (用户 " + userId + ") ============");
                        //进入到二级菜单
                        while (loop) {
                            System.out.println("\n============网络通信系统二级菜单(用户 " + 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.print("请输入你的选择:");
                            key = Utility.readString(1);
                            switch (key) {
                                case "1":
                                    System.out.println("显示在线用户列表");
                                    break;
                                case "2":
                                    System.out.println("群发消息");
                                    break;
                                case "3":
                                    System.out.println("私聊消息");
                                    break;
                                case "4":
                                    System.out.println("发送文件");
                                    break;
                                case "9":
                                    loop = false;
                                    break;
                            }
                        }
                    } else {//登录失败
                        System.out.println("============登录失败============");
                    }
                    break;
                case "9":
                    loop = false;
                    break;
            }
        }
    }
}
//该类完成用户登录验证和用户注册等功能
public class UserClientService {

    //因为可能在其他地方使用User信息,因此作为成员属性
    private User u = new User();
    private Socket socket;

    //根据用户名和密码验证用户是否合法
    public boolean checkUser(String userId, String pwd) {
        boolean b = false;
        //创建User对象
        u.setUserId(userId);
        u.setPassword(pwd);
        //连接到服务器
        try {
            socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999);
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(u);//发送User对象

            //读取从服务器回复的Message对象
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            Message ms = (Message) ois.readObject();

            if (ms.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCEED)) {//登录成功

                //创建一个和服务器端保持通讯的线程->创建一个类ClientConnectServerThread
                ClientConnectServerThread clientConnectServerThread = new ClientConnectServerThread(socket);
                //启动客户端的线程
                clientConnectServerThread.start();
                //将线程放在集合中,方便管理
                ManageClinetConnectServerThread.addClientConnectServerThread(userId, clientConnectServerThread);
                b = true;

            } else {
                //如果登录失败。。我们要关闭socket
                socket.close();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        return b;
    }
}
//客户端的线程,持有socket
public class ClientConnectServerThread extends Thread {
    //该线程需要持有Socket
    private Socket socket;

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

    @Override
    public void run() {
        while (true) {

            try {
                System.out.println("客户端线程,等待读取从服务端发送来的信息...");
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                //如果服务器没有发送Message对象,线程就会阻塞在这里
                Message message = (Message) ois.readObject();
                //后面需要使用message

            } catch (Exception e) {
                e.printStackTrace();
            }


        }
    }

    //为了更方便获得Socket
    public Socket getSocket() {
        return socket;
    }
}
//管理客户端连接到服务器的线程的类
public class ManageClinetConnectServerThread {
    //我们把多个线程放入到一个HashMap集合中,key就是用户id,vlaue就是线程
    private static HashMap<String, ClientConnectServerThread> hm = new HashMap<>();

    //将某个线程加入到集合
    public static void addClientConnectServerThread(String userId, ClientConnectServerThread clientConnectServerThread) {
        hm.put(userId, clientConnectServerThread);
    }

    //通过userId,得到对应的线程
    public static ClientConnectServerThread getClientConnectServerThread(String userId) {
        return hm.get(userId);
    }
}

服务端

//这是服务器,在监听9999,等待客户端的连接,并保持通讯
public class QQServer {

    private ServerSocket ss = null;
    //创建一个集合,存放多个用户,如果是这些用户登录,就认为是合法的
    //HashMap 没有处理线程安全,因此在多线程情况下是不安全的
    //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("紫霞仙子", new User("紫霞仙子", "123456"));
        validUsers.put("菩提老祖", new User("菩提老祖", "123456"));

    }

    //验证用户是否有效的方法
    private boolean checkUser(String userId, String password) {

        User user = validUsers.get(userId);
        if (user == null) {//说明userId没有存在在validUsers 的key中
            return false;
        }
        if (!user.getPassword().equals(password)) {//userId正确,但是密码错误
            return false;
        }
        return true;
    }

    public QQServer() {
        //注意:端口可以写在配置文件里
        try {
            System.out.println("服务端在9999端口监听。。。");
            ss = new ServerSocket(9999);

            while (true) {//当和某个客户端连接后,会继续监听
                Socket socket = ss.accept();
                //得到socket的输入流
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                User u = (User) ois.readObject();//读取客户端发送的User对象
                //创建一个Message对象,准备回复客户端
                Message message = new Message();
                //获得socket输出流
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                //验证
                if (checkUser(u.getUserId(),u.getPassword())) {//登录成功
                    message.setMesType(MessageType.MESSAGE_LOGIN_SUCCEED);
                    //将message对象回复给客户端
                    oos.writeObject(message);
                    //创建一个线程,和客户端保持通信,该线程需要持有socket
                    ServerConnectClientThread serverConnectClientThread =
                            new ServerConnectClientThread(socket, u.getUserId());
                    //启动该线程
                    serverConnectClientThread.start();
                    //把该线程对象,放到一个集合中
                    ManageClientThreads.addClientThread(u.getUserId(), serverConnectClientThread);

                } else {//登录失败
                    message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
                    oos.writeObject(message);
                    //关闭socket
                    socket.close();
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //如果服务器退出了while,说明服务端不在监听,要关闭ServerSocket
            try {
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
//和某个客户端保持通讯的类
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() {//可以发送/接收消息

        while (true) {
            try {
                System.out.println("服务端和客户端" + userId + "保持通讯,读取数据...");
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                Message message = (Message) ois.readObject();
                //后面使用message
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
//该类用于管理和客户端通信的线程
public class ManageClientThreads {
    private static HashMap<String,ServerConnectClientThread> hm = new HashMap<>();

    //添加线程对象到 hm 集合
    public static void addClientThread(String userId,ServerConnectClientThread serverConnectClientThread){

        hm.put(userId, serverConnectClientThread);
    }
    //根据userId 返回serverConnectClientThread
    public static ServerConnectClientThread getServerConnectClientThread(String userId){
        return hm.get(userId);
    }
}

2、拉取在线用户列表

客户端

//UserClientService add!!!!!!
//向服务端请求在线用户列表
public void onlineFriendList(){

    //发送一个Message,类型MESSAGE_GET_ONLINE_FRIEND
    Message message = new Message();
    message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
    message.setSender(u.getUserId());
    
    //发送给服务器
    //得到当前线程的socket,对应的输出流对象
    try {
        ObjectOutputStream oos = new ObjectOutputStream
            (ManageClinetConnectServerThread.getClientConnectServerThread(u.getUserId()).getSocket().getOutputStream());
        oos.writeObject(message);
    } catch (IOException e) {
        e.printStackTrace();
    }

}
//ClientConnectServerThread  run() update!!!!!!
@Override
public void run() {
    while (true) {

        try {
            System.out.println("客户端线程,等待读取从服务端发送来的信息...");
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            //如果服务器没有发送Message对象,线程就会阻塞在这里
            Message message = (Message) ois.readObject();
            //判断message类型,然后做相应的业务处理
            //如果读取的是服务端返回的在线用户列表
            if (message.getMesType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)) {
                //取出在线列表信息,并显示
                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("是其他类型,暂时不处理。。。");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

服务端

//ManageClientThreads add!!!!!!!!!
//返回在线用户列表
public static String getonlineUser() {
    //遍历集合,遍历 hashmap的key
    Iterator<String> iterator = hm.keySet().iterator();
    String onlineUserList = "";
    while (iterator.hasNext()) {
        onlineUserList += iterator.next().toString() + " ";
    }
    return onlineUserList;
}
//ServerConnectClientThread  run()  update!!!!
@Override
public void run() {//可以发送/接收消息

    while (true) {
        try {
            System.out.println("服务端和客户端" + userId + "保持通讯,读取数据...");
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            Message message = (Message) ois.readObject();
            //后面使用message
            if(message.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)){
                //客户端要在线用户列表
                System.out.println(message.getSender()+" 要在线用户列表");
                String onlineUser = ManageClientThreads.getonlineUser();
                //构建一个message对象,返回给客户端
                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);

            }else{
                System.out.println("其他类型的message,暂时不处理。。。");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3、无异常退出

客户端

//UserClientService add!!!!!!!!!!!!!!!
//退出客户端,并给服务端发送一个退出系统的message对象
public void logout(){
    Message message = new Message();
    message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
    message.setSender(u.getUserId());//一定要指定是哪个客户端

    //发送
    try {
        ObjectOutputStream oos =
            new ObjectOutputStream(ManageClinetConnectServerThread.getClientConnectServerThread(u.getUserId()).getSocket().getOutputStream());
        oos.writeObject(message);
        System.out.println(u.getUserId()+"退出系统");
        System.exit(0);//结束进程
    } catch (IOException e) {
        e.printStackTrace();
    }
}

服务端

//ServerConnectClientThread update!!!!!!!!!!!
else if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)) {
    //客户端退出
    System.out.println(message.getSender()+"要求退出");
    //将这个客户端从线程集合中移除
    ManageClientThreads.removeServerConnectClientThread(message.getSender());
    socket.close();//关闭连接
    //退出线程
    break;
}

4、私聊

客户端

//QQView update!!!!!
case "3":
    System.out.print("请输入想要聊天的用户号(在线):");
    String getterId = Utility.readString(50);
    System.out.print("请输入想说的话:");
    String content = Utility.readString(50);
    //将消息发送给服务端
    messageClientService.sendMessageToOne(content, userId, getterId);
    break;
//该类提供和消息相关的服务方法
public class MessageClientService {

    /**
     * @param content  内容
     * @param senderId 发送用户id
     * @param getterId 接收用户id
     */
    public void sendMessageToOne(String content, String senderId, String getterId) {
        //构建message
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_COMM_MES);
        message.setSender(senderId);
        message.setGetter(getterId);
        message.setContent(content);
        message.setSendTime(new Date().toString());
        System.out.println(senderId + " 对 " + getterId + " 说 " + content);
        //发送给服务端
        try {
            ObjectOutputStream oos =
                    new ObjectOutputStream(ManageClinetConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
//ClientConnectServerThread update!!!!
else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {
    //接收到普通聊天消息
    //把从服务端转发的消息,显示到控制台即可
    System.out.println("\n" + message.getSendTime() + "\n" + message.getSender()
                       + " 对 " + message.getGetter() + " 说 " + message.getContent());
}

服务端

//ServerConnectClientThread update!!!!!!
else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {
    //客户端私聊
    ServerConnectClientThread serverConnectClientThread = ManageClientThreads.getServerConnectClientThread(message.getGetter());
    //得到对应的socket输出流,将message发送给指定的客户端
    ObjectOutputStream oos =
        new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
    oos.writeObject(message);//如果客户不在线,可以保存到数据库,做成离线消息

}

5、群聊

无非就是服务端一次性遍历多个客户端socket。

客户端

//MessageClientService update!!!!!!!
/**
  *
  * @param content 内容
  * @param senderId 发送者id
*/
public void sendMessageToAll(String content,String senderId){
    //构建message
    Message message = new Message();
    message.setMesType(MessageType.MESSAGE_TO_ALL_MES);
    message.setSender(senderId);
    message.setContent(content);
    message.setSendTime(new Date().toString());
    System.out.println(senderId + " 对大家说 " + content);
    //发送给服务端
    try {
        ObjectOutputStream oos =
            new ObjectOutputStream(ManageClinetConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
        oos.writeObject(message);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
//ClientConnectServerThread update!!!!!
else if (message.getMesType().equals(MessageType.MESSAGE_TO_ALL_MES)) {
    //接收到群发消息
    //把从服务端转发的消息,显示到控制台即可
    System.out.println("\n" + message.getSendTime() + "\n" + message.getSender()
                       + " 对大家说 " + message.getContent());
}

服务端

//ServerConnectClientThread update!!!!!!
else if (message.getMesType().equals(MessageType.MESSAGE_TO_ALL_MES)) {
    //需要遍历,管理线程的集合,把所有线程的socket得到,然后把message进行转发
    HashMap<String, ServerConnectClientThread> hm = ManageClientThreads.getHm();

    Iterator<String> iterator = hm.keySet().iterator();
    while(iterator.hasNext()){

        //取出在线用户id
        String onlineUserId = iterator.next().toString();

        if(!onlineUserId.equals(message.getSender())){//排除群发消息的发起人

            //进行转发message
            ObjectOutputStream oos =
                new ObjectOutputStream(hm.get(onlineUserId).getSocket().getOutputStream());
            oos.writeObject(message);
        }

    }

}

6、发文件

客户端

//该类完成文件的传输
public class FileClientService {

    /**
     * @param src      源文件
     * @param dest     把文件传输到对方的哪个目录
     * @param senderId 发送用户id
     * @param getterId 接收用户id
     */
    public void sendFileToOne(String src, String dest, String senderId, String getterId) {

        //读取src文件 ,做成一个message
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_FILE_MES);
        message.setSender(senderId);
        message.setGetter(getterId);
        message.setSrc(src);
        message.setDest(dest);

        //需要将文件读取
        FileInputStream fileInputStream = null;
        byte[] fileBytes = new byte[(int) new File(src).length()];

        try {
            fileInputStream = new FileInputStream(src);
            fileInputStream.read(fileBytes);//将src文件读入到程序的字节数组中
            message.setFileBytes(fileBytes);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        //提示信息
        System.out.println("\n" + senderId + " 给 " + getterId + " 发送文件:" + src
                + " 到对方电脑的目录 " + dest);

        //发送
        try {
            ObjectOutputStream oos =
                    new ObjectOutputStream(ManageClinetConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
//ClientConnectServerThread update!!!!
else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {
    //文件消息
    System.out.println("\n" + message.getSender() + " 给 " + message.getGetter()
                       + " 发送文件:" + message.getSrc()
                       + " 到我的电脑的目录 " + message.getDest());

    //取出message的文件字节数组,通过文件输出流,写入磁盘
    FileOutputStream fileOutputStream = new FileOutputStream(message.getDest());
    fileOutputStream.write(message.getFileBytes());
    fileOutputStream.close();
    System.out.println("\n 保存文件成功~");

}
//QQView update!!!!!!
case "4":
    System.out.println("请输入你想发送文件给的用户(在线用户):");
    getterId = Utility.readString(50);
    System.out.println("请输入发送文件的路径(d:\\xx.jpg): ");
    String src = Utility.readString(100);
    System.out.println("请输入保存文件的最终路径(d:\\xx.jpg)");
    String dest = Utility.readString(100);
    fileClientService.sendFileToOne(src,dest,userId,getterId);
    break;

服务端

//ServerConnectClientThread update!!!!!!
else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {
    //客户端发送文件,服务端转发给getterId
    ObjectOutputStream oos =
        new ObjectOutputStream(ManageClientThreads.getServerConnectClientThread(message.getGetter()).getSocket().getOutputStream());
    oos.writeObject(message);

}

7、服务器推送新闻

服务端

//SendNewsToAllService add!!!!!!
public class SendNewsToAllService implements Runnable {

    @Override
    public void run() {
        while (true) {
            System.out.println("请输入服务器要推送的新闻、消息[输入exit表示退出推送服务]");
            String news = Utility.readString(100);
            if("exit".equals(news)){
                break;
            }
            //构建一个消息,群发消息
            Message message = new Message();
            message.setMesType(MessageType.MESSAGE_TO_ALL_MES);
            message.setContent(news);
            message.setSendTime(new Date().toString());
            message.setSender("服务器");
            System.out.println("服务器推送消息给所有人 说:" + news);

            //遍历当前所有的在线线程,得到socket,发送message
            HashMap<String, ServerConnectClientThread> hm = ManageClientThreads.getHm();

            Iterator<String> iterator = hm.keySet().iterator();

            while(iterator.hasNext()){
                String onlineUserId = iterator.next().toString();
                try {
                    ObjectOutputStream oos =
                        new ObjectOutputStream(hm.get(onlineUserId).getSocket().getOutputStream());
                    oos.writeObject(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }
}

8、离线消息和离线文件

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值