在前面的文章里,我阐述过自己使用Java编写简易的快递管理系统的过程,这一次的更新算是一个跨度较大的更新,包含了全新的内容。对此有兴趣的伙伴可以继续往下阅读了解:
2.0版本新加入的特性:
- 使用链表存储数据;
- 加入服务器端(Server)和客户端(Client),并且加入了数据交互的操作;
- 加入文件存储部分,使用了文件流的功能
- 使用了“封装传参”的方法,将传输的数据都封装成参数再进行阐述。
接下来就对这些功能进行详细概述:
思路框架
本次设计的大体思路基本和前一次相同,都是面向对象进行编程。设计思路为:使用服务器和客户端进行交互,从客户端传入想要实现的命令,然后由服务器完成各项功能后再把数据返回。基于这一点,本次整个系统将分为5个类(class)进行编写,他们分别是:
快递类(Express)
作用:定义快递的数据结构和封装方式
管理类(Manage)
作用:对快递进行管理,实现各种操作
服务端(Server)
作用:接收客户端的数据,并执行客户端发送的请求
客户端(Client)
作用:使用快递系统,向服务端传输想要执行的任务
**指令端(Command)
作用:封装参数的传递方式==(难点)==
本文旨在阐述新特性的使用,因此,“快递类(Express)”的编写就不再赘述,大家可以参考上周的博文。
新功能概述
Part I. Socket 类的使用
- 首先创建客户端和服务端,请看如下代码:
// Server端
// 搭建服务器(参数为port)
ServerSocket server = new ServerSocket(5601);
System.out.println("服务器搭建成功,等待连接...");
// 等待连接
Socket s = server.accept();
// Client端
// 建立客户端,并连接到服务器(参数为:proxy,port)
Socket connect = new Socket("localhost", 5601);
- 接下来使用流来进行数据通信:
// 数据输出(传送)
OutputStream os = s.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("确认系统已启动!");
// 数据输入(接收)
InputStream is = connect.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String text = br.readLine();
System.out.println(text);
此时,可以从客户端控制台打印到“确认系统已启动!”的提示信息。
注意:在进行数据交互时,一定要避免双方都等待接收或双方都传送数据的情况发生,否则程序将出现死锁的状态,只能强制停止执行。
Part II. 文件读写操作
由于本次文件会存储对象数据,因此使用序列化进行读写操作,请看如下代码:
- 将数据写入文件的方法:
public void writeIn() throws IOException, ClassNotFoundException {
// 把快递信息录入文件
File f = new File("Express.txt");
if (f.length()!=0){
// 文件长度不为0时,把数据先读出,再追加写入
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Express.txt"));
try{
LinkedList<Express> saveBox = (LinkedList<Express>)ois.readObject();
box.addAll(saveBox);
}catch (EOFException ignored){
}
ois.close();
// 序列化操作
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Express.txt"));
out.writeObject(box);
out.close();
}else {
// 文件长度为0时,直接写入
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Express.txt"));
out.writeObject(box);
out.close();
}
}
- 将数据读出的方法
public void loadAll() throws IOException, ClassNotFoundException {
// 加载文件内的快递信息
File f = new File("Express.txt");
f.createNewFile();
ObjectInputStream ois = null;
try{
// !文件没有数据时,就不进行加载
ois = new ObjectInputStream(new FileInputStream("Express.txt"));
LinkedList<Express> saveBox = (LinkedList<Express>)ois.readObject();
// 添加快递数据到LinkedList内部
box.addAll(saveBox);
}catch (EOFException ignored){
}
if (ois!=null){
ois.close();
}
}
Part III. 客户端与服务器端数据不共通(出错点)
编码时,第一次把管理快递的方法都放在Server端调用(即只在Server端实例化Manage),结果就出现了录入快递信息是在服务器端输入的情况,与实际使用相比稍有不合理,于是就想进行修改,让录入快递数据操作在Client端执行。只是万万没想到,这竟然是噩梦的开始——
第一次修改:在Client端也实例化一个Manage对象,调用快递录入的方法进行快递存储,结果在服务器端使用查看等方法时,发现链表里没有数据,即出现了下图所示的报错信息:
第二次修改:在Manage内部把存储数据的链表改成静态(加上static修饰符),失败;
第三次修改:尝试把Server 端实例化的对象给到Client 端进行引用,即Client端使用Server端的实例化对象中的方法,仍然失败;
最终解决方案:在第三次修改后的基础上,Client端实例化“快递”对象(Express),等Client端添加好之后,再把对象数据发送到Server端,再让Server 端存储到链表中。最终成功。
问题原因:Client与Server运行时,二者使用的JVM(虚拟机)不同,因此数据不能直接进行共享,只能传输。
Extra. 序列化传递参数(难点)
在客户端与服务端进行数据传输时,发现3个功能下,传输的数据类型,数据量是不同的,并且在传输快递数据时,需要进行对象传送,需要实例化操作。因此干脆把需要传输的数据全部先进行对象化,然后再进行传输操作。
通过分析,本次客户端向服务端传输的数据可以分为两部分:一是操作指令(数字功能序号),二是参数传递(快递对象,取件码)。因此,再次新增Command类,直接对两个参数进行封装,再进行使用。
请看如下操作代码(同时注意看后面的源代码):
class Command
import java.io.Serializable;
/**
* 请求命令对象
*
* type : 这个命令的类型(添加、删除、查询)
* body : 这个命令的请求体
*
*/
public class Command implements Serializable {
// 使用时注意添加Serializable接口
private int type;
private byte[] body;
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public byte[] getBody() {
return body;
}
public void setBody(byte[] body) {
this.body = body;
}
}
使用实例:Client端传入快递信息
// 传参对象进行字符数组序列化的准备
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
byte[] bytes = null;
// 在客户端进行快递录入操作
// 创建一个快递对象
Express ex = manage.createItem();
// 将快递对象进行序列化为byte数组
oos.writeObject(ex);
oos.flush()