引言:
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 { 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; 960px; margin: auto; margin-bottom: 10px; } #addDeviceForm { text-align: left; 400px; margin: auto; padding: 10px; } #addDeviceForm span { display: block; } #content { margin: auto; 960px; } .device { 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
此学习得出的经验之谈:如果暂时理解不了,就先使用他,然后再理解