多用户及时通信系统之服务端推送新闻及离线发送功能实现(7)

服务端推送新闻

思路分析
服务端

  1. 推送消息/新闻,实质上就是群发消息
  2. 在服务端启动一条独立的线程,专门负责发送推送新闻

具体实现
服务端

SendNewsToAllService.java : 创建线程,管理新闻的推送

package com.ming.qqserver.service;

import com.ming.qqcommon.Message;
import com.ming.qqcommon.MessageType;
import com.ming.qqserver.untils.Utility;

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

/**
 * @Author: mei_ming
 * @DateTime: 2022/10/3 23:25
 * @Description: 服务器端推送新闻的服务
 */
public class SendNewsToAllService implements Runnable{

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

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

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

QQServer .java : 在监听端口下面,加上启动推送新闻的线程

//启动推送新闻线程
new Thread(new SendNewsToAllService()).start();

运行效果

  1. 登录2个用户。孙悟空、猪八戒
  2. 服务器上发送消息,孙悟空、猪八戒接收系统消息
    bj
    wk

离线发送

思路分析

  1. 在QQServer 写一个集合用于接收离线消息ConcurrentHashMap<String,ArrayList<Message>>
  2. 用户登录后判断他是否有离线消息
  3. 新增MessageType 用于返回私聊消息 ‘对方不在线’

功能实现
共通

MessageType.java :新增消息类型

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";  //群发的消息
    String MESSAGE_FILE_MES = "8";  //文件消息(发送文件)
    String MESSAGE_RET_COMM_MES = "9";  //延迟发送,返回提示信息
}


服务端

package com.ming.qqserver.service;

import com.ming.qqcommon.Message;
import com.ming.qqcommon.MessageType;
import com.ming.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;

/**
 * @Author: mei_ming
 * @DateTime: 2022/10/2 20:52
 * @Description: 这是服务器,在监听9999,等待客户端的连接,并保持通信
 */
public class QQServer {
    private ServerSocket ss = null;

    //创建一个集合,存放多个用户,如果是这些用户则登录通过
    private static ConcurrentHashMap<String, User> validUsers = new ConcurrentHashMap<>();
    //创建一个集合,存放离线消息Message, Message可以有多条 所以用List
    private static ConcurrentHashMap<String, ArrayList<Message>> offMessages = new ConcurrentHashMap<>();

    public static ConcurrentHashMap<String, ArrayList<Message>> getOffMessages() {
        return offMessages;
    }

    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 void checkOffMessages(String userId, Socket socket) {
        ArrayList<Message> messages = offMessages.get(userId);
        if (messages == null) {
            return;
        }
        for (Message message : messages) {
            try {
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                oos.writeObject(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        offMessages.remove(userId);
    }

    //验证用户是否有效的方法
    private boolean checkUser(String userId, String passwd) {
        User user = validUsers.get(userId);
        if (user == null) {
            return false;
        }
        if (!user.getPasswd().equals(passwd)) {
            return false;
        }
        return true;
    }

    public QQServer() {
        try {
            //注意:端口可以写在配置文件中
            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());
                // 得到socket关联的对象输出流
                ObjectOutputStream oos =
                        new ObjectOutputStream(socket.getOutputStream());

                User user = (User) ois.readObject();
                //创建一个Message对象,准备回复客户端
                Message message = new Message();
                //验证用户,是否有效
                boolean isExists = checkUser(user.getUserId(), user.getPasswd());
                if (isExists) {  //登录成功
                    message.setMesType(MessageType.MESSAGE_LOGIN_SUCCEED);
                    //将message对象回复到客户端
                    oos.writeObject(message);
                    //创建一个线程,和客户端保持通信,该线程需要持有socket对象 ServerConnectClientThread
                    ServerConnectClientThread connectClientThread = new ServerConnectClientThread(socket, user.getUserId());
                    connectClientThread.start();
                    //把该线程放入集合
                    ManageClientThreads.addServerConnectClientThread(user.getUserId(), connectClientThread);
                    //检查登录用户是否有离线消息,若有,则发送
                    checkOffMessages(user.getUserId(),socket);
                } else {
                    System.out.println("用户 id=" + user.getUserId() + " pwd=" + user.getPasswd() + "登录失败");
                    message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
                    oos.writeObject(message);
                    //关闭socket
                    socket.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

ServerConnectClientThread.java : 修改私聊逻辑代码,先判断对方是否在线,若不在线,则存到离线消息集合中

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.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @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)){
                    //私聊功能
                    //判断对方是否在线,若不在线,则存到 offMessages 集合中去
                    boolean userAlive = isUserAlive(message);
                    if (userAlive){
                        //根据message获取getterId,然后得到对应的线程
                        ServerConnectClientThread serverConnectClientThread = ManageClientThreads.getServerConnectClientThread(message.getGetter());
                        //在线程里获取对应的socket,在获取socket对应的对象输出流
                        ObjectOutputStream oos = new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
                        oos.writeObject(message);  //转发, 如果客户不在线,可以把内容存到数据库, 做成离线发送消息
                    }else{  //返回提示消息给自己,说明当前不在线,消息延后送达
                        ServerConnectClientThread serverConnectClientThread = ManageClientThreads.getServerConnectClientThread(message.getSender());
                        ObjectOutputStream oos = new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
                        Message message2 = new Message();
                        message2.setSender("服务器");
                        message2.setContent(message.getGetter()+" 当前不在线,消息将延时送达。");
                        message2.setMesType(MessageType.MESSAGE_RET_COMM_MES);
                        oos.writeObject(message2);
                    }

                }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_FILE_MES)){
                    //发送文件消息功能
                    //将message转发给getterId的线程的socket
                    ObjectOutputStream oos = new ObjectOutputStream(ManageClientThreads.getServerConnectClientThread(message.getGetter()).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();
            }
        }
    }

    //判断对方是否在线,若不在线,则存到 offMessages 集合中去
    private boolean isUserAlive(Message message) {
        String getter = message.getGetter();
        String onlineUser = ManageClientThreads.getOnlineUser();
        String[] users = onlineUser.split(" ");
        for (int i =0;i<users.length;i++){
            if (!getter.equals(users[i])){
                //不在线
                ConcurrentHashMap<String, ArrayList<Message>> offMessages = QQServer.getOffMessages();
                ArrayList<Message> messages = offMessages.get(getter);
                if (messages == null){
                    messages =new ArrayList<>();
                }
                messages.add(message);
                offMessages.put(getter,messages);
                return false;
            }else{
                return true;
            }
        }
        return true;
    }
}


客户端
ClientConnectServerThread.java : 新增对类型的判断MESSAGE_RET_COMM_MES


package com.ming.qqclient.service;

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

import java.io.FileOutputStream;
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_RET_COMM_MES)){
                    //当对方不在线,发送消息返回提示
                    System.out.println("\n"+ message.getSender()+"通知:"+message.getContent());
                } else if (message.getMesType().equals(MessageType.MESSAGE_TO_ALL_MES)){
                    //显示在客户端的控制台
                    System.out.println("\n"+ message.getSender()+"对大家说: "+message.getContent());
                } 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 保存文件成功");
                }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;
    }
}


运行效果

  1. 孙悟空 上线,对猪八戒发送消息
    swk

  2. 猪八戒上线,接收孙悟空发的消息
    zbj

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值