模块四解题思路
第一题
- 基于学生信息管理系统增加以下两个功能:
- 自定义学号异常类和年龄异常类,并在该成员变量不合理时产生异常对象并抛出。
- 当系统退出时将 List 集合中所有学生信息写入到文件中,当系统启动时读取文件中所 有学生信息到 List 集合中。
解题思路
1.学生信息管理系统在模块三作业中已完成
2.自定义异常类,创建两个新的类IdException 和 AgeException继承自Exception类,并在内部提供一个无参构造方法和一个参数为字符串类型的消息的有参构造方法,然后设置一个序列化版本号
3.在student类中的Id和age的设置方法中,进行不合理判断,并抛出相应的异常
4.在用户选择操作6退出系统时,使用ObejectOutputStream对象流将学生信息的List集合整体写入文件;在用户打开学生信息管理系统时,使用ObejctInputStream对象输入流读取文件内容,重新赋值给集合students
5.要使用ObjectInputStream 进行对象的读写,对象所对应的类要实现序列化接口Serializable,并提供序列化版本号
代码演示
AgeException.java
public class AgeException extends Exception {
private final long serialVersionUID = 7818375828146090155L;
public AgeException() {
}
public AgeException(String message) {
super(message);
}
}
IdExceptiong.java
public class IdException extends Exception {
// 序列化版本号
private final long serialVersionUID = 7818375828146090156L;
public IdException(){
}
public IdException(String message){
// 执行父类有参构造方法
super(message);
}
}
Student.java
import java.io.Serializable;
/**
* 创建学生类,实现序列化接口
*/
public class Student implements Serializable {
// 提供序列化版本号
private static final long serialVersionUID = -5814716593800822421L;
private int id; // 学号
private String name; // 姓名
private int age; // 年龄
public Student() {
}
public Student(int id, String name, int age) throws IdException, AgeException {
setId(id);
setName(name);
setAge(age);
}
public int getId() {
return id;
}
public void setId(int id) throws IdException {
// 学号不合理异常处理,
if(("" + id).matches("[^0\\D]\\d{3,5}")){ //非零开头的3-5位数字
this.id = id;
}else{
throw new IdException("学号必须是非零开头的3-5位数字");
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) throws AgeException {
// 年龄不合理异常处理
if(0 < age && age <=150) {
this.age = age;
}else{
throw new AgeException("年龄应该在0-150之间");
}
}
// 重写toString方法
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
StudentFunction.java
/**
* 学生管理系统功能接口
*/
public interface StudentFunction {
// 学生管理界面
void showMenu();
// 添加学生
void addStudent(int id, String name, int age) throws AgeException, IdException;
// 根据学号查找学生
Student findStudent(int id);
// 根据姓名查找学生
Student findStudent(String name);
// 遍历学生
void forStudent();
// 根据学号删除学生
boolean deleteStudent(int id);
// 根据姓名删除学生
boolean deleteStudent(String name);
// 根据学号修改学生
Student modifyStudent(int id, String newName, int newAge) throws AgeException;
// 根据姓名修改学生
Student modifyStudent(String name, String newName, int newAge) throws AgeException;
// 写入文件
void insertStudentToText();
// 读取文件
void readStudentFromText();
}
StudentFunctionImpl.java
import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 学生管理系统功能实现类
*/
public class StudentFunctionImpl implements StudentFunction {
// 声明一个List集合对象存放学生对象
private List<Student> students = new ArrayList<>();
// 学生管理系统界面
@Override
public void showMenu(){
System.out.println("\n\n\n");
System.out.println(" 学生信息管理系统 ");
System.out.println("-------------------------------------");
System.out.println(" 1.增加学生信息 | 2.删除学生信息 ");
System.out.println("-------------------------------------");
System.out.println(" 3.修改学生信息 | 4.查找学生信息 ");
System.out.println("-------------------------------------");
System.out.println(" 5.遍历学生信息 | 6.退出学生系统 ");
System.out.println("-------------------------------------");
}
// 添加学生信息
@Override
public void addStudent(int id, String name, int age) throws AgeException, IdException{
// 使用有参构造方法创建学生类对象,并添加到学生集合students中
Student student = new Student(id, name, age);
students.add(student);
}
// 根据学号查找学生
@Override
public Student findStudent(int id){
// 遍历学生集合,取得每个学生对象
for(Student student:students){
// 判断学生对象中的id是否和查找id相同,相同则返回此学生对象
if(student.getId() == id){
return student;
}
}
// 未查找到此学生则返回null
return null;
}
// 根据学生姓名查找学生,目前只查找第一个姓名相同的同学
@Override
public Student findStudent(String name){
// 若传入的学生姓名为null 则直接返回null
if(name == null){
return null;
}
// 遍历学生集合
for(Student student:students){
// 判断学生对象中的姓名和查找的姓名是否相同
if(name.equals(student.getName())){ // 相同则返回此学生对象
return student;
}
}
// 未查找到此学生姓名则返回null
return null;
}
// 遍历学生集合
@Override
public void forStudent(){
for(Student student:students){
// 使用student中重写的toString方法打印学生信息
System.out.println(student.toString());
}
}
// 根据学号删除学生
@Override
public boolean deleteStudent(int id){
// 将学生集合迭代器化,未了遍历学生集合时,能够移除对象
Iterator<Student> iterator = students.iterator();
// 循环,当迭代器无下一个迭代对象时,退出循环
while(iterator.hasNext()){
// 获取学生对象
Student student = iterator.next();
// 判断学生对象的学号和输入的学号是否相同
if(student.getId() == id){ // 相同移除此学生对象,并返回true
iterator.remove();
return true;
}
}
// 移除失败,无此学生则返回false
return false;
}
// 重载删除学生,根据学生姓名
@Override
public boolean deleteStudent(String name){
// 当输入的姓名为null时,则直接返回false
if(name == null){
return false;
}
// 迭代化学生集合
Iterator<Student> iterator = students.iterator();
// 循环
while(iterator.hasNext()){
Student student = iterator.next();
// 判断学生对象的学号和输入的学号
if(name.equals(student.getName())){
iterator.remove();
return true;
}
}
return false;
}
// 根据学号修改学生信息
@Override
public Student modifyStudent(int id, String newName, int newAge) throws AgeException{
// 遍历学生集合
for(Student student:students){
// 判断学生对象的学号是否为要修改信息的学生的雪蛤
if(student.getId() == id){ // 是则修改学生姓名和年龄。并返回此学生对象
student.setName(newName);
student.setAge(newAge);
return student;
}
}
// 修改失败,无此学生
return null;
}
// 根据姓名修改学生信息
@Override
public Student modifyStudent(String name, String newName, int newAge) throws AgeException{
// 当name为null时,直接返回null
if(name == null){
return null;
}
// 遍历学生集合
for(Student student:students){
if(name.equals(student.getName())){
student.setName(newName);
student.setAge(newAge);
return student;
}
}
return null;
}
// 将学生信息写入文本文件中
public void insertStudentToText() {
ObjectOutputStream oos = null;
// 异常处理
try {
// 创建对象流对象
oos = new ObjectOutputStream(new FileOutputStream("student.txt"));
// 写入内容
oos.writeObject(students);
} catch (IOException e) {
e.printStackTrace();
}finally {
if(oos != null) {
try {// 关闭对象流
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 从文本中读取信息到学生管理系统
public void readStudentFromText() {
// 获取文件对象,判断文件是否存在,不存在则新建一个,第一次开启时,并没有这个文件,读取会出错
File file = new File("student.txt");
if(!file.exists()){
try { // 创建文件
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
ObjectInputStream ois = null;
try {
if(file.length() != 0) { // 如果文件内容不为空,则读取文件内容复制给集合students
ois = new ObjectInputStream(new FileInputStream("student.txt"));
students = (List<Student>) ois.readObject();
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
if(ois != null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
StudentItem.java
package com.panxiaojia.homework4.student;
/**
* 学生管理系统类
*/
import java.util.Scanner;
public class StudentItem {
// 声明一个StudentFunction类型的引用指向StudentFunctionImpl类型的对象,形成多态
private StudentFunction studentFunction = new StudentFunctionImpl();
// 创建扫描器对象,获取输入的内容
private Scanner sc = new Scanner(System.in);
// 提取用户输入为一个方法,简化代码,方便调用
private int getInputInt(String message, String error){
System.out.println(message);
int temp = 0;
while(true){
try{
temp = sc.nextInt();
}catch (Exception e){
sc.next(); // 使扫描器进入下一行,不然会进入死循环
System.out.println(error);
continue;
}
return temp;
}
}
// 提取用户输入为一个方法,简化代码,方便调用
private String getInputString(String message, String error){
System.out.println(message);
String temp = null;
while(true){
try{
temp = sc.next();
}catch (Exception e){
sc.next();
System.out.println(error);
continue;
}
return temp;
}
}
// 菜单目录,功能选择
public void studentItemMenu(){
studentFunction.readStudentFromText();
// 无限循环,当用户选择退出系统时,结束循环
while(true) {
// 展示功能菜单
studentFunction.showMenu();
// 提示用户输入功能,并获取
int num = 0;
// 处理用户操作指令输入错误异常
try {
System.out.println("请输入要进行的操作(1-6): ");
num = sc.nextInt();
} catch (Exception e) {
sc.next();
System.out.println("请输入正确的操作指令!");
continue;
}
// 功能判断,并进行功能操作
if(1 == num){
// 提示用户输入数据,输入格式不正确给予一定的提示
int id = getInputInt("请输入学生学号: ", "输入的学号格式不正确,请重新输入");
String name = getInputString("请输入学生姓名:", "输入的姓名格式不正确,请重新输入");
int age = getInputInt("请输入学生年龄:", "输入的年龄格式不正确,请重新输入");
// 添加学生
try {
studentFunction.addStudent(id, name, age);
} catch (IdException | AgeException e) {
System.out.println(e.getMessage());
}
}else if(2 == num){
// 提示用户输入
String im = getInputString("请输入要删除的学生id或姓名: ", "");
// 判断输入的是学号还是姓名,选择对应的删除学生方法
boolean result;
if(im.matches("^[0-9]*$")){
int id =Integer.parseInt(im);
result = studentFunction.deleteStudent(id);
}else{
result = studentFunction.deleteStudent(im);
}
// 判断删除的结果,并打印提示
if(result){
System.out.println("删除成功!");
}else{
System.out.println("删除失败!");
}
}else if(3 == num){
// 提示用户输入
String im = getInputString("请输入要修改的学生id或姓名: ", "");
String newName = getInputString("请输入修改后的姓名:", "");
int newAge = getInputInt("请输入修改后的年龄: ", "输入的年龄类型不正确,请重新输入");
// 判断输入的是学号还是姓名,并选择对应的修改方法修改学生信息
Student result = null;
if(im.matches("^[0-9]*$")){
int id =Integer.parseInt(im);
try {
result = studentFunction.modifyStudent(id, newName, newAge);
} catch (AgeException e) {
e.printStackTrace();
}
}else{
try {
result = studentFunction.modifyStudent(im, newName, newAge);
} catch (AgeException e) {
e.printStackTrace();
}
}
// 判断修改结果,并给出提示
if(result != null){
System.out.println(result.toString());
}else{
System.out.println("修改失败!");
}
}else if(4 == num){
// 提示用户输入
String im = getInputString("请输入要查找的学生id或姓名: ", "");
// 判断输入的是学号还是姓名,并选择对应的查找方法查找学生
Student result;
if(im.matches("^[0-9]*$")){
int id =Integer.parseInt(im);
result = studentFunction.findStudent(id);
}else{
result = studentFunction.findStudent(im);
}
// 判断查找结果,并给出提示
if(result != null){
System.out.println(result.toString());
}else{
System.out.println("无此学生信息!");
}
}else if(5 == num){
// 遍历所有学生
studentFunction.forStudent();
}else if(6 == num){
studentFunction.insertStudentToText();
// 用户退出系统,break退出循环,并关闭扫描器
System.out.println("退出系统!");
sc.close();
break;
}else{
// 用户输入的内容超出功能范围
System.out.println("无此功能,请重新输入: ");
}
}
}
}
package com.panxiaojia.homework4.student;
/**
* 测试类
*/
/**
* 使用 List 集合实现简易的学生信息管理系统,
* 要求打印字符界面提示用户选择相应的功能,
* 根据用户输入的选择去实现增加、删除、修改、查找以及遍历所有学生信息的功能。
*/
public class StudentItemTest {
public static void main(String[] args){
// 创建对象,并调用系统启动方法
StudentItem studentItem = new StudentItem();
studentItem.studentItemMenu();
}
}
第二题
- 实现将指定目录中的所有内容删除,包含子目录中的内容都要全部删除。
解题思路
1.使用递归的方式,遍历整个目录,包括子目录
2.即遍历目录所有文件时,判断是否为文件,是文件则删除,是目录则将新目录作为参数调用本身方法
代码演示
package com.panxiaojia.homework4;
import java.io.File;
public class DeleteFileTest {
// 递归方式实现文件的删除
private static void deleteFile(String path){
File files = new File(path); // 创建文件对象
// 文件和目录的删除
if(files.exists()){ // 判断文件对象是否存在,不存在则提示路径不存在
// 遍历当前目录文件列表(文件和目录)
for (File file:files.listFiles()){
if(file.isFile()){ // 判断是否为文件,是文件则删除
System.out.println("正在删除文件: " + file.getName());
file.delete();
}else{ // 为目录则继续调用deleteFile方法,并删除目录
deleteFile(file.getPath());
System.out.println("正在删除目录: " + file.getName());
file.delete();
}
}
}else{
System.out.println("路径不存在");
}
}
// 程序路口
public static void main(String[] args){
// 1.申明变量指定目录
String path = "D:/test";
// 调用删除文件的方法
deleteFile(path);
}
}
第三题
- 使用线程池将一个目录中的所有内容拷贝到另外一个目录中,包含子目录中的内容。
解题思路
1.使用线程池的方式复制整个目录内容到另一个文件,首先先创建一个固定数为10的线程池,然后添加任务要线程池中,这里是将一个获取被复制文件的任务,和三个复制文件的任务添加到线程池中
2.创建一个文件仓库,用于存储所有要被复制的文件路径和被复制到目录路径的组合体,提供添加和提取文件路径的方法,并要使用方法锁,确保线程安全
3.创建获取被复制文件的类继承Runnable接口,重写run方法中提供被复制目录下所有文件的获取;使用递归的方式遍历获取一个目录下的所有文件,并将所有文件添加到文件仓库中
4.创建复制文件操作的类继承Runnable接口,重写run方法中提供复制文件的操作,使用缓冲字节流的方式,从文件一读并写入文件二中
代码演示
FileStore.java
import java.util.LinkedList;
import java.util.Queue;
public class FileStore {
// 声明一个队列,用来存储所有要被复制的文件的路径
private Queue<String> queue = new LinkedList<>();
private boolean flag = true; // 用来表示所有要被复制的路径是否都已经存入队列
// 返回标志信息
public boolean getFlag() {
return flag;
}
// 设置标志信息
public void setFlag(boolean flag) {
this.flag = flag;
}
// 对队列的添加操作,使用synchronized实现方法锁
public synchronized void addMessage(String str){
queue.add(str);
}
// 实现从队列中取数据,使用synchronized实现方法锁
public synchronized String getMessage(){
if(!queue.isEmpty()) { // 但队列不为空时,则从队列中取出一个数据返回
return queue.poll();
}else{
return "";
}
}
}
GetFile.java
import java.io.File;
/**
* 文件迭代类,实现接口Runnable
*/
public class GetFile implements Runnable{
private FileStore fileStore; // 文件仓库,确保使用同一个文件仓库
private String copyPath = ""; // 复制目录
private String copyToPath = ""; // 复制到目录
public GetFile() {
}
// 有参构造方法
public GetFile(String copyPath, String copyToPath, FileStore fileStore) {
this.copyPath = copyPath;
this.copyToPath = copyToPath;
this.fileStore = fileStore;
}
// 获取所有文件,添加到文件仓库
public void workListPath(String path, String toPath){
// 创建文件对象
File copyFile = new File(path);
File toFile = new File(toPath);
if(copyFile.exists()){ // 判断要被复制的目录是否存在
if(!toFile.exists()) { // 判断复制到的文件目录是否存在不存在则创建
System.out.println("线程:"+ Thread.currentThread().getId() +",正在创建目录: " + toFile.getPath());
toFile.mkdirs();
}
for (File file : copyFile.listFiles()) { // 遍历目录下所有文件/目录
if (file.isFile()) { // 判断是否为文件
// 将被复制文件路径和复制到文件路径通过","连接,存储文件仓库
String message = file.getAbsolutePath() + "," + toFile.getAbsolutePath() + File.separator + file.getName();
fileStore.addMessage(message);
} else { // 非文件的情况
// 获取当前被复制目录路径和要复制到的目录路径作为参数调用本身方法,实现递归
String newPath = file.getPath();
String newToPath = toPath + File.separator + file.getName();
workListPath(newPath, newToPath);
}
}
}else{
System.out.println(copyFile + "路径不存在");
}
}
// 实现Runnable要重写run方法
@Override
public void run() {
// 调用遍历文件方法
workListPath(this.copyPath, this.copyToPath);
// 设置文件仓库的标志信息为false,表示所有文件都已添加完毕
fileStore.setFlag(false);
}
}
CopyFileRunnable.java
import java.io.*;
// 复制文件类,Runnable接口的实现类
public class CopyFileRunnable implements Runnable {
private FileStore fileStore; // 文件仓库,保证共用同一个文件仓库
// 无参构造方法
public CopyFileRunnable() {
}
// 有参构造方法
public CopyFileRunnable(FileStore fileStore) {
this.fileStore = fileStore;
}
// 重写run方法
@Override
public void run() {
String path = ""; // 被复制文件路径
String toPath = ""; // 复制到文件路径
// 循环复制每一个文件
while (true) {
// 从文件仓库中获取文件路径
String paths = fileStore.getMessage();
if (paths == "") { // 路径若为空字符串,则表示文件仓库中无文件目录了
if(fileStore.getFlag()){ // 判断文件仓库是否还会有新文件加入,是则等待重新获取
continue;
}else {// 不是则所有文件复制完毕,退出循环
return;
}
} else { // 路径不为空字符串,则拆分文件仓库中获取的路径
path = paths.split(",")[0];
toPath = paths.split(",")[1];
}
// 文件复制操作
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
// 创建文件对象,获取文件名
File file = new File(path);
System.out.println("线程:"+ Thread.currentThread().getId() +",正在复制文件: " + file.getName());
// 1.从文件一读取内容
bis = new BufferedInputStream(new FileInputStream(path));
// 2.将内容写入文件二
bos = new BufferedOutputStream(new FileOutputStream(toPath));
// 缓冲区字节数组1024
byte[] arr = new byte[1024];
int res = 0;
while ((res = bis.read(arr)) != -1) { // 循环每次读取1024个字节,当为-1时结束循环
bos.write(arr, 0, res); // 写入文件
}
} catch (IOException e) { // 出现IO异常处理
e.printStackTrace();
} finally { // 最终指向,无论最终是否有异常都执行
if (null != bis) {
try {
bis.close(); // 关闭读IO流
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != bos) {
try {
bos.close(); // 关闭写IO流
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
CopyFileThreadPool.java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CopyFileThreadPool {
// 主程序入口
public static void main(String[] args){
// 声明一个文件仓库
FileStore fileStore = new FileStore();
// 创建线程池,固定线程数为10
ExecutorService executor = Executors.newFixedThreadPool(10);
// 将获取文件,复制文件的操作添加的线程池中
executor.execute(new GetFile("D:\\test", "F:\\one", fileStore));
executor.execute(new CopyFileRunnable(fileStore));
executor.execute(new CopyFileRunnable(fileStore));
executor.execute(new CopyFileRunnable(fileStore));
// 所有任务执行完毕,关闭线程池
executor.shutdown();
}
}
第四题
\4. 编程题
使用基于 tcp 协议的编程模型实现将 UserMessage 类型对象由客户端发送给服务器;
服 务 器接 收到 对象 后判 断 用户 对象 信息 是否 为 “admin” 和 “123456”, 若 是则 将 UserMessage 对象中的类型改为"success",否则将类型改为"fail"并回发给客户端,客户 端接收到服务器发来的对象后判断并给出登录成功或者失败的提示。
其中 UserMessage 类的特征有:类型(字符串类型) 和 用户对象(User 类型)。
其中 User 类的特征有:用户名、密码(字符串类型)。
如:
UserMessage tum = new UserMessage(“check”, new User(“admin”, “123456”));
解题思路
1.创建User类,实现序列化接口serializable(进行对象流读写时要求),声明序列化版本号,私有变量用户名和密码,并提供相应的操作方法
2.创建UserMessage类,实现徐丽华接口serializble,声明序列化版本号,私有变量type和User类对象
3.建立服务器,根据tcp协议编程模型,建立套接字绑定端口号,使用accpet方法等待客户端连接,建立线程处理客户端和服务器间的通信
4.创建服务器和客户端通信处理类,继承Thread线程类,重写run方法完成信息的处理,使用ObjectInputStream对象流进行信息的传递,服务器先接收客户端信息,再根据信息进行判断账号密码的正确性,并放回相应结果
5.创建客户端,根据tcp协议编程模型,建立套接字绑定服务器ip和端口号
代码演示
ServerThread.java
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
public class ServerThread extends Thread {
private Socket socket;
public ServerThread() {
}
public ServerThread(Socket socket) {
this.socket = socket;
}
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
@Override
public void run() {
try {
// 3. 服务器与客户端间通信
// 获取客户端提交的信息
ois = new ObjectInputStream(socket.getInputStream());
UserMessage um =(UserMessage) ois.readObject();
// 判断客户端提交的用户密码是否正确,并对信息进行处理
if("admin".equals(um.getUser().getUserName()) && "123456".equals(um.getUser().getPassword())){
um.setType("success");
}else{
um.setType("fail");
}
// 返回客户端信息
oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(um);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
if(null != socket) {
// 关闭服务并释放资源
try {
System.out.println("客户端:" + socket.getInetAddress() + "断开连接");
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(null != oos){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(null != ois){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
LoginServer.java
/**
* 使用基于 tcp 协议的编程模型实现将 UserMessage 类型对象由客户端发送给服务器;
*
* 服务器接收到对象后判断用户对象信息是否为 "admin" 和 "123456",
* 若是则 将 UserMessage 对象中的类型改为"success",
* 否则将类型改为"fail"并回发给客户端,客户 端接收到服务器发来的对象后判断并给出登录成功或者失败的提示。
*
* 其中 UserMessage 类的特征有:类型(字符串类型) 和 用户对象(User 类型)。
* 其中 User 类的特征有:用户名、密码(字符串类型)
*/
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class LoginServer {
public static void main(String[] args){
ServerSocket serverSocket = null;
Socket socket = null;
try {
// 1.创建服务器套接字
serverSocket = new ServerSocket(8888);
// 死循环等待客户端接入
while (true) {
// 2.等待客户端的连接
System.out.println("等待客户端连接中----");
socket = serverSocket.accept();
System.out.println("客户端:" + socket.getInetAddress() + "连接");
// 没接入一个客户端,则创建一个对应的线程,确保多客户端互不干扰
ServerThread serverThread = new ServerThread(socket);
serverThread.start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(null != socket) {
// 关闭服务并释放资源
try {
System.out.println("客户端:" + socket.getInetAddress() + "断开连接");
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(null != serverSocket) {
// 关闭服务并释放资源
try {
System.out.println("服务器关闭");
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
LoginClient.java
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Scanner;
public class Longincilent {
public static void main(String[] args){
User user = null;
UserMessage um = null;
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
Socket socket = null;
try {
// 创建套接字并绑定服务器
System.out.println("连接服务器!");
socket = new Socket("127.0.0.1", 8888);
// 提示用户输入账号和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入账号: ");
String userName = sc.next();
System.out.println("请输入密码: ");
String password = sc.next();
// 创建用户对象
user = new User(userName, password);
// 创建用户信息对象
um = new UserMessage(user);
// 实现客户端与服务器间通信
// 向服务器提交数据
oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(um);
// 获取服务器返回的数据
ois = new ObjectInputStream(socket.getInputStream());
UserMessage umResult =(UserMessage)ois.readObject();
// 判断是否登录成功
String result = umResult.getType();
if("success".equals(result)){
System.out.println("登录成功!");
}else{
System.out.println("登录失败!");
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
if(null != ois) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(null != oos) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(null != socket){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
User.java
import java.io.Serializable;
public class User implements Serializable {
// 实现序列化接口,一定要指定序列化版本号
private static final long serialVersionUID = -5814716593800822422L;
private String userName; // 用户名
private String password; // 密码
public User() {
}
public User(String userName, String password) {
this.userName = userName;
this.password = password;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// 重写toString方法
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", password='" + password + '\'' +
'}';
}
}
UserMessage.java
import java.io.Serializable;
public class UserMessage implements Serializable {
// 实现序列化接口,一定要指定序列化版本号
private static final long serialVersionUID = -5814716593800822421L;
private String type; // 类型
private User user; // 用户对象
public UserMessage() {
}
public UserMessage(User user) {
this.user = user;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
第五题
- 使用基于 tcp 协议的编程模型实现多人同时在线聊天和传输文件,要求每个客户端将发 送的聊天内容和文件发送到服务器,服务器接收到后转发给当前所有在线的客户端
解题思路
1.建立服务器,使用ServerSocket创建服务器套接字并绑定端口,然后主进程循环等待客户端接入,每接入一个客户端将客户端套接字作为键其对象输入流作为值保存到map集合中,并建立一个新线程处理和客户端的消息交互;注意以socket.getOutputStream为输出流的ObjectOutputStream在每个线程中都只能有一个
2.将和客户端的交互放在一个新线程里,就要写一个继承Thread类的自定义类并重写run方法,在其中循环等待客户端的消息传输,判断客户端消息传入的类型,若是普通消息内容,则返回给所有客户端,若是文件内容,则将文件保存到服务器文件夹,并将上传的文件信息发送给客户端
3.建立客户端,使用Socket创建客户端套接字并绑定服务器ip和端口,创建两个线程,一个处理向服务器发送消息,一个处理接收服务器的消息,使用jion使得主进程等待子线程完全执行完毕才可以结束程序
4.客户端接收消息线程。循环等待服务器的消息,判断包装类中消息的类型,若为普通类型则打印到控制台,若为文件内容,则保存到客户端文件
5.客户端发送消息线程。循环等待用户输入消息,非POST和DOWNLOAD关键字,则将消息内容直接传递给服务器;若为POST则再次提醒用户输入上传的文件的绝对路径,然后先将文件名包装的包装类发送的服务器,再将文件流发送到服务器;若为download则再次提醒用户输入要下载的文件名,若服务器存在此文件,则会收到此文件的文件流,若文件不存在则会收到文件不存在的提示
6.消息包装类。使用对象流的方式传输消息,所以提供了一个序列化的自定义包装类,将要传输的消息内容包装到类中,再通过对象流进行传输
7.工具类。提供了发送文件,发送消息以及下载文件的方法,简化代码,方便调用
代码演示
ChatServer.java
import java.io.File;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ChatServer {
public static void main(String[] args){
Map<Socket, ObjectOutputStream> map = new HashMap<>();
ServerSocket serverSocket = null;
Socket socket = null;
try {
// 1.创建服务器套接字
serverSocket = new ServerSocket(8888);
File file = new File("./文件/服务器");
if(!file.exists()){
file.mkdirs();
}
// 死循环等待客户端接入,表示服务器永不断开
ObjectOutputStream oos = null;
while(true){
System.out.println("等待客户端连接-----");
socket = serverSocket.accept(); // 等待客户端接入
oos = new ObjectOutputStream(socket.getOutputStream());
map.put(socket, oos); // 添加到客户端套接字数组中
System.out.println(socket.getInetAddress() + "已上线");
// 接入一个客户端则创建一个线程进行服务器和客户端间的同学
ChatServerThread cst = new ChatServerThread(map, socket);
cst.start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(null != serverSocket) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
ChatServerThread.java
import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.List;
import java.util.Map;
public class ChatServerThread extends Thread {
private Map<Socket, ObjectOutputStream> sockets;
private Socket socket;
public ChatServerThread(){
}
public ChatServerThread(Map<Socket, ObjectOutputStream> sockets, Socket socket) {
this.sockets = sockets;
this.socket = socket;
}
// 给所有客户端发送消息
private static void sendMessage(Map<Socket, ObjectOutputStream> sockets, Socket socket, Transmission tm){
for (Socket socket1 : sockets.keySet()) {
// 判断如果是本身,就不返回信息
if(socket == socket1){
continue;
}
ObjectOutputStream oos = sockets.get(socket1);
boolean flag = false;
try {
// 判断客户端是否还在线,false表示在线,异常表示不在线
flag = socket1.getKeepAlive();
}catch(Exception e){
continue;
}
if (!flag) {// 在线则发送消息
Util.sendMessage(oos, tm);
}
}
}
PrintStream ps = null;
ObjectInputStream ois = null;
@Override
public void run() {
// 消息包装类对象
Transmission tm = new Transmission();
try {
// 获取客户端传输内容
ois = new ObjectInputStream(socket.getInputStream());
int socketName = sockets.size();
tm.itemType = 0;
tm.userName = "服务器";
tm.content = socketName + "号进入群聊!";
sendMessage(sockets, socket, tm);
while (true) {
try {
tm = (Transmission) ois.readObject(); // 不做异常处理,会出现Connection reset异常
}catch (SocketException e){
System.out.println(socket.getInetAddress() + ",断开连接");
tm.itemType = 0;
tm.content = socketName + "号退出群聊!";
tm.userName = "服务器";
sendMessage(sockets, socket, tm);
// 客户端下线,则移出集合
sockets.remove(socket);
break;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if(tm.itemType == 0){ // 判断消息类型,普通聊天内容
System.out.println(tm.content);
tm.itemType = 0;
sendMessage(sockets, socket, tm);
}else if(tm.itemType == 1){ // 客户端上传文件内容
String fileName = tm.fileName;
String path = "./文件/服务器/" + fileName;
Util.downloadFile(socket, path); // 保存到服务器
tm.itemType = 0;
tm.content = tm.userName + "分享文件: " + fileName;
tm.userName = "服务器";
sendMessage(sockets, socket, tm); // 将某客户端上传某文件提示给所有客户端
}else if(tm.itemType == 2) { // 客户端下载文件
String fileName = tm.content;
String path = "./文件/服务器/" + fileName;
File file = new File(path);
// 判断要下载的文件是否存在
if (file.exists() && file.isFile()) {
tm.fileName = fileName;
tm.itemType = 1;
Util.sendMessage(sockets.get(socket), tm); // 先发送文件名称
Util.sendFile(socket, path); // 在发送文件内容
}else{
tm.itemType = 0;
tm.content = "文件不存在";
tm.userName = "服务器";
Util.sendMessage(sockets.get(socket), tm);
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(null != socket){
try {
sockets.remove(socket);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(null != ps){
ps.close();
}
if(null != ois){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
ChatClient1.java
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class ChatClient1 {
public static void main(String[] args){
Socket socket = null;
try {
// 创建客户端套接字
socket = new Socket("127.0.0.1", 8888);
String name = "用户1";
String path = "./文件/" + name + "/";
File file = new File(path);
if(!file.exists()){
file.mkdirs();
}
// 发送信息线程
ClientSendThread cst = new ClientSendThread(socket, name);
// 接收信息线程
ClientReceiveThread crt = new ClientReceiveThread(socket, path);
// 线程启动
cst.start();
crt.start();
// 主进程阻塞等待线程执行完毕
cst.join();
crt.join();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
if(null != socket) {
// 关闭套接字
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
ClientReceiveThread.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.net.Socket;
import java.net.SocketException;
public class ClientReceiveThread extends Thread {
private Socket socket;
private String path;
public ClientReceiveThread() {
}
public ClientReceiveThread(Socket socket, String path) {
this.socket = socket;
this.path = path;
}
BufferedReader bufferedReader = null;
ObjectInputStream ois = null;
Transmission tm = null;
@Override
public void run() {
try {
ois = new ObjectInputStream(socket.getInputStream());
while(true) {
try {
// 客户端接收服务端消息
tm = (Transmission) ois.readObject();
if(tm.itemType == 0){ // 普通聊天内容
System.out.println(tm.userName + ": " + tm.content);
}else if(tm.itemType == 1){ // 文件内容
String fileName = tm.fileName;
Util.downloadFile(socket, path + fileName); // 保存到客户端对于目录
}
}catch (SocketException e){
System.out.println("服务器断开连接");
break;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(null != bufferedReader){
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
ClientSendThread.java
import java.io.File;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Base64;
import java.util.Scanner;
public class ClientSendThread extends Thread{
private Socket socket;
private String name;
public ClientSendThread() {
}
public ClientSendThread(Socket socket, String name){
this.socket = socket;
this.name = name;
}
@Override
public void run() {
ObjectOutputStream oos = null;
try {
// 客户端发送消息给服务端
oos = new ObjectOutputStream(socket.getOutputStream());
Scanner sc = new Scanner(System.in);
Transmission tm = new Transmission();
while (true) {
// 实现通信 输入bye退出客户端
System.out.println("请输入聊天内容: ");
String message = sc.next();
tm.userName = name;
// 传输文件
if("post".equalsIgnoreCase(message)){
System.out.println("请输入文件全路径:");
String path = sc.next();
File file = new File(path);
if(!file.exists()){ // 判断文件是否存在
System.out.println("文件路径不存在");
continue;
}else {
tm.fileName = file.getName(); // 文件名
tm.itemType = 1; // 发送类型 0 普通内容, 1文件 2下载文件
Util.sendMessage(oos, tm); // 先发送文件名
Util.sendFile(socket, path); // 再发送文件内容
}
// 下载文件
}else if("download".equalsIgnoreCase(message)){
System.out.println("请输入要下载的文件");
String fileName = sc.next();
tm.content = fileName;
tm.itemType = 2;
Util.sendMessage(oos, tm);
// 文字内容
}else {
tm.content = message;
tm.itemType = 0;
Util.sendMessage(oos, tm);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
}
Transmission.java
import java.io.Serializable;
public class Transmission implements Serializable {
private static final long serialVersionUID = -5814716593800822421L;
// 用户名
public String userName;
//文件名称
public String fileName;
//传输内容
public String content;
//消息类型 0 普通内容 1 传输文件 2 下载文件
public int itemType = 1;
}
Util.java
import java.io.*;
import java.net.Socket;
// 工具类
public class Util {
// 发送文件
public static void sendFile(Socket socket, String path){
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
// 1.从文件一读取内容
bis = new BufferedInputStream(new FileInputStream(path));
bos = new BufferedOutputStream(socket.getOutputStream());
// 缓冲区字节数组1024
byte[] arr = new byte[1024];
int res = 0;
while ((res = bis.read(arr)) != -1) { // 循环每次读取1024个字节,当为-1时结束循环
bos.write(arr, 0, res);
bos.flush();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(null != bis) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 发送消息
public static void sendMessage(ObjectOutputStream oos, Transmission tm) {
try {
oos.writeObject(tm); // 传输
oos.reset();
} catch (IOException e) {
e.printStackTrace();
}
}
// 下载文件
public static void downloadFile(Socket socket, String path){
BufferedOutputStream bos = null;
BufferedInputStream bis = null;
try {
// 1.从文件一读取内容
bis = new BufferedInputStream(socket.getInputStream());
bos = new BufferedOutputStream(new FileOutputStream(path));
// 缓冲区字节数组1024
byte[] arr = new byte[1024];
int res = 0;
while ((res = bis.read(arr)) > 0) { // 循环每次读取1024个字节
bos.write(arr, 0, res);
bos.flush();
if(res < 1024){ //当res不大于1024时结束循环
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(null != bos){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}