33130| 35
|
[项目] 小题大做之远程LED控制 |
这个项目主要是为了给孩纸们透彻解释B/S的运作方式,涉及到的技术点比较杂,不过,项目仅仅控制几个LED灯、收集温度数值,故而称之为小题大做;P。 预备工作: 1. 工信部为了打击钓鱼网站,将家庭宽带的80端口都封掉了,且光猫没有固定IP地址。试图直接访问80端口的话,显示的是光猫的维护页面。为了实现从外网访问这个小小的装置,我向Oray购买了一个域名(www.openkevin.net)和花生壳内网版商业服务。 2. 花生壳内网版安装在一台老式的Lenovo E49上,开启内网映射。外网80端口直接穿透到内网192.168.1.5这台笔记本电脑的80端口上。如此这般,依赖花生壳动态解析,实现了只要能上网的地方,就能访问我们的装置。由于通过花生壳服务的转发,境外访问可能要比境内访问快。 3. Lenovo E49上安装了Tomcat 8.0.15作为Web服务器(Servlet/JSP container)。官方Ethernet Shield的兼容卡DFRobot Ethernet W5100将作为Web Client访问该服务器,进行后续的数据交换。 4. 后台数据库MySQL 5.6.12(配合图形界面Workbench 6.2CE),对孩纸们学习SQL很有帮助。真是羡慕如今的小孩纸,老夫想当年是生活在dBase III时代啊。 5. Servlet/JSP开发环境用了NetBeans IDE 8.0.2,估计要被Eclipse们嘲笑了,哈哈哈。老夫上手写Java时用的是Forte for Java,结果落下这个毛病。孩纸们以后多亲近亲近Eclipse,显得多么滴高大上呀。 |
6. 代码部分 有了以上这些解释,想必孩纸们都能直接看代码了。老夫就不多费口舌,老夫领进门,运作靠个人,孩儿们操练起来。 6.1 首页Web page代码(mys.html): <!DOCTYPE html> <html> <head> <title>Remotely controlled LEDs</title> <meta charset="UTF-8"> </head> <body> <br><br> <form method="POST" action="http://www.openkevin.net/servlet/myservlet1?p=1"> <table align="center" cellpadding="0" cellspacing="0"> <tr> <td colspan="3" width="100" height="1" bgcolor="#AAAAAA"></td> </tr> <tr> <td width="1" height="170" bgcolor="#AAAAAA"></td> <td width="150" height="200" align="center"><img src="img/led01.jpg" width="100" height="170"></td> <td width="1" height="170" bgcolor="#AAAAAA"></td> </tr> <tr> <td colspan="3" width="100" height="1" bgcolor="#AAAAAA"></td> </tr> <tr> <td colspan="3" width="100" height="35" align="center"><input type="submit" value=" Next " style="border:1px solid; border-radius:15px; border-color:grey; font-family:Arial; font-size:15px;"></td> </tr> </table> </form> </body> </html> 效果图: 6.2 myservlet1.java: import java.io.IOException; import java.io.PrintWriter; 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 java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.sql.ResultSet; @WebServlet(urlPatterns = {"/myservlet1"}) public class myservlet1 extends HttpServlet { protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); String p = request.getParameter("p"); String a4 = "0"; String a5 = "0"; String a6 = "0"; String a7 = "0"; String n = "0"; String a4s = "0"; String a5s = "0"; String a6s = "0"; String a7s = "0"; Connection conn = null; Statement stmt = null; ResultSet rs1 = null; int s = 0; try (PrintWriter out = response.getWriter()) { if (p.equals("1")) { try { conn = DriverManager.getConnection("jdbc:mysql://localhost/mydata?" + "user=root&password=15186okia"); stmt = conn.createStatement(); System.out.println("=="); System.out.println("查询最后一条记录..."); rs1 = stmt.executeQuery("SELECT * FROM t1 ORDER BY aid DESC LIMIT 1;"); while(rs1.next()) { n = rs1.getString(2); if (n.equals("1")) { a4s = "1"; } else { a4s = "0"; } n = rs1.getString(3); if (n.equals("1")) { a5s = "1"; } else { a5s = "0"; } n = rs1.getString(4); if (n.equals("1")) { a6s = "1"; } else { a6s = "0"; } n = rs1.getString(5); if (n.equals("1")) { a7s = "1"; } else { a7s = "0"; } } } catch (SQLException ex) { System.out.println("Error in connection: " + ex.toString()); System.out.println("SQLException: " + ex.getMessage()); System.out.println("SQLState: " + ex.getSQLState()); System.out.println("VendorError: " + ex.getErrorCode()); } /* TODO output your page here. You may use following sample code. */ out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<title>Remote Controlled LEDs.</title>"); out.println("<meta charset=\"UTF-8\">"); out.println("</head>"); out.println("<body onLoad=\"setForm();\">"); out.println("<br><br>"); out.println("<form id=\"f1\" method=\"POST\" action=\"http://www.openkevin.net/servlet/myservlet1?p=2\">"); out.println("<table align=\"center\" border=\"1\">"); out.println("<tr>"); out.println("<td>A4:</td>"); if (a4s.equals("0")) { out.println("<td><input type=\"radio\" name=\"a4\" value=\"0\" checked>Off</td>"); out.println("<td><input type=\"radio\" name=\"a4\" value=\"1\">On</td>"); } else { out.println("<td><input type=\"radio\" name=\"a4\" value=\"0\">Off</td>"); out.println("<td><input type=\"radio\" name=\"a4\" value=\"1\" checked>On</td>"); } out.println("</tr>"); out.println("<tr>"); out.println("<td>A5:</td>"); if (a5s.equals("0")) { out.println("<td><input type=\"radio\" name=\"a5\" value=\"0\" checked>Off</td>"); out.println("<td><input type=\"radio\" name=\"a5\" value=\"1\">On</td>"); } else { out.println("<td><input type=\"radio\" name=\"a5\" value=\"0\">Off</td>"); out.println("<td><input type=\"radio\" name=\"a5\" value=\"1\" checked>On</td>"); } out.println("</tr>"); out.println("<tr>"); out.println("<td>A6:</td>"); if (a6s.equals("0")) { out.println("<td><input type=\"radio\" name=\"a6\" value=\"0\" checked>Off</td>"); out.println("<td><input type=\"radio\" name=\"a6\" value=\"1\">On</td>"); } else { out.println("<td><input type=\"radio\" name=\"a6\" value=\"0\">Off</td>"); out.println("<td><input type=\"radio\" name=\"a6\" value=\"1\" checked>On</td>"); } out.println("</tr>"); out.println("<tr>"); out.println("<td>A7:</td>"); if (a7s.equals("0")) { out.println("<td><input type=\"radio\" name=\"a7\" value=\"0\" checked>Off</td>"); out.println("<td><input type=\"radio\" name=\"a7\" value=\"1\">On</td>"); } else { out.println("<td><input type=\"radio\" name=\"a7\" value=\"0\">Off</td>"); out.println("<td><input type=\"radio\" name=\"a7\" value=\"1\" checked>On</td>"); } out.println("</tr>"); out.println("<tr>"); out.println("<td colspan=\"3\" align=\"center\"><input type=\"submit\" value=\"Submit...\"></td>"); out.println("</tr>"); out.println("</table>"); out.println("</form>"); out.println("</body>"); out.println("</html>"); } else { if (!request.getParameter("a4").equals("1")) { a4 = "0"; } else { a4 = "1"; } if (!request.getParameter("a5").equals("1")) { a5 = "0"; } else { a5 = "1"; } if (!request.getParameter("a6").equals("1")) { a6 = "0"; } else { a6 = "1"; } if (!request.getParameter("a7").equals("1")) { a7 = "0"; } else { a7 = "1"; } try { conn = DriverManager.getConnection("jdbc:mysql://localhost/mydata?" + "user=root&password=15186okia"); stmt = conn.createStatement(); System.out.println("=="); System.out.println("追加记录......"); s = stmt.executeUpdate("INSERT INTO t1 (a4,a5,a6,a7) VALUES ('" + a4 + "','" + a5 + "','" + a6 + "','" + a7 + "')"); } catch (SQLException ex) { System.out.println("Error in connection: " + ex.toString()); System.out.println("SQLException: " + ex.getMessage()); System.out.println("SQLState: " + ex.getSQLState()); System.out.println("VendorError: " + ex.getErrorCode()); } /* TODO output your page here. You may use following sample code. */ out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<title>Remote Controlled LEDs.</title>"); out.println("<meta charset=\"UTF-8\">"); out.println("</head>"); out.println("<body onLoad=\"setForm();\">"); out.println("<br><br>"); out.println("<form id=\"f1\" method=\"POST\" action=\"http://www.openkevin.net/servlet/myservlet1?p=2\">"); out.println("<table align=\"center\" border=\"1\">"); out.println("<tr>"); out.println("<td>A4:</td>"); if (a4.equals("0")) { out.println("<td><input type=\"radio\" name=\"a4\" value=\"0\" checked>Off</td>"); out.println("<td><input type=\"radio\" name=\"a4\" value=\"1\">On</td>"); } else { out.println("<td><input type=\"radio\" name=\"a4\" value=\"0\">Off</td>"); out.println("<td><input type=\"radio\" name=\"a4\" value=\"1\" checked>On</td>"); } out.println("</tr>"); out.println("<tr>"); out.println("<td>A5:</td>"); if (a5.equals("0")) { out.println("<td><input type=\"radio\" name=\"a5\" value=\"0\" checked>Off</td>"); out.println("<td><input type=\"radio\" name=\"a5\" value=\"1\">On</td>"); } else { out.println("<td><input type=\"radio\" name=\"a5\" value=\"0\">Off</td>"); out.println("<td><input type=\"radio\" name=\"a5\" value=\"1\" checked>On</td>"); } out.println("</tr>"); out.println("<tr>"); out.println("<td>A6:</td>"); if (a6.equals("0")) { out.println("<td><input type=\"radio\" name=\"a6\" value=\"0\" checked>Off</td>"); out.println("<td><input type=\"radio\" name=\"a6\" value=\"1\">On</td>"); } else { out.println("<td><input type=\"radio\" name=\"a6\" value=\"0\">Off</td>"); out.println("<td><input type=\"radio\" name=\"a6\" value=\"1\" checked>On</td>"); } out.println("</tr>"); out.println("<tr>"); out.println("<td>A7:</td>"); if (a7.equals("0")) { out.println("<td><input type=\"radio\" name=\"a7\" value=\"0\" checked>Off</td>"); out.println("<td><input type=\"radio\" name=\"a7\" value=\"1\">On</td>"); } else { out.println("<td><input type=\"radio\" name=\"a7\" value=\"0\">Off</td>"); out.println("<td><input type=\"radio\" name=\"a7\" value=\"1\" checked>On</td>"); } out.println("</tr>"); out.println("<tr>"); out.println("<td colspan=\"3\" align=\"center\"><input type=\"submit\" value=\"Submit...\"></td>"); out.println("</tr>"); out.println("</table>"); out.println("</form>"); out.println("</body>"); out.println("</html>"); } } } // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code."> /** * Handles the HTTP <code>GET</code> method. * * @param request servlet request * @param response servlet response * @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs */ @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } /** * Handles the HTTP <code>POST</code> method. * * @param request servlet request * @param response servlet response * @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs */ @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } /** * Returns a short description of the servlet. * * @return a String containing servlet description */ @Override public String getServletInfo() { return "Short description"; }// </editor-fold> } 6.3 myservlet2.java: import java.io.IOException; import java.io.PrintWriter; 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 java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.sql.ResultSet; public class myservlet2 extends HttpServlet { protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String a4 = "0"; String a5 = "0"; String a6 = "0"; String a7 = "0"; String n = "0"; response.setContentType("text/text"); try (PrintWriter out = response.getWriter()) { Connection conn = null; Statement stmt = null; ResultSet rs1 = null; int s = 0; try { conn = DriverManager.getConnection("jdbc:mysql://localhost/mydata?" + "user=root&password=15186okia"); stmt = conn.createStatement(); System.out.println("=="); System.out.println("查询最后一条记录..."); rs1 = stmt.executeQuery("SELECT * FROM t1 ORDER BY aid DESC LIMIT 1;"); while(rs1.next()) { n = rs1.getString(2); if (n.equals("1")) { a4 = "1"; } else { a4 = "0"; } n = rs1.getString(3); if (n.equals("1")) { a5 = "1"; } else { a5 = "0"; } n = rs1.getString(4); if (n.equals("1")) { a6 = "1"; } else { a6 = "0"; } n = rs1.getString(5); if (n.equals("1")) { a7 = "1"; } else { a7 = "0"; } } } catch (SQLException ex) { System.out.println("Error in connection: " + ex.toString()); System.out.println("SQLException: " + ex.getMessage()); System.out.println("SQLState: " + ex.getSQLState()); System.out.println("VendorError: " + ex.getErrorCode()); } /* TODO output your page here. You may use following sample code. */ //out.println("<!DOCTYPE html>"); //out.println("<html>"); //out.println("<head>"); //out.println("<title>Servlet myservlet2</title>"); //out.println("<meta charset=\"UTF-8\">"); //out.println("</head>"); //out.println("<body>"); //out.println("<br><br>"); //out.println("UNO Var = " + uno + "<br>"); out.println("A4=" + a4 + ",A5=" + a5 + ",A6=" + a6 + ",A7=" + a7); //out.println("</body>"); //out.println("</html>"); } } // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code."> /** * Handles the HTTP <code>GET</code> method. * * @param request servlet request * @param response servlet response * @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs */ @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } /** * Handles the HTTP <code>POST</code> method. * * @param request servlet request * @param response servlet response * @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs */ @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } /** * Returns a short description of the servlet. * * @return a String containing servlet description */ @Override public String getServletInfo() { return "Short description"; }// </editor-fold> } 6.4 myservlet3.java: import java.io.IOException; import java.io.PrintWriter; 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 java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.sql.ResultSet; public class myservlet3 extends HttpServlet { protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); try (PrintWriter out = response.getWriter()) { String unoTemp = request.getParameter("unoVar"); Connection conn = null; Statement stmt = null; ResultSet rs1 = null; int s = 0; try { conn = DriverManager.getConnection("jdbc:mysql://localhost/mydata?" + "user=root&password=15186okia"); stmt = conn.createStatement(); System.out.println("=="); s = stmt.executeUpdate("INSERT INTO t2 (b1) VALUES (ROUND(" + unoTemp + ",2));"); System.out.println("温度写入完成..."); } catch (SQLException ex) { System.out.println("Error in connection: " + ex.toString()); System.out.println("SQLException: " + ex.getMessage()); System.out.println("SQLState: " + ex.getSQLState()); System.out.println("VendorError: " + ex.getErrorCode()); } } } // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code."> /** * Handles the HTTP <code>GET</code> method. * * @param request servlet request * @param response servlet response * @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs */ @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } /** * Handles the HTTP <code>POST</code> method. * * @param request servlet request * @param response servlet response * @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs */ @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } /** * Returns a short description of the servlet. * * @return a String containing servlet description */ @Override public String getServletInfo() { return "Short description"; }// </editor-fold> } 6.5 Project_LED_01.ino: #include <SPI.h> #include <Ethernet.h> #include <OneWire.h> #include <DallasTemperature.h> #define ONE_WIRE_BUS 3 OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; IPAddress server(192, 168, 1, 5); IPAddress ip(192, 168, 1, 76); EthernetClient client; char c; String result; int myCount = 0; float unoTemp = 0.00; char unoTemps[6]; void setup() { Serial.begin(9600); Ethernet.begin(mac, ip); sensors.begin(); delay(1000); pinMode(4, OUTPUT); pinMode(5, OUTPUT); pinMode(6, OUTPUT); pinMode(7, OUTPUT); Serial.println("Ethernet connecting..."); } void loop() { if (client.connect(server, 80)) { myCount++; if (myCount <= 60) { Serial.println("Connected"); client.println("POST http://192.168.1.5/servlet/myservlet2 HTTP/1.1"); client.println("Host:192.168.1.5"); client.println(); delay(1000); while(client.available()) { c = client.read(); result += c; } Serial.println("=="); Serial.println(result.substring(147,170)); if (result.substring(147,151).equals("A4=1")) { digitalWrite(4, HIGH); } else { digitalWrite(4, LOW); } if (result.substring(152,156).equals("A5=1")) { digitalWrite(5, HIGH); } else { digitalWrite(5, LOW); } if (result.substring(157,161).equals("A6=1")) { digitalWrite(6, HIGH); } else { digitalWrite(6, LOW); } if (result.substring(162,166).equals("A7=1")) { digitalWrite(7, HIGH); } else { digitalWrite(7, LOW); } } else { sensors.requestTemperatures(); unoTemp = sensors.getTempCByIndex(0); dtostrf(unoTemp, 3, 2, unoTemps); String temps = (String)unoTemps; Serial.println(temps); delay(20); Serial.println("Connected"); client.println("POST http://192.168.1.5/servlet/myservlet3?unoVar=" + temps + " HTTP/1.1"); client.println("Host:192.168.1.5"); client.println(); delay(500); myCount = 0; } Serial.println(); client.stop(); result = ""; } else { Serial.println("connection failed"); } } |
本帖最后由 kevinzhang19701 于 2014-12-24 18:29 编辑 5. 技术要点 5.1 花生壳解析 孩纸们都知道DNS的作用,简单滴说就是将域名字符串转换成IP地址。各级DNS都有配对表,自己找不到配对的,就向上级DNS询问,直到找到或者返回失败,最后更新自己的配对表(动态的或者静态的)。如果用Whois查询我购买的域名的话,就可以看到其实这个域名并不属于我,而是属于Oray公司,再由Oray公司租给我使用。DNS能够正确找到并翻译成IP地址,不过,这个IP是Oray的,而不是我光猫上的那个。 花生壳客户端安装在我的笔记本电脑上后,会产生一个指纹。客户端用这个指纹向其服务器汇报自己的位置信息(包含了光猫的动态IP地址、端口占用信息)。当外网访客访问我购买的域名的时候,DNS解析到Oray的IP地址,由服务器查到我光猫的位置,然后转发所有的数据包。那么孩纸们会问,80端口被封了,数据包怎么穿透呢?道理很简单,根本没用80端口。 花生壳客户端会随机试探高段端口号,找到一个未被光猫封掉的,就向服务器汇报这个端口号,让所有转发进来的数据包通过这个端口号进来。数据包返回,按照同样的路径。 如此这般,外网访客访问域名的时候(默认80端口),其实所有数据包是转发到我的光猫IP地址的某个大数字端口上。孩纸们,现在知道天朝网络慢的原因了吧。其实,这也是冰山一角,待以后我给你们解释我们伟大的放火长城的时候,再深入讨论(你没看错,是放火,天朝网民火很大)。 5.2 Tomcat & Java WIKIPEDIA是这么定义的:Apache Tomcat (or simply Tomcat, formerly also Jakarta Tomcat) is an open source web server and servlet container developed by the Apache Software Foundation (ASF). Tomcat implements several Java EE specifications including Java Servlet, JavaServer Pages (JSP), Java EL, and WebSocket, and provides a "pure Java" HTTP web server environment for Java code to run in. 所以,我们选用Tomcat不仅仅因为她是一个性能不错的Web Server和Servlet container,还在于她遵循J2EE规范,是孩纸们未来步入J2EE的一个入口,而J2EE的跨平台特性是目前.Net无法比拟的。如果未来服务器访问频繁的话,可以在Tomcat前面装上Apache,让静态和动态内容分开处理。 在安装Tomcat前,需要先安装Java环境。同样道理,选用最新版本的Java SE 8u25。砖家们一直对Java技术褒贬不一,不过这些并不妨碍Java成为伟大的跨平台开发技术(微软的C#或多或少都在东施效颦)。物联网作为一个热词,最近一二年在孩纸们间流行,其实在20年前Sun基于Java的Jini技术就已经构想了万物相连的远景,只是Sun太过贪婪,此是后话,暂且按下不表。 Tomcat各版本的目录和文件结构都相同,这是向下兼容的好处。更新Tomcat后,只要把文件拷贝过来就行。注意下列几个目录与文件: (a) conf\server.xml,这个是配置文件,里面重要的是定义了Web server的端口。Tomcat默认8080,本项目修改成80(其他端口也可以,不过尽量选用高段端口,避开常用的)。 (b) webapps\ROOT,这个目录是网页的老家。这里的index.html是默认情况下的首页文件。 (c) webapps\ROOT\WEB-INF,这个目录下面都是项目文件,重要的有web.xml、classes目录和lib目录。web.xml文件里面主要涉及各个servlet程序的配置。classes目录里面保存的是我们的3个servlet编译好的class文件。lib里面存放着的是MySQL的Connector/J的jar接口文件,没有她servlet就没法与MySQL数据沟通。 (d) bin\startup.sh和shutdown.sh(对应Windows系统下startup.bat和shutdown.bat)是启动和关闭Tomcat的两个shell文件。 5.3 Servlet/JSP WIKIPEDIA如此说:Technically speaking, a "servlet" is a Java class in Java EE that conforms to the Java Servlet API, a standard for implementing Java classes which respond to requests. Servlets could in principle communicate over any client–server protocol, but they are most often used with the HTTP protocol. Thus "servlet" is often used as shorthand for "HTTP servlet". Thus, a software developer may use a servlet to add dynamic content to a web server using the Java platform. The generated content is commonly HTML, but may be other data such as XML. Servlets can maintain state in session variables across many server transactions by using HTTP cookies, or URL rewriting. Architecturally, JSP may be viewed as a high-level abstraction of Java servlets. JSPs are translated into servlets at runtime; each JSP servlet is cached and re-used until the original JSP is modified. 上述解释了servlet与JSP的区别和关系,本项目没有使用JSP,是因为在显示这部分我们着力不多。无论是来自于外网的访客,还是来自于内网的W5100,他们都向Web server提交HTTP请求,而接管这些请求的是Tomcat。Tomcat将普通的HTML请求,直接返回给客户端,如果遇到servlet调用,那么直接将控制权交给servlet。这种客户端使用Browser(浏览器),服务器端Web Server的运作结构,被称为Browser/Server模式(简称B/S)。这种模式的好处是,我们对客户端的维护将降到极点(只要有浏览器就能用我们的项目),开发人员将更多滴关注服务器端,避免被客户端干扰,也减少了客户端维护的成本。当然一剑双刃,B/S模式严重依赖服务器的数据,且所有计算均在服务器端,服务器一旦挂掉,所有客户端均死翘翘了。 所有的servlet均是Java程序,本项目使用NetBeans这个IDE来开发。用IDE的好处,就像使用DFRobot I/O板一样,可以偷懒一点,让我们更多滴关注项目逻辑本身,快速切入主题,而不要被其他东西干扰。Servlet的介绍可以写一本超过1000页的教科书,这里不打算做过多讨论。幸运的是IDE里面有现成的servlet模板,我们将自己的逻辑代码嵌入即可。另一个高大上的IDE是Eclipse,正如她的名字那样是针对Sun的,哈哈哈,查查字典就知道了。 由于我们使用servlet去与数据库MySQL打交道,以完成数据的查询和写入的工作,因此孩纸们要快速学习一点SQL语言。 5.4 SQL 估计看到这里,孩纸们是不是感觉头晕了吧。要的就是这个恶心效果,哈哈哈,不要小看了一个小小的采集温度和点亮4个LED的项目。老夫一直是小题大做滴大湿啊,跟着老夫定然让你头晕目眩,自我感觉极端爆表。 SQL的定义: Structured Query Language is a special-purpose programming language designed for managing data held in a relational database management system(RDBMS), or for stream processing in a relational data stream management system(RDSMS). |
本帖最后由 kevinzhang19701 于 2014-12-29 17:14 编辑 5.5 SQL & MySQL SQL使用query与服务器打交道,来提交查询或者获得结果,主要应用于关系型数据库系统,应用面较为广泛。SQL的S指structured,这种结构化查询语言是高级的、非过程化编程语言。允许用户在高层数据结构上工作,她不要求用户指定数据的存放方法,也不需要用户了解具体的数据存放方式,所以具有完全不同底层结构的不同数据库系统上可以使用相同的结构化查询语言作为数据输入与管理的接口。结构化查询语言语句还可以嵌套,这使她具有极大的灵活性和强大的功能。绝大多数的应用场合,完成一项任务仅需一条SQL命令即可,这也是其神奇的地方。 MySQL遵循ANSI SQL92标准且对其进行了扩展,这意味着ANSI SQL92的所有语法,MySQL都兼容可执行。 本项目中使用下列几条SQL命令,他们是---- (a) 查询数据库内LED最新状态: SELECT * FROM t1 ORDER BY aid DESC LIMIT 1; (b) 将访客设置的LED状态写入数据库: INSERT INTO t1 (A4, A5, A6, A7) VALUES (A4, A5, A6, A7); (c) 将温度传感器值写入数据库: INSERT INTO t2 (b1) VALUES (ROUND(unoTemp, 2)); 注:表t1保存4个LED的开/关状态,字段分别是A4、A5、A6、A7(分别对应LED白在PIN 4、LED蓝在PIN 5、LED绿在PIN 6、LED红在PIN7)。状态开为1,状态关为0。 注:表t2保存温度值,只有一个字段b1,温度值保留2位小数。 注:本帖遵循保留字用大写,与引脚有关用大写,变量用小写。 5.6 Web page、HTML、CSS、JavaScript 有人问我为什么不直接让Ethernet Shield充当Web Server,这样还简单,反应速度也快。我是出于两点考虑:第一,这个项目是假期给孩纸们解释B/S运作模式用的,希望技术跨度和覆盖能大一点,便于他们未来步入J2EE领域;第二,Ethernet Shield充当Web Serve速度够快,但是在支持CSS/JavaScript上,尚有一定距离。我个人认为Ethernet shield就做好自己网卡的工作,把网页设计部分交给专业的去做,孩纸们爱怎么折腾就能怎么折腾。 顺便谈一下,我觉得未来IoT进入家庭或者企业的话,信息/数据还是会汇总到某个中心点,而不是分散到各个设备上。也就是说,人们访问的是某个总呈现页面(可能是Web形式的或者App形式的),而不是一个个设备上面独自的页面。这也是为什么Google买了Nest,小米想占据客厅Router/TV,这些都是信息的汇总点,谁能抢先做成行业标准,那么很快谁就会成为国际标准。 网页(Web Page)、HTML现在已经烂大街了,差不多有口气的,都会写两句。本来据说XML会淘汰掉这货,但是现在实际情况是XML首先没落了,被逼到了Web Service那块阵地。更甚的是,那块阵地又受到后起之秀JSON的围剿,除了保守派还在坚持,其他流派都被招安了。自HTML5推出后,HTML又一次起死回生,看来应验了那句,真理往往是简单的。作为21st世纪的孩纸,HTML就像乘法口诀那样需要掌握,未来B/S结构仍然是主流模式,为什么?很简单,把客户端这块成本节省掉,别老想打400电话来烦我。 CSS对页面和文字的渲染美化越做越强,也打破了很多原先的条条框框。当然更令人瞩目的是JavaScript,简直是跳跃式爆发。据2014年11月TIOBE排行榜,她已轻松排入前十位,且排名仍有上升余地。孩纸们如果想有个好饭碗,除了Java和C以外,JavaScript应该学习一下。学习JavaScript,推荐看一下犀牛书。 本项目涉及到的HTML和JavaScript是Form,当然啦,我们需要向Servlet提交点东西,总得麻烦From君。下面这行,看上去是不是很亲切: <form method="POST" action="http://www.openkevin.net/servlet/myservlet1?p=1"> 我们会把这个放入项目的首页内。孩儿们一看便知呀,通过form来向myservlet1提交一个参数p的值是1,提交运用POST方式。 |
本帖最后由 kevinzhang19701 于 2014-12-22 14:17 编辑 2. 硬件部分 2.1 控制器部分DFRobot UNO R3。 2.2 网络通讯部分,DFRobot Ethernet W5100。 2.3 I/O部分,DFRobot IO扩展板 V7.1。用I/O板的确很偷懒,估计又要被Maker嘲笑一下,哈哈哈。好在只连接4个LED灯和一个温度传感器,就偷懒一下。 2.4 三件叠加的效果如下图。 2.5 食人鱼LED四枚,分别为白、蓝、绿、红四色。DS18B20数字温度传感器一枚。 |
本帖最后由 kevinzhang19701 于 2014-12-24 18:41 编辑 3. 软件部分 3.1 Arduino IDE 1.0.6 使用高版本自然有高版本的好处,但是也有坏处。比如在使用DFRobot官网提供的<DallasTemperature.h>、<WireOne.h>的时候,就会吃药。因为自1.0.4以后,WConstants.h已经被Arudino.h取代,DFRobot提供的cpp文件,还在引用WConstants.h,结果在编译时会出现“error: WConstants.h: No such file or directory”错误。DFRobot似乎尚无修正两个头文件的意思,那么孩儿们只能靠自己了: 3.1.1 如果在相关头文件和cpp文件内搜索到WConstants.h这样的字符,那么添加下列预处理定义: #if ARDUINO >= 100 #include "Arduino.h" #else #include "WProgram.h" #endif 3.1.2 如果不打算修改文件,那么翻墙找更新的头文件吧。 这里提醒孩儿们注意:开发一个无论多小的项目,都是集体智慧的结晶,特别是开源项目。每一个项目中都有数不清的细节待我们确认,也会碰到数不清的问题和困难。幸运的是,我们站在巨人的肩上,开源社区就是巨人,我们要善加利用开源集体智慧,同时也要尊重资源的提供者,努力反哺开源社区,使之成为巨人中的巨人。 3.2 Tomcat 8.0.15 Tomcat作为Servlet/JSP Container的历史颇为悠久,后续追随者中有名的有Jetty、Resin、Nginx。本项目的信息交换中心是一台Lenovo E49笔记本电脑,上面安装了Tomcat 8.0.15作为Web服务器和Servlet容器。孩儿们注意conf、ROOT、Web-INF这几个目录和其下的xml文件。 3.3 MySQL 5.6.12 MySQL数据库将保存下列数据: (a) 外网用户访问时的4个LED开/关状态信息; (b) 温度传感器的温度数据。 为便于孩儿们观察,使用了图形管理工具WorkBench 6.2 CE。Connector/J 5.1.34 (JDBC)作为Servlet与数据库数据传递的桥梁。 3.4 NetBeans 8.0.2 这个项目一共有3个小Servlet,用来接管三项工作: (a). myservlet1:获取数据库内4个LED开/关状态,并显示给客户看。而后采集客户对LED开/关的控制状态,写入数据库(表t1)。 (b). myservlet2:W5100客户端每隔1秒提交查询请求,获得数据库最新LED开/关状态,而后刷新I/O板上LED。 (c). myservlet3:W5100客户端每隔60秒提交一个带参数的查询请求(这个参数就是温度值),写入数据库(表t2)。这里我留了一个未完成的口子,以便寒假的时候,让孩纸们做一个扩展项目:可以查询某段日期、某个时间段的温度、能排序,且能以点阵方式绘出温度曲线图。 这3个Servlet就用大材小用NetBeans来完成吧。 |
本帖最后由 kevinzhang19701 于 2014-12-23 13:16 编辑 mickey 发表于 2014-12-23 11:31 这个方法部分地区可能可以,不过像上海这里就不可行了。运营商不提供admin密码,这样就无法做port forward,另外光猫会被随机远程重启,并更换密码。 |
4. 逻辑部分 逻辑部分相对比较简单,如上图所示。 4.1 外网访客可以通过Internet访问到偶米滴信息中心点,感谢花生壳(后面“技术要点”里我会再稍微详细滴谈一谈)。不过,还得提醒孩纸们,运营商提供的光猫并不稳定,所以在外网访问时,有时候反应会比较慢。建议过个把星期,重启一下光猫和服务器。 4.2 充当数据交换中心点的笔记本电脑(Lenovo E49)配置并不高(听说已经停产),对于小型项目已戳戳鱿鱼了。老夫给公司同事做的几个讲座的内容也放在上面,顺便孩纸们也学习一哈吧。 4.3 充当Web服务器软件和Servlet容器的Tomcat,建议孩纸们一定要及时更新(有新版本出来,一定要安装新版本,所以大家要天天关注Apache网站哦)。为什么捏?自然出于稳定和安全的考虑。虽说连接到家庭网络上的设备数量不多,并且不那么昂贵或者机密,但是,作为一个通向外网的入口,保持一颗小人度君子的心态还是很有必要的。待今后接入这一网络的设备慢慢多起来,可以考虑用旧笔记本装一个防火墙。 4.4 如上节3.4描述的,Tomcat下存放了三个Servlet,分别接管三个方向的工作。Servlet1工作比较多,主要接受来自于外网访客对LED开/关状态的变化,并要将这些变化状态写入数据库(表t1)。Servlet2的工作相对单一一点,主要是回答W5100每隔1秒提交的LED状态查询。Servlet3的工作也比较单一,主要是接受W5100每隔60秒提交的温度数值,将其写入数据库(表t2),这样孩纸们可以在WorkBench里观察。 4.5 Arduino sketch只有一个,完成两个工作: (a) 每隔1秒向服务器查询数据库里最新的LED状态,然后刷新LED高低电平。 (b) 每隔60秒向服务器提交DS18B20采集来的温度值。 |
7. 后续扩展 这个小项目以上告一段落,但是只是刚开了个头,后续留了一些口子给孩纸们今年寒假进行扩充: (a) 改进访客设置LED状态写入数据库流程,只有状态变化才写入,状态不变则不写。 (b) 温度采集添加日期与时间值,写入数据库(t2表追加两个字段)。 (c) 增加访客可以通过日期、时间段查询温度值,并可以排序(升序或者降序可选)。 (d) 查询结果温度值绘成曲线图。 8. 剧终 老夫小题大做,断断续续扯了两天,不知各位看官是否头晕恶心了。此处惊堂木一拍,落幕鞠躬。 欲知后事如何,且听下回分解。 (此处再补充一点点,本项目全部来自于开源软件的支持,在此鸣谢开源社区与所有应用到的开源项目) |
本帖最后由 kevinzhang19701 于 2014-12-26 17:46 编辑 KinFu 发表于 2014-12-26 13:56 W5200尚未使用过,看介绍似乎比W5100更好。 与W5100相比,W5200具有以下几点优势: 支持高速SPI总线(最大100MHz),支持8个独立的端口同时连接。W5100只有4个独立端口。 提供休眠模式和网络唤醒,更低功耗。 极小巧的48pin QFN无铅封装,芯片体积更小。 使用同样非常简单。只需添加我们提供的库文件,覆盖Arduino IDE原有的W5100库文件,烧录一下就可以运行了。 W5200具备32KB内部通信缓冲,W5100只有16KB。 库文件也是新的,参考下列链接内容: https://www.dfrobot.com.cn/image/data/DFR0272/Ethernet%20Shield%20For%20Arudino-%20W5200%20V1.0%20testing.zip 代码方面似乎没啥变化,5100时代的代码都可以移植过去。 |
© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed