如何實現後臺向前臺傳數據
技術交流群:233513714
這兩天正在研究如何讓後天主動向前臺展現數據,只要後臺有數據上傳的時候就向前臺上傳(因為公司有個項目,硬件設備會不斷的上傳數據,服務端將接收到的數據向前臺展示)。在網上查了一下,下面將介紹一下其中的兩種解決辦法
一、WebSocket
WebSocket 是web客戶端和服務器之間新的通訊方式, 依然架構在HTTP協議之上。使用WebSocket連接, web應用程序可以執行實時的交互, 而不是以前的poll方式。
WebSocket是HTML5開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議,可以用來創建快速的更大規模的健壯的高性能實時的web應用程序。WebSocket通信協議於2011年被IETF定為標準RFC 6455,WebSocketAPI被W3C定為標準。
在WebSocket API中,瀏覽器和服務器只需要做一個握手的動作,然後,瀏覽器和服務器之間就形成了一條快速通道。兩者之間就直接可以數據互相傳送。
什麽是WebSocket?
一個WebSocket是通過一個獨立的TCP連接實現的、異步的、雙向的、全雙工的消息傳遞實現機制。WebSockets不是一個HTTP連接,卻使用HTTP來引導一個WebSocket連接。一個全雙工的系統允許同時進行雙向的通訊。陸地線路電話是一個全雙工設施的例子,因為它們允許兩個通話者同時講話並被對方聽到。最初WebSocket被提議作為HTML5規範的一部分,HTML5承諾給現代的交互式的web應用帶來開發上的便利和網絡效率,但是隨後WebSocket被移到一個僅用來存放WebSockets規範的獨立的標準文檔裏。它包含兩件事情 -- WebSocket協議規範,即2011年12月發布的RFC 6455,和WebSocket JavaScript API。
WebSocket協議利用HTTP 升級頭信息來把一個HTTP連接升級為一個WebSocket連接。HTML5 WebSockets 解決了許多導致HTTP不適合於實時應用的問題,並且它通過避免復雜的工作方式使得應用結構很簡單。
最新的瀏覽器都支持WebSockets,
WebSocket是如何工作的?
每一個WebSocket連接的生命都是從一個HTTP請求開始的。HTTP請求跟其他請求很類似,除了它擁有一個Upgrade頭信息。Upgrade頭信息表示一個客戶端希望把連接升級為不同的協議。對WebSockets來說,它希望升級為WebSocket協議。當客戶端和服務器通過底層連接第一次握手時,WebSocket連接通過把HTTP協議轉換升級為WebSockets協議而得以建立。一旦WebSocket連接成功建立,消息就可以在客戶端和服務器之間進行雙向發送
- WebSockets比其它工作方式比如輪詢更有效也更高效。因為它需要更少的帶寬並且降低了延時。
- WebSockets簡化了實時應用的結構體系。
- WebSockets在點到點發送消息時不需要頭信息。這顯著的降低了帶寬。
一些可能的WebSockets使用案例有:
- 聊天應用
- 多人遊戲
- 股票交易和金融應用
- 文檔合作編輯
- 社交應用
JSR 356,WebSocket的Java API,規定了開發者把WebSockets 整合進他們的應用時可以使用的Java API — 包括服務器端和Java客戶端。JSR 356是Java EE 7標準中的一部分。這意味著所有Java EE 7兼容的應用服務器都將有一個遵守JSR 356標準的WebSocket協議的實現。開發者也可以在Java EE 7應用服務器之外使用JSR 356。目前Apache Tomcat 8提供了JSR 356 API的WebSocket支持。 Jboss Wildfly 8 (原JBoss Application Server)也支持JSR 356.
一個Java客戶端可以使用兼容JSR 356的客戶端實現,來連接到WebSocket服務器。對web客戶端來說,開發者可以使用WebSocket JavaScript API來和WebSocket服務器進行通訊。WebSocket客戶端和WebSocket服務器之間的區別,僅在於兩者之間是通過什麽方式連接起來的。一個WebSocket客戶端是一個WebSocket終端,它初始化了一個到對方的連接。一個WebSocket服務器也是一個WebSocket終端,它被發布出去並且等待來自對方的連接。在客戶端和服務器端都有回調監聽方法 -- onOpen , onMessage , onError, onClose。
怎麽創建你的第一個WebSocket應用呢?基本上我們還是會使用Javascript API編寫WebSocket客戶端, 在服務器端, 本文使用JSR 356規範定義的通用模式和技術處理WebSocket的通訊。
下面看一個簡單的例子, 演示了如果使用JavaScript WebSocket客戶端與運行在Wildfly 8服務器通信.
客戶端代碼
1 <html> 2 <head> 3 <meta http-equiv="content-type"content="text/html; charset=ISO-8859-1"> 4 </head> 5 6 <body> 7 <meta charset="utf-8"> 8 <title>HelloWorld Web sockets</title> 9 <script language="javascript"type="text/javascript"> 10 var wsUri = getRootUri() + "/websocket-hello/hello"; 11 12 function getRootUri() { 13 return "ws://" + (document.location.hostname == "" ? "localhost" :document.location.hostname) + ":" + 14 (document.location.port == "" ? "8080" :document.location.port); 15 } 16 17 function init() { 18 output = document.getElementById("output"); 19 } 20 21 function send_message() { 22 23 websocket = new WebSocket(wsUri); 24 websocket.onopen = function(evt) { 25 onOpen(evt) 26 }; 27 websocket.onmessage = function(evt) { 28 onMessage(evt) 29 }; 30 websocket.onerror = function(evt) { 31 onError(evt) 32 }; 33 34 } 35 36 function onOpen(evt) { 37 writeToScreen("Connected to Endpoint!"); 38 doSend(textID.value); 39 40 } 41 42 function onMessage(evt) { 43 writeToScreen("Message Received: " + evt.data); 44 } 45 46 function onError(evt) { 47 writeToScreen(‘<span style="color: red;">ERROR:</span> ‘ + evt.data); 48 } 49 50 function doSend(message) { 51 writeToScreen("Message Sent: " + message); 52 websocket.send(message); 53 } 54 55 function writeToScreen(message) { 56 var pre = document.createElement("p"); 57 pre.style.wordWrap = "break-word"; 58 pre.innerHTML = message; 59 60 output.appendChild(pre); 61 } 62 63 window.addEventListener("load", init, false); 64 65 </script> 66 67 <h1 style="text-align: center;">Hello World WebSocket Client</h2> 68 69 <br> 70 71 <div style="text-align: center;"> 72 <form action=""> 73 <input onclick="send_message()" value="Send"type="button"> 74 <input id="textID" name="message"value="Hello WebSocket!" type="text"><br> 75 </form> 76 </div> 77 <div id="output"></div> 78 </body> 79 </html>
如你所見,要想使用WebSocket協議與服務器通信, 需要一個WebSocket對象。它會自動連接服務器.
websocket = new WebSocket(wsUri);
連接上會觸發open事件:
1 websocket.onopen = function(evt) { 2 onOpen(evt) 3 };
一旦連接成功,則向服務器發送一個簡單的hello消息。
1 websocket.send(message);
服務器端代碼
有兩種創建服務器端代碼的方法:
- 註解方式Annotation-driven: 通過在POJO加上註解, 開發者就可以處理WebSocket 生命周期事件.
- 實現接口方式Interface-driven: 開發者可以實現Endpoint接口和聲明周期的各個方法.
建議開發時采用註解方式, 這樣可以使用POJO就可以實現WebSocket Endpoint. 而且不限定處理事件的方法名。 代碼也更簡單。
本例就采用註解的方式, 接收WebSocket請求的類是一個POJO, 通過@ServerEndpoint
標註. 這個註解告訴容器此類應該被當作一個WebSocket的Endpoint。value值就是WebSocket endpoint的path.
1 package com.sample.websocket; 2 3 import javax.websocket.*; 4 import javax.websocket.server.ServerEndpoint; 5 6 7 @ServerEndpoint("/hello") 8 public class HelloWorldEndpoint { 9 10 11 @OnMessage 12 public String hello(String message) { 13 System.out.println("Received : "+ message); 14 return message; 15 } 16 17 @OnOpen 18 public void myOnOpen(Session session) { 19 System.out.println("WebSocket opened: " + session.getId()); 20 } 21 22 @OnClose 23 public void myOnClose(CloseReason reason) { 24 System.out.println("Closing a WebSocket due to " + reason.getReasonPhrase()); 25 } 26 27 }
註意:這個例子還包括了其它兩個回調函數: @OnOpen標註的方法在WebSocket連接開始時被調用, Web Session作為參數。 另外一個@OnClose標註的方法在連接關閉時被調用。
就是這麽簡單。但是為了編譯這個例子你還需要Websockets API的實現,它在WildFly 8發布中(或者你用JSR 356的參考實現,或其它的容器提供的jar, 如tomcat):
1 modules\system\layers\base\javax\websocket\api\main\jboss-websocket-api_1.0_spec-1.0.0.Final.jar
對於Maven用戶, 你需要增加undertow-websockets-jsr依賴
1 <dependency> 2 <groupId>org.jboss.spec.javax.websocket</groupId> 3 <artifactId>jboss-websocket-api_1.0_spec</artifactId> 4 <version>1.0.0.Final</version> 5 </dependency>
這個例子比較早,應該是2013年寫的,jsr 256還未發布。 現在,你應該直接使用Java EE提供的API
1 <dependency> 2 <groupId>javax.websocket</groupId> 3 <artifactId>javax.websocket-api</artifactId> 4 <version>1.1</version> 5 </dependency>
編解碼器
前面的例子中WebSocket通信的消息類型默認為String。接下來的例子演示如何使用Encoder和Decoder傳輸更復雜的數據。
Websocket使用Decoder將文本消息轉換成Java對象,然後傳給@OnMessage方法處理; 而當對象寫入到session中時,Websocket將使用Encoder將Java對象轉換成文本,再發送給客戶端。
更常用的, 我們使用XML 或者 JSON 來傳送數據,所以將會會將Java對象與XML/JSON數據相互轉換.
下圖描繪了客戶端和服務器使用encoder/decoder標準通信過程。
聲明Encoder/Decoder也是相當的簡單: 你只需在@ServerEndpoint註解中增加encoder/decoder設置:
1 package com.sample.websocket; 2 3 import java.util.logging.Level; 4 import java.util.logging.Logger; 5 import javax.websocket.*; 6 import javax.websocket.server.ServerEndpoint; 7 8 @ServerEndpoint(value = "/hello", 9 decoders = { 10 MessageDecoder.class,}, 11 encoders = { 12 MessageEncoder.class 13 }) 14 public class HelloWorldEndpoint { 15 16 @OnMessage 17 public Person hello(Person person, Session session) { 18 if (person.getName().equals("john")) { 19 person.setName("Mr. John"); 20 } 21 try { 22 session.getBasicRemote().sendObject(person); 23 System.out.println("sent "); 24 } catch (Exception ex) { 25 Logger.getLogger(HelloWorldEndpoint.class.getName()).log(Level.SEVERE, null, ex); 26 } 27 return person; 28 29 } 30 31 @OnOpen 32 public void myOnOpen(Session session) { 33 } 34 35 }
正像你看到的, OnMessage方法使用Java Object person作為參數, 我們為名字增加個尊稱再返回給客戶端。通過session.getBasicRemote().sendObject(Object obj)返回數據.
容器負責使用你指定的Decoder將接收到的XML消息轉為Java對象:
1 package com.sample.websocket; 2 3 import java.io.StringReader; 4 5 import javax.websocket.Decoder; 6 import javax.websocket.EndpointConfig; 7 import javax.xml.bind.*; 8 9 10 public class MessageDecoder implementsDecoder.Text<Person> { 11 12 @Override 13 public Person decode(String s) { 14 System.out.println("Incoming XML " + s); 15 Person person = null; 16 JAXBContext jaxbContext; 17 try { 18 jaxbContext = JAXBContext.newInstance(Person.class); 19 20 Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); 21 22 StringReader reader = new StringReader(s); 23 person = (Person) unmarshaller.unmarshal(reader); 24 } catch (Exception ex) { 25 ex.printStackTrace(); 26 } 27 return person; 28 } 29 30 @Override 31 public boolean willDecode(String s) { 32 33 return (s != null); 34 } 35 36 @Override 37 public void init(EndpointConfig endpointConfig) { 38 // do nothing. 39 } 40 41 @Override 42 public void destroy() { 43 // do nothing. 44 } 45 }
這裏我們使用JAXB做轉換。我們只要實現一個泛型接口Decoder.Text 或者 Decoder.Binary, 根據你傳輸的數據是文本還是二進制選擇一個.
所以數據由Decoder解碼, 傳給Endpoint (這裏的 HelloWorldEndpoint), 在返回給client之前, 它還會被下面的Encoder轉換成XML:
1 package com.sample.websocket; 2 3 import java.io.StringWriter; 4 5 import javax.websocket.EncodeException; 6 import javax.websocket.Encoder; 7 import javax.websocket.EndpointConfig; 8 import javax.xml.bind.JAXBContext; 9 import javax.xml.bind.Marshaller; 10 11 public class MessageEncoder implementsEncoder.Text<Person> { 12 13 @Override 14 public String encode(Person object) throws EncodeException { 15 16 JAXBContext jaxbContext = null; 17 StringWriter st = null; 18 try { 19 jaxbContext = JAXBContext.newInstance(Person.class); 20 21 Marshaller marshaller = jaxbContext.createMarshaller(); 22 st = new StringWriter(); 23 marshaller.marshal(object, st); 24 System.out.println("OutGoing XML " + st.toString()); 25 26 } catch (Exception ex) { 27 ex.printStackTrace(); 28 } 29 return st.toString(); 30 } 31 32 @Override 33 public void init(EndpointConfig endpointConfig) { 34 // do nothing. 35 } 36 37 @Override 38 public void destroy() { 39 // do nothing. 40 } 41 }
為了測試這個例子,將客戶端的網頁稍微修改一下以便能在textarea中粘帖XML:
1 <html> 2 <head> 3 <meta http-equiv="content-type"content="text/html; charset=ISO-8859-1"> 4 </head> 5 6 <body> 7 <meta charset="utf-8"> 8 <title>HelloWorld Web sockets</title> 9 <script language="javascript"type="text/javascript"> 10 var wsUri = getRootUri() + "/websocket-hello/hello"; 11 12 function getRootUri() { 13 return "ws://" + (document.location.hostname == "" ? "localhost" :document.location.hostname) + ":" + 14 (document.location.port == "" ? "8080" :document.location.port); 15 } 16 17 function init() { 18 output = document.getElementById("output"); 19 } 20 21 function send_message() { 22 23 websocket = new WebSocket(wsUri); 24 websocket.onopen = function(evt) { 25 onOpen(evt) 26 }; 27 websocket.onmessage = function(evt) { 28 onMessage(evt) 29 }; 30 websocket.onerror = function(evt) { 31 onError(evt) 32 }; 33 34 } 35 36 function onOpen(evt) { 37 writeToScreen("Connected to Endpoint!"); 38 doSend(textID.value); 39 40 } 41 42 function onMessage(evt) { 43 writeToScreen("Message Received: " + evt.data); 44 } 45 46 function onError(evt) { 47 writeToScreen(‘<span style="color: red;">ERROR:</span> ‘ + evt.data); 48 } 49 50 function doSend(message) { 51 writeToScreen("Message Sent: " + message); 52 websocket.send(message); 53 } 54 55 function writeToScreen(message) { 56 alert(message); 57 58 } 59 60 window.addEventListener("load", init, false); 61 62 </script> 63 64 <h1 style="text-align: center;">Hello World WebSocket Client</h2> 65 66 <br> 67 68 <div style="text-align: center;"> 69 <form action=""> 70 <input onclick="send_message()" value="Send"type="button"> 71 <textarea id="textID" rows="4" cols="50"name="message" > 72 </textarea> 73 </form> 74 </div> 75 <div id="output"></div> 76 </body> 77 </html>
在文本框中輸入下面的XML進行測試。
1 <person> 2 <name>john</name> 3 <surname>smith</surname> 4 </person>
這篇文章摘自http://colobu.com/2015/02/27/WebSockets-tutorial-on-Wildfly-8/(翻譯自 mastertheboss的 WebSockets tutorial on Wildfly 8)
1 二、輪詢 2 前臺代碼: 3 $(document).ready(function() { 4 setInterval(checkIsExist, 1000); 5 }); 6 7 function checkIsExist() { 8 var urls = "/LogForPage/getShiftCarOrderCarId.do?ajax=1"; 9 var htmlobj = $.ajax({ 10 url : urls, 11 async : false 12 }); 13 var list = eval(htmlobj.responseText); 14 $("#textarea-input").html(list); 15 } 16 17 18 19 後臺代碼: 20 21 import javax.annotation.Resource; 22 import javax.servlet.http.HttpServletResponse; 23 24 import org.codehaus.jackson.map.ObjectMapper; 25 import org.springframework.stereotype.Controller; 26 import org.springframework.web.bind.annotation.RequestMapping; 27 28 import com.aotoso.control.ConnectionManage.PortCommunication; 29 import com.aotoso.control.base.BaseController; 30 import com.aotoso.server.FirstDataManage.EverydayPlanService; 31 32 33 @Controller 34 @RequestMapping("/LogForPage") 35 public class LogForPage extends BaseController { 36 @Resource(name = "everydayPlanService") 37 EverydayPlanService everydayPlanService; 38 39 /** 40 * 實時打印上傳的信息! 41 * @param session 42 * @param request 43 * @param response 44 */ 45 @RequestMapping(value = "/TMRInfromation") 46 public void getShiftCarOrderCarId(HttpServletResponse response) { 47 pd = this.getPageData(); 48 logger.info("實時打印上傳的信息!"); 49 ObjectMapper objectMapper = new ObjectMapper(); 50 try { 51 objectMapper.writeValue(response.getOutputStream(),new PortCommunication().getInfo()); 52 System.out.println(new PortCommunication().getInfo()); 53 } catch (Exception e) { 54 e.printStackTrace(); 55 } 56 } 57 }
如何實現後臺向前臺傳數據