物联网智能家居环境监测项目多线程说明
在Server模块的实现过程中,我们使用的是单线程来处理客户端。即开启一次服务器,受理一个客户端,仅接受一个集合。
在实际项目运行过程中,考虑到实际情况,多采用多线程的形式同时处理多个客户端。例如本项目,可能存在一个中央服务器,而有多个采集子系统在运行,向服务器发送数据。
但是本项目所搭建的架构,并非特别适合于多线程处理。在模块构建方面只是简单地模拟了一下基于TCP协议的网络传输过程。
Server模块中的reciver()方法返回值为Collection<Environment>集合,本意是模块对象调用一次该方法,接受一个客户端,则返回一个集合。也就是单线程处理过程。

先说一下多线程处理的思路:
其实很简单,只需要让服务器循环调用ss.accpet()方法,近乎时刻处于侦听状态。那么问题来了,之前服务器调用ss.accpet()方法会得到一个代表客户端的Socket对象,我们后续所有的工作都是基于这个Socket对象来实现的。现在如何让服务器接收到Socket之后立刻放下手头的工作回到侦听状态?

实现原理其实也很简单,这里使用到多线程的思想:
在服务器接收到一个客户端的连接请求后,立刻开启一条线程,把Socket对象交由这条线程去处理。

基本代码如下:
//创建服务器套接字
int port = 8989;
ServerSocket ss = new ServerSocket(port);
//可以使用无限循环让服务器始终处于开启状态
//也可以设置一个标志位来手动控制服务器的关闭
while(true){
//有客户端发来请求
Socket socket = ss.accpet();
//创建一条线程处理这个客户端
new ClientThread(socket).start();
}
这里new线程并启动只是一个时间点上所做的事情,执行完毕之后当前while循环代码全部执行完毕,会进入到下一次循环中,也就是再一次调用accept()进入侦听状态等待下一个客户端的连接。近乎做到了时刻处于侦听状态的效果。当然在单核CPU计算机中不存在绝对的同步,只是CPU运行、分配时间片和切换速度很快,我们肉眼无法察觉。在java代码中创建一条线程并调用start()方法耗时是非常非常短的。所以在这里可以近乎看做服务器永远处在accept()方法的侦听过程中。

这里一定要注意的是,服务器只开启一次,循环体内的写的代码试是用来多次调用accept()方法接受客户端的请求。之前有同学把创建服务器的步骤也写到了循环里,其实违背了多线程的初衷,也没有真正起到同步运行的目的。

ClientThread就是我们自己定义的一个线程类,现在我们把每次拿到的Socket交给它,它就可以帮助我们去实现之前我们写在reciver()方法里的功能。例如:获取套接字输入流、获取客户端传输过来的数据清单(即采集模块采集到的Collection集合)。但是这样定义的话,我们就必须在线程中调用DBStore模块的入库方法,在线程中执行入库操作。因为线程中接收到的数据清单想要再交给Server模块去处理不太容易。但是并非不可,只是需要做很麻烦的处理,得不偿失,我们没有必要去纠结这个问题。

现在回到前文所述的问题,为什么说本模块的编写方式不适合多线程处理?
试想,现在有10个客户端同时连接服务器,开始传输数据。根据上文所述,我们需将DBStore模块入库的操作放在线程里执行,那Server模块中的reciver()方法返回什么?该方法声明是存在返回值的,必须要返回一个值回去。所以在这个地方有一点的歧义。我们如果想要单纯测试一下多线程的编写方法,可以把Server接口中的返回值声明为void。

但是这样做又存在一个新的问题,我们在项目中如何实现模拟多个客户端同时连接同时发送数据?
当然了大家可能会想到直接把客户端的测试类执行三次,但是我们现在已经引入了备份模块,每次解析完原始数据,再去解析都会跳过已经读取过的字符。如果想模拟这个过程的同学,可以把采集模块代码复制三分,分别去采集三个不同的原始文件。

给大家一份线程代码的简单实现自己参考一下:
class ClientThread extends Thread {

// 客户端套接字
//Server模块在创建线程时通过构造器传入
private Socket client;
public ClientThread(Socket socket) {
this.client = socket;
}

@Override
public void run() {
// 客户端套接字的输入流
InputStream is;
try {
is = client.getInputStream();
// 使用对象输入流进行包装
ObjectInputStream ois = new ObjectInputStream(is);

Collection<Environment> coll = (Collection<Environment>) ois.readObject();
System.out.println("接收完毕!总数据量:" + coll.size());
DBStore dbStore = new DBStoreImpl();
dbStore.saveDb(coll);
} catch (Exception e) {
e.printStackTrace();
}
}
}