About ARTS - Complete one ARTS per week:
● Algorithm: Do at least one LeetCode algorithm per week
Review: Read and comment on at least one technical article in English
● Tips: Learn at least one technical trick
● Share: Share a technical article with opinions and thoughts
It is hoped that this event will gather a wave of people who love technology and continue the spirit of curiosity, exploration, practice and sharing.
one, QQClient
class Message
package com.study.qqcommon;
import java.io.Serializable;
/**
* 表示客户端和服务端通信的
*/
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 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;
}
}
class User
package com.study.qqcommon;
import java.io.Serializable;
public class User implements Serializable {
// private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 1L;
private String userID;
private String passwd;
public User() {}
public User(String userID, String passwd) {
this.userID = userID;
this.passwd = passwd;
}
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;
}
}
interface MessageType
package com.study.qqcommon;
/**
* 表示消息类型
*/
public interface MessageType {
String MESSAGE_LOGIN_SUCCEED = "1";
String MESSAGE_LOGIN_FIAL = "2";
}
class ClientConnectServerThread
package com.study.qqclient.service;
import com.study.qqcommon.Message;
import java.io.ObjectInputStream;
import java.net.Socket;
public class ClientConnectServerThread extends Thread {
//该线程需要持有Socket
private Socket socket;
public ClientConnectServerThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
while (true) {
System.out.println("客户端线程, 等待从读取从服务器端发送的消息");
try {
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// super.run();
}
public Socket getSocket() {
return socket;
}
}
class ManageClientConnectServerThread
package com.study.qqclient.service;
//管理客户端连接到服务器端的线程的类
import java.util.HashMap;
public class ManageClientConnectServerThread {
//我们把多个线程放入一个HashMap集合,key就是用户id,value就是线程
private static HashMap<String, ClientConnectServerThread> hm = new HashMap<>();
public static void addClientConnectServerThread(String userId, ClientConnectServerThread clientConnectServerThread) {
hm.put(userId, clientConnectServerThread);
}
public static ClientConnectServerThread getClientConnectServerThread(String userId) {
return hm.get(userId);
}
}
class UserClientService
package com.study.qqclient.service;
import com.study.qqcommon.Message;
import com.study.qqcommon.MessageType;
import com.study.qqcommon.User;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
/**
* 该类完成用户登录验证和用户注册等功能
*/
public class UserClientService {
private User u = new User();
private Socket socket;
public boolean checkUser(String userId, String pwd) {
boolean b= false;
u.setUserID(userId);
u.setPasswd(pwd);
//连接到服务器,发送u对象
try {
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(u);// 发送User对象
//读取从服务器回复的Message对象
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message ms = (Message) ois.readObject();
if (ms.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCEED)) {
b = true;
//创建一个和服务器端保持通信的线程 -> 创建一个类 ClientConnectServerThread
ClientConnectServerThread clientConnectServerThread =
new ClientConnectServerThread(socket);
clientConnectServerThread.start();
ManageClientConnectServerThread.addClientConnectServerThread(userId, clientConnectServerThread);
b = true;
} else {
//如果登录失败,不能启动和服务器通信的线程
socket.close();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return b;
}
}
class Utility
package com.study.qqclient.utils;
/**
工具类的作用:
处理各种情况的用户输入,并且能够按照程序员的需求,得到用户的控制台输入。
*/
import java.util.*;
/**
*/
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;
}
}
Two, QQServer
class QQFrame
package com.study.qqframe;
import com.study.qqserver.service.QQServer;
public class QQFrame {
public static void main(String[] args) {
new QQServer();
}
}
class ManageClientThreads
package com.study.qqserver.service;
//该类用于管理和客户端通信的线程
import java.util.HashMap;
public class ManageClientThreads {
private static HashMap<String, ServerConnectClientThread> hm = new HashMap<>();
//添加线程对象到 hm 集合
public static void addClientThread(String userId, ServerConnectClientThread serverConnectClientThread) {
hm.put(userId, serverConnectClientThread);
}
public static ServerConnectClientThread getServerConnectClientThread(String userId) {
return hm.get(userId);
}
}
class QQServer
package com.study.qqserver.service;
import com.study.qqcommon.Message;
import com.study.qqcommon.MessageType;
import com.study.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;
public class QQServer {
private ServerSocket ss = null;
//创建一个集合
private static HashMap<String, User> validUsers = new HashMap<>();
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 boolean checkUser(String userId, String passwd) {
User user = validUsers.get(userId);
if (user == null) { //userId不存在
return false;
}
if (user.getPasswd().equals(passwd)) { //密码错误
return false;
}
return true;
}
// public static void main(String[] args) {
// new QQServer();
// }
public QQServer() {
try {
System.out.println("服务端在9999端口监听...");
ss = new ServerSocket(9999);
while (true) {
Socket socket = ss.accept();
//得到socket关联的对象输入流
ObjectInputStream ois =
new ObjectInputStream(socket.getInputStream());
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
User u = (User) ois.readObject();
//创建一个Message对象,准备回复客户端
Message message = new Message();
//验证
// if (u.getUserID().equals("100") && u.getPasswd().equals("123456")) {
if (checkUser(u.getUserID(), u.getPasswd())) { //登录通过
message.setMesType(MessageType.MESSAGE_LOGIN_SUCCEED);
//将message对象回复客户端
oos.writeObject((message));
//创建一个线程,和客户端保持通信,该线程需要持有socket对象
ServerConnectClientThread serverConnectClientThread = new ServerConnectClientThread(socket, u.getUserID());
serverConnectClientThread.start();
//把该线程对象,放入到一个集合中,进行管理
ManageClientThreads.addClientThread(u.getUserID(), serverConnectClientThread);
} else { //登录失败
System.out.println("用户 id=" + u.getUserID() + " pwd=" + u.getPasswd() + "验证失败");
message.setMesType(MessageType.MESSAGE_LOGIN_FIAL);
oos.writeObject(message);
socket.close();
}
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
//如果服务端退出了while, 说明服务器端不再监听,因此关闭ServerSocket
try {
ss.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
class ServerConnectClientThread
package com.study.qqserver.service;
//该类对应的对象和某个客户端保持通信
import com.study.qqcommon.Message;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;
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() {
while (true) {
System.out.println("服务端和客户端" + userId + "保持通信,读取数据...");
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
Test
The end.
Continue the analysis next time.