多用户即时通信系统(文字版山寨QQ)

项目开发流程简介

 

 

 

 

项目结构

服务器端

 

客户端

 

 

 

项目说明

功能实现

  • 已经实现功能:
  1. 用户的登录和无异常退出
  2. 显示在线用户列表
  3. 群发消息
  4. 在线私聊消息
  5. 离线私聊消息(单条或多条)
  6. 在线文件发送
  7. 离线文件发送
  8. 服务器推送新闻
  • 未实现功能:
  1. 用户注册
  2. 图形界面
  3. 数据库连接(暂时使用集合替代)

都是自己按照老韩的视频写的,离线功能参考了一些其他地方的

截图

 

 

 

 

 具体代码

服务器端

common

  • common_Message
package com.recorder.common;

import java.io.Serializable;

/**
 * @author 紫英
 * @version 1.0
 * @discription 消息类
 */
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 src;//发送路径
    private String dest;//目标路径

    public byte[] getFileBytes() {
        return fileBytes;
    }

    public void setFileBytes(byte[] fileBytes) {
        this.fileBytes = fileBytes;
    }

    public int getFileLen() {
        return fileLen;
    }

    public void setFileLen(int fileLen) {
        this.fileLen = fileLen;
    }

    public String getSrc() {
        return src;
    }

    public void setSrc(String src) {
        this.src = src;
    }

    public String getDest() {
        return dest;
    }

    public void setDest(String dest) {
        this.dest = dest;
    }

    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 String getMesType() {
        return mesType;
    }

    public void setMesType(String mesType) {
        this.mesType = mesType;
    }
}
  • common_MessageType
package com.recorder.common;

/**
 * @author 紫英
 * @version 1.0
 * @discription 消息类型接口
 */
public interface MessageType {
    //在接口中定义一些常量,不同的常量值表示不同的消息类型
    String MESSAGE_LOGIN_SUCCESS = "1";//表示登陆成功
    String MESSAGE_LOGIN_FAILED = "2";//表示登陆失败
    String MESSAGE_COMMON = "3";//表示普通消息包
    String MESSAGE_COMMON_ALL = "4";//表示群发消息包
    String MESSAGE_GET_ONLINEFRIEND = "5";//表示请求在线用户信息
    String MESSAGE_RETURN_ONLINEFRIEND = "6";//表示返回在线用户信息
    String MESSAGE_CLIENT_EXIT = "7";//表示客户端请求退出
    String MESSAGE_FILE = "8";//表示文件发送
    
}
  • common_User
package com.recorder.common;

import java.io.Serializable;

/**
 * @author 紫英
 * @version 1.0
 * @discription 用户类
 */
public class User implements Serializable {
    private String userID;
    private String password;
    private static final long serialVersionUID = 1L;

    public String getUserID() {
        return userID;
    }

    public void setUserID(String userID) {
        this.userID = userID;
    }

    public String getPassword() {
        return password;
    }

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

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

 

service

  • service_QQServer
package com.recorder.service;

import com.recorder.common.Message;
import com.recorder.common.MessageType;
import com.recorder.common.User;
import com.sun.org.apache.bcel.internal.generic.NEW;

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 紫英
 * @version 1.0
 * @discription 服务器端, 监听9999端口
 */
public class QQServer {

    private ServerSocket serverSocket = null;
    //创建一个集合用于存放合法用户
    //使用ConcurrentHashMap没有线程安全问题(处理了线程同步问题),而hashmap没有处理线程安全问题
    //所以在多线程中推荐使用ConcurrentHashMap
    private static ConcurrentHashMap<String, User> validUsers = new ConcurrentHashMap<>();
    //存储发给离线用户的消息
    public static ConcurrentHashMap<String, ArrayList<Message>> offlineUsers = new ConcurrentHashMap<>();

    static {
        //在静态代码块初始化一些合法用户
        validUsers.put("user", new User("user", "password"));
        validUsers.put("root", new User("root", "root"));
        validUsers.put("666", new User("666", "123456"));
        validUsers.put("100", new User("100", "123456"));
        validUsers.put("紫英", new User("紫英", "123456"));
        validUsers.put("天河", new User("天河", "123456"));
        validUsers.put("至尊宝", new User("至尊宝", "123456"));
        validUsers.put("紫霞", new User("紫霞", "123456"));
    }

    //添加离线用户
    public static void addOfflineUser(String getter, ArrayList<Message> messages) {
        offlineUsers.put(getter, messages);
    }

    //获取离线消息
    public static ArrayList<Message> getOfflineUser(String getter) {
        return offlineUsers.get(getter);
    }

    //判断是否是离线用户
    public static boolean containsOfflineUser(String getter){
        return offlineUsers.containsKey(getter);
    }

    //移除离线用户
    public static void removeOfflineUser(String getter){
        offlineUsers.remove(getter);
    }



    //验证用户是否为合法用户的方法
    private boolean checkUser(String userId, String password) {
        User validUser = validUsers.get(userId);
        if (validUser == null) {
            //get返回null说明该用户不在合法用户集合中返回false
            return false;
        }
        if (!validUser.getPassword().equals(password)) {
            //用户名正确但是密码错误
            return false;
        }
        /*if (!validUsers.isEmpty()) {//判断是否重复登录
            for (int i = 0; i < validUsers.size(); i++) {
                if (validUser.equals(validUsers.get(i))) {
                    return false;
                }
            }
        }*/
        else return true;//过关斩将的方式

    }


    public QQServer() {
        //启动新闻推送线程
        try {
            System.out.println("服务端正在9999端口监听...");
            new SendNews().start();
            System.out.println("新闻推送线程启动...");
            this.serverSocket = new ServerSocket(9999);

            while (true) {
                //即使已经有一个客户端连接还是要继续监听,所以用while
                Socket socket = serverSocket.accept();//如果没有客户端连接会阻塞在这里
                //得到socket相关的对象输入流
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                //读取客户端发送来的user对象
                User user = (User) ois.readObject();
                //创建一个Message对象用于回复客户端
                Message message = new Message();
                //创建一个socket相关的对象输出流用于发送message
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                //验证
                if (checkUser(user.getUserID(), user.getPassword())) {//调用方法判断是否为合法用户
                    //合法用户
                    message.setMesType(MessageType.MESSAGE_LOGIN_SUCCESS);
                    oos.writeObject(message);
                    //这时创建一个持有socket的线程用于和客户端保持通信
                    ServerConnetClientThread serverConnetClientThread
                            = new ServerConnetClientThread(socket, user.getUserID());
                    //启动线程
                    serverConnetClientThread.start();
                    //将线程放入集合管理
                    ServerConnetClientThreadManager.addServerConnetClientThread(user.getUserID(), serverConnetClientThread);

                    //登陆成功后先检测是否有离线消息
                    ArrayList<Message> arrayList = getOfflineUser(user.getUserID());
                    if (arrayList != null){
                        for (int i = 0; i < arrayList.size(); i++) {

                            Message mes = arrayList.get(i);
                            ObjectOutputStream objectOutputStream = new ObjectOutputStream(ServerConnetClientThreadManager.
                                    getServerConnetClientThread(user.getUserID()).getSocket().getOutputStream());
                            objectOutputStream.writeObject(mes);
                            objectOutputStream.flush();

                            //消息发送完后移除
                            removeOfflineUser(mes.getGetter());

                            
                        }
                    }


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

                }


            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //如果退出了while循环说明服务器不再监听,这时关闭serverSocket
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
  • service_SendNews
package com.recorder.service;

import com.recorder.common.Message;
import com.recorder.common.MessageType;
import com.recorder.utils.Utility;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author 紫英
 * @version 1.0
 * @discription 用于服务器端推送消息
 */
public class SendNews extends Thread {
    @Override
    public void run() {
        while (true) {
            //实现一直推送
            System.out.println("请输入要推送的内容:(回复TD退出推送线程)");
            String news = Utility.readString(50);
            if ("TD".equals(news)){
                break;
            }
            //创建一个消息对象对在线用户实施群发
            Message message = new Message();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            message.setSendTime(sdf.format(System.currentTimeMillis()));
            message.setContent(news);
            message.setMesType(MessageType.MESSAGE_COMMON_ALL);
            message.setSender("服务器");
            System.out.println("服务器推送消息:" + news);

            HashMap<String, ServerConnetClientThread> hm = ServerConnetClientThreadManager.getHm();
            Set<String> keySet = hm.keySet();
            Iterator<String> iterator = keySet.iterator();
            while (iterator.hasNext()) {
                String getterId = iterator.next().toString();

                try {
                    ObjectOutputStream oos = new ObjectOutputStream(hm.get(getterId).getSocket().getOutputStream());
                    oos.writeObject(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }


            }
        }
        System.out.println("推送线程已退出!");
    }
}
  • service_ServerConnetClientThread
package com.recorder.service;

import com.recorder.common.Message;
import com.recorder.common.MessageType;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

/**
 * @author 紫英
 * @version 1.0
 * @discription 该类的某个对象用于和某个客户端保持通信
 */
public class ServerConnetClientThread extends Thread {
    private Socket socket;
    private String userId;//连接到服务器端的userId

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

    public Socket getSocket() {
        return socket;
    }

    @Override
    public void run() {
        //用于发送和接收消息
        while (true) {
            System.out.println("服务端和客户端" + userId + "保持通信,读取数据...");
            try {
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                Message message = (Message) ois.readObject();

                //根据message类型做相应的业务处理
                if (message.getMesType().equals(MessageType.MESSAGE_GET_ONLINEFRIEND)) {
                    System.out.println(message.getSender() + "请求拉取在线用户列表");
                    Message serverMessage = new Message();
                    serverMessage.setMesType(MessageType.MESSAGE_RETURN_ONLINEFRIEND);
                    String onlineUserList = ServerConnetClientThreadManager.getOnlineUserList();
                    serverMessage.setContent(onlineUserList);
                    serverMessage.setGetter(message.getSender());
                    //返回客户端
                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                    oos.writeObject(serverMessage);

                } else if (message.getMesType().equals(MessageType.MESSAGE_COMMON)) {
                    //先查看用户是否在线
                    if (!ServerConnetClientThreadManager.isOnline(message.getGetter())) {
                        //线程中不存在就说明离线,将消息存在服务器的ConcurrentHashMap集合中
                        if (!QQServer.containsOfflineUser(message.getGetter())) {
                            //集合中没有就添加
                            ArrayList<Message> messageArrayList = new ArrayList<>();
                            messageArrayList.add(message);
                            QQServer.addOfflineUser(message.getGetter(), messageArrayList);
                        } else {
                            //集合中存在的话就往已经存在的(离线用户消息集合)里边放
                            ArrayList<Message> arrayList = QQServer.getOfflineUser(message.getGetter());
                            arrayList.add(message);
                            QQServer.addOfflineUser(message.getGetter(), arrayList);
                        }


                        System.out.println("离线消息记录——【" + message.getSender() + "】对【"
                                + message.getGetter() + "】" + "发送了一条消息");


                    } else {
                        //在线的话
                        //消息转发
                        //先根据message取得消息接收方id,再获取对应的线程
                        ServerConnetClientThread serverConnetClientThread =
                                ServerConnetClientThreadManager.getServerConnetClientThread(message.getGetter());
                        //得到socket对应的对象输出流
                        ObjectOutputStream oos = new ObjectOutputStream(serverConnetClientThread.getSocket().getOutputStream());
                        oos.writeObject(message);
                        //如果用户不在线可以将消息保存到数据库,实现离线消息
                    }


                } else if (message.getMesType().equals(MessageType.MESSAGE_COMMON_ALL)) {
                    //消息群发
                    //遍历线程集合取出keymap
                    HashMap<String, ServerConnetClientThread> hm = ServerConnetClientThreadManager.getHm();
                    Set<String> keySet = hm.keySet();
                    Iterator<String> iterator = keySet.iterator();
                    while (iterator.hasNext()) {
                        String getterId = iterator.next().toString();
                        if (!getterId.equals(message.getSender())) {//首先排除自己
                            ObjectOutputStream oos = new ObjectOutputStream(hm.get(getterId).getSocket().getOutputStream());
                            oos.writeObject(message);
                        }
                    }
                } else if (message.getMesType().equals(MessageType.MESSAGE_FILE)) {
                    //文件消息
                    //先判断用户是否在线
                    if (!ServerConnetClientThreadManager.isOnline(message.getGetter())) {
                        //如果不在线
                        if (!QQServer.containsOfflineUser(message.getGetter())) {
                            //集合中没有就添加
                            ArrayList<Message> messageArrayList = new ArrayList<>();
                            messageArrayList.add(message);
                            QQServer.addOfflineUser(message.getGetter(), messageArrayList);
                        } else {
                            //集合中存在的话就往已经存在的(离线用户消息集合)里边放
                            ArrayList<Message> arrayList = QQServer.getOfflineUser(message.getGetter());
                            arrayList.add(message);
                            QQServer.addOfflineUser(message.getGetter(), arrayList);
                        }
                        System.out.println("离线消息记录——【" + message.getSender() + "】给【"
                                + message.getGetter() + "】" + "发送文件【" + message.getSrc() + "】到" + message.getGetter()
                                + "的【" + message.getDest() + "】");


                    } else {
                        ObjectOutputStream oos = new ObjectOutputStream(ServerConnetClientThreadManager.
                                getServerConnetClientThread(message.getGetter()).getSocket().getOutputStream());
                        oos.writeObject(message);//消息转发
                    }


                } else if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)) {

                    System.out.println("客户端" + message.getSender() + "退出");
                    ServerConnetClientThreadManager.removeClient(message.getSender());
                    socket.close();//关闭socket
                    break;//退出循环结束线程
                }


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


        }
    }
}
  • service_ServerConnetClientThreadManager
package com.recorder.service;

import java.util.HashMap;
import java.util.Iterator;

/**
 * @author 紫英
 * @version 1.0
 * @discription 用于管理和客户端通信的线程
 */
public class ServerConnetClientThreadManager {


    private static HashMap<String, ServerConnetClientThread> hm = new HashMap<>();

    //查看线程是否存在(用户是否在线)
    public static boolean isOnline(String userId){
        return hm.containsKey(userId);
    }

    //添加方法
    public static void addServerConnetClientThread(String userId, ServerConnetClientThread serverConnetClientThread) {
        hm.put(userId, serverConnetClientThread);
    }
    public static HashMap<String, ServerConnetClientThread> getHm() {
        return hm;
    }

    //根据userId返回线程
    public static ServerConnetClientThread getServerConnetClientThread(String userId) {
        return hm.get(userId);
    }

    //移除方法
    public static void removeClient(String userId) {
        hm.remove(userId);
    }

    //返回在线用户列表
    public static String getOnlineUserList() {
        String onlineUserList = "";
        Iterator<String> iterator = hm.keySet().iterator();
        while (iterator.hasNext()) {
            onlineUserList += iterator.next().toString() + " ";

        }
        return onlineUserList;

    }

}

frame

package com.recorder.frame;

import com.recorder.service.QQServer;

/**
 * @author 紫英
 * @version 1.0
 * @discription 创建QQServer(),用于启动法服务器
 */
public class QQFrame {
    public static void main(String[] args) {
        new QQServer();
        System.out.println("服务器关闭");
    }
}

util

package com.recorder.utility;
import java.util.*;
/**
 * @author 紫英
 * @version 1.0
 * @discription 接受用户输入工具类
 */


/**
 工具类的作用:
 处理各种情况的用户输入,并且能够按照程序员的需求,得到用户的控制台输入。
 */

/**


 */
public class Utility {
    //静态属性。。。
    private static Scanner scanner = new Scanner(System.in);


    /**
     * 功能:读取键盘输入的一个菜单选项,值:1——5的范围
     * @return 1——5
     */
    public static char readMenuSelection() {
        char c;
        for (; ; ) {
            String str = readKeyBoard(1, false);//包含一个字符的字符串
            c = str.charAt(0);//将字符串转换成字符char类型
            if (c != '1' && c != '2' &&
                    c != '3' && c != '4' && c != '5'&& c != '6') {
                System.out.print("选择错误,请重新输入:");
            } else break;
        }
        return c;
    }

    /**
     * 功能:读取键盘输入的一个字符
     * @return 一个字符
     */
    public static char readChar() {
        String str = readKeyBoard(1, false);//就是一个字符
        return str.charAt(0);
    }
    /**
     * 功能:读取键盘输入的一个字符,如果直接按回车,则返回指定的默认值;否则返回输入的那个字符
     * @param defaultValue 指定的默认值
     * @return 默认值或输入的字符
     */

    public static char readChar(char defaultValue) {
        String str = readKeyBoard(1, true);//要么是空字符串,要么是一个字符
        return (str.length() == 0) ? defaultValue : str.charAt(0);
    }

    /**
     * 功能:读取键盘输入的整型,长度小于2位
     * @return 整数
     */
    public static int readInt() {
        int n;
        for (; ; ) {
            String str = readKeyBoard(10, false);//一个整数,长度<=10位
            try {
                n = Integer.parseInt(str);//将字符串转换成整数
                break;
            } catch (NumberFormatException e) {
                System.out.print("数字输入错误,请重新输入:");
            }
        }
        return n;
    }
    /**
     * 功能:读取键盘输入的 整数或默认值,如果直接回车,则返回默认值,否则返回输入的整数
     * @param defaultValue 指定的默认值
     * @return 整数或默认值
     */
    public static int readInt(int defaultValue) {
        int n;
        for (; ; ) {
            String str = readKeyBoard(10, true);
            if (str.equals("")) {
                return defaultValue;
            }

            //异常处理...
            try {
                n = Integer.parseInt(str);
                break;
            } catch (NumberFormatException e) {
                System.out.print("数字输入错误,请重新输入:");
            }
        }
        return n;
    }

    /**
     * 功能:读取键盘输入的指定长度的字符串
     * @param limit 限制的长度
     * @return 指定长度的字符串
     */

    public static String readString(int limit) {
        return readKeyBoard(limit, false);
    }

    /**
     * 功能:读取键盘输入的指定长度的字符串或默认值,如果直接回车,返回默认值,否则返回字符串
     * @param limit 限制的长度
     * @param defaultValue 指定的默认值
     * @return 指定长度的字符串
     */

    public static String readString(int limit, String defaultValue) {
        String str = readKeyBoard(limit, true);
        return str.equals("")? defaultValue : str;
    }


    /**
     * 功能:读取键盘输入的确认选项,Y或N
     * 将小的功能,封装到一个方法中.
     * @return Y或N
     */
    public static char readConfirmSelection() {
        System.out.println("请输入你的选择(Y/N): 请小心选择");
        char c;
        for (; ; ) {//无限循环
            //在这里,将接受到字符,转成了大写字母
            //y => Y n=>N
            String str = readKeyBoard(1, false).toUpperCase();
            c = str.charAt(0);
            if (c == 'Y' || c == 'N') {
                break;
            } else {
                System.out.print("选择错误,请重新输入:");
            }
        }
        return c;
    }

    /**
     * 功能: 读取一个字符串
     * @param limit 读取的长度
     * @param blankReturn 如果为true ,表示 可以读空字符串。
     *                       如果为false表示 不能读空字符串。
     *
     *    如果输入为空,或者输入大于limit的长度,就会提示重新输入。
     * @return
     */
    private static String readKeyBoard(int limit, boolean blankReturn) {

        //定义了字符串
        String line = "";

        //scanner.hasNextLine() 判断有没有下一行
        while (scanner.hasNextLine()) {
            line = scanner.nextLine();//读取这一行

            //如果line.length=0, 即用户没有输入任何内容,直接回车
            if (line.length() == 0) {
                if (blankReturn) return line;//如果blankReturn=true,可以返回空串
                else continue; //如果blankReturn=false,不接受空串,必须输入内容
            }

            //如果用户输入的内容大于了 limit,就提示重写输入
            //如果用户如的内容 >0 <= limit ,我就接受
            if (line.length() < 1 || line.length() > limit) {
                System.out.print("输入长度(不能大于" + limit + ")错误,请重新输入:");
                continue;
            }
            break;
        }

        return line;
    }
}

 

客户端

common、util同服务器端

view

package com.recorder.view;

import com.recorder.service.FileClientService;
import com.recorder.service.MessageClientService;
import com.recorder.service.UserClientService;
import com.recorder.utility.Utility;

/**
 * @author 紫英
 * @version 1.0
 * @discription
 */
public class QQviwe {
    private boolean loop = true;//是否显示菜单
    private String key = " ";
    private UserClientService userClientService = new UserClientService();//用于校验用户信息
    private MessageClientService messageClientService = new MessageClientService();//用于用户消息的转发
    private FileClientService fileClientService = new FileClientService();//用于发送文件

    public static void main(String[] args) {
        new QQviwe().mainView();
        System.out.println("客户端退出登录...");

    }

    public void mainView() {

        while (loop) {
            System.out.println("==========欢迎登陆文字版QQ==========");
            System.out.println(" \t\t1.用户登录");
            System.out.println(" \t\t2.退出系统");
            System.out.println(" 请输入你的选择:");
            key = Utility.readString(1);//获取一个选项
//            根据选项来处理不同的请求
            switch (key) {
                case "1":
                    System.out.println("请输入用户名:");
                    String userId = Utility.readString(50);
                    System.out.println("请输入密 码:");
                    String pwd = Utility.readString(50);
//                    验证是否正确
                    if (userClientService.checkUser(userId, pwd)) {
                        System.out.println("~~~User:\t" + userId + "\twelcome!~~~");
                        //登录成功进入二级菜单
                        while (loop) {
                            System.out.println("==========欢迎登陆文字版QQ(" + userId + "の界面)==========");
                            System.out.println(" \t\t1.显示在线用户列表");
                            System.out.println(" \t\t2.群发消息");
                            System.out.println(" \t\t3.私聊消息");
                            System.out.println(" \t\t4.发送文件");
                            System.out.println(" \t\t5.退出系统");
                            System.out.println(" 请输入你的选择:\n");
                            key = Utility.readString(1);//获取一个选项
                            switch (key) {
                                case "1":
                                    userClientService.getOnlineList();
                                    break;
                                case "2":
                                    System.out.println("请输入想对大家说的话:");
                                    String s = Utility.readString(50);
                                    //调用方法
                                    messageClientService.sendMessageAll(userId, s);
                                    break;
                                case "3":
                                    System.out.println("请输入聊天对象ID(如不在线会发送离线消息):");
                                    String getter = Utility.readString(50);
                                    System.out.println("请输入消息内容:");
                                    String content = Utility.readString(200);
                                    //调用方法发送消息
                                    messageClientService.sendMessage(userId, getter, content);
                                    break;
                                case "4":
                                    System.out.println("请输入目标名称:");
                                    String getterId = Utility.readString(50);
                                    System.out.println("请输入源文件路径:(x:\\xx.jpg)");
                                    String src = Utility.readString(50);
                                    System.out.println("请输入目标文件路径:(x:\\xx.jpg)");
                                    String dest = Utility.readString(50);
                                    fileClientService.sendFile(src,dest,userId,getterId);

                                    break;
                                case "5":
                                    loop = false;
                                    userClientService.logout();
                                    break;
                            }
                        }
                    } else {
                        System.out.println("用户名或密码有误!");
                    }


                    break;
                case "2":
                    loop = false;
                    break;

            }
        }

    }


}

 

service

  • service_UserClientService 
package com.recorder.service;

import com.recorder.common.Message;
import com.recorder.common.MessageType;
import com.recorder.common.User;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;

/**
 * @author 紫英
 * @version 1.0
 * @discription 用于校验用户信息(登陆注册)等服务
 */


public class UserClientService {

    //    因为可能在其他的方用到用户信息所以做成成员属性
    User user = new User();
    //同理socket也需要在别的地方用到所以也做成属性
    Socket socket = new Socket();

    //写一个方法用于校验用户名密码是否正确
    public boolean checkUser(String userId, String pwd) {
        boolean flag = false;
        //获取User对象信息,将键盘输入赋值给当前的user
        user.setUserID(userId);
        user.setPassword(pwd);

        //连接到服务器端发送user对象
        try {
            socket = new Socket(InetAddress.getLocalHost(), 9999);
            //得到一个对象输出流来发送对象
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            //发送user对象
            oos.writeObject(user);
            //读取从服务器发来的Message对象
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            Message message = (Message) ois.readObject();

            //判断消息类型来决定是否登陆成功
            if (message.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCESS)) {
                //登陆成功

                //创建一个和服务器端保持通信的线程 (ClientConnetServerThread类)
                ClientConnetServerThread clientConnetServerThread = new ClientConnetServerThread(socket);
                //启动客户端的线程
                clientConnetServerThread.start();
                //为了以后客户端功能的拓展我们将线程放入集合中管理
                ClientConnetServerThreadManager.addClientConnetServerThread(userId, clientConnetServerThread);
                flag = true;

            } else {
                //登陆失败,就不启动线程,关闭socket
                socket.close();
            }

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


    }

    //向服务器端请求用户列表
    public void getOnlineList() {

        //发送一个message
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_GET_ONLINEFRIEND);
        message.setSender(user.getUserID());

        //获取当前线程的socket对应的对象输出流
        try {
            ObjectOutputStream oos = new ObjectOutputStream(ClientConnetServerThreadManager
                    .getClientConnetServerThread(user.getUserID())/*通过用户id取得对应线程*/.getSocket().getOutputStream());
            //发送message对象,请求在线用户列表
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    //向服务器发送结束线程消息
    public void logout() {
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
        //因为在上面的checkUser中已经给user复制了所以可以直接用
        message.setSender(user.getUserID());
        try {
            ObjectOutputStream oos = new ObjectOutputStream(ClientConnetServerThreadManager.
                    getClientConnetServerThread(user.getUserID()).getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(user.getUserID() + "客户端退出");
        System.exit(0);
    }

}
  • service_FileClientService 
package com.recorder.service;

import com.recorder.common.Message;
import com.recorder.common.MessageType;

import java.io.*;

/**
 * @author 紫英
 * @version 1.0
 * @discription 文件传输服务
 */
public class FileClientService {
    /***
     *
     * @param src 源文件目录
     * @param dest 目标目录
     * @param sender 发送者
     * @param getter 接收者
     */
    public void sendFile(String src, String dest, String sender, String getter) {
        //读取src路径的文件封装到message对象中
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_FILE);
        message.setSender(sender);
        message.setGetter(getter);
        message.setSrc(src);
        message.setDest(dest);

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

        try {
            fis = new FileInputStream(src);
            fis.read(fileBytes);//将文件读取到程序的字节数组
            //将文件对应的字节数组设置到message对象
            message.setFileBytes(fileBytes);

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

            if (fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        //提示消息
        System.out.println("\n" + sender + "给" + getter + "发送文件:" + src
         + "到" + getter + "电脑的:" + dest + "路径下");

        //发送
        try {
            ObjectOutputStream oos = new ObjectOutputStream(ClientConnetServerThreadManager.
                    getClientConnetServerThread(sender).getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }


    }


}
  • service_MessageClientService 
package com.recorder.service;

import com.recorder.common.Message;
import com.recorder.common.MessageType;

import javax.swing.text.StringContent;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author 紫英
 * @version 1.0
 * @discription 用于发送消息
 */
public class MessageClientService {
    /**
     * @param sender  发送方
     * @param getter  接收方
     * @param content 消息内容
     */
    public void sendMessage(String sender, String getter, String content) {

        Message message = new Message();
        message.setSender(sender);
        message.setGetter(getter);
        message.setContent(content);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        message.setSendTime(sdf.format(System.currentTimeMillis()));
        message.setMesType(MessageType.MESSAGE_COMMON);
      /*  System.out.println(message.getSendTime()  + ":\n" + sender+ "对"
                + getter + "说" + content );*/
        System.out.println(sender + "对"
                + getter + "的消息发送成功!");

        try {
            ObjectOutputStream oos = new ObjectOutputStream(ClientConnetServerThreadManager.
                    getClientConnetServerThread(message.getSender()).getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public void sendMessageAll(String sender, String content) {
        Message message = new Message();
        message.setSender(sender);
        message.setContent(content);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        message.setSendTime(sdf.format(System.currentTimeMillis()));
        message.setMesType(MessageType.MESSAGE_COMMON_ALL);
        System.out.println(sender + "对大家发送的消息发送成功!");
        try {
            ObjectOutputStream oos = new ObjectOutputStream(ClientConnetServerThreadManager.
                    getClientConnetServerThread(message.getSender()).getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}
  • service_ClientConnetServerThread 
package com.recorder.service;

import com.recorder.common.Message;
import com.recorder.common.MessageType;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.management.MemoryType;
import java.net.Socket;
import java.util.concurrent.TransferQueue;

/**
 * @author 紫英
 * @version 1.0
 * @discription 和服务器端保持通信的线程,持有一个socket类
 */
public class ClientConnetServerThread extends Thread {
    //    该线程需要持有一个socket
    private boolean loop = true;

    public boolean isLoop() {
        return loop;
    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }

    private Socket socket;

    //为了更方便的使用socket属性添加getset方法
    public Socket getSocket() {
        return socket;
    }

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

    public ClientConnetServerThread(Socket socket) {
        //使用构造器接收一个socket对象
        this.socket = socket;
    }

    @Override
    public void run() {

        //Thread需要在后台和服务器端通信,因此使用while循环
        while (loop) {

            System.out.println("客户端线程等待读取从服务器端发来的消息...");
            try {
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
//                如果没有接收到服务器端发来的Message对象线程会阻塞在readObject()方法
                Message message = (Message) ois.readObject();
                //根据不同的message种类来写对应的业务逻辑
                if (message.getMesType().equals
                        (MessageType.MESSAGE_RETURN_ONLINEFRIEND)) {//如果读取到的message类型是返回在线用户列表
                    //取出message信息并显示
                    System.out.println("==========当前用户列表==========");
                    String[] onlineUser = message.getContent().split(" ");
                    //规定返回用户信息用空格隔开
                    for (int i = 0; i < onlineUser.length; i++) {
                        System.out.println("用户:\t【" + onlineUser[i] + "】\t在线");

                    }

                } else if (message.getMesType().equals(MessageType.MESSAGE_COMMON)) {
                    //如果读取到的message类型是私聊消息,显示在控制台
                    System.out.println( message.getSendTime() + ":\n【私聊消息】——【" + message.getSender() + "】对您说:" + message.getContent());

                } else if (message.getMesType().equals(MessageType.MESSAGE_COMMON_ALL)) {
                    System.out.println(message.getSendTime() + ":\n【群发消息】【" + message.getSender() + "】对大家说:" + message.getContent());
                } else if (message.getMesType().equals(MessageType.MESSAGE_FILE)) {
                    //可以让用户指定路径

                    System.out.println("\n【" + message.getSender() + "】给" + message.getGetter() + "发送文件:" + message.getSrc()
                            + "到我电脑的:" + message.getDest() + "路径下");
                    //取出message对象的文件保存到本地磁盘
                    FileOutputStream fos = new FileOutputStream(message.getDest());
                    fos.write(message.getFileBytes());
                    fos.close();
                    System.out.println("\n文件保存成功!");
                }


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

        }
    }
}
  • service_ClientConnetServerThreadManager 
package com.recorder.service;

import java.util.HashMap;

/**
 * @author 紫英
 * @version 1.0
 * @discription 管理客户端线程(集合)
 */
public class ClientConnetServerThreadManager {
    private static HashMap<String, ClientConnetServerThread> hm = new HashMap<>();


    //添加到集合的方法
    public static void addClientConnetServerThread(String userId, ClientConnetServerThread clientConnetServerThread) {
        hm.put(userId, clientConnetServerThread);
    }

    //根据userId获取对应线程
    public static ClientConnetServerThread getClientConnetServerThread(String userId) {
        return hm.get(userId);


    }


}

 

关于2021版idea并行程序的设置

1.

 

2.

 

3.

 

 

 

 

 

 

                      都看到这里了,有用的话点个赞吧!

 

posted @ 2022-02-03 23:28  紫英626  阅读(325)  评论(3编辑  收藏  举报

紫英