综合项目实训 —— 聊天室

本篇博文是本人《Java SE》专栏的最后一篇博文了!

虽然还有些不舍,但是,在将近一学期的学习中,SE的讲解终究是迎来了尾声

那么,作为收尾博文,本人将在本篇博文中带着同学们来重温一下我们在SE学习阶段所学到的常用知识点。

那么,话不多说,现在,本人就开始本篇博文的主题的讲解吧:

首先,我们要做聊天室,就要清楚一个问题:

无论是服务器与客户端的聊天,还是客户端与客户端的聊天,它们的本质都是客户端与服务器之间的聊天

基本功能:

那么,本人再来介绍下此次所要制作的聊天室项目所具备的功能吧:

功能

  1. 私聊
  2. 公聊
  3. 在线列表
  4. 下线
  5. 发送文件
  6. 隐身/活跃状态 切换
  7. 查询聊天记录

工具类:

那么,为了方便我们之后的代码编写,本人先来给出三个工具类:

1.处理用户输入信息的 InputUtil 工具:

package edu.youzg.chatroom.utils;

import java.util.Scanner;

public class InputUtil {
	
	public static int inputNeededIntType(int start, int end){
		Scanner sc = new Scanner(System.in);
		int choose = 0;
		while(true){
			try {
				choose = sc.nextInt();
				if (choose >= start && choose <= end) {
					break;
				}

			} catch (Exception e) {
				sc = new Scanner(System.in);
			}
			System.out.println("输入的类型不正确请重新输入整数:");
		}
		return choose;
	}

	public static String inputUserName() {
		Scanner sc = new Scanner(System.in);
		String res = "";
		String pattern = "[a-zA-Z][a-zA-Z_]\\w{4,14}";
		while (true) {
			System.out.println("请输入6~16位用户名:(只能以 数字/字母/下划线 组成,且只能以字母开头)");
			res = sc.nextLine();
			if (res.matches(pattern)) {
				break;
			}
			sc = new Scanner(System.in);
			System.out.println("输入的类型不正确,请重新输入:");
		}

		return res;
	}

	public static String inputUserPassword() {
		Scanner sc = new Scanner(System.in);
		String res = "";
		String pattern = ".{6,20}";
		while (true) {
			System.out.println("请输入6~20位密码:");
			res = sc.nextLine();
			if (res.matches(pattern)) {
				break;
			}
			sc = new Scanner(System.in);
			System.out.println("输入的类型不正确,请重新输入:");
		}
		return res;
	}

    public static String inputChoice() {
		Scanner sc = new Scanner(System.in);
		String res = "";
		String pattern = "[y,n]";
		while (true) {
			System.out.println("是否接收?y/n");
			res = sc.nextLine();
			if (res.matches(pattern)) {
				break;
			}
			sc = new Scanner(System.in);
			System.out.println("输入的类型不正确,请重新输入:");
		}
		return res;
    }

}

2.将 毫秒值式 的日期,转换为指定格式 的 TimeUtil 工具:

package edu.youzg.chatroom.utils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class TimeUtil {
	//将dateStr转换为formatStr格式的日期字符串
	public static String changeDate2Week(String dateStr,String formatStr){
		SimpleDateFormat sdf = new SimpleDateFormat(formatStr);
		Date date = null;
		try {
			date = sdf.parse(dateStr);
		} catch (ParseException e) {
			e.printStackTrace();
		}
		SimpleDateFormat sdf1 = new SimpleDateFormat("E");
		return sdf1.format(date);
	}
	
	//把毫秒值转换成日器字符串
	public static String changeMils2Date(long mils,String formatStr){
		SimpleDateFormat sdf = new SimpleDateFormat(formatStr);
		Date date = new Date(mils);
		return sdf.format(date);
	}
	
}

3.用于传输文件的 InputAndOutputUtil工具:

package edu.youzg.chatroom.utils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class InputAndOutputUtil {

	public static byte[] readFile(String path){
		File file = new File(path);
		byte datas[] = null;
		if(!file.exists()){
			datas = null;
		} else {
			try {
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				FileInputStream fis = new FileInputStream(file);
				byte data[] = new byte[1024*1024];
				int len = 0;
				while((len = fis.read(data))>0){
					baos.write(data, 0, len);
				}
				datas = baos.toByteArray();
				baos.flush();
				baos.close();
				fis.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return datas;
	}
	
	public static boolean writeFile(String path,byte datas[]){
		try {
			File file = new File(path);
			FileOutputStream fos = new FileOutputStream(file);
			fos.write(datas);
			fos.flush();
			fos.close();
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

}

思路:

那么,我们还要明确两个问题 :

  1. 客户端和服务器之间传送的信息是何种类型?
  2. 对端接收到本端信息后,如何识别?

这就牵扯到一个非常高大上、但是很容易理解的名词 —— 协议

说简单的,就是双方所指定的规则。

也就是说,我们规定好消息的类型,以及消息的识别方式。

那么,现在,本人来给出本人此篇博文所需要的消息类型

首先是用于表名消息的请求类型的枚举:

package edu.youzg.chatroom.config;

public enum MsgType {
    ONLINE, //上线提醒
    PRIVATE,    //私聊
    PUBLIC, //公聊
    CLIENT_INEXISTENCE, //好友不在线或不存在
    FRIEND_LIST,    //在线列表
    PASS_FILE,  //文件传输
    ACCEPT, //对端接收文件
    REFUSE, //对端拒绝接收文件
    EXIT,    //下线
    SWITCH, //切换 隐身/在线 状态
    SWITCH_ACTIVE,  //切换至在线状态
    SWITCH_HIDDEN,  //切换至隐身状态
    QUERY_PUBLIC,   //查询公聊内容
    QUERY_PRIVATE,  //查询私聊内容

    USER_CONTAINED, //用户名已存在
    ILLEAGAL_USER,  //非法用户名
    REGISTER,  //请求注册
    REGISTER_SUCCESSFUL,    //注册成功
    REGISTER_FAILURE,   //注册失败
    LOGIN, //请求登录
    LOGIN_OK, //允许登录
    LOGIN_NO, //不允许登录
}

然后是存储消息的类:

package edu.youzg.chatroom.bean;

import edu.youzg.chatroom.server.config.MsgType;

import java.io.Serializable;

public class ChatMsgBean implements Serializable { //实现序列化接口
	private static final long serialVersionUID = 1L;

	private String reciver;//接收者
	private String sender;//发送者
	private String content;//消息内容
	private long time;//时间
	private MsgType msgType;//消息类型

	private String fileName;//文件名
	private long fileLength;//文件大小
	private byte fileData[];//文件的字节数据


	//构造方法 封装普通消息的数据
	public ChatMsgBean(String reciver, String sender, String content,
			long time, MsgType msgType) {
		super();
		this.reciver = reciver;
		this.sender = sender;
		this.content = content;
		this.time = time;
		this.msgType = msgType;
	}
	//封装发送文件所需数据
	public ChatMsgBean(String reciver, String sender, String content,
			long time, MsgType msgType, String fileName, long fileLength,
			byte[] fileData) {
		super();
		this.reciver = reciver;
		this.sender = sender;
		this.content = content;
		this.time = time;
		this.msgType = msgType;
		this.fileName = fileName;
		this.fileLength = fileLength;
		this.fileData = fileData;
	}

	public static long getSerialVersionUID() {
		return serialVersionUID;
	}

	public String getReciver() {
		return reciver;
	}

	public void setReciver(String reciver) {
		this.reciver = reciver;
	}

	public String getSender() {
		return sender;
	}

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

	public String getContent() {
		return content;
	}

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

	public long getTime() {
		return time;
	}

	public void setTime(long time) {
		this.time = time;
	}

	public MsgType getMsgType() {
		return msgType;
	}

	public void setMsgType(MsgType msgType) {
		this.msgType = msgType;
	}

	public String getFileName() {
		return fileName;
	}

	public void setFileName(String fileName) {
		this.fileName = fileName;
	}

	public long getFileLength() {
		return fileLength;
	}

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

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

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

}

有了上述 小工具 以及 协议,本人就将博文分为两个部分 —— 服务器端 和 客户端 来讲解吧:

服务器端

在本人 《详解 网络编程》 这篇博文中就讲过,我们若是通过TCP协议进行网络编程,就要先通过服务器去侦听客户端,在侦听到客户端之后,我们就开启处理该客户端 登录/注册 请求的线程。

那么,依照上述原理,本人来编写下代码:

package edu.youzg.chatroom.server.ui;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class MyServer {

    public static void main(String[] args) {
        HashMap<String, ObjectOutputStream> hm = new HashMap<String, ObjectOutputStream>();
        List<String> activeClients = new ArrayList<String>();
        try {
            ServerSocket ss = new ServerSocket(6666);

            System.out.println("服务器已经开启,等待连接...");
            //循环侦听连接上来的客户端
            int i = 1;
            while (true) {
                Socket sk = ss.accept();	//侦听客户端
                System.out.println("第" + (i++) + "个客户端已连接!");
                new SaveUserThread(hm, activeClients, sk).start();	//侦听到客户端后,开启处理该客户端 登录/注册 请求的线程
            }
        } catch (IOException e) {
        }
    }
}

在侦听到客户端后,我们就要询问客户端要登录还是进行账号注册,所以,本人还是通过一个线程来处理这个需求:

package edu.youzg.chatroom.server.ui;

import edu.youzg.chatroom.bean.ChatMsgBean;
import edu.youzg.chatroom.config.MsgType;
import edu.youzg.chatroom.utils.TimeUtil;

import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import java.util.Set;

//这个线程,专门用来处理 注册/登录
public class SaveUserThread extends Thread {
    HashMap<String, ObjectOutputStream> hm;
    List<String> activeClients;
    Socket sk;
    ObjectOutputStream out;
    ObjectInputStream in;
    private String userName;

    public SaveUserThread(HashMap<String, ObjectOutputStream> hm, List<String> activeClients, Socket sk) throws FileNotFoundException {
        this.hm = hm;
        this.activeClients = activeClients;
        this.sk = sk;
    }

    @Override
    public void run() {
        try {
            //注册好了之后
            //1.读取客户端,发来的用户名进行注册。
            //包装通道中的字节流
            out = new ObjectOutputStream(sk.getOutputStream());
            in = new ObjectInputStream(sk.getInputStream());
            boolean flag = true;
            while (flag) {
                ChatMsgBean bean = (ChatMsgBean) in.readObject();
                userName = bean.getSender();
                MsgType type = bean.getMsgType();
                if (type == MsgType.REGISTER) { //处理注册
                    dealRegister(bean);
                } else {    //处理登录
                    flag = dealLogin(bean);
                }

            }
            //2.上线功能也可以做
            //遍历集合,取出每个人的管道,给他发一个谁谁谁上线了的提醒
            Set<String> keySet = hm.keySet();
            for (String key : keySet) {
                //排除自己,不要给自己发上线提醒
                if (userName.equals(key)) {
                    continue;
                }
                ChatMsgBean bean = new ChatMsgBean(key, userName, null, System.currentTimeMillis(), MsgType.ONLINE);
                hm.get(key).writeObject(bean);
            }
            //最后开启 聊天线程,把集合 发送者,还有sk 传到读取消息的线程
            new ServerTransmitThread(hm, activeClients, userName, in).start();
        } catch (SocketException e) {
            hm.remove(userName);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private boolean dealLogin(ChatMsgBean bean) throws IOException {
        Properties properties = new Properties();
        properties.load(new FileReader("userInfo.properties"));
        String password = (String) properties.get(userName);
        if (bean.getContent().equals(password)) {    //用户名存在,检验密码
            if (hm.containsKey(userName)) {
                out.writeObject(new ChatMsgBean(userName, null, null, System.currentTimeMillis(), MsgType.USER_CONTAINED));
                return true;
            }
            out.writeObject(new ChatMsgBean(userName, null, null, System.currentTimeMillis(), MsgType.LOGIN_OK));
            hm.put(userName, out);
            activeClients.add(userName);
            System.out.println(TimeUtil.changeMils2Date(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss:"));
            System.out.println("客户端[" + userName + "]上线!");
            return false;
        } else {    //用户名不存在
            out.writeObject(new ChatMsgBean(userName, null, null, System.currentTimeMillis(), MsgType.LOGIN_NO));
            return true;
        }
    }

    private void dealRegister(ChatMsgBean bean) throws IOException {
        if (userName == "-q") {
            out.writeObject(new ChatMsgBean(userName, null, null, System.currentTimeMillis(), MsgType.ILLEAGAL_USER));
            return;
        }
        Properties properties = new Properties();
        properties.load(new FileReader("userInfo.properties"));
        if (!properties.stringPropertyNames().contains(bean.getSender())) {
            synchronized (SaveUserThread.class) {
                properties.clear(); //防止我们向文件中录入之前文件中就存在的值
                properties.setProperty(userName, bean.getContent());
                properties.store(new FileWriter("userInfo.properties", true), null);
            }
            out.writeObject(new ChatMsgBean(userName, null, null, System.currentTimeMillis(), MsgType.REGISTER_SUCCESSFUL));
        } else {
            out.writeObject(new ChatMsgBean(userName, null, null, System.currentTimeMillis(), MsgType.REGISTER_FAILURE));
        }
    }

}

最后是 用于 处理客户端操作请求 的线程类:

package edu.youzg.chatroom.server.ui;

import edu.youzg.chatroom.bean.ChatMsgBean;
import edu.youzg.chatroom.config.MsgType;
import edu.youzg.chatroom.utils.TimeUtil;

import java.io.*;
import java.net.SocketException;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

//服务端的子线程,用来读取客户端发来的消息(对象)
public class ServerTransmitThread extends Thread {
    ObjectInputStream in;
    BufferedWriter bw;
    BufferedReader br;
    HashMap<String,ObjectOutputStream> hm;
    List<String> activeClients;
    String username;

    public ServerTransmitThread(HashMap<String, ObjectOutputStream> hm, List<String> activeClients, String username, ObjectInputStream in) {
        this.in = in;
        this.username = username;
        this.hm = hm;
        this.activeClients = activeClients;
    }

    @Override
    public void run() {
        try {
            while (true) {
                //读取客户端,发来的对象
                ChatMsgBean bean = (ChatMsgBean) in.readObject();
                String reciver = bean.getReciver();
                String msgContent = bean.getContent();
                MsgType msgType = bean.getMsgType();
                //把对象中缺的数据补上
                bean.setSender(username);
                bean.setTime(System.currentTimeMillis());

                //根据消息类型做出不同的处理
                if (msgType == MsgType.PRIVATE) {
                    //私聊
                    if (!activeClients.contains(reciver)) {
                        hm.get(username).writeObject(new ChatMsgBean(null, reciver, null, System.currentTimeMillis(), MsgType.CLIENT_INEXISTENCE));
                        continue;
                    }
                    bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("private.txt", true)));
                    synchronized (ServerTransmitThread.class) { //向文件中写入聊天信息
                        String time = TimeUtil.changeMils2Date(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss ");
                        bw.write(time + "[" + bean.getReciver() + "]对" + "[" + username + "]说:" + bean.getContent());
                        bw.newLine();
                        bw.flush();
                    }
                    //取出这个人的管道,写给他
                    hm.get(reciver).writeObject(bean);
                } else if (msgType == MsgType.PUBLIC) {
                    bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("public.txt", true)));
                    synchronized (ServerTransmitThread.class) { //向文件中写入聊天信息
                        String time = TimeUtil.changeMils2Date(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss ");
                        bw.write(time + "[" + username + "]说:" + bean.getContent());
                        bw.newLine();
                        bw.flush();
                    }
                    //公聊:遍历所有人,取出每个人的管道,写给每个人
                    Set<String> keySet = hm.keySet();
                    for (String key : keySet) {
                        //排除自己,不要给自己发
                        if (username.equals(key)) {
                            continue;
                        }
                        hm.get(key).writeObject(bean);
                    }
                } else if (msgType == MsgType.FRIEND_LIST) {
                    //获取在线列表:把集合中的人,拼接好,发送给请求者
                    StringBuffer sb = new StringBuffer();
                    int i = 1;
                    for (String key : activeClients) {
                        if (username.equals(key)) {
                            continue;
                        }
                        //拼接每个人
                        sb.append((i++)).append(".").append(key).append("\n");
                    }
                    bean.setContent(sb.toString());
                    hm.get(username).writeObject(bean);
                } else if (msgType == MsgType.EXIT) {
                    //下线:服务端通知其他人
                    Set<String> keySet = hm.keySet();
                    for (String key : keySet) {
                        //排除自己,不要给自己发
                        if (username.equals(key)) {
                            continue;
                        }
                        hm.get(key).writeObject(bean);
                    }
                    hm.remove(username);
                    activeClients.remove(username);
                    break;
                } else if (msgType == MsgType.SWITCH) {
                    if (activeClients.contains(username)) {
                        activeClients.remove(username);
                        hm.get(username).writeObject(new ChatMsgBean(null, null, null, System.currentTimeMillis(), MsgType.SWITCH_HIDDEN));
                    } else {
                        activeClients.add(username);
                        hm.get(username).writeObject(new ChatMsgBean(null, null, null, System.currentTimeMillis(), MsgType.SWITCH_ACTIVE));
                    }
                } else if (msgType == MsgType.PASS_FILE) {
                    //处理客户端发来的文件
                    //1.从消息内容中,取出文件名和文件大小
                    if (activeClients.contains(reciver)) {
                        hm.get(reciver).writeObject(bean);
                    } else {
                        hm.get(username).writeObject(new ChatMsgBean(null, null, null, System.currentTimeMillis(), MsgType.CLIENT_INEXISTENCE));
                    }
                } else if (MsgType.ACCEPT == msgType) {
                    hm.get(username).writeObject(new ChatMsgBean(null, username, null, System.currentTimeMillis(), MsgType.ACCEPT));
                } else if (MsgType.REFUSE == msgType) {
                    hm.get(username).writeObject(new ChatMsgBean(null, username, null, System.currentTimeMillis(), MsgType.REFUSE));
                } else if (MsgType.QUERY_PUBLIC == msgType){    //查询公聊聊天记录
                    StringBuffer str = new StringBuffer();
                    String line = null;
                    br = new BufferedReader(new InputStreamReader(new FileInputStream("public.txt")));
                    while ((line=br.readLine()) != null) {
                        str.append(line).append("\n");
                    }
                    ChatMsgBean msgBean = new ChatMsgBean(null, null, str.toString(), System.currentTimeMillis(), MsgType.QUERY_PUBLIC);
                    hm.get(username).writeObject(msgBean);
                } else {    //查询私聊聊天记录
                    StringBuffer str = new StringBuffer();
                    String line = null;
                    br = new BufferedReader(new InputStreamReader(new FileInputStream("private.txt")));
                    while ((line=br.readLine()) != null) {
                        str.append(line).append("\n");
                    }
                    ChatMsgBean msgBean = new ChatMsgBean(null, null, str.toString(), System.currentTimeMillis(), MsgType.QUERY_PRIVATE);
                    hm.get(username).writeObject(msgBean);
                }

            }
        } catch (SocketException e){
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                //1.关闭下线者的管道
                hm.get(username).close();
                String time = TimeUtil.changeMils2Date(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss:");
                System.out.println("\r\n" + time);
                System.out.println("客户端" + "[" + username + "]下线!");
                //2.从集合中移除这个人
                hm.remove(username);
            } catch (Exception e) {
            }
        }
    }
}

客户端

首先,我们要先连接服务器端,

在连接到服务器端之后,我们再进行身份验证(或 注册),

身份验证成功后,我们就可以开启一个线程来侦听服务器端发来的消息,

并且向服务器端发送各种操作的请求。

在本篇博文中,本人简化了上述的步骤,将 身份验证向服务器端发送操作请求的功能写在了主线程中:

package edu.youzg.chatroom.client.ui;

import edu.youzg.chatroom.bean.ChatMsgBean;
import edu.youzg.chatroom.config.MsgType;
import edu.youzg.chatroom.utils.InputAndOutputUtil;
import edu.youzg.chatroom.utils.InputUtil;
import edu.youzg.chatroom.utils.TimeUtil;

import javax.management.Query;
import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.Scanner;

public class MyClient {
    private static ObjectInputStream in;
    private static ObjectOutputStream out;
    private static Scanner sc;
    private static Socket sk;
    private static ClientChatThread th;

    public static void main(String[] args) {
        //TCP协议的Socket
        try {
            sk = new Socket("localhost", 6666);
            //1.包装通道中的字节流
            in = new ObjectInputStream(sk.getInputStream());
            out = new ObjectOutputStream(sk.getOutputStream());

            sc = new Scanner(System.in);
            beforeChat();
            //2.开启子线程读取服务端转发回来的消息
            th = new ClientChatThread(in);
            th.start();

            //3.提供功能菜单选项
            boolean flag=true;
            while (flag) {
                System.out.println("\r\n请选择:1.私聊 2.公聊 3.在线列表 4.下线 5.发送文件 6.隐身/上线 7.查询聊天记录");
                int num = InputUtil.inputNeededIntType(1, 7);
                switch (num) {
                    case 1:
                        //私聊
                        privateTalk();
                        break;
                    case 2:
                        //公聊
                        publicTalk();
                        break;
                    case 3:
                        //在线列表
                        getOnLineList();
                        break;
                    case 4:
                        //下线的逻辑
                        //客户端:1.你给服务端发下线指令,2.关闭客户端的Socket 3.关闭客户端读取消息的线程
                        //服务端:0.通知其他人,谁谁下线了。1.关闭下线的这个人的Socket  2.把下线的这个人名字从集合中移除掉
                        exitTalk();
                        flag = false; //修改标记,结束死循环
                        break;
                    case 5:
                        //发送文件
                        sendFile();
                        break;
                    case 6:
                        switchState();
                        break;
                    case 7:
                        queryChatRecords();
                        break;
                }
            }
        }catch (SocketException e){
            //客户端下线,有能会出现SocketException这个异常,我们在这里做个空处理
        }catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                //释放资源
                th.stop(); //强制关闭掉客户端读取消息线程
                sk.close();//关闭客户端的socket
            } catch (Exception e) {
            }
        }

    }

    private static void queryChatRecords() throws IOException {
        System.out.println("---请选择查询的类别---");
        System.out.println("1.公聊内容 2.私聊内容");
        int i = InputUtil.inputNeededIntType(1, 2);
        MsgType type = null;
        String content = null;
        if (1 == i) {
            type = MsgType.QUERY_PUBLIC;
        } else {
            content = sc.nextLine();
            type = MsgType.QUERY_PRIVATE;
        }

        ChatMsgBean chatMsgBean = new ChatMsgBean(null, null, content, 0, type);
        out.writeObject(chatMsgBean);
    }

    private static void switchState() throws IOException {
        ChatMsgBean chatMsgBean = new ChatMsgBean(null, null, null, 0, MsgType.SWITCH);
        out.writeObject(chatMsgBean);
    }

    private static void beforeChat() throws IOException, ClassNotFoundException {
        //1.注册用户名
        boolean flag = true;
        while (flag) {
            System.out.println(TimeUtil.changeMils2Date(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss") + ":");
            System.out.println("---------------------------欢迎使用右转聊天室---------------------------");
            System.out.println("-------------------------------请输入命令-------------------------------");
            System.out.println("                     1.注册                  2.登录                     ");
            int choose = InputUtil.inputNeededIntType(1, 2);
            String username = InputUtil.inputUserName();  //对用户进行正则校验
            String password = InputUtil.inputUserPassword();
            if (choose == 1) {
                dealRegister(username, password);
            } else {
                flag = dealLogin(username, password);
            }
        }
    }

    private static boolean dealLogin(String username, String password) throws IOException, ClassNotFoundException {
        //创建对象
        ChatMsgBean chatMsgBean = new ChatMsgBean(null, username, password, 0, MsgType.LOGIN);
        out.writeObject(chatMsgBean);
        //读取服务端,对这个用户名的是否注册成功的反馈
        //读取服务器返回的对象
        ChatMsgBean bean = (ChatMsgBean) in.readObject();
        MsgType fk = bean.getMsgType();
        if (fk.equals(MsgType.LOGIN_OK)) {
            System.out.println("\r\n" + TimeUtil.changeMils2Date(bean.getTime(), "yyyy-MM-dd HH:mm:ss") + ":");
            System.out.println("登陆成功!");
            return false;
        } else if (fk.equals(MsgType.USER_CONTAINED)) {
            System.out.println("\r\n" + TimeUtil.changeMils2Date(bean.getTime(), "yyyy-MM-dd HH:mm:ss") + ":");
            System.out.println("该账号已登陆,请稍后重试!");
        } else {
            System.out.println("\r\n" + TimeUtil.changeMils2Date(bean.getTime(), "yyyy-MM-dd HH:mm:ss") + ":");
            System.out.println("用户信息错误,登录失败!");
        }
        return true;
    }

    private static void dealRegister(String username, String password) throws IOException, ClassNotFoundException {
        //创建对象
        ChatMsgBean chatMsgBean = new ChatMsgBean(null, username, password, 0, MsgType.REGISTER);
        out.writeObject(chatMsgBean);
        //读取服务端,对这个用户名的是否注册成功的反馈
        //读取服务器返回的对象
        ChatMsgBean bean = (ChatMsgBean) in.readObject();
        MsgType fk = bean.getMsgType();
        if (fk.equals(MsgType.REGISTER_SUCCESSFUL)) {
            System.out.println("\r\n" + TimeUtil.changeMils2Date(bean.getTime(), "yyyy-MM-dd HH:mm:ss") + ":");
            System.out.println("用户注册成功!");
        } else if (fk.equals(MsgType.ILLEAGAL_USER)) {
            System.out.println("\r\n" + TimeUtil.changeMils2Date(bean.getTime(), "yyyy-MM-dd HH:mm:ss") + ":");
            System.out.println("非法用户名,注册失败!");
        } else {
            System.out.println("\r\n" + TimeUtil.changeMils2Date(bean.getTime(), "yyyy-MM-dd HH:mm:ss") + ":");
            System.out.println("用户名已经存在,注册失败!");
        }
    }

    private static void sendFile() throws IOException {
        //发送文件
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入接收者");
        String reciver = sc.nextLine();
        System.out.println("请输入文件的路径");
        String filePath = sc.nextLine();
        //封装文件
        File file = new File(filePath);
        if(file.exists()){
            //获取文件的字节数组
            byte[] fileBytes = InputAndOutputUtil.readFile(filePath);
            ChatMsgBean bean = new ChatMsgBean(reciver, null, null, 0, MsgType.PASS_FILE, file.getName(), file.length(), fileBytes);
            out.writeObject(bean);
        }else{
            System.out.println("文件不存在,请检查文件路径");
        }
    }

    private static void exitTalk() throws IOException {
        //退出聊天室
        ChatMsgBean chatMsgBean = new ChatMsgBean(null, null, null, 0,MsgType.EXIT);
        out.writeObject(chatMsgBean);

    }

    private static void getOnLineList() throws IOException{
        //获取在线列表
        ChatMsgBean chatMsgBean = new ChatMsgBean(null, null,null, 0, MsgType.FRIEND_LIST);
        out.writeObject(chatMsgBean);

    }

    private static void publicTalk() throws IOException {
        while (true) {
            System.out.println("\r\n" + "(您当前模式-公聊模式)  接收者输入\"-q\"退出当前模式");
            Scanner sc = new Scanner(System.in);
            String msg = sc.nextLine();
            if ("-q".equals(msg)) {
                break;
            }
            ChatMsgBean chatMsgBean = new ChatMsgBean(null, null, msg, 0, MsgType.PUBLIC);
            out.writeObject(chatMsgBean);
        }
    }

    private static void privateTalk() throws IOException {
        while (true) {
            Scanner sc = new Scanner(System.in);
            System.out.println("\r\n" + "(您当前模式-私聊模式)  接收者输入\"-q\"退出当前模式");
            System.out.println("请输入接收者姓名:");
            String reciver = sc.nextLine();
            if ("-q".equals(reciver)) {
                break;
            }
            System.out.println("请输入消息内容:");
            String msg = sc.nextLine();
            //把要发的数据封装进对象,发给服务器
            ChatMsgBean chatMsgBean = new ChatMsgBean(reciver, null, msg, 0, MsgType.PRIVATE);
            out.writeObject(chatMsgBean);
        }
    }

}

那么,至于侦听服务器端发来的信息,就交由身份验证成功后,开启的ClientChatThread类来处理:

package edu.youzg.chatroom.client.ui;

import edu.youzg.chatroom.bean.ChatMsgBean;
import edu.youzg.chatroom.config.MsgType;
import edu.youzg.chatroom.utils.InputAndOutputUtil;
import edu.youzg.chatroom.utils.InputUtil;
import edu.youzg.chatroom.utils.TimeUtil;

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

//客户端的子线程用来读取,服务端转发过来的消息
public class ClientChatThread extends Thread {
    ObjectInputStream in;

    public ClientChatThread(ObjectInputStream in) {
        this.in = in;
    }

    @Override
    public void run() {
        try {
            while (true) {
                //读取服务端,转发回来的对象
                ChatMsgBean bean = (ChatMsgBean) in.readObject();
                String sender = bean.getSender();
                String msgContent = bean.getContent();
                MsgType msgType = bean.getMsgType();
                long timeLong = bean.getTime();
                String dateStr = TimeUtil.changeMils2Date(timeLong, "yyyy-MM-dd HH:mm:ss");
                //根据消息类型,判断展示
                if (msgType == MsgType.PRIVATE) {
                    System.out.println("\r\n" + dateStr);
                    System.out.println("好友[" + sender + "]对你说:" + msgContent);
                } else if (msgType == MsgType.CLIENT_INEXISTENCE) {
                    System.out.println("\r\n" + dateStr);
                    System.out.println("好友[" + bean.getReciver() + "]不存在 或 未上线!");
                } else if (msgType == MsgType.ONLINE) {
                    System.out.println("\r\n" + dateStr);
                    System.out.println("您的好友[" + sender + "]已上线!");
                } else if (msgType == MsgType.PUBLIC) {
                    System.out.println("\r\n" + dateStr);
                    System.out.println("好友[" + sender + "]对大家说:" + msgContent);
                } else if (msgType == MsgType.FRIEND_LIST) {
                    System.out.println(dateStr + "-当前在线的人:");
                    System.out.println(msgContent);
                } else if (msgType == MsgType.EXIT) {
                    System.out.println("\r\n" + dateStr);
                    System.out.println("好友[" + sender + "]已下线!");
                } else if (msgType == MsgType.QUERY_PUBLIC || msgType == MsgType.QUERY_PRIVATE) {
                    System.out.println("\n当前时间为:" + dateStr);
                    System.out.println(msgContent);
                } else if (msgType == MsgType.PASS_FILE) {
                    //1.从消息内容中,取出文件名和文件大小
                    System.out.println("\r\n" + dateStr);
                    System.out.println("好友[" + sender + "]给你发来一个文件:" + bean.getFileName() + " 文件大小;" + (bean.getFileLength() / 1024 / 1024.0) + " MB");
                    String choose = InputUtil.inputChoice();
                    if ("y".equals(choose)) {
                        byte[] fileBytes = bean.getFileData();
                        //此处本人设置默认保存在D盘中
                        boolean b = InputAndOutputUtil.writeFile("D:\\" + bean.getFileName(), fileBytes);
                        if (b) {
                            System.out.println("文件保存成功在" + "D:\\" + bean.getFileName());
                        } else {
                            System.out.println("文件保存失败");
                        }
                    } else {
                        byte[] fileBytes = bean.getFileData();
                    }
                } else if (MsgType.ACCEPT == msgType) {
                    System.out.println(TimeUtil.changeMils2Date(timeLong, "yyyy-MM-dd HH:mm:ss :"));
                    System.out.println("好友[" + sender +"]接收文件成功!");
                } else if (MsgType.REFUSE == msgType) {
                    System.out.println("好友[" + sender +"]拒绝接收该文件!");
                } else {
                    String status = null;
                    if (msgType == MsgType.SWITCH_ACTIVE) {
                        status = "在线";
                    } else {
                        status = "隐身";
                    }
                    System.out.println("\n" + TimeUtil.changeMils2Date(bean.getTime(), "yyyy-MM-dd HH:mm:ss"));
                    System.out.println("已切换至" + status + "状态");
                }
            }
        } catch (SocketException e) {
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}

那么,在这里,本人《Java SE》专栏的所有知识点,就讲解完成了!

希望看到这里的同学,能够记得本人在总集篇说过的一句话:

Java是一门面向对象的语言

相信学到这里的同学,大概能明白为何 本人特别强调了这个点了

😀😀😀

那么,让我们《Web》专栏右转见喽!(ง •_•)ง

posted @ 2020-03-05 22:12  在下右转,有何贵干  阅读(377)  评论(0编辑  收藏  举报