引言:
websocket,webservice傻傻分不清楚,都覺得是很高深的東西,理解中的webservice是一種協議,通信協議,類似http協議的那種,比如使用webservice協議調后台接口,而websocket呢?與socket掛鈎?長連接?對未知的東西總是恐懼的,所以默默不敢說話
啟航:
學習過程中突然接觸到了websocket的簡單講解,哦,websocket也是一種協議,它類似ajax,但連接不中斷,接到消息就響應。叫什么雙端通信。websocket請求頭是ws://或者wss://開頭,非安全與安全,后面就和http請求類似。后台寫法當然與默認的http servlet有些不同,但變化不大,與springMVC的requestMapping有些相似,接受到請求可以進行攔截等處理,當然也可以限制接收請求的具體參數。
概念來一波:



原先實現模擬雙端通信的手段:
在WebSocket規范提出之前,開發人員若要實現這些實時性較強的功能,經常會使用折衷的解決方法:輪詢(polling)和Comet技術。其實后者本質上也是一種輪詢,只不過有所改進。
輪詢是最原始的實現實時Web應用的解決方案。輪詢技術要求客戶端以設定的時間間隔周期性地向服務端發送請求,頻繁地查詢是否有新的數據改動。明顯地,這種方法會導致過多不必要的請求,浪費流量和服務器資源。
Comet技術又可以分為長輪詢和流技術。長輪詢改進了上述的輪詢技術,減小了無用的請求。它會為某些數據設定過期時間,當數據過期后才會向服務端發送請求;這種機制適合數據的改動不是特別頻繁的情況。流技術通常是指客戶端使用一個隱藏的窗口與服務端建立一個HTTP長連接,服務端會不斷更新連接狀態以保持HTTP長連接存活;這樣的話,服務端就可以通過這條長連接主動將數據發送給客戶端;流技術在大並發環境下,可能會考驗到服務端的性能。
這兩種技術都是基於請求-應答模式,都不算是真正意義上的實時技術;它們的每一次請求、應答,都浪費了一定流量在相同的頭部信息上,並且開發復雜度也較大。
特點:
1、雙端通信
2、建立在TCP之上
3、協議標識符是ws(如果加密,則為wss),服務器網址就是 URL。
接入:
注意:JavaEE 7中出了JSR-356:Java API for WebSocket規范。不少Web容器,如Tomcat,Nginx,Jetty等都支持WebSocket。Tomcat從7.0.27開始支持 WebSocket,從7.0.47開始支持JSR-356
websocket客戶端:
在客戶端,沒有必要為 WebSockets 使用 JavaScript 庫。實現 WebSockets 的 Web 瀏覽器將通過 WebSockets 對象公開所有必需的客戶端功能(主要指支持 Html5 的瀏覽器)。
客戶端API:
以下 API 用於創建 WebSocket 對象。
var Socket = new WebSocket(url, [protocol] );
以上代碼中的第一個參數 url, 指定連接的 URL。第二個參數 protocol 是可選的,指定了可接受的子協議。
WebSocket 屬性
以下是 WebSocket 對象的屬性。假定我們使用了以上代碼創建了 Socket 對象:
| 屬性 | 描述 |
|---|---|
| Socket.readyState | 只讀屬性 readyState 表示連接狀態,可以是以下值:0 - 表示連接尚未建立。1 - 表示連接已建立,可以進行通信。2 - 表示連接正在進行關閉。3 - 表示連接已經關閉或者連接不能打開。 |
| Socket.bufferedAmount | 只讀屬性 bufferedAmount 已被 send() 放入正在隊列中等待傳輸,但是還沒有發出的 UTF-8 文本字節數。 |
WebSocket 事件
以下是 WebSocket 對象的相關事件。假定我們使用了以上代碼創建了 Socket 對象:
| 事件 | 事件處理程序 | 描述 |
|---|---|---|
| open | Socket.onopen | 連接建立時觸發 |
| message | Socket.onmessage | 客戶端接收服務端數據時觸發 |
| error | Socket.onerror | 通信發生錯誤時觸發 |
| close | Socket.onclose | 連接關閉時觸發 |
WebSocket 方法
以下是 WebSocket 對象的相關方法。假定我們使用了以上代碼創建了 Socket 對象:
| 方法 | 描述 |
|---|---|
| Socket.send() | 使用連接發送數據 |
| Socket.close() | 關閉連接 |
客戶端實例:
<%-- Created by IntelliJ IDEA. User: zhen Date: 2018/12/10 Time: 17:36 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>welcome page</title> </head> <body> welcome to ssm all annotation project! <input id="text" type="text"/> <button onclick="send()">發送消息</button> <hr/> <button onclick="closeWebSocket()">關閉webSocket連接</button> <hr/> <div id="message"></div> </body> <script type="text/javascript"> var webSocket = null; //判斷當前瀏覽器是否支持webSocket if ('WebSocket' in window) { webSocket = new WebSocket("ws://localhost:8080/spring4webSocket/myHandler") } else { alert("當前瀏覽器 Not support webSocket"); } webSocket.onerror = onError; webSocket.onopen = onOpen; webSocket.onmessage = onMessage; webSocket.onclose = onClose; function onError() { setMessageInnerHTML("WebSocket連接發生錯誤"); } function onOpen() { setMessageInnerHTML("WebSocket連接成功"); } function onMessage(event){ //將接受到的數據直接輸出 setMessageInnerHTML(event.data); } function onClose() { setMessageInnerHTML("webSocket連接關閉"); } function setMessageInnerHTML(message) { var messageDiv = document.getElementById("message"); messageDiv.innerHTML = messageDiv.innerHTML + "<br/>" + message; } //監聽串口關閉事件,當窗口關閉時,主動去關閉webSocket連接,防止還沒斷開就關閉窗口,srever端會拋異常 window.onbeforeunload = function (ev) { closeWebSocket(); } function closeWebSocket(){ webSocket.close(); } //發送消息 function send() { var message = document.getElementById("text").value; webSocket.send(message); } </script> </html>
服務端(java):
基於servlet api:
1、導入有關jar包
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
API:
@ServerEnpoint 聲明webSocket服務端,指明映射url
@OnMessage 標注接收到消息執行監聽方法
@OnOpen 標注打開連接時候執行監聽方法
@OnClose 標注關閉連接時執行監聽方法
@OnError 標注連接異常時執行監聽方法
服務端實例:
package com.zhen.websocket; /** * @author zhen * @Date 2018/12/6 10:29 */ import java.io.*; import java.util.*; import javax.websocket.EncodeException; import javax.websocket.OnClose; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import javax.websocket.OnMessage; import javax.websocket.OnOpen; // @ServerEndpoint 注解允許你聲明一個WeoSocket,定義url映射,定義編碼和解碼 @ServerEndpoint( value="/story/notifications", encoders={StickerEncoder.class}, decoders={StickerDecoder.class} ) public class StoryWebSocket { private static final List<Sticker> stickers = Collections.synchronizedList(new LinkedList<Sticker>()); private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<Session>()); @OnMessage public void onMessage(Session session, Sticker sticker){ // 有消息從客戶端發送過來,保存到列表中,然后通知所有的客戶端 stickers.add(sticker); for(Session openSession : sessions){ try { openSession.getBasicRemote().sendObject(sticker); } catch (IOException | EncodeException e) { sessions.remove(openSession); } } } @OnOpen public void onOpen(Session session) throws IOException, EncodeException{ // 有新的客戶端連接時,保存此客戶端的session,並且把當前所有的sticker發送給它 sessions.add(session); for(Sticker sticker : stickers){ session.getBasicRemote().sendObject(sticker); } } @OnClose public void onClose(Session session){ // 有客戶端斷開連接時 ,從session列表中移除此客戶端的session sessions.remove(session); } }
看的有些混亂不能很好理解的時候就敲一些例子,功能出來就更容易理解了。
跟着下面兩篇教程的案例
package com.zhen.websocket; /** * @author zhen * @Date 2018/12/6 10:28 */ public class Sticker { private int x; private int y; private String image; public Sticker() { } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public String getImage() { return image; } public void setImage(String image) { this.image = image; } } package com.zhen.websocket; import javax.json.JsonObject; import javax.json.JsonReader; import javax.json.spi.JsonProvider; import javax.websocket.DecodeException; import javax.websocket.Decoder; import javax.websocket.EndpointConfig; import java.io.IOException; import java.io.Reader; /** * @author zhen * @Date 2018/12/6 11:08 * 用來讀取webSocket流中的數據使用Decode.TextStream接口。 這個接口允許你讀取數據從socket中通過JsonReader對象,構造傳入Reader對象,並且轉換客戶端返回文本JSON數據 */ public class StickerDecoder implements Decoder.TextStream<Sticker>{ // Do not create a JsonReader object. To create readers and writes, use the // JsonProvider class. @Override public void init(EndpointConfig config) { // TODO Auto-generated method stub } @Override public void destroy() { // TODO Auto-generated method stub } @Override public Sticker decode(Reader reader) throws DecodeException, IOException { JsonProvider provider = JsonProvider.provider(); JsonReader jsonReader = provider.createReader(reader); JsonObject jsonSticker = jsonReader.readObject(); Sticker sticker = new Sticker(); sticker.setX(jsonSticker.getInt("x")); sticker.setY(jsonSticker.getInt("y")); sticker.setImage(jsonSticker.getString("sticker")); return sticker; } } package com.zhen.websocket; import javax.json.JsonObject; import javax.json.JsonWriter; import javax.json.spi.JsonProvider; import javax.websocket.EncodeException; import javax.websocket.Encoder; import javax.websocket.EndpointConfig; import java.io.IOException; import java.io.Writer; /** * @author zhen * @Date 2018/12/6 10:59 * 這個類編碼Sticker對象並且傳遞給WebSocket服務器通過寫入流中 */ public class StickerEncoder implements Encoder.TextStream<Sticker> { @Override public void init(EndpointConfig config) { // TODO Auto-generated method stub } @Override public void destroy() { // TODO Auto-generated method stub } @Override public void encode(Sticker sticker, Writer writer) throws EncodeException, IOException { JsonProvider provider = JsonProvider.provider(); JsonObject jsonSticker = provider.createObjectBuilder() .add("action", "add") .add("x", sticker.getX()) .add("y", sticker.getY()) .add("sticker", sticker.getImage()) .build(); JsonWriter jsonWriter = provider.createWriter(writer); jsonWriter.write(jsonSticker); } } package com.zhen.websocket; /** * @author zhen * @Date 2018/12/6 10:29 */ import java.io.*; import java.util.*; import javax.websocket.EncodeException; import javax.websocket.OnClose; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import javax.websocket.OnMessage; import javax.websocket.OnOpen; // @ServerEndpoint 注解允許你聲明一個WeoSocket,定義url映射,定義編碼和解碼 @ServerEndpoint( value="/story/notifications", encoders={StickerEncoder.class}, decoders={StickerDecoder.class} ) public class StoryWebSocket { private static final List<Sticker> stickers = Collections.synchronizedList(new LinkedList<Sticker>()); private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<Session>()); @OnMessage public void onMessage(Session session, Sticker sticker){ // 有消息從客戶端發送過來,保存到列表中,然后通知所有的客戶端 stickers.add(sticker); for(Session openSession : sessions){ try { openSession.getBasicRemote().sendObject(sticker); } catch (IOException | EncodeException e) { sessions.remove(openSession); } } } @OnOpen public void onOpen(Session session) throws IOException, EncodeException{ // 有新的客戶端連接時,保存此客戶端的session,並且把當前所有的sticker發送給它 sessions.add(session); for(Sticker sticker : stickers){ session.getBasicRemote().sendObject(sticker); } } @OnClose public void onClose(Session session){ // 有客戶端斷開連接時 ,從session列表中移除此客戶端的session sessions.remove(session); } } var socket = null; function initialize() { var canvas = document.getElementById("board"); var ctx = canvas.getContext("2d"); var img = document.getElementById("background_img"); ctx.drawImage(img, 0, 0); socket = new WebSocket("ws://localhost:8080/stickStory/story/notifications"); socket.onmessage = onSocketMessage; } function drag(ev) { var bounds = ev.target.getBoundingClientRect(); var draggedSticker = { sticker: ev.target.getAttribute("data-sticker"), offsetX: ev.clientX - bounds.left, offsetY: ev.clientY - bounds.top }; var draggedText = JSON.stringify(draggedSticker); ev.dataTransfer.setData("text", draggedText); } function drop(ev) { ev.preventDefault(); var bounds = document.getElementById("board").getBoundingClientRect(); var draggedText = ev.dataTransfer.getData("text"); var draggedSticker = JSON.parse(draggedText); var stickerToSend = { action: "add", x: ev.clientX - draggedSticker.offsetX - bounds.left, y: ev.clientY - draggedSticker.offsetY - bounds.top, sticker: draggedSticker.sticker }; socket.send(JSON.stringify(stickerToSend)); log("Sending Object " + JSON.stringify(stickerToSend)); } function allowDrop(ev) { ev.preventDefault(); } function onSocketMessage(event) { if (event.data) { var receivedSticker = JSON.parse(event.data); log("Received Object: " + JSON.stringify(receivedSticker)); if (receivedSticker.action === "add") { var imageObj = new Image(); imageObj.onload = function() { var canvas = document.getElementById("board"); var context = canvas.getContext("2d"); context.drawImage(imageObj, receivedSticker.x, receivedSticker.y); }; imageObj.src = "resources/stickers/" + receivedSticker.sticker; } } } function toggleLog() { var log = document.getElementById("logContainer"); if (!log.getAttribute("style")) { log.setAttribute("style", "display:block;"); } else { log.setAttribute("style", ""); } } var logCount = 0; function log(logstr) { var logElement = document.getElementById("log"); logElement.innerHTML = "<b>[" + logCount + "]: </b>" + logstr + "<br>" + logElement.innerHTML; logCount++; } window.onload = initialize; <?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html> <html> <head> <title>Sticker Story</title> <link href="resources/styles.css" rel="stylesheet" type="text/css" > <script src="resources/story-page.js" type="text/javascript"></script> </head> <body> <header> <h1>Sticker Story Book</h1> </header> <nav> Drag stickers from the left bar to the canvas. </nav> <aside> <h2>Stickers</h2> <div id="stickerContainer"> <img src="resources/stickers/bear.png" data-sticker="bear.png" style="float:left" draggable="true" ondragstart="drag(event);" > <img src="resources/stickers/chicken.png" data-sticker="chicken.png" style="float:left" draggable="true" ondragstart="drag(event);" > <img src="resources/stickers/leopard.png" data-sticker="leopard.png" style="float:left" draggable="true" ondragstart="drag(event);" > <img src="resources/stickers/monkey.png" data-sticker="monkey.png" style="float:left" draggable="true" ondragstart="drag(event);" > <img src="resources/stickers/horse.png" data-sticker="horse.png" style="float:left" draggable="true" ondragstart="drag(event);" > <img src="resources/stickers/tiger.png" data-sticker="tiger.png" style="float:left" draggable="true" ondragstart="drag(event);" > </div> </aside> <div id="content"> <canvas id="board" width="1000" height="580" ondrop="drop(event);" ondragover="allowDrop(event);"> Canvas Not Supported. </canvas> <img src="resources/canvas2.png" id="background_img" width="1000" height="580" style="display:none;"/> </div> <footer> <small>Made with HTML5 + WebSockets and JSON</small> <ol> <li onclick="toggleLog();">Log</li> </ol> </footer> <div id="logContainer"> <h2>log</h2> <div id="log"></div> </div> </body> </html> <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zhen</groupId> <artifactId>StickStory</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <!-- Servlet --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> </dependency> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> <!-- JSON工具包 --> <dependency> <groupId>org.glassfish</groupId> <artifactId>javax.json</artifactId> <version>1.0.4</version> </dependency> </dependencies> <build> <plugins> <!-- 編譯插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <configuration> <source>1.7</source> <target>1.7</target> <encoding>UTF-8</encoding> </configuration> </plugin> <!-- tomcat 插件 --> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>8080</port> <path>/stickStory</path> </configuration> </plugin> </plugins> </build> </project>
這里接受返回消息用到了轉換器,接受對象類型json返回對象類型json
實現發布訂閱模式
項目目錄如下:

第二篇敲的項目代碼:
package com.zhen.model; /** * @author zhen * @Date 2018/12/6 15:30 */ public class Device { private int id; private String name; private String status; private String type; private String description; public Device() { } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } } package com.zhen.websocket; import com.zhen.model.Device; import javax.enterprise.context.ApplicationScoped; import javax.json.JsonObject; import javax.json.spi.JsonProvider; import javax.websocket.Session; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; /** * @author zhen * @Date 2018/12/6 15:50 */ @ApplicationScoped public class DeviceSessionHandler { private final Set<Session> sessions = new HashSet<>(); private final Set<Device> devices = new HashSet<>(); private static AtomicInteger deviceId = new AtomicInteger(0); public void addSession(Session session) { sessions.add(session); for (Device device : devices) { JsonObject addMessage = createAndMessage(device); sendToSession(session, addMessage); } } public void removeSession(Session session) { sessions.remove(session); } public void addDevice(Device device) { device.setId(deviceId.incrementAndGet()); devices.add(device); JsonObject addMessage = createAndMessage(device); sendToAllConnectedSessions(addMessage); } public void removeDevice(int id) { Device device = getDeviceById(id); if (device != null) { devices.remove(device); JsonProvider provider = JsonProvider.provider(); JsonObject removeMessage = provider.createObjectBuilder() .add("action", "remove") .add("id", id) .build(); sendToAllConnectedSessions(removeMessage); } } public void toggleDevice(int id) { JsonProvider provider = JsonProvider.provider(); Device device = getDeviceById(id); if (device != null) { if ("On".equals(device.getStatus())) { device.setStatus("Off"); } else { device.setStatus("On"); } JsonObject updateDevMessage = provider.createObjectBuilder() .add("action", "toggle") .add("id", device.getId()) .add("status", device.getStatus()) .build(); sendToAllConnectedSessions(updateDevMessage); } } public List<Device> getDevices(){ return new ArrayList<>(devices); } public Device getDeviceById(int id) { for (Device device : devices) { if (device.getId() == id) { return device; } } return null; } public JsonObject createAndMessage(Device device) { JsonProvider provider = JsonProvider.provider(); JsonObject addMessage = provider.createObjectBuilder() .add("action", "add") .add("name", device.getName()) .add("type", device.getType()) .add("status", device.getStatus()) .add("description", device.getDescription()) .build(); return addMessage; } private void sendToAllConnectedSessions(JsonObject message) { for (Session session : sessions) { sendToSession(session, message); } } private void sendToSession(Session session, JsonObject message) { try{ session.getBasicRemote().sendText(message.toString()); } catch (IOException ex) { sessions.remove(session); Logger.getLogger(DeviceSessionHandler.class.getName()).log(Level.SEVERE, null, ex); } } } package com.zhen.websocket; import com.zhen.model.Device; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import javax.json.Json; import javax.json.JsonObject; import javax.json.JsonReader; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.StringReader; import java.util.logging.Level; import java.util.logging.Logger; /** * @author zhen * @Date 2018/12/6 15:32 */ @ApplicationScoped @ServerEndpoint("/actions") public class DeviceWebSocketServer { @Inject private DeviceSessionHandler sessionHandler = new DeviceSessionHandler(); @OnOpen public void open(Session session) { sessionHandler.addSession(session); } @OnClose public void close(Session session) { sessionHandler.removeSession(session); } @OnError public void onError(Throwable error) { Logger.getLogger(DeviceWebSocketServer.class.getName()).log(Level.SEVERE, null, error); } @OnMessage public void handleMessage(Session session, String message) { try(JsonReader reader = Json.createReader(new StringReader(message))){ JsonObject jsonMessage = reader.readObject(); if ("add".equals(jsonMessage.getString("action"))) { Device device = new Device(); device.setName(jsonMessage.getString("name")); device.setDescription(jsonMessage.getString("description")); device.setType(jsonMessage.getString("type")); device.setStatus("Off"); sessionHandler.addDevice(device); } if ("remove".equals(jsonMessage.getString("action"))) { int id = (int) jsonMessage.getInt("id"); sessionHandler.removeDevice(id); } if ("toggle".equals(jsonMessage.getString("action"))) { int id = (int) jsonMessage.getInt("id"); sessionHandler.toggleDevice(id); } } } } <?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html> <html> <head> <title>Index</title> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <link rel="stylesheet" href="style.css"> <script src="websocket.js"></script> </head> <body> <div id="wrapper"> <h1>Java WebSocket Home</h1> <p>Welcome to the Java WebSocket Home. Click the Add a device button to start adding devices.</p> <br/> <div id="addDevice"> <div class="button"><a href="#" onclick="showForm()">Add a device</a> </div> <form id="addDeviceForm"> <h3>Add a new device</h3> <span>Name: <input type="text" name="device_name" id="device_name"></span> <span> Type: <select id="device_type"> <option name="type" value="Appliance">Appliance</option> <option name="type" value="Electronics">Electronics</option> <option name="type" value="Lights">Lights</option> <option name="type" value="Other">Other</option> </select> </span> <span> Description:<br/> <textarea name="description" id="device_description" rows="2" cols="50"></textarea> </span> <input type="button" class="button" value="Add" onclick="formSubmit();"> <input type="reset" class="button" value="Cancel" onclick="hideForm();"> </form> </div> <br/> <h3>Currently connected devices:</h3> <div id="content"></div> </div> </body> </html> body { font-family: Arial, Helvetica, sans-serif; font-size: 80%; background-color: #1f1f1f; } #wrapper { width: 960px; margin: auto; text-align: left; color: #d9d9d9; } p { text-align: left; } .button { display: inline; color: #fff; background-color: #f2791d; padding: 8px; margin: auto; border-radius: 8px; -moz-border-radius: 8px; -webkit-border-radius: 8px; box-shadow: none; border: none; } .button:hover { background-color: #ffb15e; } .button a, a:visited, a:hover, a:active { color: #fff; text-decoration: none; } #addDevice { text-align: center; width: 960px; margin: auto; margin-bottom: 10px; } #addDeviceForm { text-align: left; width: 400px; margin: auto; padding: 10px; } #addDeviceForm span { display: block; } #content { margin: auto; width: 960px; } .device { width: 180px; height: 110px; margin: 10px; padding: 16px; color: #fff; vertical-align: top; border-radius: 8px; -moz-border-radius: 8px; -webkit-border-radius: 8px; display: inline-block; } .device.off { background-color: #c8cccf; } .device span { display: block; } .deviceName { text-align: center; font-weight: bold; margin-bottom: 12px; } .removeDevice { margin-top: 12px; text-align: center; } .device.Appliance { background-color: #5eb85e; } .device.Appliance a:hover { color: #a1ed82; } .device.Electronics { background-color: #0f90d1; } .device.Electronics a:hover { color: #4badd1; } .device.Lights { background-color: #c2a00c; } .device.Lights a:hover { color: #fad232; } .device.Other { background-color: #db524d; } .device.Other a:hover { color: #ff907d; } .device a { text-decoration: none; } .device a:visited, a:active, a:hover { color: #fff; } .device a:hover { text-decoration: underline; } window.onload = init; var socket = new WebSocket("ws://localhost:8080/webSocketHome/actions"); socket.onmessage = onMessage; function onMessage(event) { var device = JSON.parse(event.data); if (device.action === "add") { printDeviceElement(device); } if (device.action === "remove") { document.getElementById(device.id).remove(); //device.parentNode.removeChild(device); } if (device.action === "toggle") { var node = document.getElementById(device.id); var statusText = node.children[2]; if (device.status === "On") { statusText.innerHTML = "Status: " + device.status + " (<a href=\"#\" OnClick=toggleDevice(" + device.id + ")>Turn off</a>)"; } else if (device.status === "Off") { statusText.innerHTML = "Status: " + device.status + " (<a href=\"#\" OnClick=toggleDevice(" + device.id + ")>Turn on</a>)"; } } } function addDevice(name, type, description) { var DeviceAction = { action: "add", name: name, type: type, description: description }; socket.send(JSON.stringify(DeviceAction)); } function removeDevice(element) { var id = element; var DeviceAction = { action: "remove", id: id }; socket.send(JSON.stringify(DeviceAction)); } function toggleDevice(element) { var id = element; var DeviceAction = { action: "toggle", id: id }; socket.send(JSON.stringify(DeviceAction)); } function printDeviceElement(device) { var content = document.getElementById("content"); var deviceDiv = document.createElement("div"); deviceDiv.setAttribute("id", device.id); deviceDiv.setAttribute("class", "device " + device.type); content.appendChild(deviceDiv); var deviceName = document.createElement("span"); deviceName.setAttribute("class", "deviceName"); deviceName.innerHTML = device.name; deviceDiv.appendChild(deviceName); var deviceType = document.createElement("span"); deviceType.innerHTML = "<b>Type:</b> " + device.type; deviceDiv.appendChild(deviceType); var deviceStatus = document.createElement("span"); if (device.status === "On") { deviceStatus.innerHTML = "<b>Status:</b> " + device.status + " (<a href=\"#\" OnClick=toggleDevice(" + device.id + ")>Turn off</a>)"; } else if (device.status === "Off") { deviceStatus.innerHTML = "<b>Status:</b> " + device.status + " (<a href=\"#\" OnClick=toggleDevice(" + device.id + ")>Turn on</a>)"; //deviceDiv.setAttribute("class", "device off"); } deviceDiv.appendChild(deviceStatus); var deviceDescription = document.createElement("span"); deviceDescription.innerHTML = "<b>Comments:</b> " + device.description; deviceDiv.appendChild(deviceDescription); var removeDevice = document.createElement("span"); removeDevice.setAttribute("class", "removeDevice"); removeDevice.innerHTML = "<a href=\"#\" OnClick=removeDevice(" + device.id + ")>Remove device</a>"; deviceDiv.appendChild(removeDevice); } function showForm() { document.getElementById("addDeviceForm").style.display = ''; } function hideForm() { document.getElementById("addDeviceForm").style.display = "none"; } function formSubmit() { var form = document.getElementById("addDeviceForm"); var name = form.elements["device_name"].value; var type = form.elements["device_type"].value; var description = form.elements["device_description"].value; hideForm(); document.getElementById("addDeviceForm").reset(); addDevice(name, type, description); } function init() { hideForm(); } <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zhen</groupId> <artifactId>WebSocketHome</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <!-- Servlet --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> </dependency> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> <!-- JSON工具包 --> <dependency> <groupId>org.glassfish</groupId> <artifactId>javax.json</artifactId> <version>1.0.4</version> </dependency> </dependencies> <build> <plugins> <!-- 編譯插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <configuration> <source>1.7</source> <target>1.7</target> <encoding>UTF-8</encoding> </configuration> </plugin> <!-- tomcat 插件 --> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>8080</port> <path>/webSocketHome</path> </configuration> </plugin> </plugins> </build> </project>
此案例利用websocket實現了一套增刪改查
項目中使用了CDI注解,如@ApplicationScope,@Inject進行注入功能
項目結構:

spring的websocket支持:
spring4提供了對websocket的支持
<!-- spring-webSocket,不使用javaee7的api里面了,使用spring的封裝 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${spring.version}</version>
</dependency>
服務端配置:
package com.zhen.spring_websocket.config; import com.zhen.spring_websocket.service.MyHandler; import com.zhen.spring_websocket.service.MyHandler1; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; /** * @author zhen * @Date 2018/12/10 18:34 */ @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(myHandler(),"/myHandler"); registry.addHandler(myHandler1(),"/myHandler1").withSockJS(); } public WebSocketHandler myHandler() { return new MyHandler(); } public WebSocketHandler myHandler1() { return new MyHandler1(); } }
package com.zhen.spring_websocket.service; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.AbstractWebSocketHandler; import org.springframework.web.socket.handler.TextWebSocketHandler; import java.io.IOException; /** * @author zhen * @Date 2018/12/10 18:32 * 此類實現WebSocketHandler,執行處理請求等操作,這里只是接受請求然后再將請求轉發回去的功能 */ public class MyHandler1 extends AbstractWebSocketHandler { @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) { try{ session.sendMessage(message); }catch (IOException e){ e.printStackTrace(); } } @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { super.afterConnectionEstablished(session); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { super.afterConnectionClosed(session, status); } }
Handler並不一定是繼承abstractWebSocketHandler,只要是WebSocketHandler的子類即可。
和servlet的websocket api差不多吧
這句代碼:
registry.addHandler(myHandler1(),"/myHandler1").withSockJS();
是表示接受的是前端sockJs對象發送的請求。是spring-websocket模塊的一個封裝功能。
sockJs是什么呢?
在不支持WebSocket的情況下,也可以很簡單地實現WebSocket的功能的,方法就是使用 SockJS。
它會優先選擇WebSocket進行連接,但是當服務器或客戶端不支持WebSocket時,會自動在 XHR流、XDR流、iFrame事件源、iFrame HTML文件、XHR輪詢、XDR輪詢、iFrame XHR輪詢、JSONP輪詢 這幾個方案中擇優進行連接。
它是websocket客戶端的拓展與補充。
使用sockJs之后,spring注冊websocket鏈接就上面代碼這樣既可。
客戶端:
引入sockjs.min.js
使用sockJs對象替代WebSocket對象發送請求,它的語法和原生幾乎一致
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page session="false" %> <html> <head> <title>WebSocket with SockJS</title> </head> <body> <h1>Welcome!</h1> <ul id="ul"> </ul> <script src="${pageContext.request.contextPath}/static/js/sockjs.min.js"></script> <script> // SockJS與原生的WebSocket的方法基本是一致的, // 所以只需要將 new WebSocket(url); 換成 new SockJS(url); 就可以了 var url = "/spring4webSocket/myHandler1"; var sock = new SockJS(url); sock.onopen = function (ev) { console.log("opening"); sayHey(); }; sock.onmessage = function (ev) { console.log(ev.data); var li = document.createElement("li"); li.innerText = ev.data; document.getElementById("ul").appendChild(li); setTimeout(sayHey, 2000); }; sock.onclose = function (ev) { console.log("closed"); }; function sayHey() { console.log("sending 'Hey guy!'"); sock.send("Hey guy!"); }; </script> </body> </html>
spring的封裝還有基於stomp的部分:
這個沒理解好,以后再做研究。與spring配置請求的也涉及到再做補充。雖然寫過demo,但是不太理解,用的springbot,使用了spring security的例子,暫過。
參考鏈接:https://www.jianshu.com/p/942a2b16e26c
參考鏈接:https://baike.baidu.com/item/WebSocket/1953845?fr=aladdin
參考鏈接:https://www.cnblogs.com/jingmoxukong/p/7755643.html
參考鏈接:http://www.ruanyifeng.com/blog/2017/05/websocket.html
參考鏈接:http://www.cnblogs.com/xdp-gacl/p/5193279.html
參考鏈接:https://blog.csdn.net/john_62/article/details/78208177
參考鏈接:https://blog.csdn.net/dadiyang/article/details/83715569
此學習得出的經驗之談:如果暫時理解不了,就先使用他,然后再理解
