day25综合案例-学员管理系统【C/S版】
反馈和复习
1.建议下午也上课(9-12 2-3)
2."正则反向原理"
复习:
1.枚举
public enum 枚举名{
枚举项1,枚举项2,...枚举项n;
}
===>
public enum Sex{
BOY,GIRL;
}
本质:
public final class Sex extends java.lang.Enum<Sex>{
//枚举 项,其实就是当前类的一个静态对象
public static final Sex BOY = new Sex();
public static final Sex GIRL = new Sex();
//类中其实也可以添加我们自己的成员变量/成员方法/构造方法
}
2.JDK剩下的2个特性
方法引用: 把已经存在的方法,直接拿过来,给函数式接口类型的变量赋值
System.out::println
集合.foreach(System.out::println)
流.foreach(System.out::println)
Base64编码:
Decoder(解码器) Encoder(编码器)
3.正则表达式
普通字符串.matches(正则表达式);
普通字符串.split(正则表达式);
普通字符串.replaceAll(正则表达式,新的字符串);
正则的语法:
字符类: [abc][^abc][0-9][a-z][A-Z][0-9a-zA-Z][a-em-q]
逻辑运算符类: && |
预定义字符: \d \w .
数量词: A* A? A+ A{n} A{n,} A{n,m}
分组: (..){2}
今日内容
学生管理系统(C/S)
用户在客户端操作(增删改查)
通过TCP编程把数据发送给服务器
服务器获取数据后要把数据保存到本地
通过TCP编程把结果反馈给客户端
一 项目演示
1.1 打开项目
1.2 运行项目
- 运行服务器
- 运行客户端
1.3 增删改查演示
-
添加学生
-
根据id查询学生
- 查询所有学生
- 修改学生
- 删除学生
二 项目说明
2.1 所采用的知识点要求
-
IO流技术
服务器读写本地文件时,我们采用普通字符流/缓冲流技术, 要求一个学生一行,并且学生的属性之间使用","间隔 比如: 1,张三,男,18 2,李四,女,28
-
网络编程TCP技术
客户端和服务器采用TCP"短连接",每个功能当需要与服务器连接时,才建立连接,功能完毕,连接立即 断开,即客户端使用一个功能连接服务器一次,使用完毕断开连接
-
多线程技术
为了同时支持多个客户端,可以连接服务器,要求每个客户端来了,服务器都要开启一个新的线程处理
-
序列化和反序列化技术
服务器给客户端返回一个学生对象或者一个学生集合时,我们采用序列化流发送给客户端,客户端采用反序列化流读取数据
2.2 客户端与服务器交互
- 交互过程图解
-
数据格式说明
添加:"[1]数据",例如:"[1]张三,男,22" 根据id查询一条数据:"[2]id",例如:"[2]1",意思:查询id为1的学员信息 修改一条数据:"[3]新数据"。例如:"[3]1,张三2,女,19",意思:将id=1的学员改为后面的新数据。 查询所有数据:"[4]"。例如:"[4]",意思:后面不用带任何数据。 删除一条数据:"[5]id"。例如:"[5]1",意思:删除id为1的记录
三 客户端代码实现
3.1 学生类和打印学生信息的工具类介绍
学生类:
public class Student implements Serializable{
private int id;
private String name;
private String sex;
private int age;
//其他省略
}
工具类:
public class StudentUtils {
//打印ArrayList<Student>的方法
public static void printStudentList(ArrayList<Student> stuList) {
System.out.println("--------------------------------------------------");
System.out.println("编号\t\t姓名\t\t\t性别\t\t年龄");
for (int i = 0; i < stuList.size(); i++) {
Student stu = stuList.get(i);
System.out.println(stu.getId() + "\t\t" + stu.getName() + "\t\t\t" + stu.getSex() + "\t\t" + stu.getAge());
}
System.out.println("--------------------------------------------------");
}
//打印单个学生的方法
public static void printStudent(Student stu) {
System.out.println("--------------------------------------------------");
System.out.println("编号\t\t姓名\t\t\t性别\t\t年龄");
System.out.println(stu.getId() + "\t\t" + stu.getName() + "\t\t\t" + stu.getSex() + "\t\t" + stu.getAge());
System.out.println("--------------------------------------------------");
}
}
3.2 编写客户端主程序
public class StudentClient {
//定义一个静态的Scanner对象
public static Scanner sc = new Scanner(System.in);
public static void main(String[] args) {
System.out.println("欢迎使用学生管理系统~~~");
while (true) {
//1.菜单
System.out.println();//空行,为了美观
System.out.println("1.添加学生 2.修改学生 3.删除学生 4.查询学生 5.查看所有学生 6.退出");
//2.键盘录入
System.out.println("请选择:");
int user = sc.nextInt();
//3.判断
switch (user) {
case 1:
//添加学生
addStudent();
break;
case 2:
//修改学生
updateStudent();
break;
case 3:
//删除学生
deleteStudent();
break;
case 4:
//根据ID查询
findStudent();
break;
case 5:
//查询所有学生
findStudents();
break;
case 6:
//退出
System.out.println("欢迎下次光临~~");
System.exit(0);
default:
//提示输入有误
System.out.println("您输入的功能有误~~");
break;
}
}
}
}
3.3 添加学生功能
1.客户端得到数据(用户输入)
2.使用TCP连接将数据发送给服务器
3.读取服务器发送回来的反馈
4.释放资源
//添加学生
public static void addStudent() {
System.out.println("【添加学生】");
//1.客户端得到数据(用户输入)
System.out.println("请输入姓名:");
String name = sc.next();
System.out.println("请输入性别:");
String sex = sc.next();
System.out.println("请输入年龄:");
int age = sc.nextInt();
//2.使用TCP连接将数据发送给服务器
Socket socket = getSocket();
if (socket == null) {
System.out.println("服务器暂时无法连接...");
return;
}
try (OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();) {
//给服务器发送数据的格式:"[1]数据",例如:"[1]张三,男,22"
out.write(("[1]" + name + "," + sex + "," + age).getBytes());
//3.读取服务器发送回来的反馈
int result = in.read();
if (result == 1) {
System.out.println("【添加成功】");
} else {
System.out.println("【添加失败,请联系系统管理员~~~】");
}
//4.释放资源
socket.close();
} catch (IOException ie) {
ie.printStackTrace();
}
}
3.4 修改学生功能
//修改学生
public static void updateStudent() {
System.out.println("【修改学生】");
//1.客户端得到数据(用户输入)
System.out.println("请输入要修改学生的ID:");
int ID = sc.nextInt();
//我们修改学生之前,先要查询该学生,如果不存在,提示不存在,如果存在,再让用户输入其他修改后的信息
//2.使用TCP连接将数据发送给服务器
Socket socket = getSocket();
if (socket == null) {
System.out.println("服务器暂时无法连接...");
return;
}
Student student = null;
//获取输出流
try (OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();) {
//查询学生的格式:"[2]id",例如:"[2]1"
out.write(("[2]" + ID).getBytes());
//3.读取服务器发送回来的反馈
ObjectInputStream ois = new ObjectInputStream(in);
student = (Student) ois.readObject();
if (student == null) {
System.out.println("【查无此人】");
return;
} else {
StudentUtils.printStudent(student);
}
//4.释放资源
socket.close();
ois.close();
} catch (Exception ie) {
ie.printStackTrace();
}
//我们修改学生之前,先要查询该学生,如果不存在,提示不存在,如果存在,再让用户输入其他修改后的信息
//程序执行到这,说明有此学生,我们就可以修改
//1.客户端得到数据(用户输入其他的信息)
System.out.println("请输入新的姓名(如果不修改输入0):");
String name = sc.next();
System.out.println("请输入新的性别(如果不修改输入0):");
String sex = sc.next();
System.out.println("请输入新的年龄(如果不修改输入0):");
int age = sc.nextInt();
//用户输入完毕之后,我们先判断
if (!"0".equals(name)) {
student.setName(name);
}
if (!"0".equals(sex)) {
student.setSex(sex);
}
if (age != 0) {
student.setAge(age);
}
//2.使用TCP连接将数据发送给服务器
Socket socketUpdate = getSocket();
if (socketUpdate == null) {
System.out.println("服务器暂时无法连接...");
return;
}
try (OutputStream out = socketUpdate.getOutputStream();
InputStream in = socketUpdate.getInputStream();) {
//发送修改信息数据格式: "[3]新数据"。例如:"[3]1,张三2,女,19"
out.write(("[3]" + ID + "," + student.getName() + "," + student.getSex() + "," + student.getAge()).getBytes());
//3.读取服务器发送回来的反馈
int result = in.read();
if (result == 1) {
System.out.println("【修改成功】");
} else {
System.out.println("【修改失败,请联系管理员~~~】");
}
//4.释放资源
socketUpdate.close();
} catch (IOException ie) {
ie.printStackTrace();
}
}
3.5 根据id查询学生功能
//根据ID查询
public static void findStudent() {
System.out.println("【根据ID查询】");
//1.客户端得到数据(用户输入)
System.out.println("请输入要查询的学生的ID:");
int ID = sc.nextInt();
//2.使用TCP连接将数据发送给服务器
Socket socket = getSocket();
if (socket == null) {
System.out.println("服务器暂时无法连接...");
return;
}
//获取输出流
try (OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();) {
//查询学生的格式:"[2]id",例如:"[2]1"
out.write(("[2]" + ID).getBytes());
//3.读取服务器发送回来的反馈
ObjectInputStream ois = new ObjectInputStream(in);
Student student = (Student) ois.readObject();
if (student == null) {
System.out.println("【查无此人】");
} else {
StudentUtils.printStudent(student);
}
//4.释放资源
socket.close();
ois.close();
} catch (Exception ie) {
ie.printStackTrace();
}
}
3.6 查询所有学生功能
//查询所有学生
public static void findStudents() {
System.out.println("【查询所有学生】");
//1.客户端得到数据(用户输入)
//客户端不需要输入任何东西
//2.使用TCP连接将数据发送给服务器
Socket socket = getSocket();
if (socket == null) {
System.out.println("服务器暂时无法连接...");
return;
}
try (OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();) {
//查询所有的格式:"[4]"。例如:"[4]"
out.write("[4]".getBytes());
//3.读取服务器发送回来的反馈
ObjectInputStream ois = new ObjectInputStream(in);
ArrayList<Student> students = (ArrayList<Student>) ois.readObject();
if (students == null || students.size() == 0) {
System.out.println("【服务器暂无学生信息】");
} else {
//调用工具类
StudentUtils.printStudentList(students);
}
//4.释放资源
socket.close();
} catch (Exception ie) {
ie.printStackTrace();
}
}
3.6 删除学生功能
//删除学生
public static void deleteStudent() {
System.out.println("【删除学生】");
//1.客户端得到数据(用户输入)
System.out.println("请输入您要删除学生的ID:");
int ID = sc.nextInt();
//用户输入完毕ID之后,我们不应该立刻发送给服务器进行删除
//应该先把该ID的学生查询出来,展示给我用户,然后问他真的确认删除吗
//2.使用TCP连接将数据发送给服务器
Socket socket = getSocket();
if (socket == null) {
System.out.println("服务器暂时无法连接...");
return;
}
Student student = null;
//获取输出流
try (OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();) {
//查询学生的格式:"[2]id",例如:"[2]1"
out.write(("[2]" + ID).getBytes());
//3.读取服务器发送回来的反馈
student = (Student) ois.readObject();
if (student == null) {
System.out.println("【查无此人】");
return;
} else {
StudentUtils.printStudent(student);
}
//4.释放资源
socket.close();
ois.close();
} catch (Exception ie) {
ie.printStackTrace();
}
//用户输入完毕ID之后,我们不应该立刻发送给服务器进行删除
//应该先把该ID的学生查询出来,展示给我用户,然后问他真的确认删除吗
//程序执行到此处,说明有该ID的学生
//1.客户端得到数据(用户输入确定信息)
System.out.println("您确定要删除以上信息的学生吗(y/n)?");
String user = sc.next();
if (!"y".equals(user)) { //只要用户输入的不是y 我们就认为取消!!
System.out.println("【删除操作已经取消】");
return;
}
//2.使用TCP连接将数据发送给服务器
Socket socketDelete = getSocket();
if (socketDelete == null) {
System.out.println("服务器暂时无法连接...");
return;
}
try (OutputStream out = socketDelete.getOutputStream();
InputStream in = socketDelete.getInputStream();) {
//发送删除数据格式:"[5]id"。例如:"[5]1"
out.write(("[5]" + ID).getBytes());
//3.读取服务器发送回来的反馈
int result = in.read();
if (result == 1) {
System.out.println("【删除成功】");
} else {
System.out.println("【删除失败,请联系管理员~~】");
}
//4.释放资源
socketDelete.close();
} catch (IOException ie) {
ie.printStackTrace();
}
}
四 服务器代码实现
4.1 学生类和保存学生数据的工具类介绍
学生类:
public class Student implements Serializable{
private int id;
private String name;
private String sex;
private int age;
//其他省略
}
保存学生数据的工具类
public class StudentDao {
//将集合中所有学生对象,写入到文件中
public static void writeAll(ArrayList<Student> stuList) {
try (FileWriter out = new FileWriter("student.txt")) {
for (Student stu : stuList) {
//格式: id,姓名,性别,年龄
out.write(stu.getId() + "," + stu.getName() + "," + stu.getSex() + "," + stu.getAge());
//换行
out.write("\r\n");
}
} catch (IOException e) {
e.printStackTrace();
}
}
//从文件中读取所有学生的信息,返回学生集合
public static ArrayList<Student> readAll() {
ArrayList<Student> stuList = new ArrayList<>();
//1.创建File对象
File file = new File("student.txt");
if (!file.exists()) {
try {
//2.如果不存在,则创建,否则读取会抛异常
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
//3.读数据,一次一行 格式: id,姓名,性别,年龄
try (BufferedReader bufIn = new BufferedReader(
new FileReader("student.txt"))) {
String line = null;
while ((line = bufIn.readLine()) != null) {
//4.一行切割成一个数组,[id,姓名,性别,年龄]
String[] rowArray = line.split(",");
//5.创建学生对象,封装数据
Student stu = new Student();
stu.setId(Integer.parseInt(rowArray[0]));
stu.setName(rowArray[1]);
stu.setSex(rowArray[2]);
stu.setAge(Integer.parseInt(rowArray[3]));
//6.添加到集合中
stuList.add(stu);
}
} catch (IOException e) {
return null;
}
//7.返回整个集合
return stuList;
}
//添加一个学生,返回boolean代表是否添加成功
public static boolean addStudent(Student student) {
//1.先读取所有学生
ArrayList<Student> stuList = readAll();
if (stuList == null) {//说明读取文件出错
return false;
}
//2.获取最后一个学生的id,加1后作为新学生的id
if (stuList.size() != 0) {
student.setId(stuList.get(stuList.size() - 1).getId() + 1);//取最后一个对象的id + 1
} else {
//3.如果没有学生,说明是第一个,则id设置为1
student.setId(1);//第一次添加,文件中没有内容
}
//4.添加到集合中
stuList.add(student);
//5.把集合重写写入到文件中
writeAll(stuList);
//6.返回添加成功
return true;
}
//根据id删除一个学生,返回boolean代表是否删除成功
public static boolean deleteById(int id) {
//1.先读取所有学生
ArrayList<Student> stuList = readAll();
if (stuList == null) {//说明读取文件出错
return false;
}
//2.遍历集合
for (int i = 0; i < stuList.size(); i++) {
Student stu = stuList.get(i);
//3.判断学生的id是否和要删除的id相等
if (stu.getId() == id) {
//4.从集合中删除学生
stuList.remove(i);
//5.重写写入到文件中
writeAll(stuList);
//6.返回成功
return true;
}
}
//7.如果没找到学生返回失败
return false;
}
//修改学生,返回boolean代表是否修改成功
public static boolean updateStudent(Student student) {
//1.先读取所有学生
ArrayList<Student> stuList = readAll();
if (stuList == null) {//说明读取文件出错
return false;
}
System.out.println("修改的数据:" + student);
//2.遍历集合
for (int i = 0; i < stuList.size(); i++) {
Student stu = stuList.get(i);
//3.判断哪个学生id和要修改的学生id相同
if (stu.getId() == student.getId()) {
//4.将学生改为新的学生
stuList.set(i, student);
//5.重写将集合写入到文集中
writeAll(stuList);//写回文件
//6.返回成功
return true;
}
}
//7.返回失败
return false;//没找到
}
//根据id查询学生,返回查询到的学生
public static Student findById(int id) {
//1.先读取所有学生
ArrayList<Student> stuList = readAll();
if (stuList == null) {//说明读取文件出错
return null;
}
//2.遍历集合
for (int i = 0; i < stuList.size(); i++) {
Student stu = stuList.get(i);
//3.比较id
if (stu.getId() == id) {
//4.找到返回学生对象
return stu;
}
}
//5.找不到返回null
return null;
}
}
4.2 编写服务器主程序
注意我们的需求: 每个客户端连接后要开启一个线程来处与这个客户端的数据交互!!
public class StudentServer {
public static void main(String[] args) throws IOException {
//1.启动
ServerSocket server = new ServerSocket(8888);
System.out.println("学生管理系统服务器启动...");
//2.等待客户端连接
while (true) {
System.out.println("等待客户端...");
Socket socket = server.accept();
//3.开启一个线程,传入Socket对象
new ServerThread(socket).start();
}
}
}
public class ServerThread extends Thread {
public Socket socket;
public ServerThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
}
}
4.3 编写线程的任务代码
public class ServerThread extends Thread {
public Socket socket;
public ServerThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//1.读取客户端发送的数据
try (InputStream in = socket.getInputStream();) {
byte[] bs = new byte[1024];
int len = in.read(bs);
String msg = new String(bs, 0, len);
System.out.println("客户端的数据:" + msg);
//2.分析数据
//合法性判断
if (msg.charAt(0) != '[' || msg.charAt(2) != ']') {
System.out.println("客户端数据格式有误~~~");
socket.close();
in.close();
return;
}
//截取数据
String num = msg.substring(1, 2);//[1]再赶,男,30
//3.判断
switch (num) {
case "1":
//添加学生
addStudent(msg);
break;
case "2":
//根据ID查询
findStudent(msg);
break;
case "3":
//修改数据
updateStudent(msg);
break;
case "4":
//查询所有
findStudents(msg);
break;
case "5":
//删除一条
deleteStudent(msg);
break;
default:
//客户端数据有误~~
System.out.println("该客户端数据有误~~");
socket.close();
break;
}
} catch (IOException ie) {
ie.printStackTrace();
}
}
}
4.4 线程中的增删改查实现
思考:服务器增删改查的方法,有没有共同的步骤
1.封装数据
2.调用StudentDao的增删改查方法
3.反馈数据给客户端
4.释放资源
-
添加学生
//添加学生 public void addStudent(String msg) { //msg [1]再赶,男,30 //1.封装数据 String stuMsg = msg.substring(3); String[] msgArr = stuMsg.split(",");//[再赶,男,30] Student newStudent = new Student(msgArr[0],msgArr[1],Integer.parseInt(msgArr[2])); //2.调用StudentDao的增删改查方法 StudentDao.addStudent(newStudent); //3.反馈数据给客户端 try (OutputStream out = socket.getOutputStream();) { out.write(1); //4.释放资源 socket.close(); } catch (IOException ie) { ie.printStackTrace(); } }
-
查询所有学生
//查询所有学生 public void findStudents(String msg) { //1.封装数据 //不需要封装任何数据 //2.调用StudentDao的增删改查方法 ArrayList<Student> stuList = StudentDao.readAll(); //3.反馈数据给客户端 try { OutputStream out = socket.getOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(out); oos.writeObject(stuList); //4.释放资源 oos.close(); out.close(); socket.close(); } catch (IOException ie) { ie.printStackTrace(); } }
-
根据id查询
//根据ID查询学生 public void findStudent(String msg) { //msg: "[2]1" //1.封装数据 int findID = Integer.parseInt(msg.substring(3)); //2.调用StudentDao的增删改查方法 Student student = StudentDao.findById(findID); //3.反馈数据给客户端 try { OutputStream out = socket.getOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(out); oos.writeObject(student); //4.释放资源 oos.close(); out.close(); socket.close(); } catch (IOException ie) { ie.printStackTrace(); } }
-
修改学生
//修改学生 public void updateStudent(String msg) { //msg: [3]1,张三2,女,19 //1.封装数据 String msgStu = msg.substring(3); String[] msgArr = msgStu.split(",");//[1,张三2,女,19] Student s = new Student(); s.setId(Integer.parseInt(msgArr[0])); s.setName(msgArr[1]); s.setSex(msgArr[2]); s.setAge(Integer.parseInt(msgArr[3])); //2.调用StudentDao的增删改查方法 StudentDao.updateStudent(s); //3.反馈数据给客户端 try { OutputStream out = socket.getOutputStream(); out.write(1); //4.释放资源 out.close(); socket.close(); } catch (IOException ie) { ie.printStackTrace(); } }
-
根据id删除
//根据ID删除学生 public void deleteStudent(String msg) { //msg: [5]1 //1.封装数据 int deleteID = Integer.parseInt(msg.substring(3)); //2.调用StudentDao的增删改查方法 StudentDao.deleteById(deleteID); //3.反馈数据给客户端 try { OutputStream out = socket.getOutputStream(); out.write(1); //4.释放资源 out.close(); socket.close(); } catch (IOException ie) { ie.printStackTrace(); } }
总结
能够完成客户端添加功能
[1]name,sex,age
能够完成客户端修改功能
[3]id,name,sex,age
能够完成客户端删除功能
[5]id
能够完成客户端获取功能
[2]id
[4]
能够完成服务端功能
根据客户端的编码,完成不同的操作