Java初学笔记30-【MiniQQ聊天部分代码】

[一] 项目开发流程的简介

在这里插入图片描述

(1)需求分析
一般由需求分析师来完成,最后写出一个需求分析报告,其中包括项目的功能,客户的需求
(2)设计阶段
由架构师或者项目经理完成,设计出UML类图、流程图、模块设计,数据库设计,以及原型开发;组建团队。
(3)实现阶段
程序员,完成架构师的模块功能,测试自己的模块
(4)测试阶段
由测试工程师完成,单元测试,白盒测试,黑盒测试,集合测试,找出程序员写的代码bug
(5)实施阶段
由实施工程师完成,把项目部署到客户平台,并保障运行正常。
(6)维护阶段
发现Bug解决,升级

[二] 多用户及时通讯系统

1. 涉及到知识点

项目框架设计、java面向对象编程、网络编程、多线程、IO流、 Mysql(可以先使用集合充当数据库)

2. 需求分析

(1)用户登录
(2)拉取在线用户列表
(3)无异常退出
(4)私聊
(5)群聊
(6)发文件
(7)服务器推送新闻

3. 整体框架

在这里插入图片描述

一、commen

Message类

package commen;

import java.io.Serializable;

/**
 * @Package: qqCommen
 * @ClassName: Message
 * @Author: 爱吃凉拌辣芒果
 * @CreateTime: 2021/11/15 17:42
 * @Description:  消息对象
 */
public class Message implements Serializable {
    private static final long serialVersionUID = 1l; //串行化好兼容
    private String sender; //发送者
    private String getter; //接收者
    private String content; //消息内容
    private String sendTime; //发送时间
    private String mesType; //消息类型

    private byte[] fileBytes;
    private int fileLen = 0;
    private String dest; //目标文件路径
    private String src; //源头文件路径

    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 getDest() {
        return dest;
    }

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

    public String getSrc() {
        return src;
    }

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

    /**
     * 构造器
     * @param sender
     * @param getter
     * @param content
     * @param sendTime
     * @param mesType
     */
    public Message(String sender, String getter,
                   String content, String sendTime, String mesType) {
        this.sender = sender;
        this.getter = getter;
        this.content = content;
        this.sendTime = sendTime;
        this.mesType = mesType;
    }

    public Message() {
    }

    public String getMesType() {
        return mesType;
    }

    public void setMesType(String mesType) {
        this.mesType = mesType;
    }

    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;
    }
}

User类

package commen;

import java.io.Serializable;

/**
 * @Package: qqCommen
 * @ClassName: User
 * @Author: 爱吃凉拌辣芒果
 * @CreateTime: 2021/11/15 17:41
 * @Description: 用户对象
 * 通过对象流传输
 */
public class User implements Serializable {
    /**
     * 串行化好兼容
     */
    private static final long serialVersionUID = 1l;

    /**
     * 用户ID
     */
    private String userID;

    /**
     * 用户密码
     */
    private String passwd;

    /**
     * 以下为构造器
     */

    public User() {
    }

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

    /**
     * get set方法
     * @return
     */

    public String getUserID() {
        return userID;
    }

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

    public String getPasswd() {
        return passwd;
    }

    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }
}

MessageType接口

package commen;

/**
 * @Package: qqCommen
 * @ClassName: MessageType
 * @Author: 爱吃凉拌辣芒果
 * @CreateTime: 2021/11/15 17:48
 * @Description:
 */
public interface MessageType {
    //登录成功
    String MESSAGE_LOGIN_SUCCEED = "1";

    //登录失败
    String MESSAGE_LOGIN_FAIL = "2";

    //普通信息包
    String MESSAGE_COMMON_MESSAGE = "3";

    //要求返回在线用户列表
    String MESSAGE_GET_ONLINE_FRIEND = "4";

    //返回在线用户列表
    String MESSAGE_RETURN_ONLINE_FRIEND = "5";

    //客户端请求推出
    String MESSAGE_CLIENT_EXIT = "6";

    //群发信息包
    String MESSAGE_TO_ALL_MESSAGE = "7";

    //发送文件数据
    String MESSAGE_FILE_MESSAGE = "8";

    //离线消息
    String MESSAGE_UnLine_MESSAGE = "9";
}

Utility 工具类

package utility;


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

import java.util.Scanner;

/**

	
*/
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') {
                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(2, false);//一个整数,长度<=2位
            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;
    }
}

二、Server

ManageClientThread

package Service;

import commen.Message;

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

/**
 * @Package: Service
 * @ClassName: ManageClientThread
 * @Author: 爱吃凉拌辣芒果
 * @CreateTime: 2021/11/17 18:42
 * @Description: 服务器中 管理线程的集合 类
 */
public class ManageClientThread {
    //用hashmap存储服务器连接到客户端的线程
    private static HashMap<String,SeverConnectClientThread> hm = new HashMap<>();

    //用ConcurrentHashMap存储离线的线程的message
    private static ConcurrentHashMap<String, ArrayList<Message>> chm
            = new ConcurrentHashMap<>();

    /**
     * 返回ConcurrentHashMap
     * @return
     */
    public static ConcurrentHashMap<String, ArrayList<Message>> getChm() {
        return chm;
    }


    /**
     * 返回hashmap
     * @return
     */
    public static HashMap<String, SeverConnectClientThread> getHm() {
        return hm;
    }

    /**
     * 添加线程到服务器的Hashmap中
     * @param userID
     * @param severConnectClientThread
     */
    public static void addClientThread(
            String userID,SeverConnectClientThread severConnectClientThread){
        hm.put(userID, severConnectClientThread);
    }

    /**
     * 添加离线线程的message到服务器的Hashmap中的arraylist中
     * @param userID  离线的线程ID
     * @param message 离线的线程的message对象
     */
    public static void addOffLineClientThreadMessage(
            String userID,Message message){
        //一开始存在离线线程
        if(chm.containsKey(userID)){
            ArrayList<Message> messageArrayList = chm.get(userID);
            messageArrayList.add(message);
            System.out.println("===================接着存入消息===================");
        }else{ //一开始不存在离线线程
            ArrayList<Message> messageArrayList = new ArrayList<>();
            messageArrayList.add(message);
            chm.put(userID,messageArrayList);
            System.out.println("【服务器已经存入 "+ message.getGetter() +" 的离线消息】");
        }
    }

    /**
     * 由用户ID获取对应的线程
     * @param userID
     * @return
     */
    public static SeverConnectClientThread getSeverConnectClientThread(String userID){
        return hm.get(userID);
    }

    /**
     * 由用户ID获取离线的message对象数组
     * @param userID 离线的ID
     * @return
     */
    public static Message[] getOffLineClientThread(String userID){
        ArrayList<Message> messageArrayList = chm.get(userID);
        Message[] messages = new Message[messageArrayList.size()];
        for (int i = 0; i < messageArrayList.size(); i++) {
            messages[i] = messageArrayList.get(i);
        }
        return messages;
    }

    /**
     * 由于客户端ID移除对应服务器Hashmap集合中的线程
     * @param userID
     */
    public static void remove(String userID){
        hm.remove(userID);
    }

    /**
     * 由于客户端ID移除对应服务器 ConcurrentHashMap 集合中的离线线程
     * @param userID 用户ID
     */
    public static void removeOffLineClientThread(String userID){
        chm.remove(userID);
    }

    /**
     * 服务器返回在线用户列表
     * @return
     */
    public static String getOnlineUserList(){
        Set<String> keySet = hm.keySet();
        Iterator<String> iterator = keySet.iterator();
        String str = "";
        while (iterator.hasNext()){
            str += iterator.next().toString() + " ";
        }
        return str;
    }
}

QQServer

package Service;

import commen.Message;
import commen.MessageType;
import commen.User;

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

/**
 * @Package: Service
 * @ClassName: QQServer
 * @Author: 爱吃凉拌辣芒果
 * @CreateTime: 2021/11/17 15:53
 * @Description: miniQQ 服务器,在监听9999,等待客户端的连接,并保持通信
 */
public class QQServer {

    private ServerSocket ss = null;

    /**
     * 创建一个集合,保存多个合法用户
     * 这里也可以使用 ConcurrentHashMap,可以处理并发的集合,没有线程安全
     * HashMap没有处理线程安全,因此在多线程情况下是不安全
     * ConcurrentHashMap处理的线程安全,即线程同步处理,在多线程情况下是安全
     * 这里只是对集合进行读的操作,故使用两者都可以,在写的时候使用 ConcurrentHashMap
     */
    private static ConcurrentHashMap<String , User> validUsers =
            new ConcurrentHashMap<>();

    //用静态代码块初始化一次 validUsers
    static {
        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"));
    }

    /**
     * 检验用户是否合法
     * @param userID
     * @param pswd
     * @return
     */
    public boolean isCheckUserValid(String userID,String pswd){
        User user = validUsers.get(userID);
        if(user == null){  //账户不存在
            return false;
        }
        if(!user.getPasswd().equals(pswd)){  //账户存在但密码不对
            return false;
        }
        return true;
    }

    /**
     * 构造器
     */
    public QQServer() {
        //端口可以写在配置文件中
        try {
            System.out.println("服务器在9999监听......");
            ss = new ServerSocket(9999);

            //启动推送消息的线程
            new Thread(new SendNewsToAllClientService()).start();

            //当和某个客户端连接后,用while循环继续监听
            while (true){
                Socket socket = ss.accept();

                //得到Socket关联的对象流
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                User user = (User) ois.readObject();

                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

                //用于回复客户端消息的Message对象
                Message message = new Message();

                //验证账号和密码
                if(isCheckUserValid(user.getUserID(),user.getPasswd())){
                    //将Message对象回复给客户端
                    message.setMesType(MessageType.MESSAGE_LOGIN_SUCCEED);
                    oos.writeObject(message);

                    //创建一个线程,与客户端保持通讯,该线程
                    SeverConnectClientThread severConnectClientThread =
                            new SeverConnectClientThread(socket, user.getUserID());
                    //启动线程
                    severConnectClientThread.start();

                    //将线程加入到服务器中的集合中进行管理
                    ManageClientThread.addClientThread(
                            user.getUserID(), severConnectClientThread);

                } else{  //验证失败
                    System.out.println("用户:"+user.getUserID()+" 验证信息失败!");
                    //将Message对象回复给客户端
                    message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
                    oos.writeObject(message);
                    //登录失败关闭该 socket
                    socket.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //如果服务器停止,则关闭 ServerSocket
            try {
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

SendNewsToAllClient

package Service;

import commen.Message;
import commen.MessageType;
import utility.Utility;

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

/**
 * @Package: Service
 * @ClassName: SendNewsToAllClient
 * @Author: 爱吃凉拌辣芒果
 * @CreateTime: 2021/11/20 22:06
 * @Description: 推送消息给所有客户端的服务
 */
public class SendNewsToAllClientService implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("请输入要推送的消息[输入exit退出]:");
            String news = Utility.readString(100);
            if(news.equals("exit")){
                break;
            }
            //构建一个群发的消息
            Message message = new Message();
            message.setMesType(MessageType.MESSAGE_TO_ALL_MESSAGE);
            message.setSender("服务器");
            message.setContent(news);
            message.setSendTime(new Date().toString());
            System.out.println("【服务器已经推送了消息】");

            //遍历在线用户
            HashMap<String, SeverConnectClientThread> hm = ManageClientThread.getHm();
            Iterator<String> iterator = hm.keySet().iterator();
            while (iterator.hasNext()){
                String onLineUsers = iterator.next();
                SeverConnectClientThread thread =
                        ManageClientThread.getSeverConnectClientThread(onLineUsers);
                try {
                    ObjectOutputStream oos =
                            new ObjectOutputStream(thread.getSocket().getOutputStream());
                    oos.writeObject(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

SeverConnectClientThread

package Service;

import commen.Message;
import commen.MessageType;

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

/**
 * @Package: Service
 * @ClassName: SeverConnectClientThread
 * @Author: 爱吃凉拌辣芒果
 * @CreateTime: 2021/11/17 16:33
 * @Description: 该类的一个对象和某个客户端保持通讯
 */
public class SeverConnectClientThread extends Thread{

    private Socket socket;
    //连接到服务器的用户ID
    private String userID;

    /**
     * 构造器
     * @param socket
     * @param userID
     */
    public SeverConnectClientThread(Socket socket, String userID) {
        this.socket = socket;
        this.userID = userID;
    }

    public Socket getSocket() {
        return socket;
    }

    /**
     * 这里线程处于run状态,可以接受和发送消息
     */
    @Override
    public void run() {

        while(true){
            try {
                System.out.println("服务器与客户端 "+ userID +" 保持通讯,读取数据.......");
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                Message message = (Message) ois.readObject();

                //收到客户端Message做出对应判断
                if(message.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)){
                    //从服务器获取在线用户列表
                    System.out.println("客户端 "+ message.getSender() +" 想要获取在线用户");
                    String onlineUserList = ManageClientThread.getOnlineUserList();
                    //构建Message对象返回给客户端
                    Message message2 = new Message();
                    message2.setMesType(MessageType.MESSAGE_RETURN_ONLINE_FRIEND);
                    message2.setContent(onlineUserList);
                    message2.setGetter(message.getSender());
                    //创建数据通道返回message
                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                    oos.writeObject(message2);

                }else if(message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)){
                    System.out.println("客户端 " + message.getSender()+" 请求退出------>");
                    //将这个客户端对应的线程从集合中移除
                    ManageClientThread.remove(message.getSender());
                    //关闭socket连接
                    socket.close();
                    //关闭改线程
                    break;
                }else if(message.getMesType().equals(MessageType.MESSAGE_COMMON_MESSAGE)){
                    //此处先判断服务器集合中目标客户端是否存在
                    HashMap<String, SeverConnectClientThread> hm = ManageClientThread.getHm();
                    //如果管理线程的集合中存在目标线程
                    if(hm.containsKey(message.getGetter())){
                        //在服务器的管理线程的集合中,获取私聊目标对象的线程
                        SeverConnectClientThread severConnectClientThread =
                                ManageClientThread.getSeverConnectClientThread(message.getGetter());
                        //获取目标线程对应的socket
                        Socket socket = severConnectClientThread.getSocket();
                        //创建数据通道
                        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                        //服务器转发消息给目标客户端
                        oos.writeObject(message);
                    }else{  //如果管理线程的集合中不存在目标线程
                        ManageClientThread.addOffLineClientThreadMessage(message.getGetter(),message);
                    }

                }else if(message.getMesType().equals(MessageType.MESSAGE_TO_ALL_MESSAGE)){
                    HashMap<String, SeverConnectClientThread> hm = ManageClientThread.getHm();

                    //遍历集合
                    Iterator<String> iterator = hm.keySet().iterator();
                    while (iterator.hasNext()){
                        String onLineID = iterator.next();
                        if(!onLineID.equals(message.getSender())){
                            SeverConnectClientThread thread = hm.get(onLineID);
                            ObjectOutputStream oos = new ObjectOutputStream(
                                    thread.getSocket().getOutputStream());
                            oos.writeObject(message);
                        }
                    }
                }else if(message.getMesType().equals(MessageType.MESSAGE_FILE_MESSAGE)){
                    SeverConnectClientThread thread =
                            ManageClientThread.getSeverConnectClientThread(message.getGetter());
                    Socket socket = thread.getSocket();
                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                    oos.writeObject(message);
                }else if(message.getMesType().equals(MessageType.MESSAGE_UnLine_MESSAGE)){
                    //如果存在离线消息
                    if(ManageClientThread.getChm().containsKey(message.getSender())){
                        Message[] messages = ManageClientThread.getOffLineClientThread(message.getSender());
                        SeverConnectClientThread thread =
                                ManageClientThread.getSeverConnectClientThread(message.getSender());
                        Socket socket = thread.getSocket();
                        for (int i = 0; i < messages.length; i++) {
                            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                            Message message1 = messages[i];
                            message1.setMesType(MessageType.MESSAGE_UnLine_MESSAGE);
                            oos.writeObject(message1);
                        }
                        System.out.println("==============服务器发送离线消息完毕============");
                        ManageClientThread.removeOffLineClientThread(message.getSender());
                    }
                }else{
                    System.out.println("其它业务");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

qqFrame

package QQFrame;

import Service.QQServer;

/**
 * @Package: QQFrame
 * @ClassName: qqFrame
 * @Author: 爱吃凉拌辣芒果
 * @CreateTime: 2021/11/17 19:17
 * @Description: 创建qq服务器对象,并启动
 */
public class qqFrame {
    public static void main(String[] args) {
        QQServer qqServer = new QQServer();
    }
}

三、Client

ClinetConnectServerThread

package Service;

import commen.Message;
import commen.MessageType;

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

/**
 * @Package: Service
 * @ClassName: ClinetConnectServerThread
 * @Author: 爱吃凉拌辣芒果
 * @CreateTime: 2021/11/16 22:38
 * @Description: 客户端连接服务器的线程类
 */
public class ClinetConnectServerThread extends Thread{
    //Socket
    private Socket socket;

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

    @Override
    public void run() {
        //因为这个线程里面有一个Socket,用来一直与服务器保持通信,故用while循环
        while (true){
            try {
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                //如果服务器没有消息会一直阻塞在下面这里
                Message message = (Message) ois.readObject();
                //将从服务器收到的消息做处理,做出对应的业务逻辑
                if(message.getMesType().equals(MessageType.MESSAGE_RETURN_ONLINE_FRIEND)){
                    //获取信息内容并做出对应消息处理
                    String content = message.getContent();
                    String[] onlineUsers = content.split(" ");
                    System.out.println("\n\n------------在线用户------------");
                    for (int i = 0; i < onlineUsers.length; i++) {
                        System.out.println("用户:"+onlineUsers[i]);
                    }
                }else if(message.getMesType().equals(MessageType.MESSAGE_COMMON_MESSAGE)){
                    System.out.println("\n"+message.getSendTime());
                    System.out.println("【"+message.getSender()+" 对 "
                            + message.getGetter()+" 说:】 "+message.getContent());
                }else if(message.getMesType().equals(MessageType.MESSAGE_TO_ALL_MESSAGE)){
                    System.out.println("\n【" + message.getSender()+" 对大家说:】 "
                            + message.getContent());
                }else if(message.getMesType().equals(MessageType.MESSAGE_FILE_MESSAGE)){
                    System.out.println("\n"+message.getSender()+" 发送文件到自己的 "+message.getDest());
                    FileOutputStream fos = new FileOutputStream(message.getDest());
                    fos.write(message.getFileBytes());
                    fos.close();
                    System.out.println("\n文件保存成功~~");
                }else if(message.getMesType().equals(MessageType.MESSAGE_UnLine_MESSAGE)){
                    System.out.println("\n"+message.getSendTime());
                    System.out.println("【"+message.getSender()+" 留言说:】"+message.getContent());
                }else{
                    System.out.println("其他业务....");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public Socket getSocket() {
        return socket;
    }

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

FileClientService

package Service;

import commen.Message;
import commen.MessageType;

import java.io.*;
import java.net.Socket;

/**
 * @Package: Service
 * @ClassName: FileClientService
 * @Author: 爱吃凉拌辣芒果
 * @CreateTime: 2021/11/20 19:13
 * @Description: 客户端到服务器文件传输
 */
public class FileClientService {
    public void sendFileToOne(String src,String dest,String senderID,String getterID){

        Message message = new Message();
        message.setSender(senderID);
        message.setGetter(getterID);
        message.setSrc(src);
        message.setDest(dest);
        message.setMesType(MessageType.MESSAGE_FILE_MESSAGE);

        //从src读取文件到客户端程序
        FileInputStream fileInputStream = null;
        byte[] filebytes = new byte[(int) new File(src).length()];

        try {
            fileInputStream = new FileInputStream(src);
            fileInputStream.read(filebytes);
            message.setFileBytes(filebytes);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fileInputStream!= null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        //发送message对象
        ClinetConnectServerThread thread =
                ManageClinetConnectServerThread.getClinetConnectServerThread(senderID);
        Socket socket = thread.getSocket();
        try {
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //提示信息
        System.out.println("【已经发送文件给" + getterID + "】");

    }
}

ManageClinetConnectServerThread

package Service;

import java.util.HashMap;

/**
 * @Package: Service
 * @ClassName: ManageClinetConnectServerThread
 * @Author: 爱吃凉拌辣芒果
 * @CreateTime: 2021/11/17 10:15
 * @Description:  客户端---管理客户端连接到服务器的线程
 */
public class ManageClinetConnectServerThread {
    //创建hashmap来存储键值对,key - UserID  Value - 线程
    private static HashMap<String,ClinetConnectServerThread> hm = new HashMap<>();

   //将线程加入集合中
    public static void addClinetConnectServerThread(
            String uerID,ClinetConnectServerThread clinetConnectServerThread){
        hm.put(uerID,clinetConnectServerThread);
    }

    //通过uerID获取对应的线程
    public static ClinetConnectServerThread getClinetConnectServerThread(String uerID){
        return hm.get(uerID);
    }
}

MessageClientService

package Service;

import commen.Message;
import commen.MessageType;

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

/**
 * @Package: Service
 * @ClassName: MessageClientService
 * @Author: 爱吃凉拌辣芒果
 * @CreateTime: 2021/11/20 11:03
 * @Description: 改类实现客户端消息与服务端消息的处理
 */
public class MessageClientService {

    /**
     * 私发消息
     * @param sendID   发送者
     * @param getterID 接收者
     * @param content  内容
     */
    public void sendMessageToOne(String sendID,String getterID,String content){
        //构建message对象
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_COMMON_MESSAGE);
        message.setSender(sendID);
        message.setGetter(getterID);
        message.setContent(content);
        message.setSendTime(new Date().toString());
        System.out.println("【"+ sendID +" 对 "+ getterID +" 的消息已发送】");

        //发送message对象
        try {
            ObjectOutputStream oos = new ObjectOutputStream(ManageClinetConnectServerThread
                    .getClinetConnectServerThread(sendID)
                    .getSocket()
                    .getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 群发消息
     * @param sendID 发送者
     * @param content 内容
     */
    public void sendMessagetoAll(String sendID,String content){
        //构建message对象
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_TO_ALL_MESSAGE);
        message.setSender(sendID);
        message.setContent(content);
        message.setSendTime(new Date().toString());
        System.out.println("【"+ sendID +" 对大家的消息已发送】");

        //发送message对象
        try {
            ObjectOutputStream oos = new ObjectOutputStream(ManageClinetConnectServerThread
                    .getClinetConnectServerThread(sendID)
                    .getSocket()
                    .getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 查看自己的离线消息
     * @param userID
     */
    public void lookUnlineMessage(String userID){
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_UnLine_MESSAGE);
        message.setSender(userID);
        message.setGetter("服务器");
        try {
            ObjectOutputStream oos = new ObjectOutputStream(
                    ManageClinetConnectServerThread
                            .getClinetConnectServerThread(userID)
                            .getSocket()
                            .getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(">>>>>>>>>>>>>>>(离线消息)<<<<<<<<<<<<<");
    }
}

UserClientService

package Service;

import commen.Message;
import commen.MessageType;
import commen.User;

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

/**
 * @Package: Service
 * @ClassName: UserClientService
 * @Author: 爱吃凉拌辣芒果
 * @CreateTime: 2021/11/16 22:18
 * @Description: 客户端服务---用于校验用户密码账号是否正确、以及注册账号密码
 */
public class UserClientService {

    //用户对象
    private User user = new User();
    //socket对象
    private Socket socket;

    /**
     * 根据userID与pwd到服务器校验是否合法
     * @param userID
     * @param pwd
     * @return
     */
    public boolean checkUser(String userID,String pwd){
        //判断账户是否验证成功
        boolean flag = false;
        //创建用户对象
        user.setUserID(userID);
        user.setPasswd(pwd);

        //连接到服务器,发送user对象
        try {
            socket = new Socket(InetAddress.getByName("169.254.25.66"),9999);

            //客户端发送User对象到数据通道
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(user);

            //客户端从数据通道读取服务器返回的信息
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            Message message = (Message) ois.readObject();

            //对返回的信息进行判断
            if(message.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCEED)){ //登录成功
                //此处需要一个持有Socket的线程,来保持和客户端的通信
                ClinetConnectServerThread clinetConnectServerThread =
                        new ClinetConnectServerThread(socket);
                //启动线程
                clinetConnectServerThread.start();
                //将线程加入到集合中
                ManageClinetConnectServerThread.addClinetConnectServerThread(
                        userID,clinetConnectServerThread);
                flag = true;

            }else{  //登录失败,关闭socket
                socket.close();
            }

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

        return flag;
    }

    /**
     * 向服务器获取在线用户列表的方法
     */
    public void onlineFrindList(){
        //准备message信息
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
        message.setSender(user.getUserID());

        //发送给服务器
        //1. 从客户端管理线程的集合中,拿到当前对应线程
        ClinetConnectServerThread thread = ManageClinetConnectServerThread.getClinetConnectServerThread(user.getUserID());
        //2. 从改线程获取 Socket
        Socket socket = thread.getSocket();
        //3. 用 Socket 创建数据通道发送消息
        try {
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 客户端退出,并且向服务器发送退出请求
     */
    public void logout(){
        //构建退出请求
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
        message.setSender(user.getUserID());

        //发送message
        try {
            //ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            ObjectOutputStream oos = new ObjectOutputStream(
                    ManageClinetConnectServerThread
                            .getClinetConnectServerThread(user.getUserID())
                            .getSocket()
                            .getOutputStream());
            oos.writeObject(message);
            System.out.println(">>>>>>>>客户端 "+ user.getUserID() +" 退出系统<<<<<<<<");
            //直接结束客户端进程,其所有线程也会自动结束
            System.exit(0);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

qqView

package View;

import Service.FileClientService;
import Service.MessageClientService;
import Service.UserClientService;
import Utility.Utility;

/**
 * @Package: View
 * @ClassName: qqView
 * @Author: 爱吃凉拌辣芒果
 * @CreateTime: 2021/11/16 14:12
 * @Description:
 */
public class qqView {
    private boolean loop = true;

    //该对象用于登录验证/注册用户/对应服务
    private UserClientService userClientService = new UserClientService();

    //用于用户聊天
    private MessageClientService messageClientService = new MessageClientService();

    //用于文件传输
    private FileClientService fileClientService = new FileClientService();

    public static void main(String[] args) {
        new qqView().mainMenu();
    }

    /**
     * 主菜单
     */
    private void mainMenu(){
        while (loop){
            System.out.println("============欢迎登录MiniQQ============");
            System.out.println("\t\t1 登录系统");
            System.out.println("\t\t9 退出系统");
            System.out.print("请输入:");
            String readString = Utility.readString(1);

            switch (readString){
                case "1":
                    System.out.print("请输入用户账号:");
                    String userName = Utility.readString(20);
                    System.out.print("请输入用户密码:");
                    String userPswd = Utility.readString(20);

                    //此处将客户端输入的账号密码传输到服务器作验证
                    if(userClientService.checkUser(userName,userPswd)){
                        System.out.println(">>>>>>>>>>>>欢迎用户"+userName+"<<<<<<<<<<<<");

                        while (loop){
                            System.out.println("\n============小QQ二级菜单(用户"+userName+")============");
                            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("\t\t9 退出系统");
                            System.out.print("请输入你的选择:");

                            int chooce = Utility.readInt();
                            switch (chooce){
                                case 1:
                                    //编写方法从服务器获取用户列表
                                    userClientService.onlineFrindList();
                                    break;
                                case 2:
                                    System.out.print("请输入想对大家说的话:");
                                    String readString1 = Utility.readString(50);
                                    messageClientService.sendMessagetoAll(userName,readString1);
                                    break;
                                case 3:
                                    System.out.print("请输入想要聊天的用户(在线):");
                                    String getterID = Utility.readString(10);
                                    System.out.println("请输入想要要说的话:");
                                    String content = Utility.readString(50);
                                    //用方法实现发送
                                    messageClientService.sendMessageToOne(userName,getterID,content);
                                    break;
                                case 4:
                                    System.out.println("请输入目标用户:");
                                    getterID = Utility.readString(40);
                                    System.out.println("请输入发送文件路径:");
                                    String src = Utility.readString(100);;
                                    System.out.println("请输入接收文件路径:");
                                    String dest = Utility.readString(100);
                                    fileClientService.sendFileToOne(src,dest,userName,getterID);
                                    break;
                                case 5:
                                    messageClientService.lookUnlineMessage(userName);
                                    break;
                                case 9:
                                    userClientService.logout();
                                    loop = false;
                                    break;
                            }
                        }
                    }else{
                        System.out.println("<<<<<<<<<<<<信息错误,登录失败>>>>>>>>>>>>");
                    }
                    break;
                case "9":
                    loop = false;
                    System.out.println("<<<<<<<<<<<<退出系统>>>>>>>>>>>>");
                    break;
                default:
                    System.out.println("<<<<<<<<<<<<输入错误>>>>>>>>>>>>");
                    break;
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

爱吃凉拌辣芒果

不断学习,不断进步,共勉~

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

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

打赏作者

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

抵扣说明:

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

余额充值