二、具体功能实现
2.1 用户登录功能
用户的登录功能的流程:
- 用户进入系统界面,选择登录
- 输入登录信息之后,客户端与服务端建立连接,把信息发送给服务端
- 服务端接收信息,在数据库中进行校验,作出判断
- 服务端将判断返回客户端
- 客户端接收信息后,进行下一步操作(成功则进入二级菜单,失败则请求用户重新输入)
共通
Message.java (消息类)
package com.ming.qqcommon;
import java.io.Serializable;
/**
* @Author: mei_ming
* @DateTime: 2022/10/2 18:23
* @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; //消息类型[可以在接口定义消息类型]
public Message() {
}
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 String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getGetter() {
return getter;
}
public void setGetter(String getter) {
this.getter = getter;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getSendTime() {
return sendTime;
}
public void setSendTime(String sendTime) {
this.sendTime = sendTime;
}
public String getMesType() {
return mesType;
}
public void setMesType(String mesType) {
this.mesType = mesType;
}
}
MessageType.java (消息类型类)
package com.ming.qqcommon;
/**
* @Author: mei_ming
* @DateTime: 2022/10/2 18:31
* @Description: 表示消息类型
*/
public interface MessageType {
String MESSAGE_LOGIN_SUCCEED = "1"; // 登录成功
String MESSAGE_LOGIN_FAIL = "2"; // 登录失败
}
User.java (用户类)
package com.ming.qqcommon;
import java.io.Serializable;
/**
* @Author: mei_ming
* @DateTime: 2022/10/2 18:23
* @Description: 表示一个用户信息
*/
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String userId; //用户Id/用户名
private String passwd; //用户密码
public User(String userId, String passwd) {
this.userId = userId;
this.passwd = passwd;
}
public User() {
}
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;
}
}
客户端
QQView.java (用户主界面)
package com.ming.qqclient.view;
import com.ming.qqclient.service.UserClientService;
import com.ming.qqclient.utils.Utility;
/**
* @Author: mei_ming
* @DateTime: 2022/10/2 19:22
* @Description: 用户主界面
*/
public class QQView {
private boolean loop = true; //控制是否显示菜单
private String key= ""; // 接收用户的键盘输入
private UserClientService userClientService=new UserClientService();
public static void main(String[] args) {
new QQView().mainMenu();
System.out.println("客户端退出");
}
//显示主菜单
private void mainMenu(){
while(loop){
System.out.println("========欢迎登录网络通信系统========");
System.out.println("\t\t 1 登录系统");
System.out.println("\t\t 9 退出系统");
System.out.print("请输入你的选择:");
key= Utility.readString(1);
// 根据用户的输入,来处理不同的逻辑
switch (key){
case "1":
System.out.print("请输入用户号: ");
String userId = Utility.readString(50);
System.out.print("请输入密 码: ");
String pwd = Utility.readString(50);
//需要到服务端去验证该用户是否合法
//调用UserClientService类的服务
boolean isLogin = userClientService.checkUser(userId, pwd);
if (isLogin){
System.out.println("========欢迎(用户 "+userId+" 登录成功)========");
//进入二级菜单
while(loop){
System.out.println("\n========网络通信系统二级菜单(用户 "+userId+ " )========");
System.out.println("\t\t 1 显示在线用户列表");
System.out.println("\t\t 2 群发消息");
System.out.println("\t\t 3 私聊消息");
System.out.println("\t\t 4 发送文件");
System.out.println("\t\t 9 退出系统");
System.out.print("请输入你的选择:");
key = Utility.readString(1);
switch (key){
case "1":
System.out.println("显示在线用户列表");
break;
case "2":
System.out.println("群发消息");
break;
case "3":
System.out.println("私聊消息");
break;
case "4":
System.out.println("发送文件");
break;
case "9":
System.out.println("退出系统");
loop=false;
break;
}
}
}else{ //登录服务器失败
System.out.println("登录服务器失败");
}
break;
case "9":
loop=false;
break;
}
}
}
}
Utility.java (系统写入工具类)
package com.ming.qqclient.utils;
/**
* 工具类的作用:
* 处理各种情况的用户输入,并且能够按照程序员的需求,得到用户的控制台输入。
*/
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(10, false);//一个整数,长度<=10位
try {
n = Integer.parseInt(str);//将字符串转换成整数
break;
} catch (NumberFormatException e) {
System.out.print("数字输入错误,请重新输入:");
}
}
return n;
}
/**
* 功能:读取键盘输入的 整数或默认值,如果直接回车,则返回默认值,否则返回输入的整数
* @param defaultValue 指定的默认值
* @return 整数或默认值
*/
public static int readInt(int defaultValue) {
int n;
for (; ; ) {
String str = readKeyBoard(10, true);
if (str.equals("")) {
return defaultValue;
}
//异常处理...
try {
n = Integer.parseInt(str);
break;
} catch (NumberFormatException e) {
System.out.print("数字输入错误,请重新输入:");
}
}
return n;
}
/**
* 功能:读取键盘输入的指定长度的字符串
* @param limit 限制的长度
* @return 指定长度的字符串
*/
public static String readString(int limit) {
return readKeyBoard(limit, false);
}
/**
* 功能:读取键盘输入的指定长度的字符串或默认值,如果直接回车,返回默认值,否则返回字符串
* @param limit 限制的长度
* @param defaultValue 指定的默认值
* @return 指定长度的字符串
*/
public static String readString(int limit, String defaultValue) {
String str = readKeyBoard(limit, true);
return str.equals("") ? defaultValue : str;
}
/**
* 功能:读取键盘输入的确认选项,Y或N
* 将小的功能,封装到一个方法中.
* @return Y或N
*/
public static char readConfirmSelection() {
System.out.println("请输入你的选择(Y/N): 请小心选择");
char c;
for (; ; ) {//无限循环
//在这里,将接受到字符,转成了大写字母
//y => Y n=>N
String str = readKeyBoard(1, false).toUpperCase();
c = str.charAt(0);
if (c == 'Y' || c == 'N') {
break;
} else {
System.out.print("选择错误,请重新输入:");
}
}
return c;
}
/**
* 功能: 读取一个字符串
* @param limit 读取的长度
* @param blankReturn 如果为true ,表示 可以读空字符串。
* 如果为false表示 不能读空字符串。
*
* 如果输入为空,或者输入大于limit的长度,就会提示重新输入。
* @return
*/
private static String readKeyBoard(int limit, boolean blankReturn) {
//定义了字符串
String line = "";
//scanner.hasNextLine() 判断有没有下一行
while (scanner.hasNextLine()) {
line = scanner.nextLine();//读取这一行
//如果line.length=0, 即用户没有输入任何内容,直接回车
if (line.length() == 0) {
if (blankReturn) return line;//如果blankReturn=true,可以返回空串
else continue; //如果blankReturn=false,不接受空串,必须输入内容
}
//如果用户输入的内容大于了 limit,就提示重写输入
//如果用户如的内容 >0 <= limit ,我就接受
if (line.length() < 1 || line.length() > limit) {
System.out.print("输入长度(不能大于" + limit + ")错误,请重新输入:");
continue;
}
break;
}
return line;
}
}
UserClientService.java (业务类)
package com.ming.qqclient.service;
import com.ming.qqcommon.Message;
import com.ming.qqcommon.MessageType;
import com.ming.qqcommon.User;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
/**
* @Author: mei_ming
* @DateTime: 2022/10/2 20:13
* @Description: 用户登录、注册等功能的实现类
*/
public class UserClientService {
//因为我们可能在其他地方使用user信息,因此作为成员属性
private User u = new User();
//因为Socket在其他地方也有可能使用
private Socket socket;
//根据userId 和 pwd 到服务器验证该用户是否合法
public boolean checkUser(String userId,String pwd){
boolean b = false;
//创建User对象
u.setUserId(userId);
u.setPasswd(pwd);
try {
//连接到服务端,发送User对象 u
socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999);
//得到ObjectOutputStream对象
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(u);
//读取从服务器回复的Message对象
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message =(Message) ois.readObject();
if (message.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCEED)){ //登录成功
//创建一个和服务器端保持通信的线程 ClientConnectServerThread 类
//将socket放入线程中
ClientConnectServerThread connectServerThread = new ClientConnectServerThread(socket);
connectServerThread.start();
//将线程放在集合中
ManageClientConnectServerThread.addClientConnectServerThread(userId,connectServerThread);
b=true;
}else{ //登录失败
//关闭socket
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return b;
}
}
ManageClientConnectServerThread.java (线程管理类)
package com.ming.qqclient.service;
import java.util.HashMap;
/**
* @Author: mei_ming
* @DateTime: 2022/10/2 20:36
* @Description: 该类用于管理客户端连接到服务端的线程
*/
public class ManageClientConnectServerThread {
//我们把多个线程放入一个HashMap集合中, key 就是用户id,value 就是线程
private static HashMap<String, ClientConnectServerThread> hm = new HashMap<>();
//将某个线程加入到集合
public static void addClientConnectServerThread(String userId,ClientConnectServerThread thread){
hm.put(userId,thread);
}
//通过userId获取对应的线程
public static ClientConnectServerThread getClientConnectServerThread(String userId){
return hm.get(userId);
}
}
ClientConnectServerThread.java (线程生成类)
package com.ming.qqclient.service;
import com.ming.qqcommon.Message;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;
/**
* @Author: mei_ming
* @DateTime: 2022/10/2 20:25
* @Description: 线程生成类
*/
public class ClientConnectServerThread extends Thread{
//该线程需要持有Socket
private Socket socket;
//构造器可以接受一个Socket对象
public ClientConnectServerThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//因为Thread需要在后台和服务器通信,所以用while
while(true){
try {
System.out.println("客户端线程,等待读取从服务端发送的消息");
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
public Socket getSocket() {
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
}
服务端
QQFrame.java(启动类)
package com.ming.qqframe;
import com.ming.qqserver.service.QQServer;
/**
* @Author: mei_ming
* @DateTime: 2022/10/2 21:32
* @Description: 该类创建QQServer,启动后台的服务
*/
public class QQFrame {
public static void main(String[] args) {
new QQServer();
}
}
QQServer.java(服务端主类)
package com.ming.qqserver.service;
import com.ming.qqcommon.Message;
import com.ming.qqcommon.MessageType;
import com.ming.qqcommon.User;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
/**
* @Author: mei_ming
* @DateTime: 2022/10/2 20:52
* @Description: 这是服务器,在监听9999,等待客户端的连接,并保持通信
*/
public class QQServer {
private ServerSocket ss = null;
//创建一个集合,存放多个用户,如果是这些用户则登录通过
private static HashMap<String,User> validUsers = new HashMap<>();
static{ // 在静态代码块,初始化 validUsers
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"));
}
//验证用户是否有效的方法
private boolean checkUser(String userId,String passwd){
User user = validUsers.get(userId);
if (user== null){
return false;
}
if (!user.getPasswd().equals(passwd)){
return false;
}
return true;
}
public QQServer(){
try {
//注意:端口可以写在配置文件中
System.out.println("服务端在9999端口监听...");
ss = new ServerSocket(9999);
while(true){ // 当和某个客户端连接后,会继续监听
Socket socket = ss.accept();
ObjectInputStream ois =
new ObjectInputStream(socket.getInputStream());
// 得到socket关联的对象输出流
ObjectOutputStream oos =
new ObjectOutputStream(socket.getOutputStream());
User user = (User) ois.readObject();
//创建一个Message对象,准备回复客户端
Message message = new Message();
//验证用户,是否有效
boolean isExists = checkUser(user.getUserId(), user.getPasswd());
if (isExists) { //登录成功
message.setMesType(MessageType.MESSAGE_LOGIN_SUCCEED);
//将message对象回复到客户端
oos.writeObject(message);
//创建一个线程,和客户端保持通信,该线程需要持有socket对象 ServerConnectClientThread
ServerConnectClientThread connectClientThread = new ServerConnectClientThread(socket, user.getUserId());
connectClientThread.start();
//把该线程放入集合
ManageClientThreads.addServerConnectClientThread(user.getUserId(),connectClientThread);
}else{
System.out.println("用户 id="+user.getUserId()+" pwd=" + user.getPasswd()+"登录失败");
message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
oos.writeObject(message);
//关闭socket
socket.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
ServerConnectClientThread.java (线程生成类)
package com.ming.qqserver.service;
import com.ming.qqcommon.Message;
import com.sun.deploy.net.proxy.ProxyUnavailableException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;
/**
* @Author: mei_ming
* @DateTime: 2022/10/2 21:09
* @Description: 该类的一个对象和某个客户端保持通信
*/
public class ServerConnectClientThread extends Thread {
private Socket socket;
private String userId; //区分是哪一个客户端
public ServerConnectClientThread(Socket socket, String userId) {
this.socket = socket;
this.userId = userId;
}
@Override
public void run() { //这里线程处于run的状态,可以发送/接收消息
while(true){
try {
System.out.println("服务端和客户端"+userId+"保持通信,读取数据....");
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
ManageClientThreads.java(管理服务端线程类)
package com.ming.qqserver.service;
import java.util.HashMap;
/**
* @Author: mei_ming
* @DateTime: 2022/10/2 21:20
* @Description: 该类用于管理和客户端通信的线程
*/
public class ManageClientThreads {
private static HashMap<String,ServerConnectClientThread> hm=new HashMap<>();
//添加线程到hm中
public static void addServerConnectClientThread(String userId,ServerConnectClientThread serverConnectClientThread){
hm.put(userId,serverConnectClientThread);
}
//根据userId返回ServerConnectClientThread线程
public static ServerConnectClientThread getServerConnectClientThread(String userId){
return hm.get(userId);
}
}
运行效果
下一篇: 拉取在线用户功能