网络编程作业之完整版通讯系统(下)

由于篇幅过长,完整版代码分为上下两篇文章

本篇文章主要是展示服务端的所有代码😊

相比韩顺平老师的服务端代码,我新增了以下几个内容
  • 对用户的登录进行了检查,判断是否重复登录或者出现账号不存在的问题。如果登陆失败会返回失败的消息类型给客户端。
  • 对于用户发送的消息对象,如果对象不存在就会返回发送消息失败的消息类型给客户端。发送文件也加了这条限制。
  • 在最后的离线发送消息扩展也已经完成,并且保证用户能够收到多条离线信息。
  • 新增了注册账号的功能。
  • 新增了退出账号的功能。
下面开始展示我的代码模块已经各个模块介绍🚚

服务端代码图示🏡
在这里插入图片描述

服务端代码

公共部分 -> common

Message消息模块🍧

package com.all.qqcommon;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: 捶捶自己
 * @version: 1.0
 * @Date: 2022/07/03/18:57
 * @Description: 消息管理模块
 */
public class Message implements Serializable {
    private static final long serializable = 1L;

    private String sender; // -- 发送者
    private String getter; // -- 接收者
    private String content; // -- 发送内容
    private String sendTime; // -- 发送时间
    private String messageType; // -- 消息类型

    // -- 进行文件信息传输扩展
    private byte[] fileData; // -- 文件信息
    private int fileLength = 0; // -- 文件大小
    private String fileFromPath; // -- 文件传输来源地址
    private String fileToPath; // -- 文件传输目标地址

    public String getSendMessageNowTime() {
        // -- 消息发送时间
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        Date Date = new Date();

        return simpleDateFormat.format(Date);
    }

    public byte[] getFileData() {
        return fileData;
    }

    public void setFileData(byte[] fileData) {
        this.fileData = fileData;
    }

    public int getFileLength() {
        return fileLength;
    }

    public void setFileLength(int fileLength) {
        this.fileLength = fileLength;
    }

    public String getFileFromPath() {
        return fileFromPath;
    }

    public void setFileFromPath(String fileFromPath) {
        this.fileFromPath = fileFromPath;
    }

    public String getFileToPath() {
        return fileToPath;
    }

    public void setFileToPath(String fileToPath) {
        this.fileToPath = fileToPath;
    }

    public String getMessageType() {
        return messageType;
    }

    public void setMessageType(String messageType) {
        this.messageType = messageType;
    }

    public String getSender() {
        return sender;
    }

    public void setSender(String sender) {
        this.sender = sender;
    }

    public String getGetter() {
        return getter;
    }

    public void setGetter(String getter) {
        this.getter = getter;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getSendTime() {
        return sendTime;
    }

    public void setSendTime(String sendTime) {
        this.sendTime = sendTime;
    }

    public Message() {
    }

    public Message(String sender, String content, String sendTime, String messageType) {
        this.sender = sender;
        this.content = content;
        this.sendTime = sendTime;
        this.messageType = messageType;
    }
}


User用户模块🍧

package com.all.qqcommon;

import java.io.Serializable;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: 捶捶自己
 * @version: 1.0
 * @Date: 2022/07/03/18:56
 * @Description: 用户模块
 */
public class User implements Serializable {
    /*
     *   序列化id (serialVersionUID)
     *
     *   序列化ID,相当于身份认证,主要用于程序的版本控制,保持不同版本的兼容性,在程序版本升级时避免程序报出版本不一致的错误。
     *   如果定义了private static final long serialVersionUID = 1L,那么如果你忘记修改这个信息,而且你对这个类进行修改的话,
     *   这个类也能被进行反序列化,而且不会报错。一个简单的概括就是,如果你忘记修改,那么它是会版本向上兼容的。
     *
     *   如果没有定义一个名为serialVersionUID,类型为long的变量,Java序列化机制会根据编译的class自动生成一个serialVersionUID,
     *   即隐式声明。这种情况下,只有同一次编译生成的class才会生成相同的serialVersionUID 。此时如果对某个类进行修改的话,那么版本上面
     *   是不兼容的,就会出现反序列化报错的情况。
     *
     *   在实际的开发中,重新编译会影响项目进度部署,所以我们为了提高开发效率,不希望通过编译来强制划分软件版本,就需要显式地定义一个名
     *   为serialVersionUID,类型为long的变量,不修改这个变量值的序列化实体都可以相互进行串行化和反串行化。
     * */
    private static final long serializable = 1L;

    private String userId;// -- 用户Id
    private String password;// -- 用户密码
    private boolean isRegister; // -- 是否创建新的账号

    public User() {
    }

    public User(String userId, String password) {
        this.userId = userId;
        this.password = password;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public boolean isRegister() {
        return isRegister;
    }

    public void setRegister(boolean register) {
        isRegister = register;
    }

    // -- 此处序列化要求客户端和服务端 代码完全相同,否则会报错~~~~
}


MessageType消息类型接口🍧

package com.all.qqcommon;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: 捶捶自己
 * @version: 1.0
 * @Date: 2022/07/03/19:13
 * @Description: 定义消息类型
 */
public interface MessageType {
    String MESSAGE_FILE_MES = "000"; // -- 发送文件信息
    String MESSAGE_FILE_MES_FAIL = "007"; // -- 发送文件信息失败
    String MESSAGE_COMM_MES = "100"; // -- 普通信息包
    String MESSAGE_COMM_MES_FAIL = "101"; // -- 普通信息包发送失败
    String MESSAGE_COMM_MES_TO_ALL = "111"; // -- 群发消息
    String MESSAGE_LOGIN_SUCCEED = "200"; // -- 表示登录成功
    String MESSAGE_LOGIN_EXIST = "202"; // -- 表示退出登录
    String MESSAGE_GET_ONLINE_FRIEND = "310"; // -- 获取在线用户列表
    String MESSAGE_RET_ONLINE_FRIEND = "311"; // -- 返回在线用户列表
    String MESSAGE_GET_REGISTER_REQUEST = "400"; // -- 注册用户请求
    String MESSAGE_RET_REGISTER_SUCCEED = "401"; // -- 注册用户成功
    String MESSAGE_RET_REGISTER_FAIL = "404"; // -- 注册用户失败
    String MESSAGE_LOGIN_FAIL = "500"; // -- 表示登录失败
    String MESSAGE_GET_FEED_BACK = "666"; // -- 表示用户发送反馈信息
    String MESSAGE_RET_FEED_BACK = "777"; // -- 表示用户收到的反馈信息
    String MESSAGE_RET_USER_LOGIN_STATE = "EXIST"; // -- 返回登录用户重复信息
    String MESSAGE_CLIENT_EXIT = "888"; // -- 客户端请求退出
}

服务端服务模块 -> service

ManageServerConnectClientThread管理服务端和客户端之间的线程🍧

package com.all.qqserver.service;


import com.all.qqcommon.Message;

import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: 捶捶自己
 * @version: 1.0
 * @Date: 2022/07/04/16:23
 * @Description: 用于管理服务端连接客户端线程的集合
 */
public class ManageServerConnectClientThread {
    private static HashMap<String, ServerConnectClientThread> hm = new HashMap<>();

    // -- 将线程加入集合中去
    public static void addServerConnectClientThread(String userId, ServerConnectClientThread serverConnectClientThread) {
        hm.put(userId, serverConnectClientThread);
    }

    public static HashMap<String, ServerConnectClientThread> getHm() {
        return hm;
    }

    public static void setHm(HashMap<String, ServerConnectClientThread> hm) {
        ManageServerConnectClientThread.hm = hm;
    }

    // -- 通过用户Id获取线程连接
    public static ServerConnectClientThread byUserIdGetThread(String userId) {
        return hm.get(userId);
    }

    // -- 返回拼接字符串的在线列表
    public static String getOnlineUserIdList() {
        Iterator<String> iterator = hm.keySet().iterator();
        String onlineUserIdList = "";
        while (iterator.hasNext()) {
            onlineUserIdList += iterator.next() + " ";
        }

        return onlineUserIdList;
    }

    // -- 返回除指定用户的在线列表
    public static String[] getOnlineUserIdList(String userId) {
        int count = 0;
        Iterator<String> iterator = hm.keySet().iterator();
        String[] userIdData = new String[hm.size() - 1];
//        System.out.println("userId为: " + userId);
        while (iterator.hasNext()) {
            String otherUserId = iterator.next();
//            System.out.println("取出的Id为: " + otherUserId);
            if (!otherUserId.equals(userId)) {
                userIdData[count++] = otherUserId;
//                System.out.println("当前取出的用户Id为: " + userIdData[count - 1] + hm.size());
            }
        }

        return userIdData;
    }

    // -- 返回所有的在线列表, count为在线数量
    public static String[] getOnlineUserIdList(int count) {
        Iterator<String> iterator = hm.keySet().iterator();
        String[] userIdData = new String[hm.size()];
        while (iterator.hasNext()) {
            String otherUserId = iterator.next();
            userIdData[count++] = otherUserId;
        }

        return userIdData;
    }


    // -- 判断在线列表中是否有该用户名
    public static boolean isExistOnlineUserId(String userId) {
        String[] userIdData = getOnlineUserIdList(0);
        for (int i = 0; i < userIdData.length; i++) {
            if (userIdData[i].equals(userId)) {
                return true;
            }
        }

        return false;
    }

    // -- 移除已经退出的用户
    public static void removeExitUserId(String userId) {
        hm.remove(userId);
    }

    // -- 检查离线消息中是否有新登录用户的消息
    public static boolean checkOfflineUserMassage(String userId) {
        return ServerConnect.getOfflineCacheDB().containsKey(userId);
    }
}


SendNewsToAllUser服务端新闻发布(大喇叭)🍧

package com.all.qqserver.service;

import com.all.qqcommon.Message;
import com.all.qqcommon.MessageType;
import com.all.qqserver.utils.readOperate;

import java.io.IOException;
import java.io.ObjectOutputStream;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: 捶捶自己
 * @version: 1.0
 * @Date: 2022/07/06/18:29
 * @Description: 发送消息给所有用户
 */
public class SendNewsToAllUser implements Runnable {

    // -- TODO 这里新闻推送消息可以更改为离线人员也可以收到信息
    @Override
    public void run() {
        while (true) {
            System.out.println("请输入你想要推送的新闻: 输入[exit]退出推送服务");
            String News = readOperate.readString(100);
            if (News.equals("exit")) {
                System.out.println("推送服务已经关闭!");
                break;
            }
            Message message = new Message();
            message.setSender("服务器");
            message.setSendTime(message.getSendMessageNowTime());
            message.setMessageType(MessageType.MESSAGE_COMM_MES_TO_ALL);
            message.setContent(News);
            // -- 遍历所有在线的用户
            String[] userIdData = ManageServerConnectClientThread.getOnlineUserIdList(0);
            for (int i = 0; i < userIdData.length; i++) {
                // -- 将消息转发给对方
                try {
                    ObjectOutputStream oos
                            = new ObjectOutputStream(ManageServerConnectClientThread.byUserIdGetThread(userIdData[i]).getSocket().getOutputStream());
                    oos.writeObject(message);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

ServerConnect服务端监听模式启动🍧

package com.all.qqserver.service;

import com.all.qqcommon.Message;
import com.all.qqcommon.MessageType;
import com.all.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.concurrent.ConcurrentHashMap;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: 捶捶自己
 * @version: 1.0
 * @Date: 2022/07/04/16:00
 * @Description: 这里需要和客户端保持连接 端口号为 4399
 */
@SuppressWarnings({"all"})
public class ServerConnect {
    private ServerSocket ss;
    // -- 创建一个集合用来存放用户数据
    // -- ConcurrentHashMap可以在并发的情况下处理线程问题
    // -- HashMap 会有线程安全问题存在
    private static ConcurrentHashMap<String, User> userData = new ConcurrentHashMap<>();
    private static ConcurrentHashMap<String, ArrayList<Message>> offlineCacheDB = new ConcurrentHashMap<>();

    static {
        // -- 用静态代码块来初始化集合
        userData.put("admin", new User("admin", "123456"));
        userData.put("tom", new User("tom", "541688"));
        userData.put("张三", new User("张三", "zhangsan"));
        userData.put("bear", new User("bear", "bear"));
        userData.put("思思", new User("思思", "beautifulGirl"));
    }

    public ServerConnect() {
        try {
            System.out.println("服务端正在4399端口进行监听");
            // -- 启动推送服务
            new Thread(new SendNewsToAllUser()).start();
            // -- 开启端口监听服务
            ss = new ServerSocket(4399);
            // -- 监听服务是循环的,并不是监听一次就退出
            while (true) {
                Socket socket = ss.accept();
                // -- 得到socket的对象输入流和输出流
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                User user = (User) ois.readObject();
                // -- 创建一个Message对象进行信息回复
                Message message = new Message();
//                System.out.println("是否接收到了message" + message.getMessageType());
                // -- 对客户端用户是否重复登录进行判断
                if (user.isRegister()) {
                    if (userData.containsKey(user.getUserId())) {
                        // -- 表示该用户名已经存在
                        System.out.println("用户" + user.getUserId() + "注册账号失败");
                        message.setMessageType(MessageType.MESSAGE_RET_REGISTER_FAIL);
                        message.setSendTime(message.getSendMessageNowTime());
                        message.setContent("不能重复创建,因为该用户名已经存在!");
                        oos.writeObject(message);
                        socket.close();
                        continue;
                    } else {
                        System.out.println("用户" + user.getUserId() + "注册账号成功");
                        userData.put(user.getUserId(), new User(user.getUserId(), user.getPassword()));
                        message.setMessageType(MessageType.MESSAGE_RET_REGISTER_SUCCEED);
                        message.setSendTime(message.getSendMessageNowTime());
                        message.setSender(user.getUserId());
                        message.setContent(user.getPassword());
                        oos.writeObject(message);
                        socket.close();
                        continue;
                    }
                }

                if (!IsExistUserLoginInfo(user.getUserId()) && !user.isRegister()) {
                    // -- 该用户已经登录过,无法再次登录
                    System.out.println("用户" + user.getUserId() + "重复登陆");
                    message.setMessageType(MessageType.MESSAGE_RET_USER_LOGIN_STATE);
                    oos.writeObject(message);
                    socket.close();
                    continue;
                }

                // -- 对客户端发来的用户对象进行验证
                if (checkUser(user.getUserId(), user.getPassword()) && !user.isRegister()) {
                    message.setMessageType(MessageType.MESSAGE_LOGIN_SUCCEED);
                    // -- 回复信息给客户端
                    oos.writeObject(message);
                    // -- 此时应该创建一个线程和客户端进行通信
                    ServerConnectClientThread serverConnectClientThread = new ServerConnectClientThread(user.getUserId(), socket);
                    serverConnectClientThread.start();
                    // -- 将线程对象放入集合中
                    ManageServerConnectClientThread.addServerConnectClientThread(user.getUserId(), serverConnectClientThread);
                    // -- 检查离线消息
                    if (ManageServerConnectClientThread.checkOfflineUserMassage(user.getUserId())) {
                        System.out.println("检测到用户登录!");
                        serverConnectClientThread.sendOfflineMassage(serverConnectClientThread);
                    }
                } else {
                    System.out.println("用户" + user.getUserId() + "登录失败");
                    message.setMessageType(MessageType.MESSAGE_LOGIN_FAIL);
                    oos.writeObject(message);
                    // -- 关闭socket
                    socket.close();
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // -- 如果服务端退出while循环,则退出serverSocket
            try {
                ss.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    // -- 判断用户是否重复登录
    private boolean IsExistUserLoginInfo(String userId) {
//        String Info = ManageServerConnectClientThread.getOnlineUserIdList();
//        // -- 如果返回的信息为空,则直接为true
//        if (Info.equals("")) {
//            return true;
//        }
//        String[] userInfos = Info.split(" ");
//        for (int i = 0; i < userInfos.length; i++) {
//            if (userId.equals(userInfos[i])) {
//                return false;
//            }
//        }
        // -- 更换为新方法
        if (ManageServerConnectClientThread.isExistOnlineUserId(userId)) {
            return false;
        }

        return true;
    }

    // -- 对用户的账号和密码进行check
    private boolean checkUser(String userId, String password) {
        User user = userData.get(userId);
        if (user == null || !user.getPassword().equals(password)) {
            return false;
        }
        return true;
    }

    // -- 将离线消息进行存储
    public static void setUserOffonlineMessage(String userId, ArrayList<Message> messageArrayList) {
//        Message[] messages = new Message[messageInfo.size()];
//        for (int i = 0; i < messages.length; i++) {
//            Message message = new Message();
//            for (int j = 0; j < messageInfo.size(); j++) {
//                message.setSender(messageInfo.get(i));
//            }
//            messages[i] = new Message(messageInfo.get(i),)
//        }
        offlineCacheDB.put(userId, messageArrayList);
    }

    // -- 在离线数据库中移除离线消息
    public static void removeUserOffonlineMessage(String userId) {
        offlineCacheDB.remove(userId);
    }

    // -- 判断集合中是否有该用户名
    public static boolean isExistUserId(String userId) {
        return userData.containsKey(userId);
    }

    public static ConcurrentHashMap<String, ArrayList<Message>> getOfflineCacheDB() {
        return offlineCacheDB;
    }

    public static void setOfflineCacheDB(ConcurrentHashMap<String, ArrayList<Message>> offlineCacheDB) {
        ServerConnect.offlineCacheDB = offlineCacheDB;
    }


    public ServerConnect(ServerSocket socket) {
        this.ss = socket;
    }

}


ServerConnectClientThread服务端与客户端多线程启动🍧

package com.all.qqserver.service;

import com.all.qqcommon.Message;
import com.all.qqcommon.MessageType;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;


/**
 * Created with IntelliJ IDEA.
 *
 * @Author: 捶捶自己
 * @version: 1.0
 * @Date: 2022/07/04/16:14
 * @Description: 服务端与客户端进行连接的线程
 */
public class ServerConnectClientThread extends Thread {
    private Socket socket;
    private String userId;// -- 与哪个用户进行连接
    private static ArrayList<Message> messageArrayList = new ArrayList<>();

    public ServerConnectClientThread(String userId, Socket socket) {
        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();
                // -- 客户端要拉取当前在线列表
                if (message.getMessageType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)) {
                    System.out.println(message.getSender() + " 要求返回在线列表");
                    // -- 通过管理线程获取用户在线列表,进行消息封装和填充消息类型
                    String onlineUserIdList = ManageServerConnectClientThread.getOnlineUserIdList();
                    Message onlineUserMessage = new Message();
                    onlineUserMessage.setMessageType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
                    onlineUserMessage.setContent(onlineUserIdList);
                    onlineUserMessage.setSender(message.getSender());
                    // -- 然后通过socket输出流对象返回信息
                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                    oos.writeObject(onlineUserMessage);
                }
                if (message.getMessageType().equals(MessageType.MESSAGE_COMM_MES)) {
                    System.out.println(message.getSender() + "要发送消息给" + message.getGetter());
                    // -- 先判断对象是否存在
                    if (ServerConnect.isExistUserId(message.getGetter())) {
                        // -- 判断对方是否在线
                        if (ManageServerConnectClientThread.isExistOnlineUserId(message.getGetter())) {
                            // -- 先获得发送对象的线程
                            ServerConnectClientThread serverConnectClientThread
                                    = ManageServerConnectClientThread.byUserIdGetThread(message.getGetter());
                            // -- 将消息转发给对方
                            ObjectOutputStream oos
                                    = new ObjectOutputStream(serverConnectClientThread.socket.getOutputStream());
                            oos.writeObject(message);
                        } else {
                            // -- TODO 如果对方不在线,可以保存在数据库,这里暂时用了HashMap来模拟数据库
                            // -- 对方并不在线,先将信息存储至集合中
                            messageArrayList.add(message);
                            ServerConnect.setUserOffonlineMessage(message.getGetter(), messageArrayList);
                        }
                    } else {
                        message.setMessageType(MessageType.MESSAGE_COMM_MES_FAIL);
                        message.setSendTime(message.getSendMessageNowTime());
                        message.setContent("无此用户!");
                        ObjectOutputStream oos
                                = new ObjectOutputStream(ManageServerConnectClientThread.byUserIdGetThread(message.getSender()).socket.getOutputStream());
                        // -- 将消息进行退回
                        oos.writeObject(message);
                    }
                }
                if (message.getMessageType().equals(MessageType.MESSAGE_COMM_MES_TO_ALL)) {
                    String[] othersUserIdList = ManageServerConnectClientThread.getOnlineUserIdList(userId);
                    for (int i = 0; i < othersUserIdList.length; i++) {
                        // -- 先获得发送对象的线程
                        ServerConnectClientThread serverConnectClientThread
                                = ManageServerConnectClientThread.byUserIdGetThread(othersUserIdList[i]);
                        // -- 将消息转发给对方
                        ObjectOutputStream oos
                                = new ObjectOutputStream(serverConnectClientThread.socket.getOutputStream());
                        oos.writeObject(message);
                    }
                }
                if (message.getMessageType().equals(MessageType.MESSAGE_FILE_MES)) {
                    if (ServerConnect.isExistUserId(message.getGetter())) {
                        ObjectOutputStream oos
                                = new ObjectOutputStream(ManageServerConnectClientThread.byUserIdGetThread(message.getGetter()).socket.getOutputStream());
                        // -- 将文件进行转发
                        oos.writeObject(message);
                    } else {
                        message.setMessageType(MessageType.MESSAGE_FILE_MES_FAIL);
                        message.setSendTime(message.getSendMessageNowTime());
                        message.setContent("无此用户!");
                        ObjectOutputStream oos
                                = new ObjectOutputStream(ManageServerConnectClientThread.byUserIdGetThread(message.getSender()).socket.getOutputStream());
                        // -- 将文件进行退回
                        oos.writeObject(message);
                    }
                }
                if (message.getMessageType().equals(MessageType.MESSAGE_GET_FEED_BACK)) {
                    System.out.println("用户" + message.getSender() + "反馈的内容为: "
                            + message.getContent() + "\n如果需要反馈,可以用大喇叭!");
                }
                if (message.getMessageType().equals(MessageType.MESSAGE_LOGIN_EXIST)) {
                    System.out.println("用户" + message.getSender() + "已经退出登录!");
                    ObjectOutputStream oos
                            = new ObjectOutputStream(ManageServerConnectClientThread.byUserIdGetThread(message.getSender()).socket.getOutputStream());
                    // -- 将消息发送回去
                    oos.writeObject(message);
                    ManageServerConnectClientThread.removeExitUserId(message.getSender());
//                    ManageServerConnectClientThread.byUserIdGetThread(message.getSender()).socket.close();
                    break;
                }
                if (message.getMessageType().equals(MessageType.MESSAGE_CLIENT_EXIT)) {
                    System.out.println("用户" + message.getSender() + "已经退出系统!");
                    ManageServerConnectClientThread.removeExitUserId(message.getSender());
                    /*
                     *  TODO 这里出现了BUG,如果关闭socket就会出现异常
                     *       , 但并不影响程序运行,如果不关闭就不会出现
                     *       异常,但并不合理.
                     *       最新的bug为不关闭socket没事,开启socket还会报错!
                     */
//                    ManageServerConnectClientThread.byUserIdGetThread(message.getSender()).socket.close();
                    break;
                }
                /*
                   TODO 这里可以新增一个T出用户选项,需要遍历一遍在线用户(方法已经实现过了)
                    思路就是将这个用户从集合中删除并且关闭线程,发送给客户端消息,并且在客户端
                    将View中的outlook给改变,然后强制输入一个key选项(不给客户看的特殊空选项)
                    发送回客户端一个消息,提示已经被T出,整体实现很简单,唯一需要注意的就是大喇叭模式的兼容
                 */
            } catch (Exception e) {
                throw new RuntimeException();
            }
        }
    }

    // -- 发送离线消息
    public void sendOfflineMassage(ServerConnectClientThread serverConnectClientThread) {
//        ConcurrentHashMap<String, Message> concurrentHashMap = ServerConnect.getOfflineCacheDB();
        ConcurrentHashMap<String, ArrayList<Message>> concurrentHashMap = ServerConnect.getOfflineCacheDB();
        // -- 先获得存储的所有离线消息
        ArrayList<Message> sendMessage = concurrentHashMap.get(userId);
        Message[] messages = new Message[sendMessage.size()];
        for (int i = 0; i < sendMessage.size(); i++) {
            messages[i] = sendMessage.get(i);
        }
        try {
            // -- 将消息转发给对方
            for (int i = 0; i < messages.length; i++) {
                ObjectOutputStream oos = new ObjectOutputStream(serverConnectClientThread.socket.getOutputStream());
                oos.writeObject(messages[i]);
                ServerConnect.removeUserOffonlineMessage(userId);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public Socket getSocket() {
        return socket;
    }

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


QQFrame服务端主程序入口🍧

package com.all.qqserver.qqFrame;

import com.all.qqserver.service.ServerConnect;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: 捶捶自己
 * @version: 1.0
 * @Date: 2022/07/04/16:45
 * @Description: 启动QQ 后台服务程序
 */
public class QQFrame {
    public static void main(String[] args) {
        new ServerConnect();
    }
}


readOperate 工具输入类🍧 工具类我新增了一些代码,有200行之多,放到了另外一篇文章中。这篇文章实在太长了!!!

其中服务端存储的初始化用户账号和密码截图如下:
在这里插入图片描述

小总结:

写这个网络编程的项目作业,爆红了不知道多少次,修修改改好多天。总归是完成了自己的功能增加和整个项目。o( ̄▽ ̄)ブ

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

捶捶自己

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值