WebSocket介绍
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。
当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
以下 API 用于创建 WebSocket 对象。
var Socket = new WebSocket(url, [protocol] );
以上代码中的第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。
WebSocket 客户端页面实现
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>WebSocket测试</title> </head> <body style="text-align: center;"> <h2>WebSocket测试</h2> <div> <input type="text" id="txt"/> <button onclick="sendWebSocket()">发送</button><br> <button onclick="checkWebSocket()">测试WebSocket</button> <button onclick="connectWebSocket()">连接WebSocket</button> <button onclick="closeWebSocket()">关闭WebSocket</button><br> <hr> <div id="message"></div> </div> </body> <script type="text/javascript"> var websocket = null; function checkWebSocket(){ if ("WebSocket" in window) { // alert("您的浏览器支持 WebSocket!"); setMessageInnerHTML("您的浏览器支持 WebSocket!"); } else { // 浏览器不支持 WebSocket // alert("您的浏览器不支持 WebSocket!"); setMessageInnerHTML("您的浏览器不支持 WebSocket!"); } } // 连接 WebSocket function connectWebSocket(){ // 打开一个 web socket websocket = new WebSocket("ws://localhost:8080/websocket"); websocket.onopen = function() { // Web Socket 已连接上,使用 send() 方法发送数据 setMessageInnerHTML("WebSocket 已连接..."); }; websocket.onmessage = function(evt) { var received_msg = evt.data; setMessageInnerHTML("收到消息:" + received_msg); }; websocket.onclose = function() { setMessageInnerHTML("WebSocket 已关闭..."); }; } // 向WebSocket服务端发送消息 function sendWebSocket(){ if (websocket){ if (websocket.readyState == websocket.OPEN) { var message = document.getElementById('txt').value; websocket.send(message); } else { setMessageInnerHTML("WebSocket 未连接..."); } }else { setMessageInnerHTML("WebSocket 未创建..."); } } // 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 window.onbeforeunload = function() { closeWebSocket(); } // 关闭WebSocket连接 function closeWebSocket() { websocket.close(); } // 将消息显示在网页上 function setMessageInnerHTML(innerHTML) { document.getElementById('message').innerHTML += innerHTML + '<br/>'; } </script> </html>
WebSocket 服务端Java实现
1、新建一个Maven Web工程,导入依赖
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2、编辑一个WebSocket服务端类,MyWebSocket.class
package com.test.websocket; import java.io.IOException; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; @ServerEndpoint("/websocket") public class MyWebSocket { private Session session; /** * 连接建立后触发的方法 */ @OnOpen public void onOpen(Session session) { this.session = session; WebSocketMapUtil.put(session.getId(), this); System.out.println("-------- onOpen: 当前在线人数 " + WebSocketMapUtil.getOnlineCount() + ",连接人 " + session.getId() + " --------"); } /** * 连接关闭后触发的方法 */ @OnClose public void onClose() { // 从map中删除 WebSocketMapUtil.remove(session.getId()); System.out.println("-------- onClose: 当前在线人数 " + WebSocketMapUtil.getOnlineCount() + ",关闭人 " + session.getId() + " --------"); } /** * 接收到客户端消息时触发的方法 */ @OnMessage public void onMessage(String message, Session session) throws Exception { // 获取服务端到客户端的通道 MyWebSocket myWebSocket = WebSocketMapUtil.get(session.getId()); System.out.println("收到来自 " + session.getId() + " 的消息:" + message); // 返回消息给Web Socket客户端(浏览器) myWebSocket.sendMessageAll("服务端已收到消息:" + message); } /** * 发生错误时触发的方法 */ @OnError public void onError(Session session, Throwable error) { System.out.println("-------- onError: 当前在线人数 " + WebSocketMapUtil.getOnlineCount() + ",连接发生错误 " + session.getId() + "-"+ error.getMessage() + " --------"); // error.printStackTrace(); } /** * 给单个客户端发送消息 * @param message * @param sessionId * @throws IOException */ public void sendMessageSingle(String message, String sessionId) throws IOException { // session.getBasicRemote().sendText(message); 同步消息 // session.getAsyncRemote().sendText(message); 异步消息 MyWebSocket myWebSocket = WebSocketMapUtil.get(sessionId); if(myWebSocket != null) { myWebSocket.session.getBasicRemote().sendText(message); } } /** * 给所有客户端发送消息 * @param message * @throws IOException */ public void sendMessageAll(String message) throws IOException { for (MyWebSocket item : WebSocketMapUtil.getValues() ) { item.session.getAsyncRemote().sendText(message); System.out.println(item.session.getId()); System.out.println(item.session.isSecure()); System.out.println(item.session.isOpen()); } } }
3、编辑一个WebSocket 工具类,WebSocketMapUtil.class
package com.test.websocket; import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; public class WebSocketMapUtil { public static ConcurrentMap<String, MyWebSocket> webSocketMap = new ConcurrentHashMap<>(); public static void put(String key, MyWebSocket myWebSocket) { webSocketMap.put(key, myWebSocket); } public static MyWebSocket get(String key) { return webSocketMap.get(key); } public static void remove(String key) { webSocketMap.remove(key); } public static Collection<MyWebSocket> getValues() { return webSocketMap.values(); } public static int getOnlineCount() { return webSocketMap.size(); } }
4、注入一个ServerEndpointExporterBean,该Bean会自动注册使用@ServerEndpoint注解申请的websocket endpoint,代码如下:
@Component public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter(){ return new ServerEndpointExporter(); } }
启动项目效果如下:
注意:若是项目中集成了shiro会被拦截,页面控制台报302,找到项目中的shiro文件增加一行放行路径就可以了。