Java初学笔记30
[一] 项目开发流程的简介
(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;
}
}
}
}