由于篇幅过长,完整版代码分为上下两篇文章
本篇文章主要是展示服务端的所有代码😊
相比韩顺平老师的服务端代码,我新增了以下几个内容
- 对用户的登录进行了检查,判断是否重复登录或者出现账号不存在的问题。如果登陆失败会返回失败的消息类型给客户端。
- 对于用户发送的消息对象,如果对象不存在就会返回发送消息失败的消息类型给客户端。发送文件也加了这条限制。
- 在最后的离线发送消息扩展也已经完成,并且保证用户能够收到多条离线信息。
- 新增了注册账号的功能。
- 新增了退出账号的功能。
公共模块的代码在下篇文章中,由于代码量太多,所以本篇不过多赘述
下面开始展示我的代码模块已经各个模块介绍🚚
客户端代码图示🏡
客户端代码
公共部分在下篇文中中显示 -> common
—————
服务部分内容-> service
Service_User二级系统用户模块🍧
package com.all.qqclient.service;
import com.all.qqclient.utils.readOperate;
import com.all.qqcommon.Message;
import com.all.qqcommon.MessageType;
import com.all.qqcommon.User;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* Created with IntelliJ IDEA.
*
* @Author: 捶捶自己
* @version: 1.0
* @Date: 2022/07/04/15:00
* @Description: 用于二级用户系统模块服务
*/
public class Service_User {
// -- 这个user对象是从User中拉取过来的登录对象
User user = Service_System.loginUser;
// -- 获取在线用户列表
public void getOnlineFriendList() {
// -- 想要获取已经登陆到系统的用户信息
Message message = new Message();
message.setSender(user.getUserId());
message.setMessageType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
// -- 1. 先调用线程集合,然后通过用户Id获取到对应的线程
// -- 2. 再通过对应的线程获取到socket,利用socket获取到输出流
// -- 3. 将输出流赋给输出流对象,对消息进行输出
try {
ClientConnectServerThread clientConnectServerThread = ManageClientConnectServerThread.byUserIdGetThread(user.getUserId());
Socket socket = clientConnectServerThread.getSocket();
OutputStream outputStream = socket.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(outputStream);
oos.writeObject(message);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// -- 用户对系统进行的反馈
public void sendUserFeedback() {
System.out.print("♥♥请输入想要反馈的内容: ");
String feedBack = readOperate.readString(200);
// -- 想要获取已经登陆到系统的用户信息
Message message = new Message();
message.setContent(feedBack);
message.setSender(user.getUserId());
message.setMessageType(MessageType.MESSAGE_GET_FEED_BACK);
// -- 1. 先调用线程集合,然后通过用户Id获取到对应的线程
// -- 2. 再通过对应的线程获取到socket,利用socket获取到输出流
// -- 3. 将输出流赋给输出流对象,对消息进行输出
try {
ObjectOutputStream oos
= new ObjectOutputStream(ManageClientConnectServerThread.byUserIdGetThread(user.getUserId()).getSocket().getOutputStream());
oos.writeObject(message);
System.out.println("o(* ̄▽ ̄*)ブ已经向服务器反馈成功!请耐心等待!");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// -- 退出登录账号
public boolean outLookLoginAccount() {
boolean outLook = readOperate.readOutLookConfirm(false);
if (outLook) {
// -- 向服务器发送用户退出账号的消息
Message message = new Message();
message.setSender(user.getUserId());
message.setMessageType(MessageType.MESSAGE_LOGIN_EXIST);
// -- 1. 先调用线程集合,然后通过用户Id获取到对应的线程
// -- 2. 再通过对应的线程获取到socket,利用socket获取到输出流
// -- 3. 将输出流赋给输出流对象,对消息进行输出
try {
Socket socket = ManageClientConnectServerThread.byUserIdGetThread(user.getUserId()).getSocket();
ObjectOutputStream oos
= new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message);
Thread.sleep(50);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return outLook;
}
// -- 发送用户退出系统的消息
public boolean sendUserExitSystemMessage() {
boolean loop = readOperate.readExitConfirm(true);
if (!loop) {
// -- 向服务器发送用户退出系统的消息
Message message = new Message();
message.setSender(user.getUserId());
message.setMessageType(MessageType.MESSAGE_CLIENT_EXIT);
// -- 1. 先调用线程集合,然后通过用户Id获取到对应的线程
// -- 2. 再通过对应的线程获取到socket,利用socket获取到输出流
// -- 3. 将输出流赋给输出流对象,对消息进行输出
try {
ObjectOutputStream oos
= new ObjectOutputStream(ManageClientConnectServerThread.byUserIdGetThread(user.getUserId()).getSocket().getOutputStream());
oos.writeObject(message);
System.exit(0);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return loop;
}
}
Service_System一级系统模块🍧
package com.all.qqclient.service;
import com.all.qqclient.utils.readOperate;
import com.all.qqcommon.Message;
import com.all.qqcommon.MessageType;
import com.all.qqcommon.User;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
/**
* Created with IntelliJ IDEA.
*
* @Author: 捶捶自己
* @version: 1.0
* @Date: 2022/07/07/8:22
* @Description: 处理一级系统服务模块问题
*/
public class Service_System {
User user = new User();
static User loginUser = new User();
private Socket socket;
// -- 检查登陆账号的信息
public boolean checkLoginUserInfo(String userId, String password) {
boolean isSucceed = false;
user.setUserId(userId);
user.setPassword(password);
user.setRegister(false);
// -- 存储登录信息
loginUser.setUserId(userId);
loginUser.setPassword(password);
try {
socket = new Socket(InetAddress.getByName("127.0.0.1"), 4399);
// -- 向服务端发送用户信息
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(user);
// -- 接收服务端的信息
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();
// -- 判断是否登录成功
if (message.getMessageType().equals(MessageType.MESSAGE_RET_USER_LOGIN_STATE)) {
// -- 用户已经存在,无法继续登录
System.out.println("\n💔用户" + userId + "已经存在,无法重复登录");
// -- 如果登录失败,线程不会创建成功,因此要关闭socket, 避免资源浪费
oos.close();
ois.close();
socket.close();
} else if (message.getMessageType().equals(MessageType.MESSAGE_LOGIN_SUCCEED)) {
// -- 如果登录成功就启动客户端线程,等待消息的发送
ClientConnectServerThread clientConnectServerThread = new ClientConnectServerThread(socket);
clientConnectServerThread.start();
// -- 为了后方的扩展,可以将线程放入集合中管理
ManageClientConnectServerThread.addClientConnectServerThread(userId, clientConnectServerThread);
// -- 避免发生错误,将登录确认放在线程之后
isSucceed = true;
} else {
oos.close();
ois.close();
socket.close();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return isSucceed;
}
// -- 创建新的登录账号
public void registerUserAccount() {
int count = 3;
System.out.print("♥请输入您想要创建的用户名: ");
String inputUserId = readOperate.readString(10);
System.out.print("♥请输入您创建的用户名密码: ");
String inputPassword = readOperate.readString(200, "");
while (true) {
System.out.print("♥请再次输入您创建的用户名密码: ");
String confirmPassword = readOperate.readString(200, "");
if (confirmPassword.equals(inputPassword)) {
break;
}
count--;
if (count != 0) {
System.out.println("💔您输入的密码与上次不符, 请确认之后在进行输入哦!");
} else {
System.out.println("💔您已经连续输入失败三次,注册用户失败/(ㄒoㄒ)/~~");
return;
}
}
user.setUserId(inputUserId);
user.setPassword(inputPassword);
user.setRegister(true);
try {
socket = new Socket(InetAddress.getByName("127.0.0.1"), 4399);
// -- 向服务端发送用户信息
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(user);
// -- 接收服务端的信息
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();
if (message.getMessageType().equals(MessageType.MESSAGE_RET_REGISTER_SUCCEED)) {
System.out.println("♥用户创建成功!*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。\n" + "🖊新账号用户名为: "
+ message.getSender() + "\t新账号密码为: " + message.getContent()
+ "\n🖊新账号创建时间为: " + message.getSendTime()
+ "\n❀❀❀系统小助手提示您要请牢记用户名和密码哦!祝您每一天都开心愉快!\n");
oos.close();
ois.close();
socket.close();
return;
}
// -- 既然消息类型都加上去了,只用一个else也太可惜了[doge]!
if (message.getMessageType().equals(MessageType.MESSAGE_RET_REGISTER_FAIL)) {
System.out.println("💔用户创建失败!/(ㄒoㄒ)/~~\n" + "(;′⌒`)失败原因为: "
+ message.getContent() + "\n🖊新账号创建失败时间为: " + message.getSendTime()
+ "\n❀❀❀系统小助手提示您可以再次尝试创建哦!祝您每一天都开心愉快!\n");
oos.close();
ois.close();
socket.close();
return;
}
// TODO 等待新的系统功能 demo: 系统反馈功能
if (message.getMessageType().equals("")) {
System.out.println("等待扩展");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
MessageClientToServer客户端发送消息模块🍧
package com.all.qqclient.service;
import com.all.qqclient.utils.readOperate;
import com.all.qqcommon.Message;
import com.all.qqcommon.MessageType;
import java.io.IOException;
import java.io.ObjectOutputStream;
/**
* Created with IntelliJ IDEA.
*
* @Author: 捶捶自己
* @version: 1.0
* @Date: 2022/07/05/20:27
* @Description: 消息发送模块
*/
public class MessageClientToServer {
public void sendMessageToAll(String senderId) {
System.out.print("♥♥请输入您想要发送的消息: ");
String content = readOperate.readString(200);
// -- 对消息进行封装
Message message = new Message();
message.setMessageType(MessageType.MESSAGE_COMM_MES_TO_ALL);
message.setSender(senderId);
message.setContent(content);
message.setSendTime(message.getSendMessageNowTime());
// -- 将信息发送给服务器
try {
ObjectOutputStream oos
= new ObjectOutputStream(ManageClientConnectServerThread.byUserIdGetThread(senderId).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void sendMessageToOne(String senderId) {
System.out.print("♥♥请输入您想要发送消息的用户: ");
String getterId = readOperate.readString(10);
System.out.print("♥♥请输入您想要发送的消息: ");
String content = readOperate.readString(200);
// -- 对消息进行封装
Message message = new Message();
message.setMessageType(MessageType.MESSAGE_COMM_MES);
message.setSender(senderId);
message.setContent(content);
message.setGetter(getterId);
message.setSendTime(message.getSendMessageNowTime());
// -- 将信息发送给服务器
try {
ObjectOutputStream oos
= new ObjectOutputStream(ManageClientConnectServerThread.byUserIdGetThread(senderId).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
ManageClientConnectServerThread管理客户端账户开启的线程🍧
package com.all.qqclient.service;
import java.util.HashMap;
/**
* Created with IntelliJ IDEA.
*
* @Author: 捶捶自己
* @version: 1.0
* @Date: 2022/07/04/15:43
* @Description: 采用集合管理客户端连接到服务器的线程
*/
public class ManageClientConnectServerThread {
// -- 把多线程放入hashMap集合中 string 表示 id, value 表示线程
private static HashMap<String, ClientConnectServerThread> hm = new HashMap<>();
// -- 将线程加入到集合中去
public static void addClientConnectServerThread(String userId, ClientConnectServerThread clientConnectServerThread) {
hm.put(userId, clientConnectServerThread);
}
// -- 通过userId得到对应的线程
public static ClientConnectServerThread byUserIdGetThread(String userId) {
return hm.get(userId);
}
}
ClientTransmissionFileService客户端发送文件模块🍧
package com.all.qqclient.service;
import com.all.qqclient.utils.readOperate;
import com.all.qqcommon.Message;
import com.all.qqcommon.MessageType;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
/**
* Created with IntelliJ IDEA.
*
* @Author: 捶捶自己
* @version: 1.0
* @Date: 2022/07/06/15:40
* @Description: 客户端传输文件服务
*/
public class ClientTransmissionFileService {
public void sendFileToOne(String senderId) {
// -- 控制台输入文件地址
System.out.print("♥♥请输入您想要发送消息的用户: ");
String getterId = readOperate.readString(10);
System.out.print("♥♥请输入您想要发送的文件地址: ");
// -- 文件路径需要改为自己的实际路径
String fileFromPath = readOperate.readString(200, "C:\\Users\\18446\\Desktop\\beautiful.png");
System.out.print("♥♥请输入您想要发送到的目标文件地址: ");
// -- 文件路径需要改为自己的实际路径
String fileToPath = readOperate.readString(200, "C:\\Users\\18446\\Desktop\\Girl.png");
// -- 文件消息封装
Message message = new Message();
message.setMessageType(MessageType.MESSAGE_FILE_MES);
message.setSendTime(message.getSendMessageNowTime());
message.setFileFromPath(fileFromPath);
message.setFileToPath(fileToPath);
message.setSender(senderId);
message.setGetter(getterId);
// -- 将文件内容进行读取并转为字节流
FileInputStream fileInputStream = null;
byte[] fileBytes = new byte[(int) new File(fileFromPath).length()];
try {
fileInputStream = new FileInputStream(fileFromPath);
fileInputStream.read(fileBytes);
// -- 将文件内容转为字节流发送
message.setFileData(fileBytes);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// -- 提示信息
System.out.println("\n用户" + senderId + "给"
+ getterId + "发送了一个信息从" + fileFromPath + "到" + fileToPath + "路径中, 请查收");
// -- 发送信息报
try {
ObjectOutputStream oos
= new ObjectOutputStream(ManageClientConnectServerThread.byUserIdGetThread(senderId).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
ClientConnectServerThread用户登录线程开启模块🍧
package com.all.qqclient.service;
import com.all.qqcommon.Message;
import com.all.qqcommon.MessageType;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.net.Socket;
/**
* Created with IntelliJ IDEA.
*
* @Author: 捶捶自己
* @version: 1.0
* @Date: 2022/07/04/15:28
* @Description: 客户端连接线程创建
*/
public class ClientConnectServerThread extends Thread {
// -- 该线程必须持有socket
private Socket socket;
public ClientConnectServerThread(Socket socket) {
this.socket = socket;
}
// -- 为了更方便的获取socket
public Socket getSocket() {
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
boolean lock = true;
while (lock) {
try {
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();
// -- 通过判断客户端发来的信息,进行信息的分块处理
switch (message.getMessageType()) {
case MessageType.MESSAGE_RET_ONLINE_FRIEND:
// -- 通过将用户信息分割为几部分进行输出
String[] Info = message.getContent().split(" ");
System.out.println("\n=======★★当前在线的用户列表★★=======\n" +
"♥♥当前共有" + Info.length + "名用户同时在线");
for (int i = 0; i < Info.length; i++) {
System.out.println("♥♥用户" + (i < 9 ? ("0" + (i + 1)) : (i + 1)) + ": " + Info[i]);
}
System.out.print("\n♥♥请输入您的选择: ");
break;
case MessageType.MESSAGE_COMM_MES:
System.out.print("\n用户" + message.getSender() + "给你发来了一个消息🐟🐟🐟🐟\n"
+ "🆒消息内容为: " + message.getContent() + "\n🆒发送时间为: "
+ message.getSendTime() + "\n♥♥请输入你的选择: ");
break;
case MessageType.MESSAGE_COMM_MES_FAIL:
System.out.print("\n用户" + message.getSender() + "消息发送失败🐟🐟🐟🐟" + "\n🆒原因是: "
+ message.getContent() + "\n🆒消息发送失败时间为: " + message.getSendTime()
+ "\n♥♥请输入你的选择: ");
break;
case MessageType.MESSAGE_COMM_MES_TO_ALL:
System.out.print("\n用户" + message.getSender() + "给全体成员发来了一个消息🐟🐟🐟🐟\n"
+ "🆒消息内容为: " + message.getContent() + "\n🆒发送时间为: "
+ message.getSendTime() + "\n♥♥请输入你的选择: ");
break;
case MessageType.MESSAGE_FILE_MES:
System.out.print("\n用户" + message.getSender() + "给你发来了一个文件🐟🐟🐟🐟\n"
+ "📕文件存放路径为: " + message.getFileToPath() + "\n📕发送时间为: "
+ message.getSendTime());
// System.out.print("\n📕检查路径是否正确,如果正确可以直接回车,如果错误请进行填写: ");
// String getterPointerFilePath = new Scanner(System.in).next();
// if (getterPointerFilePath.equals("")) {
// getterPointerFilePath += message.getFileToPath();
// }
message.setFileToPath(message.getFileToPath());
// -- 将文件传入到指定路径中去
FileOutputStream fileOutputStream = new FileOutputStream(message.getFileToPath());
fileOutputStream.write(message.getFileData());
fileOutputStream.close();
System.out.print("\n♥♥请输入你的选择: ");
break;
case MessageType.MESSAGE_FILE_MES_FAIL:
System.out.print("\n用户" + message.getSender() + "文件发送失败🐟🐟🐟🐟" + "\n(#`O′)原因是: "
+ message.getContent() + "\n文件发送失败时间为: " + message.getSendTime()
+ "\n♥♥请输入你的选择: ");
break;
case MessageType.MESSAGE_LOGIN_EXIST:
System.out.println("\no(* ̄▽ ̄*)ブ用户" + message.getSender() + "已经退出登录");
ois.close();
socket.close();
lock = false;
break;
default:
System.out.println("其他消息暂时不处理");
break;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
主题显示部分内容-> view
view客户端主题显示区域🍧
package com.all.qqclient.view;
import com.all.qqclient.service.ClientTransmissionFileService;
import com.all.qqclient.service.MessageClientToServer;
import com.all.qqclient.service.Service_System;
import com.all.qqclient.service.Service_User;
import com.all.qqclient.utils.readOperate;
/**
* Created with IntelliJ IDEA.
*
* @Author: 捶捶自己
* @version: 1.0
* @Date: 2022/07/03/20:47
* @Description: QQ客户端界面
*/
@SuppressWarnings({"all"})
public class View {
private boolean loop = true;
private boolean outLook = false;
private Service_System service_system = new Service_System(); // -- 用于一级系统模块服务
private Service_User service_user = new Service_User();// -- 用于二级用户系统模块服务
private MessageClientToServer messageClientToServer = new MessageClientToServer();// -- 用于消息的发送
private ClientTransmissionFileService clientTransmissionFileService = new ClientTransmissionFileService();// -- 用于文件的传输
private void mainMenu() {
while (loop) {
System.out.println("==============★欢迎登录网络通讯系统★==============\n" +
"\t\t1. 登录系统♥\n" + "\t\t2. 注册账号♥\n" + "\t\t9. 退出系统♥");
System.out.print("👉请输入你的选择: ");
String key = readOperate.readString(1);
switch (key) {
case "1":
outLook = false;
System.out.print("♥请输入您的用户名: ");
String userId = readOperate.readString(10);
System.out.print("♥请输入您的密码: ");
String accountPassword = readOperate.readString(16);
if (service_system.checkLoginUserInfo(userId, accountPassword)) {
System.out.println("==============⭐⭐⭐欢迎 (用户" + userId + ") " + "登录成功⭐⭐⭐==============");
// -- 进入二级系统
while (!outLook) {
System.out.println("\n=======★★网络通讯系统二级菜单 (用户" + userId + ") ★★=======\n" +
"\t\t1. 显示用户在线列表♥♥\n" + "\t\t2. 群发消息♥♥\n" + "\t\t3. 私聊消息♥♥\n" +
"\t\t4. 发送文件♥♥\n" + "\t\t5. 系统反馈♥♥\n" + "\t\t8. 退出当前登陆账号♥♥\n" + "\t\t9. 退出系统♥♥");
System.out.print("♥♥请输入你的选择:");
key = readOperate.readString(1);
switch (key) {
case "1":
service_user.getOnlineFriendList();
break;
case "2":
messageClientToServer.sendMessageToAll(userId);
break;
case "3":
messageClientToServer.sendMessageToOne(userId);
break;
case "4":
clientTransmissionFileService.sendFileToOne(userId);
break;
case "5":
service_user.sendUserFeedback();
break;
case "8":
outLook = service_user.outLookLoginAccount();// -- true 表示退出
break;
case "9":
loop = service_user.sendUserExitSystemMessage();// -- false 表示退出
outLook = loop;
break;
}
}
} else {
System.out.println("💔登录失败,请再次尝试登录~~~\n");
}
break;
case "2":
service_system.registerUserAccount();
break;
case "9":
loop = readOperate.readExitConfirm(loop);
break;
}
}
}
public static void Run() {
new View().mainMenu();
}
}
客户端主程序入口-> main
Main客户端入口🍧
package com.all.qqclient.main;
import com.all.qqclient.view.View;
/**
* Created with IntelliJ IDEA.
*
* @Author: 捶捶自己
* @version: 1.0
* @Date: 2022/07/07/9:52
* @Description: 客户端主程序入口
*/
public class Main {
public static void main(String[] args) {
View.Run();
}
}
附上客户端也就是下篇的链接-> 点击跳转
小总结:添加了许多自己的元素,虽然很累,经常爆红,但是还是很开心能够完成,下面展示一下部分功能效果。
注册功能
登录功能
显示在线人数和在线列表以及离线私聊功能
服务器反馈功能
退出登录功能
退出系统功能
登录检测功能
群聊功能
服务端发送新闻以及关闭功能
发送文件功能