多用户及时通信系统之用户私聊/群发功能实现(5)

私聊

思路分析:
客户端

  1. 接收用户希望给某个在线用户聊天的内容
  2. 将消息构建成Message对象,通过对应的socket发给服务端
  3. 在他的线程(目标对象) 读取到发送的message消息,并显示即可

服务端

  1. 可以读取到客户端(发)发送给某个客户(收)的消息
  2. 从管理线程的集合中,根据message对象(发)的getterId 获取到对应线程的socket(收)
  3. 然后将message对象转发给指定客户(收)

功能实现
客户端
MessageClientService.java : 新增私聊类,与私聊方法

package com.ming.qqclient.service;

import com.ming.qqcommon.Message;
import com.ming.qqcommon.MessageType;

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

/**
 * @Author: mei_ming
 * @DateTime: 2022/10/3 15:33
 * @Description: 这是一个提供和消息相关的服务类
 */
public class MessageClientService {

    //私聊功能
    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(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

QQView.java : 调用私聊方法

switch (key){
    case "1":
        //调用UserClientService中的方法:
        userClientService.onlineFriendList();
        break;
    case "2":
        System.out.println("群发消息");
        break;
    case "3":
        System.out.print("请输入想聊天的用户号(在线): ");
        String getterId = Utility.readString(50);
        System.out.print("请输入想说的话: ");
        String content = Utility.readString(100);
        //将消息发送给服务端
        messageClientService.sendMessageToOne(content,userId,getterId);
        break;
    case "4":
        System.out.println("发送文件");
        break;
    case "9":
        //调用方法 退出
        userClientService.logout();
        loop=false;
        break;
}

服务端
ServerConnectClientThread.java : 在run方法里面增加判断messageType,如果是MESSAGE_COMM_MES 则进行消息的转发

package com.ming.qqserver.service;

import com.ming.qqcommon.Message;
import com.ming.qqcommon.MessageType;
import com.sun.deploy.net.proxy.ProxyUnavailableException;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

/**
 * @Author: mei_ming
 * @DateTime: 2022/10/2 21:09
 * @Description: 该类的一个对象和某个客户端保持通信
 */
public class ServerConnectClientThread extends Thread {

    private Socket socket;
    private String userId;  //区分是哪一个客户端

    public Socket getSocket() {
        return socket;
    }

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

    @Override
    public void run() {  //这里线程处于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)){
                    //客户端要在线用户列表
                    // 格式 "100 200 孙悟空"
                    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 if(message.getMesType().equals(MessageType.MESSAGE_COMM_MES)){
                    //私聊功能
                    //根据message获取getterId,然后得到对应的线程
                    ServerConnectClientThread serverConnectClientThread = ManageClientThreads.getServerConnectClientThread(message.getGetter());
                    //在线程里获取对应的socket,在获取socket对应的对象输出流
                    ObjectOutputStream oos = new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
                    oos.writeObject(message);  //转发, 如果客户不在线,可以把内容存到数据库, 做成离线发送消息
                }else if(message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)){
                    //客户端退出功能
                    System.out.println(message.getSender()+" 退出");
                    //将这个客户端对应的线程从集合中删除
                    ManageClientThreads.removeServerConnectClientThread(message.getSender());
                    socket.close();  //关闭连接
                    break;  //退出线程
                }else{
                    System.out.println("是其他类型的message,暂不处理");
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
}


运行效果

  1. 上线三个人,孙悟空、猪八戒、沙和尚

  2. 沙和尚分别对孙悟空、猪八戒说师父被妖怪抓走了
    多用户及时通信系统之用户私聊/群发功能实现(5)_第1张图片

  3. 孙悟空收到消息,回复 收到
    多用户及时通信系统之用户私聊/群发功能实现(5)_第2张图片

  4. 猪八戒收到消息,回复 收到
    多用户及时通信系统之用户私聊/群发功能实现(5)_第3张图片

  5. 沙和尚收到两个人的回复
    多用户及时通信系统之用户私聊/群发功能实现(5)_第4张图片

群发

和私聊功能类似
共通
MessageType.java : 增加群发的MessageType

package com.ming.qqcommon;

/**
 * @Author: mei_ming
 * @DateTime: 2022/10/2 18:31
 * @Description: 表示消息类型
 */
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";  //群发的消息
}


客户端
QQView.java : 修改case2为群发的功能,调用sendMessageToAll()方法

switch (key){
    case "2":
        System.out.print("请输入想说的话: ");
        String contentForAll = Utility.readString(100);
        //将消息发送给服务端
        messageClientService.sendMessageToAll(contentForAll,userId);
        break;
}

MessageClientService.java : 新增sendMessageToAll()方法

package com.ming.qqclient.service;

import com.ming.qqcommon.Message;
import com.ming.qqcommon.MessageType;

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

/**
 * @Author: mei_ming
 * @DateTime: 2022/10/3 15:33
 * @Description: 这是一个提供和消息相关的服务类
 */
public class MessageClientService {

    //群聊功能
    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(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //私聊功能  senderId : 发送者 , getterId : 接收者
    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(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

ClientConnectServerThread.java :判断服务端返回type是否是MESSAGE_TO_ALL_MES

package com.ming.qqclient.service;

import com.ming.qqcommon.Message;
import com.ming.qqcommon.MessageType;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;

/**
 * @Author: mei_ming
 * @DateTime: 2022/10/2 20:25
 * @Description: 线程生成类
 */
public class ClientConnectServerThread extends Thread{
    //该线程需要持有Socket
    private Socket socket;

    //构造器可以接受一个Socket对象
    public ClientConnectServerThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        //因为Thread需要在后台和服务器通信,所以用while
        while(true){
            try {
                System.out.println("客户端线程,等待读取从服务端发送的消息");
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                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 if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)){
                    //把从服务器转发的消息,显示到控制台即可
                    System.out.println("\n"+ message.getSendTime()+" "+ message.getSender()+" 对 "+
                            message.getGetter()+" 说: "+message.getContent());
                } else if (message.getMesType().equals(MessageType.MESSAGE_TO_ALL_MES)){
                    //显示在客户端的控制台
                    System.out.println("\n"+ message.getSender()+"对大家说: "+message.getContent());
                }else{
                    System.out.println("是其他类型的message,暂不处理");
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    public Socket getSocket() {
        return socket;
    }

    public void setSocket(Socket socket) {
        this.socket = socket;
    }
}


服务端

ServerConnectClientThread.java : 判断客户端发送的type是否为MESSAGE_TO_ALL_MES

package com.ming.qqserver.service;

import com.ming.qqcommon.Message;
import com.ming.qqcommon.MessageType;
import com.sun.deploy.net.proxy.ProxyUnavailableException;

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

/**
 * @Author: mei_ming
 * @DateTime: 2022/10/2 21:09
 * @Description: 该类的一个对象和某个客户端保持通信
 */
public class ServerConnectClientThread extends Thread {

    private Socket socket;
    private String userId;  //区分是哪一个客户端

    public Socket getSocket() {
        return socket;
    }

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

    @Override
    public void run() {  //这里线程处于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)){
                    //客户端要在线用户列表
                    // 格式 "100 200 孙悟空"
                    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 if(message.getMesType().equals(MessageType.MESSAGE_COMM_MES)){
                    //私聊功能
                    //根据message获取getterId,然后得到对应的线程
                    ServerConnectClientThread serverConnectClientThread = ManageClientThreads.getServerConnectClientThread(message.getGetter());
                    //在线程里获取对应的socket,在获取socket对应的对象输出流
                    ObjectOutputStream oos = new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
                    oos.writeObject(message);  //转发, 如果客户不在线,可以把内容存到数据库, 做成离线发送消息
                }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 onlineUser = iterator.next().toString();
                        if (!onlineUser.equals(message.getSender())){  //排除群发消息的用户
                            // 发送message
                            ServerConnectClientThread serverConnectClientThread = ManageClientThreads.getServerConnectClientThread(onlineUser);
                            OutputStream outputStream = serverConnectClientThread.getSocket().getOutputStream();
                            ObjectOutputStream oos = new ObjectOutputStream(outputStream);
                            oos.writeObject(message);
                        }
                    }
                }else if(message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)){
                    //客户端退出功能
                    System.out.println(message.getSender()+" 退出");
                    //将这个客户端对应的线程从集合中删除
                    ManageClientThreads.removeServerConnectClientThread(message.getSender());
                    socket.close();  //关闭连接
                    break;  //退出线程
                }else{
                    System.out.println("是其他类型的message,暂不处理");
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
}


运行效果

  1. 登录孙悟空、猪八戒、沙和尚

  2. 沙和尚对大家说 “大师兄,二师兄,师父被妖怪抓走了”
    多用户及时通信系统之用户私聊/群发功能实现(5)_第5张图片

  3. 孙悟空接收消息
    多用户及时通信系统之用户私聊/群发功能实现(5)_第6张图片

  4. 猪八戒接收消息
    多用户及时通信系统之用户私聊/群发功能实现(5)_第7张图片
    下一篇:用户发文件功能

你可能感兴趣的:(多用户通信系统,java,网络)