第5章
CHAPTER 5

物联网应用层



5.1物联网服务器
5.1.1基于Java的Web服务器搭建

本书中采用Java Web技术,开发物联网服务器端软件。为实训项目提供门户展示、信息管理功能,同时为硬件端和移动端提供接口。基于Java的Web服务器搭建需要几个步骤,下面依次介绍。
1. JDK的安装
JDK的安装文件可以从Oracle公司的网站https://www.oracle.com/下载。JDK的安装步骤如下。 
(1) 双击运行安装文件jdk9.0.4_windowsx64_bin.exe,会出现Java SE开发工具包安装向导,如图51所示。


图51Java SE开发工具包安装向导


(2) 单击图51中的“下一步”按钮,会出现JDK安装目录的选择界面,指定JDK安装目录为C:\Program Files\Java\jdk9.0.4,然后单击“下一步”按钮,如图52所示。


图52选择JDK安装目录







(3) 出现JDK安装进度条,如图53所示。


图53JDK安装进度条


(4) 进度条结束后,出现指定JRE安装目录的界面。此处指定JRE的安装目录为C:\Program Files\Java\jre9.0.4,然后单击“下一步”按钮,如图54所示。


图54选择JRE安装路径


(5) 选择好JRE路径,会出现Java安装进度条,如图55所示。


图55Java安装进度条界面


(6) 显示JDK安装成功界面,如图56所示。


图56JDK安装成功界面


(7) JDK安装完成后,配置环境变量。新建JAVA_HOME环境变量JAVA_HOME=C:\Program Files\Java\jdk9.0.4,修改path环境变量,在path变量尾部添加%JAVA_HOME%\bin。新建classpath环境变量classpath=.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\tools.jar。
JDK需要配置以上3个环境变量JAVA_HOME、path和classpath;  JDK1.5版本之后可以不再设置classpath,但建议保留classpath设置。
2. Tomcat的安装和使用
Tomcat的安装文件可以从Apache公司的官方网站http://tomcat.apache.org/index.html下载,采用绿色版本apachetomcat9.0.0.M9,解压到指定目录下即可。启动和关闭Tomcat服务器的文件位于Tomcat主目录下的bin文件夹下,文件名分别为startup.bat和shutdown.bat。
(1) 执行startup.bat,启动Tomcat,如图57所示。


图57启动Tomcat


(2) Tomcat正常启动后,打开浏览器,在地址栏输入URL: http://127.0.0.1:8080,将看到如图58所示界面。 


图58Tomcat系统首页


(3) Tomcat提供服务的默认端口号是8080,当与其他应用程序的端口号发生冲突时,将无法正常启动。此时,可修改Tomcat的端口号为其他未被占用的端口号。修改Tomcat端口号的位置在Tomcat主目录下的conf文件夹下,文件名为server.xml。可以用记事本等文本编辑工具将其打开,重新改写端口号,如图59所示。


图59改写Tomcat端口号


(4) Tomcat的主目录下有若干目录,其用途如表51所示。 


表51Tomcat目录结构及用途



目录用途
\bin
放置启动和关闭Tomcat的可执行文件和批处理文件
\lib放置Tomcat运行所需要加载的jar包
\conf放置Tomcat主要的配置文件
\logs放置Tomcat日志文件
\temp放置Tomcat运行时产生的临时文件
\webapps放置Web应用的目录
\work放置JSP页面转换成对应的Servlet的目录

3.  Eclipse的安装和使用


图510解压并运行Eclipse
集成开发环境


Eclipse的安装文件eclipseinstwin64.exe可以从http://www.eclipse.org/downloads/下载, 用户可以下载解压版使用。
(1) 将Eclipse的解压版文件eclipsejeeoxygen2win32x86_64.zip解压之后,双击eclipse.exe图标,运行Eclipse集成开发环境,如图510所示。
(2) 打开Eclipse Launcher对话框,提示用户选择Eclipse的工作空间,如图511所示。 


图511选择Eclipse的工作空间


(3) 打开Eclipse后,可在Servers选项卡中,配置应用服务器Tomcat,如图512所示。


图512在Eclipse中配置应用服务器


(4) 单击创建新服务器链接,会出现如图513所示的对话框,在对话框中选择解压或安装过的Tomcat应用服务器。


图513在Eclipse中关联Tomcat应用服务器


(5) 在Eclipse中关联Tomcat后,需要进行服务器名、主机名、运行时环境、服务器位置、部署路径等配置,如图514所示。


图514Tomcat服务器配置


关于Eclipse中集成的JDK版本和安装位置,可以在如图515所示的对话框中进行查看和修改。
可在如图516所示的对话框中查看和编辑Eclipse中的运行时环境关联情况,即Tomcat应用服务器的版本选择。
单击图516中的Edit按钮,即可进入图517所示的对话框,编辑Eclipse的运行时环境配置,包括Tomcat的版本、位置和JDK版本。



图515Eclipse中的JDK版本和安装位置




图516Eclipse中的运行时环境设置



4. MySQL的安装和配置
本书的实训项目“智能停车场”,将车位信息数据、用户数据等存储于MySQL数据库。
(1) 从MySQL数据库的官网http://www.mysql.com下载与操作系统匹配的MySQL安装文件,单击后进入的首页如图518所示。



图517编辑服务器运行时环境




图518MySQL官网首页


(2) 单击DOWNLOADS→Community,选择MySQL Community Server,如图519所示。 



图519选择下载MySQL Community Server


(3) 找到Recommended Download,单击Go to Download Page,如图520所示。 


图520下载Windows版本MySQL安装文件



(4) 进入下载界面,如图521所示; 选择No thanks, just start my download开始下载。



图521MySQL下载界面



(5) 在图522所示的界面中,选择MySQL安装类型为Developer Default,单击Next按钮,进入下一步。


图522选择MySQL安装类型


(6) 在如图523所示的界面中,检查MySQL的安装条件。如需要Microsoft Visual C++包的支持,则选择补充安装。


图523检查MySQL安装条件


(7) 在图524所示的界面中,单击Execute按钮执行安装,在后续的界面中,均可默认单击Next按钮,如图525所示。


图524Installation界面




图525Product Configuration界面


(8) 在如图526所示的High Availability界面中,选中Standalone MySQL Server/Classic MySQL Replication单选按钮。


图526High Availability界面


(9) 在如图527所示的界面中,设置MySQL的服务配置类型和网络设置。


图527网络设置对话框


(10) 在如图528所示的界面中,设置MySQL的root用户密码,然后单击Next按钮,进入下一步。



图528root密码设置


(11) 在图529所示的界面中,将MySQL服务配置为Windows服务,并配置在系统启动时自动启动服务,启动服务时使用标准系统账户等信息。


图529Windows服务设置


(12) 在图530所示的界面中,单击Execute按钮,即可由系统进行关闭现有服务、写配置文件、更新防火墙、启动服务、应用安全设置等安装进程了。全部执行完毕后会出现图531所示的界面。


图530执行安装过程




图531完成应用配置


(13) 进入产品配置界面,如图532所示,安装完成界面如图533所示。


图532Product Configuration界面




图533MySQL安装完成界面


(14) 在图534所示的界面中,通过Windows的服务组件管理MySQL服务的启动方式,可设置为自动或手动,可设置为本地系统账户登录。


图534测试连接MySQL服务


(15) 安装完成后进入MySQL的安装目录,进入MySQL Sever,其目录下的文件如图535所示。 


图535MySQL Server 8.0目录结构


(16) bin目录下保存了MySQL常用的命令工具以及管理工具、data目录是MySQL默认用来保存数据文件以及日志文件的地方(我的因刚安装还没有data文件夹)、docs目录下是MySQL的帮助文档、include目录和lib目录是MySQL所依赖的头文件以及库文件、share目录下保存目录文件以及日志文件。
进入bin目录,按住Shift键,然后单击鼠标右键可以选择在该目录下打开命令窗口,或者在地址栏中输入cmd进入命令窗口,输入mysqlu rootp后按Enter键,然后会提示输入密码,输入密码后就会进入MySQL的操作管理界面。
5. Navicat的安装和使用
Navicat是一套数据库开发工具,用户可以利用该工具同时连接 MySQL、MariaDB、MongoDB、SQL Server、Oracle、PostgreSQL 和 SQLite 数据库。它与 Amazon RDS、Amazon Aurora、Amazon Redshift、Microsoft Azure、Oracle Cloud、MongoDB Atlas、阿里云、腾讯云、华为云等云数据库兼容,通过Navicat可以在可视化界面下快速轻松地创建、管理和维护数据库,下载网址是https://www.navicat.com.cn/,其安装过程比较简单,在此不再赘述。
5.1.2数据库访问工具类的封装
服务器采用JDBC技术与数据库连接,并访问数据库内容。JDBC(Java Database Connectivity,Java 数据库连接)是一种用于数据库访问的Java API(Application Programming Interface,应用程序设计接口),由一组用Java语言编写的类和接口组成。有了JDBC,就可以用纯Java语言和标准的SQL语句编写完整的数据库应用程序,并且真正实现软件的跨平台性。
简单地说,JDBC 能完成下列三件事。 
(1) 为同一个数据库建立连接。 
(2) 向数据库发送 SQL 语句。 
(3) 处理数据库返回的结果。
为便于项目开发的层次化和模块化,将服务器访问数据库的流程封装为工具类DBTool,将数据库的连接、关闭、查询、更新、分页显示方法封装于DBTool类,其类图如图536所示。 


图536DBTool类图


该工具类将访问数据库的连接关闭、参数设置,结果集到Java集合类对象的转换都定义为私有方法,仅将查询方法、更新方法、和分页显示方法设置为公有方法,供其他层次的开发者调用,从而提高了代码复用率,进而提高了项目开发效率。
DBTool代码如下。



package util;



import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.ResultSetMetaData;

import java.sql.SQLException;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;





public class DBTool {

private String driver;

private String url;

private String username;

private String password;

private Connection con;

private PreparedStatement pstmt;

public static final long PAGE_REC_NUM=8;

public void setDriver(String driver) {

this.driver = driver;

}

public void setUrl(String url) {

this.url = url;

}

public void setUsername(String username) {

this.username = username;

}

public void setPassword(String password) {

this.password = password;

}

public DBTool() {

driver = "com.mysql.cj.jdbc.Driver";

url = "jdbc:mysql://localhost:3306/meal?serverTimezone=UTC";

username = "root";

password = "root";

}







private void init() {


try {

Class.forName(driver); 

con = DriverManager.getConnection(url, username, password); 

} catch (ClassNotFoundException e) {

e.printStackTrace();

} catch (SQLException e) {

e.printStackTrace();

}



}

private void close() {

if(pstmt!=null)

try {

pstmt.close();

} catch (SQLException e) {

e.printStackTrace();

}

if(con!=null)

try {

con.close();

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

private void setParams(String[] params) { 

if(params!=null) {

for(int i=0;i<params.length;i++) { 

try {

pstmt.setString(i+1, params[i]);

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} 

}

}

}

public int update(String sql,String[] params) { 

int result = 0;

init();

try {

pstmt = con.prepareStatement(sql);

setParams(params);

result = pstmt.executeUpdate();







} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}finally {

close();

}

return result;

}

public int update(String sql) { 

return update(sql, null);

}

public List<Map<String, String>> getList(String sql,String[] params){

List<Map<String, String>> list = null;

init();

try {

pstmt = con.prepareStatement(sql);

setParams(params);

ResultSet rs = pstmt.executeQuery();

list = getListFromRS(rs); 

rs.close();

} catch (SQLException e) {

e.printStackTrace();

}finally {

close();

}

return list;

}

private List<Map<String, String>> getListFromRS(ResultSet rs) throws SQLException {

List<Map<String, String>> list = new ArrayList<Map<String,String>>();

ResultSetMetaData rsmd = rs.getMetaData();

while(rs.next()) {

Map<String, String> m = new HashMap<String, String>();

for(int i = 1;i <= rsmd.getColumnCount();i++) {

String colName = rsmd.getColumnLabel(i);

String value = rs.getString(colName);

if(value != null) {

m.put(colName, value);

}

}

list.add(m);

}

return list ;

}

public List<Map<String, String>> getList(String sql){

return getList(sql, null);







}

public Map<String, String> getMap(String sql,String[] params){

Map<String, String> m = new HashMap<String, String>();

List<Map<String, String>> list = getList(sql, params);

if(list!=null&&list.size()!=0) {

m = list.get(0);

}

return m;

}

public Map<String,String> getMap(String sql){

return getMap(sql, null);

}

public Map<String,Object> getPage(String sql,String[] params,String curPage){

Map<String, Object> page = new HashMap<String, Object>();

String newSql = sql+" limit "+(Long.parseLong(curPage)-1)*PAGE_REC_NUM+","+PAGE_REC_NUM;

List<Map<String, String>> pageList = getList(newSql, params);

sql = sql.toLowerCase();

String countSql = "";

if(sql.indexOf("group")>=0) {

countSql = "select count(*) as tempNum from("+sql+") as temp";

}

else {

countSql = "select count(*) as tempNum "+sql.substring(sql.indexOf("from"));

}

String count_s = (String)getMap(countSql, params).get("tempNum");

long count= Long.parseLong(count_s); 

long totalPage = 0;

if(count%PAGE_REC_NUM==0)

totalPage = count/PAGE_REC_NUM;

else

page.put("list", pageList);

page.put("totalPage", totalPage);

page.put("recNum", PAGE_REC_NUM);

return page;

}

}



5.1.3服务器端数据管理功能
服务器端的数据管理功能涉及项目的具体业务逻辑。例如,智能停车场系统,其主要业务逻辑包括车辆进出场的RFID刷卡识别、进出场时长统计、计费管理等。如果是环境监测系统,则主要的业务逻辑包括环境数据的显示、实时更新和报表统计。无论项目的具体业务逻辑如何,在服务器端对项目数据的管理,基本上涵盖增加、删除、修改、查询这些典型的业务逻辑。下面以用户管理为例,阐述服务器端的数据管理功能。
(1) 在Eclipse中新建动态Web工程,如图537所示。


图537在Eclipse中新建动态Web工程


(2) 单击Next按钮,将输出文件夹改为WebContent\WEBINF\classes,如图538所示。
(3) 单击Finish按钮,即可新建Web工程。在WEBINF的lib文件夹下复制MySQL驱动jar包,如图539所示。
(4) 在工程名上右击,在弹出的快捷菜单中选择build path→cofigure build path选项,进入如图540所示的界面。 
(5) 单击Add JARs按钮,进入jar包选择和添加界面,如图541所示。 
(6) 导入jar包之后,单击OK按钮,完成数据库驱动包的导入。
下面即可新建工具类DBTool,在工程目录树的src处右击,新建class,指定包名和类名,如图542所示。
编写DBTool的代码,如5.1.2小节数据库访问工具类的封装。下面就可以开始用户管理功能的实现了。用户管理功能基于数据库中的user表。在MySQL中的数据库名为parking。



图538修改Web工程输出文件夹




图539复制MySQL驱动包




图540configure build path界面




图541选择并添加jar包




图542创建DBTool类


在parking数据库中的user表,定义了智能停车场的用户信息,用户在停车场中凭RFID刷卡出入,如图543所示。


图543parking数据库中的user表内容


首先创建UserService类。封装停车场用户管理的业务逻辑,包括用户开卡、注销、修改停车卡信息和查询停车卡信息4个业务逻辑。UserService位于model包下,其类图如图544所示。 


图544UserService类图


UserService代码如下所示。



package model;



import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;



import tools.DBTool;



public class UserService {

private DBTool db;



public UserService() {

db = new DBTool();

}

public List<Map<String, String>> getUsers(String name){

List<Map<String, String>> list=new ArrayList<Map<String,String>>();

String sql = "select * from user";

String[] params = null;

if(name!=null) {

sql = sql+" where name like ?";

params = new String[] {"%"+name+"%"};

}

list = db.getList(sql, params); 

return list;

}

public int delUser(String cardid) {

String[] params = {cardid};

String sql = "delete from user where cardid = ?";

return db.update(sql, params);

}






public int addUser(String cardid,String name,String count,String license,String phone) {

String sql = "select * from user where cardid=?";

List<Map<String, String>> list = db.getList(sql,new String[] {cardid});

if(list.size()!=0)

return 0;

else {

sql = "insert into user values(?,?,?,?,?,now())";

return db.update(sql, new String[] {cardid,name,count,license,phone});

}		

}

public Map<String, String> getUser(String cardid){

Map<String, String> user = new HashMap<String, String>();

String sql = "select * from user where cardid = ?";

user = db.getMap(sql, new String[] {cardid});

return user;

}

public int updateUser(String cardid,String name,String count,String license,String phone) {

String sql = "update user set name=?,count =?,license=?,phone=?,opendate=now() where cardid=?";

return db.update(sql, new String[] {name,count,license,phone,cardid});

}

}


查询用户信息的界面为list_user.jsp,代码如下。 



<%@page import="java.util.Map"%>

<%@page import="java.util.List"%>

<%@page import="model.UserService"%>

<%@ page language="java" contentType="text/html; charset=UTF-8"

pageEncoding="UTF-8"%>

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>Insert title here</title>

</head>

<body>

<% String s_un = request.getParameter("name");

UserService us = new UserService();

List<Map<String,String>> users = us.getUsers(s_un);

%>

<div align = "center">

<form action="list_users.jsp" method = "post">

<input type="text" name = "s_un" placeholder="请输入用户名查询">







<input type = "submit" value="搜索">

</form>

<a href = "add_user.html">添加停车用户卡</a>

<table border="1">

<tr>

<th>序号</th>

<th>卡号</th>

<th>用户名</th>

<th>停车次数</th>

<th>车牌号</th>

<th>电话</th>

<th>办卡时间</th>

<th>操作</th>

</tr>

<%

int num = 0;

for(Map<String,String> user:users){

num++;

%>

<tr>

<td><%=num %></td>

<td><%=user.get("cardid") %></td>

<td><%=user.get("name") %></td>

<td><%=user.get("count") %></td>

<td><%=user.get("license") %></td>

<td><%=user.get("phone") %></td>

<td><%=user.get("opendate") %></td>

<td><a href="del_user.jsp?cardid=<%=user.get("cardid")%>">删除</a> 

<a href="edit_user.jsp?cardid=<%=user.get("cardid")%>">修改</a></td>

</tr>

<%

}

%>

</table>

</div>

</body>

</html>



删除用户信息的界面为del_user.jsp,代码如下。



<%@page import="java.util.Map"%>

<%@page import="java.util.List"%>

<%@page import="model.UserService"%>

<%@ page language="java" contentType="text/html; charset=UTF-8"

pageEncoding="UTF-8"%>

<!DOCTYPE html>






<html>

<head>

<meta charset="UTF-8">

<title>删除用户</title>

</head>

<body>

<%

String cardid = request.getParameter("cardid");

UserService us = new UserService();

int r = us.delUser(cardid);

if(r==1)

out.println("删除用户成功!");

else

out.println("删除用户失败");

%>

<a href = "list_users.jsp">返回用户列表</a>

</body>

</html>



用户的增加和修改功能,用户可以参照以上的用户列表和用户删除功能,自行实现,在此不再赘述。
5.1.4为硬件端和移动端提供服务
作为物联网服务器,除了门户展示和信息管理功能之外,另一个重要功能是为物联网系统中的硬件端和移动端提供服务,使得硬件端实时采集的数据可以上传至服务器持久化存储,并由服务器进行后续的数据分析处理。同时使得移动端可以通过服务器实时查看数据变化,或发送指令给服务器,硬件端查询后,执行器件做出相应的改变。
下面以智能停车场的车位泊车信息为例,实现硬件端接口和移动端接口。智能停车场的车位信息对应数据库中的psd表,其表结构如图545所示。


图545psd表结构


其中,node表示停车位节点,status表示停车位状态。每个停车位上方部署有超声测距传感器,测量传感器与地面距离。没有车辆泊车的情况下,距离约为3.5m,当有车辆泊车的情况下,测距结果小于2.5m。依据这个原理进行车位空闲和占用的判断。硬件端会将测距结果发送给服务器,服务器端则修改psd表中的status状态,从而存储车位的泊车状态。
移动端通过网络通信框架访问服务器端,查询psd表中对应的车位节点的status状态,从而实现用户通过Android APP远程查看车位占用情况。
因此,服务器提供两套接口,分别为硬件端和移动端。硬件端组成ZigBee网络后,终端节点部署于每个车位上方,I/O端口连接超声测距传感器,检测车位状态,发送给协调器,协调器将信息发送至服务器。协调器与服务器端的接口采用Servlet编写,代码如下。
Coord.java



package parkinglot;



import java.io.IOException;

import java.sql.Date;

import java.sql.Timestamp;

import java.text.DateFormat;

import java.text.SimpleDateFormat;

import java.util.Iterator;

import java.util.List;

import java.util.Locale;

import java.util.Map;



import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;



import utils.DBUtil;



/**

 * Servlet implementation class Coord

 */

@WebServlet("/Coord")

public class Coord extends HttpServlet {

private static final long serialVersionUID = 1L;



/**

* @see HttpServlet#HttpServlet()

*/

public Coord() {

super();

// TODO Auto-generated constructor stub

}


/**

* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)







*/

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

DBUtil db;

db = new DBUtil();

String sql;



String cardId = null;

cardId = request.getParameter("card");



String role = null;

role = request.getParameter("role");



String dis = null;

dis = request.getParameter("dis");



String temp = null;

String hu = null;

temp = request.getParameter("temp");

hu = request.getParameter("hu");

System.out.println("yes");



if(cardId != null) {

if(role != null) {

if(role.equals("enter")) {

sql="insert into card(cardid,starttime,status) VALUES('" + cardId + "',now(),'泊车');";

db.update(sql);

sql="select * from card where cardid='" + cardId + "';";

List<Map<String, String>> list = db.getList(sql);

Iterator<Map<String, String>> iter = list.iterator();



//inverse control relay to control machine

if (list.isEmpty()){

response.getWriter().append("0");

} else {

response.getWriter().append("1");

}

} else if (role.equals("exit")) {

//update endtime

sql="update card set endtime=now() where cardid='" + cardId + "';";

db.update(sql);



//acquire charge rule -- fee

sql="select fee from chargerule;";

Map<String,String> res = db.getMap(sql);







String feeString = res.get("fee");

int fee = Integer.parseInt(feeString);



int charge = 0;



//acquire timestamp through cardid

sql="select * from card where cardid='" + cardId + "';";

res = db.getMap(sql);

//calculate min

try {

Timestamp times = string2Time(res.get("starttime"));

Timestamp timee = string2Time(res.get("endtime"));



long nd = 1000 * 24 * 60 * 60;

long nh = 1000 * 60 * 60;

long nm = 1000 * 60;

// ms

long diff = timee.getTime() - times.getTime();

// min

long min = diff % nd % nh / nm;

charge = (int) ((min/15)*fee);

} catch (Exception e) {

e.printStackTrace();

}



//update status and charge

sql="update card set status='离开',charge='" + charge + "' where cardid='" + cardId + "';";

db.update(sql);



request.getRequestDispatcher("UpdateUser?cardid=" + cardId).forward(request, response);

}

}

}



if(dis != null && role != null) {

int disint = Integer.parseInt(dis);

//no car

if(disint >= 100) {

sql="update psd set status='空闲' where node='" + role + "';";

} else {

sql="update psd set status='泊车' where node='" + role + "';";

}

db.update(sql);

}








if(temp != null && hu != null && role != null) {

System.out.println("温湿度");

sql="insert into dht11(temp,hu,time,node) VALUES('" + temp + "','" + hu + "',now(),'" + role + "');";

db.update(sql);

}



}



/**

* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)

*/

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

doGet(request, response);

}





/**

*method 将字符串类型的日期转换为一个Date(java.sql.Date)

*@param dateString 需要转换为Date的字符串

*@return dataTime Date

*/



public final static java.sql.Date string2Date(String dateString)

throws java.lang.Exception {

DateFormat dateFormat;

dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);

dateFormat.setLenient(false);

java.util.Date timeDate = dateFormat.parse(dateString);   //util类型

java.sql.Date dateTime = new java.sql.Date(timeDate.getTime());   //sql类型

return dateTime;

}



/**

*method 将字符串类型的日期转换为一个timestamp(时间戳记java.sql.Timestamp)

*@param dateString 需要转换为timestamp的字符串

*@return dataTime timestamp

*/



public final static java.sql.Timestamp string2Time(String dateString)

throws java.text.ParseException {

DateFormat dateFormat;

dateFormat = new SimpleDateFormat("yyyy-MM-dd kk:mm:ss.SSS", Locale.ENGLISH);  //设定格式







//dateFormat = new SimpleDateFormat("yyyy-MM-dd kk:mm:ss", Locale.ENGLISH);

dateFormat.setLenient(false);

java.util.Date timeDate = dateFormat.parse(dateString);   //util类型

java.sql.Timestamp dateTime = new java.sql.Timestamp(timeDate.getTime());   

//Timestamp类型,timeDate.getTime()返回一个long型

return dateTime;

}



}


移动端采用Android技术实现,用户通过移动端远程查看车位状态。移动端通过网络请求,访问服务器。服务器端为移动端开发提供的接口代码如下。
QueryAllPSDData.java



package parkinglot;



import java.io.IOException;

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

import java.util.Map;



import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;



import bean.PSD;

import utils.DBUtil;



/**

 * Servlet implementation class QueryAllUsers

 */

@WebServlet("/QueryPSDData")

public class QueryPSDData extends HttpServlet {

private static final long serialVersionUID = 1L;



/**

* @see HttpServlet#HttpServlet()

*/

public QueryPSDData() {

super();

// TODO Auto-generated constructor stub

}







/**

* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)

*/

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

doPost(request, response);

}



/**

* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)

*/

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

request.setCharacterEncoding("utf-8");

response.setContentType("text/html;charset=utf-8");

response.setCharacterEncoding("utf-8");



List<PSD> psdList = new ArrayList<PSD>();



DBUtil db = new DBUtil();

String sql;



sql="select * from psd;";

List<Map<String, String>> list = db.getList(sql);

Iterator<Map<String, String>> iter = list.iterator();



if (list.isEmpty()){

System.out.println("query nothing!");

} else {

while (iter.hasNext()) {

Map<String, String> tempmap = (Map<String, String>) iter.next();

PSD psd = new PSD();

psd.setNode(tempmap.get("node"));

psd.setStatus(tempmap.get("status"));

psdList.add(psd);

}

}





request.setAttribute("psdList",psdList);

request.getRequestDispatcher("psdlist.jsp").forward(request, response);

}



}


5.2物联网移动端
5.2.1Android平台

Android平台是我们生活中接触较多的平台,许多手机厂商是基于Android定制的系统,很多开发商开发基于Android平台的App,在物联网中使用Android开发嵌入式控制程序。可以说,Android是人们工作和生活中不可缺少的平台。因此,本书所研究的项目对于Android平台专门开发了一款App。图546是一个简化的Android软件层次结构。


图546Android软件层次结构


5.2.2Android Studio环境的安装
IDE是Intelligent Development Environment 的简称,即智能开发环境。Android IDE是为Android应用开发提供支持的开发软件,有关Android的项目和代码将在 Android IDE中管理。Android IDE是一个集成开发环境,常用的Android IDE有Eclipse+ADT、ADTBundle和Android Studio。本书采用Android Studio作为移动端程序的集成开发环境。下面介绍Android Studio的安装配置过程。
(1) 从Android Studio官方网址http://www.androidstudio.org/下载Android Studio,界面如图547所示。


图547Android Studio官方网站


(2) 下载好安装包之后,单击Next按钮进行安装,如图548所示。


图548Android安装开始界面


(3) 在选择组件对话框中,选择安装Android Studio虚拟机,如图549所示。
(4) 在如图550所示的界面中,选择Android Studio的安装路径。
(5) 在如图551所示的界面中,为Android Studio选择一个开始菜单文件夹,以便创建编程中使用到的快捷键。 
(6) 进入安装界面及安装完成界面如图552~图554所示。


图549选择组件界面




图550选择Android Studio安装路径




图551选择Android Studio开始菜单文件夹





图552Android Studio安装进度




图553Android Studio安装完成界面1




图554Android Studio安装完成界面2



(7) 在导入Android Studio设置选项中,选择Do not import settings,如图555所示。


图555暂不导入Android Studio设置


(8) 在如图556所示的界面中,提示无法找到Android SDK,此时单击Cancel按钮。


图556取消Android SDK获取


(9) 进入Android Studio的安装设置欢迎界面,如图557所示。


图557Android Studio设置欢迎界面


(10) 在安装类型中,选择标准安装,如图558所示。


图558选择安装类型


(11) 选择Android Studio的界面风格界面,可以选择自己熟悉的编程界面风格,如图559所示。


图559选择Android Studio界面风格


SDK是Software Development Kit 的简称,即软件开发工具包,一般是被软件工程师用于为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件的开发工具的集合。
在Android 中,Android SDK为开发者提供了库文件以及其他开发所用到的工具。简单理解为 Android 开发工具包集合,是整体开发中所用到的工具包。这里需要指定SDK的本地路径,如果之前计算机中已经存在SDK,可以指定该路径,后续就可以不用下载SDK; 这里暂时可以指定一个后续将保存SDK的路径,如图560所示。随后进入下载组件过程,如图561和图562所示。


图560指定SDK路径




图561下载组件过程1




图562下载组件过程2


组件下载完毕后,Android Studio即安装成功,如图563所示。


图563Android Studio 安装成功


单击图563中的Start a new Android Studio project新建一个工程,进入如图564所示界面,选择Empty Activity作为一个新的界面。
在如图565所示的界面中,新建一个新的工程,并加以配置。建立新工程后会出现工程编辑界面,如图566所示。



图564新建一个Activity




图565新建一个新的Android工程





图566工程编辑界面


工程建立完成后,系统开始构建gradle,如图567所示。


图567构建gradle


gradle构建成功之后,出现如图568所示的界面,代表一个Android Studio工程已经建立成功。即可在编码区域开始工程的编码实现,如图569所示。


图568Android Studio工程建立成功




图569Android Studio工程代码编辑区域


Android工程编码完成后,可通过单击Build→Build Bundle(s)/APK(S)选项,生成Android APK文件,用于在Android模拟器或Android系统的真机上运行测试,如图570所示。


图570生成Android APK


5.2.3Android网络通信
OkHttp框架是一个处理网络请求的开源项目,是安卓端的轻量级框架,由Square公司开发,用于替代HttpUrlConnection和Apache HttpClient。OkHttp框架可以支持Android 2.3及以上版本,需要JDK 1.7及以上版本。本书介绍利用OkHttp框架实现安卓端与服务器网络通信的方法。
OkHttp框架优点如下。 
(1) 允许连接到同一个主机地址的所有请求,提高请求效率。 
(2) 共享Socket,减少对服务器的请求次数。
(3) 通过连接池,减少了请求延迟。 
(4) 通过缓存响应数据来减少重复的网络请求。 
(5) 减少了对数据流量的消耗。 
(6) 自动处理GZip压缩。
使用OkHttp框架之前,需要在工程中引入OkHttp包,OkHttp框架还依赖另一个okio包,同样需要引入,代码如下所示; 也可以在相应的model中的build.gradle配置文件中填入,然后将工程同步,环境会自动下载需要的包。



compile 'com.squareup.okhttp3:okhttp:3.2.0'

compile 'com.squareup.okio:okio:1.7.0'

//可以修改版本号



需要在Android工程中设置网络权限:



<user-permission andriod:name="andriod.premission.INTERNET"/>  <!--用户连接网络权限-->



OkHttp框架的使用涉及OkHttpClient、RequestBody、Request、Call、Response等基本类。
(1) OkHttpClient: 对于该类创建对象实例化的方式有默认的标准形式和自定义形式两种。
 标准形式采用如下代码进行实例化。



OkHttpClient mOkHttpClient = new OkHttpClient();



 以自定义形式实例化OkHttpClient对象时,可以设置网络连接的超时时长、读取超时时长和写入超时时长,可以调用build()方法进行实例化,代码如
下。



OkHttpClient mOkHttpClient = new OkHttpClient.Builder()

.connectTimeout(10,TimeUtil.SECONDS)     //为新连接设置默认连接超时时长,第一个参数是时长,第二个参数是单位

.readTimeout(10,TimeUtil.SECONDS)       //设置新连接的默认读取超时时长

.writeTimeout(10,TimeUtil.SECONDS)      //设置新连接的默认写入超时时长

.cache(setCache)    //设置用于读取和写入缓存响应的响应缓存[1]

.build();



在如上代码的[1]处,需要一个参数为Cache的对象,如下代码定义了Cache对象,大小是10×1024×1024,在访问File对象指定的路径filePath时,可以扩展缓存区域大小。



//[1]:参数为Cache对象

File filePath = new File(getExternalCacheDir(),"netCache");

int cacheSize = 10 * 1024 * 1024;

Cache setCache = new Cache(filePath,cacheSize);



在OkHttp2.x版本中,设置以上超时时长的代码有所不同,如下所示。



mOkHttpClient.setConnectTimeout(10,TimeUtil.SECONDS);

mOkHttpCLient.setConnectTimeout(10,TimeUtil.SECONDS);

mOkHttpClient.setConnectTimeout(10,TimeUtil.SECONDS);

mOkHttpClient.setCache(setCache);



(2) RequestBody: 该类是用于封装OkHttp框架进行网络通信时的请求体,适用于post请求方式,用于上传数据到服务器。其核心方法有如下4个。



public abstract MediaType contentType()

public long MediaType contentLength()

public abstract void writeTo(BufferedSink sink)

public static RequestBody create(MediaType contentType,String content) [2]



其中,MediaType可以是多种类型,该类的对象实例化方式如下。



//[2]:第二个参数可以是多种类型;该类存在create的多个重载方法 

//该类的对象实例化方式:

MediaType mMediaType = MediaType.parse("application/octet-stream");    

//http://tool.oschina.net/commons

File putFile = new File(Environment.getExternaStorageDirectory(),"文件名.扩展名"); 

//父路径和子路径两个参数拼接起来为文件地址绝对地址

RequestBody mRequestBody = RequestBody.create(mMediaType,putFile);  



RequestBody的create()方法需要两个参数: 指定要上传文件的类型和文件对象本身。如果用create()方法上传键值对类型的数据,可使用FormBody,代码如下。



//使用create()方法一般用于上传文件或字符串类型数据,对于上传键值对类型数据将会使用FormBody

RequestBody mRequestBody = FormBody.Builder()

.add("key","value1")

.add("key","value2")

.build();



(3) Request: 该类用于生成网络连接请求对象,包括请求参数、请求头、请求方式等多种信息,常用方法有url()、post()、method()、headers()等。



Request mRequest = new Request.Builder()

.url("http://www.baidu.com")

.post(mRequestBody)  [4]

.build();



请求体可以通过post()方法提交,如果不明确调用post()方法,则可使用get()方法请求网络。使用post()方法上传数据比get()方法更安全,数据大小不受限制,多个参数可以封装成请求体上传,对于长表单数据、上传文件,首选post()方法请求网络。
(4) Call: 该类是网络请求执行的最后一个需要实例的对象,该类提供了网络请求的同步与异步连接方式,网络连接属于耗时操作,因此不能编写在UI线程中。如果在网络请求后,需要更新UI布局就需要调用Handler,实现Handler的runOnUiThread()方法如下。



Call mCall = mOkHttpClient.newCall(mRequest);

//使用异步网络连接

mCall.enqueue(new CallBack(){

@override

public void onFailure(Call call,IOException e){

//网络连接失败

}

@override

public void onResponse(Call call,Response response){

if(response.isSuccessful()){

//请求数据成功,返回code为200~300    [5]   

}else{

//请求数据失败

}

}

});



其中,请求数据成功,服务器将返回响应状态码。响应状态码是在程序中经常需要判断的一个值,响应状态码大致分为: 
 1××: 信息,表示请求收到,继续处理;
 2××: 成功,表示请求成功;
 3××: 重定向,为完成请求客户需进一步细化请求;
 4××: 由客户端引发的错误;
 5××: 由服务器引发的错误。
因此,2××(以2开头的状态码)表示请求成功。判断请求是否成功,及判断是否得到了请求成功的响应状态码,response.isSuccessful()的源码如下。



public boolean isSuccessful(){

return code >= 200 && code < 300;

}



使用同步方式请求网络,在Android编程中被归类为耗时操作,耗时操作不允许在UI主线程中运行,因此需要开启新的子线程,代码如下。




new Thread(new Runnable(){

Call mCall = mOkHttpClient.newCall(mRequest);

Response mResponse = mCall.execute();  //涉及的Response对象知识点

if(mResponse.isSuccessful()){

//网络同步请求成功,更新UI布局需要使用runOnUiThread()方法

}else{

 

}








}).start();    //创建一个子线程后需要使用start()方法启动该线程

 

注:runOnUiThread(new Runnable(){

@override

public void run(){

//更新UI的逻辑代码

}

});

 

//同步方法会阻塞当前线程的执行,异步方法不会阻塞当前线程的执行



(5) Response: 该类是网络请求后的响应信息对象,对于服务器返回的数据均存放在该示例对象中,而且对于Response实例一次请求中只能有一次有效调用,如果调用两次将会出现程序错误,这就使得在需要多次使用数据前要将Response实例中的数据保存下来,Response类提供了多种方法,如body、code、protocol、request、isSuccessful、headers(响应头对象)、toString等。



Response mResponse = mCall.execute();

if(mResponse.isSuccessful()){

String str = mResponse.body().string();  [6]

int code = mResponse.code();

Protocol protocol = mResponse.protocol();

Request request = mResponse.request();

String head = mResponse.toString();    

Log.i("Response响应信息集:","" + str + code + protocol + request + head);

 }



基于以上五个类,就可以写出基本的get()、post()请求,进行同步或异步的网络连接了。对于OkHttpClient表示HTTP请求的客户端类,大多数情况下推荐只使用创建一个该对象的实例,全局使用。
下面是一个完整的关于post()请求的异步方法示例。 



public class PostAsyn(){

private TextView tvShowNetInfo;

public static OkHttpClient okHttpClient;

static(

okHttpClient = new OkHttpClient.Builder()

.connectTimeout(15,TimeUnit.SECONDS)

.readTimeout(15,TimeUnit.SECONDS)

.writeTimeout(15,TimeUnit.SECOND)

.build();

);

@Overvide

public void onCreate(Bundle saveInstanceState){









super.onCreate(saveInstanceState);

setContentView(R.layout.activity_main);

tvShowNetInfo = (TextView) findViewId(R.id.tv_show_net_info);

postAsynDate();

}

public void postAsynData(){

RequestBody requestBody = new FormBody.Builder()

.add("key1","value1")

.add("key2","value2")

.build();

Request requset = new Request.Builder()

.url("http://www.baidu.com")

.post(requestBody)

.build();

Call call = okHttpClient.newCall(request);

call.enqueue(new Callback(){

@Overvide

public void onFailure(Call call,IOException e){

goUiThread("网络连接失败");

}

@Overvide

public void onResponse(Call call,Response response){

if(response.isSuccessful()){

goUiThread("请求数据成功,响应码:" + response.code());

}else{

goUiThread("网络连接成功,但是没有响应数据,响应码:" + response.code());

}

}

}



);

}

private void goUiThread(final String str){

runOnUiThread(new Runnable(){

@Overvide

public void run(){

tvShowNetInfo.setText(str);

}

});

}

 }
 

//对应的布局文件略




5.3习题
1. 下列()选项不是JSP运行所必需的软件环境。 


A. 操作系统B. JavaJDK

C. 支持JSP的Web服务器D. 数据库

2. 在JDBC中,用来描述结果集的接口是()。

A. StatementB. Connection

C. ResultSetD. DriverManager
3. 在JDBC API中,下列()接口或类可以用来保存从数据库返回的查询结果。

A. ResultSetB. Connection

C. StatementD. DriverManager
4. 在JDBC API中,下列()接口或类可以用来执行SQL语句。

A. ResultSetB. Connection

C. StatementD. DriverManager
5. 下述选项中不属于JDBC基本功能的是()。

A. 与数据库建立连接B. 提交SQL语句

C. 处理查询结果D. 数据库维护管理
6. 假设已创建了语句对象名为sta,下列语句中错误的是()。

A. sta.executeUpdate("delete from food where price < 0");

B. sta.executeQuery("delete from food where price < 0");

C. sta.execute("delete from food where price <  0");

D. sta.executeQuery("select * from food where price < >  0");
7. 创建JDBC的数据库连接对象,下列语句中正确的是()。

A. Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mealsystem", "root", "root");

B. Connection conn = Class.forName("jdbc:mysql://127.0.0.1:3306/mealsystem", "root", "root");

C. Connection conn = Driver.getConnection("jdbc:mysql://127.0.0.1:3306/mealsystem", "root", "root");

D. Connection conn = DriverManager.getConnection("com.mysql.jdbc.Driver", "root", "root");
8. 下列关于JDBC说法中错误的是()。

A. JDBC使得编程人员从复杂的驱动器调用命令和函数中解脱出来,可以致力于应用程序中的关键地方

B. JDBC支持非关系数据库,如NoSQL等

C. 用户可以使用JDBCODBC桥驱动器将JDBC函数调用转换为ODBC

D. JDBC API是面向对象的,可以让用户把常用的方法封装为一个类以备后用
9. 请阐述MVC设计模式的含义,并说明M、V、C各自代表什么层次。




10. 下列关于MVC的说法中错误的是()。


A. 在MVC模式中,如果哪一层的需求发生了变化,只需要更改相应层的代码而不会影响其他层中的代码

B. 在MVC模式中,所有的核心业务逻辑都应该放在控制层实现

C. 在MVC模式中,由于按层把系统分开,那么就能更好地实现开发中的分工

D. 使用MVC模式,有利于组件的重用
11.  Android应用程序需要打包成()文件格式在手机上安装运行。

A. classB.  .xmlC. .apkD.  dex
12. 在Activity的生命周期中,当Activity被某个AlertDialog覆盖一部分后,会处于()状态。

A.  暂停B. 活动C. 停止D. 销毁
13. Android项目启动时最先加载的是AndroidManifest.xml文件,如果有多个Activity,以下()属性决定了该Activity最先被加载。

A. android.intent.action.LAUNCH

B. android:intent.action.ACTIVITY

C. android:intent.action.MAIN

D. android:intent.action.VIEW
14. 下列关于Handler的说法中不正确的是()。

A. 它是实现不同进程间通信的一种机制

B. 它采用队列的方式来存储Message

C. Handler既是消息的发送者也是消息的处理者

D. 它是实现不同线程间通信的一种机制
15. 下列()不是Android的存储方式。

A. FileB. SharedPreferences
C. SQLiteD. ContentProvider
16. Android支持的4大重要组件,分别是Activity、、Service和Content Provider。

17. Android的事件处理机制有两种: 一种是基于回调的处理机制; 另一种是。