1、需求分析
2、代码实现
2.1、Client和Server端共有类
1)cn.com.agree.qqcommon.Message
package cn.com.agree.qqcommon;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
@Slf4j
@Data
public class Message implements Serializable {
public static final long serialVersionUID = 1L;
private String sender;//发送者
private String getter;//接受者
private String content;//消息内容
private String sendTime;//发送时间
private String msgType;//消息类型(可以在接口定义消息类型)
}
2)cn.com.agree.qqcommon.User
package cn.com.agree.qqcommon;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
@Slf4j
@Data
public class User implements Serializable {
public static final long serialVersionUID = 1L;
private String userId;//用户id代表一个用户
private String passwd;//登陆密码
}
3)cn.com.agree.qqcommon.MessageType
package cn.com.agree.qqcommon;
public interface MessageType {
//消息类型
//1、在消息类型中定义一些常量
//2、不同的常量值代表不同的消息类型
String MESSAGE_LOG_SUCCESS = "1";//表示登陆成功
String MESSAGE_LOG_FAIL = "2";//表示登陆失败
}
2.2 QQClient端:
1)界面QQView
package cn.com.agree.qqclient.QQView;
import cn.com.agree.qqclient.service.UserClientService;
import cn.com.agree.qqcommon.User;
import cn.com.agree.util.Utility;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class QQView {
public static void main(String[] args) {
new QQView().mainMenu();
log.debug("退出系统!!!");
}
private final UserClientService userClientService = new UserClientService();
private void mainMenu() {
String key = "";
boolean loop = true;//控制程序是显示主菜单
while (loop) {
System.out.println("==========欢迎登陆网络通讯系统============");
System.out.println("======\t\t 1 登陆系统\t\t==========");
System.out.println("======\t\t 9 退出系统\t\t==========");
log.debug("请输入你的选择:");
key = Utility.readString(1);
switch (key) {
case "1":
log.debug("请输入用户名:");
String userId = Utility.readString(300);
log.debug("请输入密 码:");
String passwd = Utility.readString(1234);
//todo 此处去服务端进行用户名密码校验
User user = userClientService.checkUser(userId, passwd);
if (user != null) {
System.out.println("==========欢迎" + userId + "用户=============");
while (loop) {
System.out.println("==========网络通讯系统二级菜单(用户 " + userId + ")============");
System.out.println("=======\t\t 1 显示在线用户列表================");
System.out.println("=======\t\t 2 群发消息\t\t================");
System.out.println("=======\t\t 3 私聊消息\t\t================");
System.out.println("=======\t\t 4 发送文件\t\t================");
System.out.println("=======\t\t 9 退出系统\t\t================");
log.debug("请输入你的选择:");
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":
loop = false;
break;
default:
log.debug("输入有误,请重新输入!");
break;
}
}
} else {
log.debug("=========用户名密码错误=============");
log.debug("==========登陆失败=============");
}
break;
case "9":
loop = false;
break;
default:
log.debug("输入有误,请重新输入!");
break;
}
}
}
}
2)工具类cn.com.agree.util.Utility
package cn.com.agree.util;
import lombok.extern.slf4j.Slf4j;
import java.util.Scanner;
/**
* @author andongdong
* @version 1.0
* @ClassName Utility
* @Description 工具类
* @date 2024/1/5 10:56 上午
**/
@Slf4j
public class Utility {
public static Scanner scanner;
static {
scanner = new Scanner(System.in);
}
public Utility() {
}
public static char readMenuSelection() {
while (true) {
String str = readKeyBoard(1, false);
char c = str.charAt(0);
if (c == '1' || c == '2' || c == '3' || c == '4' || c == '5') {
return c;
}
log.debug("选择错误,请重新输入:");
}
}
public static char readChar() {
String str = readKeyBoard(1, false);
return str.charAt(0);
}
public static char readChar(char defaultValue) {
String str = readKeyBoard(1, true);
return str.length() == 0 ? defaultValue : str.charAt(0);
}
public static int readInt() {
while (true) {
String str = readKeyBoard(2, false);
try {
int n = Integer.parseInt(str);
return n;
} catch (NumberFormatException var3) {
log.debug("数字输入错误,请重新输入:");
}
}
}
public static int readInt(int defaultValue) {
while (true) {
String str = readKeyBoard(2, true);
if (str.equals("")) {
return defaultValue;
}
try {
int n = Integer.parseInt(str);
return n;
} catch (NumberFormatException var3) {
log.debug("数字输入错误,请重新输入:");
}
}
}
public static String readString(int limit) {
return readKeyBoard(limit, false);
}
public static String readString(int limit, String defaultValue) {
String str = readKeyBoard(limit, true);
return str.equals("") ? defaultValue : str;
}
public static char readConfirmSelection() {
while (true) {
String str = readKeyBoard(1, false).toUpperCase();
char c = str.charAt(0);
if (c == 'Y' || c == 'N') {
return c;
}
log.debug("选择错误,请重新输入: ");
}
}
private static String readKeyBoard(int limit, boolean blankReturn) {
String line = "";
while (scanner.hasNextLine()) {
line = scanner.nextLine();
if (line.length() == 0) {
if (blankReturn) {
return line;
}
} else {
if (line.length() >= 1 && line.length() <= limit) {
break;
}
log.debug("输入长度(不大于" + limit + ")" + "错误,请重新输入:");
}
}
return line;
}
}
3)cn.com.agree.qqclient.service.UserClientService
package cn.com.agree.util;
import lombok.extern.slf4j.Slf4j;
import java.util.Scanner;
/**
* @author
* @version 1.0
* @ClassName UserClientService
* @Description TODO 类描述
* @date 2024/1/5 4:18 下午
**/
@Slf4j
public class Utility {
public static Scanner scanner;
static {
scanner = new Scanner(System.in);
}
public Utility() {
}
public static char readMenuSelection() {
while (true) {
String str = readKeyBoard(1, false);
char c = str.charAt(0);
if (c == '1' || c == '2' || c == '3' || c == '4' || c == '5') {
return c;
}
log.debug("选择错误,请重新输入:");
}
}
public static char readChar() {
String str = readKeyBoard(1, false);
return str.charAt(0);
}
public static char readChar(char defaultValue) {
String str = readKeyBoard(1, true);
return str.length() == 0 ? defaultValue : str.charAt(0);
}
public static int readInt() {
while (true) {
String str = readKeyBoard(2, false);
try {
int n = Integer.parseInt(str);
return n;
} catch (NumberFormatException var3) {
log.debug("数字输入错误,请重新输入:");
}
}
}
public static int readInt(int defaultValue) {
while (true) {
String str = readKeyBoard(2, true);
if (str.equals("")) {
return defaultValue;
}
try {
int n = Integer.parseInt(str);
return n;
} catch (NumberFormatException var3) {
log.debug("数字输入错误,请重新输入:");
}
}
}
public static String readString(int limit) {
return readKeyBoard(limit, false);
}
public static String readString(int limit, String defaultValue) {
String str = readKeyBoard(limit, true);
return str.equals("") ? defaultValue : str;
}
public static char readConfirmSelection() {
while (true) {
String str = readKeyBoard(1, false).toUpperCase();
char c = str.charAt(0);
if (c == 'Y' || c == 'N') {
return c;
}
log.debug("选择错误,请重新输入: ");
}
}
private static String readKeyBoard(int limit, boolean blankReturn) {
String line = "";
while (scanner.hasNextLine()) {
line = scanner.nextLine();
if (line.length() == 0) {
if (blankReturn) {
return line;
}
} else {
if (line.length() >= 1 && line.length() <= limit) {
break;
}
log.debug("输入长度(不大于" + limit + ")" + "错误,请重新输入:");
}
}
return line;
}
}
4)cn.com.agree.qqclient.service.ClientConnectServer
package cn.com.agree.qqclient.service;
import cn.com.agree.qqcommon.Message;
import lombok.extern.slf4j.Slf4j;
import java.io.ObjectInputStream;
import java.net.Socket;
/**
* @author
* @version 1.0
* @ClassName ClientConnectServer
* @Description 为了便于管理,将客户端的thread根据UserID存入到一个集合中
* @date 2024/1/5 3:54 下午
**/
@Slf4j
public class ClientConnectServer implements Runnable {
//该线程需要持有socket
private Socket socket;
public Socket getSocket() {
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
public ClientConnectServer(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//因为Thread需要在后台和服务器进行通信,因此我们用while循环
while (true) {
try {
log.debug("客户端线程和服务端正在通信,读取从服务端发来的消息...");
// 设置Socket的超时时间为5000毫秒(5秒)
// socket.setSoTimeout(5000);
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();//如果服务器端没有消息发过来这个线程一直会阻塞在这里
//todo 后续对读取到的message进行处理
log.debug("message:{}",message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
5)cn.com.agree.qqclient.service.ManageClientConnectServers
package cn.com.agree.qqclient.service;
import cn.com.agree.qqcommon.Message;
import lombok.extern.slf4j.Slf4j;
import java.io.ObjectInputStream;
import java.net.Socket;
/**
* @author
* @version 1.0
* @ClassName ClientConnectServer
* @Description 为了便于管理,将客户端的thread根据UserID存入到一个集合中
* @date 2024/1/5 3:54 下午
**/
@Slf4j
public class ClientConnectServer implements Runnable {
//该线程需要持有socket
private Socket socket;
public Socket getSocket() {
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
public ClientConnectServer(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//因为Thread需要在后台和服务器进行通信,因此我们用while循环
while (true) {
try {
log.debug("客户端线程和服务端正在通信,读取从服务端发来的消息...");
// 设置Socket的超时时间为5000毫秒(5秒)
// socket.setSoTimeout(5000);
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();//如果服务器端没有消息发过来这个线程一直会阻塞在这里
//todo 后续对读取到的message进行处理
log.debug("message:{}",message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
2.3、QQServer端:
1)cn.com.agree.qqserver.service.qqServer
package cn.com.agree.qqserver.service;
import cn.com.agree.qqcommon.Message;
import cn.com.agree.qqcommon.MessageType;
import cn.com.agree.qqcommon.User;
import lombok.extern.slf4j.Slf4j;
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;
/**
* @author
* @version 1.0
* @ClassName qqServer
* @Description 服务器端编码
* @date 2024/1/5 5:24 下午
**/
@Slf4j
public class qqServer {
/*
* 用hashmap模拟数据库
*/
private static ConcurrentHashMap<String,User> validUsers = new ConcurrentHashMap<>();
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"));
}
private ServerSocket serverSocket = null;
public qqServer() {
Socket socket = null;
try {
log.debug("服务端在9999端口监听,等待客户端连接...");
serverSocket = new ServerSocket(9999);
//因为服务端会监听来自不同线程到socket,所以它要保持一直在监听到状态不能只监听到一个就退出因此用循环
while (true) {
socket = serverSocket.accept();
//获取来自客户端发送过来到用户对象
log.debug("socket Client连接成功");
ObjectInputStream bis = new ObjectInputStream(socket.getInputStream());
User user = (User) bis.readObject();
//获取输出流对象写入返回给客户端的消息
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
log.debug("user:{}",user);
Message message = new Message();
//TODO 校验用户信息(数据库层面校验这里先写死判断后面改进)
if (checkUser(user.getUserId(), user.getPasswd())) {//校验成功
message.setMsgType(MessageType.MESSAGE_LOG_SUCCESS);
oos.writeObject(message);
//创建一个server连接client的线程去完成通信->ServerConnectClient
ServerConnectClient serverConnectClient = new ServerConnectClient(socket, user.getUserId());
new Thread(serverConnectClient).start();
//使用集合类管理服务端线程
ManageServerConnectClient.addServerConnectClient(user.getUserId(), serverConnectClient);
} else {
//校验失败
log.debug("userId为"+user.getUserId()+"登陆失败");
message.setMsgType(MessageType.MESSAGE_LOG_FAIL);
oos.writeObject(message);
socket.close();
}
}
} catch (Exception e) {
e.getStackTrace();
} finally {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private boolean checkUser(String userId,String passwd){
User user = validUsers.get(userId);
if(user == null){//用户不存在
return false;
}
if(!passwd.equals(user.getPasswd())){//密码错误
return false;
}
return true;
}
}
2)cn.com.agree.qqserver.service.ServerConnectClient
package cn.com.agree.qqserver.service;
import cn.com.agree.qqcommon.Message;
import cn.com.agree.qqcommon.User;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
/**
* @author
* @version 1.0
* @ClassName ServerConnectClient
* @Description TODO 类描述
* @date 2024/1/5 5:44 下午
**/
@Slf4j
public class ServerConnectClient implements Runnable {
private Socket socket;
private String userId;
public ServerConnectClient(Socket socket, String userId) {
this.socket = socket;
this.userId = userId;
}
public void run() {
while (true){
try {
log.debug("服务端和客户端"+userId+"正在通信通信,读取数据...");
// 设置Socket的超时时间为5000毫秒(5秒)
// socket.setSoTimeout(5000);
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();
//TODO 后续处理从客户端获取的消息
log.debug("message:{}",message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
3)cn.com.agree.qqclient.service.ClientConnectServer
package cn.com.agree.qqserver.service;
import java.util.HashMap;
import java.util.Map;
/**
* @author
* @version 1.0
* @ClassName ManageServerConnectClient
* @Description TODO 类描述
* @date 2024/1/5 5:50 下午
**/
public class ManageServerConnectClient {
private static Map<String,ServerConnectClient> hm = new HashMap();
public static void addServerConnectClient(String userId,ServerConnectClient serverConnectClient){
hm.put(userId,serverConnectClient);
}
public static ServerConnectClient getServerConnectClient(String userId) {
return hm.get(userId);
}
}
4)启动类
package cn.com.agree.qqserver.service; /**
* @author
* @version 1.0
* @ClassName qqFrame
* @Description TODO 类描述
* @date 2024/1/5 6:06 下午
**/
public class qqFrame {
public static void main(String[] args) {
new qqServer();
}
}
3、运行效果
1)校验错误的用户名密码
2)校验正确的用户名密码(起两个客户端一个服务端)