WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。 WebSocket通信协议于2011年被IETF定为标准RFC 6455,WebSocketAPI被W3C定为标准。 在WebSocket API中,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
以上摘自Wikipedia .
本文将说明在Intellij IDEA
下使用Gradle
构建SpringMVC
+WebSocket
实现消息推送.为了更好的阅读体验,请点击阅读原文 😉.
创建项目 在之前的文章中有详细的说明,此篇不做赘述.可参考IDEA+Gradle创建MyBatis+SpringMVC项目
导入依赖 在build.gradle
中导入denpendies
1 2 3 4 5 6 7 8 9 10 11 12 13 compile group: 'org.springframework' , name: 'spring-webmvc' , version: '4.2.4.RELEASE' compile group: 'org.springframework' , name: 'spring-context-support' , version: '4.2.4.RELEASE' compile group: 'org.springframework' , name: 'spring-websocket' , version: '4.2.4.RELEASE' compile group: 'org.springframework' , name: 'spring-messaging' , version: '4.2.4.RELEASE' compile group: 'javax.servlet' , name: 'javax.servlet-api' , version: '3.1.0' compile group: 'log4j' , name: 'log4j' , version: '1.2.17' compile group: 'com.google.code.gson' , name: 'gson' , version: '2.8.2'
此处需要注意的是Spring
从4.0+
版本开始支持WebSocket,而servlet-api
需要为3.0+
版本
配置文件 web.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <?xml version="1.0" encoding="UTF-8" ?> <web-app xmlns ="http://java.sun.com/xml/ns/javaee" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version ="3.0" > <context-param > <param-name > contextConfigLocation</param-name > <param-value > classpath:config/spring/applicationContext-*.xml</param-value > </context-param > <listener > <listener-class > org.springframework.web.context.ContextLoaderListener</listener-class > </listener > <servlet > <servlet-name > index-dispather</servlet-name > <servlet-class > org.springframework.web.servlet.DispatcherServlet</servlet-class > <init-param > <param-name > contextConfigLocation</param-name > <param-value > classpath*:config/spring/spring-mvc.xml</param-value > </init-param > <load-on-startup > 1</load-on-startup > <async-supported > true</async-supported > </servlet > <servlet-mapping > <servlet-name > index-dispather</servlet-name > <url-pattern > /</url-pattern > </servlet-mapping > <filter > <filter-name > CharacterEncodingFilter</filter-name > <filter-class > org.springframework.web.filter.CharacterEncodingFilter</filter-class > <init-param > <param-name > encoding</param-name > <param-value > utf-8</param-value > </init-param > </filter > <filter-mapping > <filter-name > CharacterEncodingFilter</filter-name > <url-pattern > /*</url-pattern > </filter-mapping > </web-app >
此处需要给servlet
及filter
添加异步<async-supported>true</async-supported>
.
spring-mvc.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:context ="http://www.springframework.org/schema/context" xmlns:mvc ="http://www.springframework.org/schema/mvc" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd" > <context:component-scan base-package ="com.lhalcyon.king.controller,com.lhalcyon.king.socket" /> <bean id ="internalResourceViewResolver" class ="org.springframework.web.servlet.view.InternalResourceViewResolver" > <property name ="prefix" value ="/WEB-INF/views/" /> <property name ="suffix" value =".jsp" /> </bean > <mvc:annotation-driven > </mvc:annotation-driven > <mvc:resources mapping ="/statics/**" location ="/WEB-INF/statics/" /> </beans >
此处需配置扫描controller和websocket所在包
还有一个applicationContext-websocket.xml
配置文件我们在代码实现中说明
代码实现 握手拦截器 HandshakeInterceptor
拦截器说明
An interceptor to copy information from the HTTP session to the “handshake attributes” map to made available via WebSocketSession.getAttributes()
Copies a subset or all HTTP session attributes and/or the HTTP session id
拦截器主要用于用户登录标识的记录,便于后面获取指定用户的会话标识并向指定用户发送消息,
这里我们继承HttpSessionHandshakeInterceptor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor { private Logger logger = Logger.getLogger(HandshakeInterceptor.class); @Override public boolean beforeHandshake (ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { logger.info("++ HandshakeInterceptor: beforeHandshake ++" +attributes); return super .beforeHandshake(request, response, wsHandler, attributes); } @Override public void afterHandshake (ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) { logger.info("++ HandshakeInterceptor: afterHandshake ++" ); super .afterHandshake(request, response, wsHandler, ex); } }
beforeHandshake(..)
Invoked before the handshake is processed.
afterHandshake(..)
Invoked after the handshake is done. The response status and headers indicate the results of the handshake, i.e. whether it was successful or not.
两个方法调用时机均为字面所述,握手前后分别调用.主要是在握手前后去做一些事,比如将需要的数据设置到attributes
里,之后在WebSocketHandler
的session中获取这些数据.
处理类 WebSocketHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public class MyWebSocketHandler implements WebSocketHandler { private static final Logger log = Logger.getLogger(MyWebSocketHandler.class); private static final ArrayList<WebSocketSession> users = new ArrayList <WebSocketSession>(); @Override public void afterConnectionEstablished (WebSocketSession session) throws Exception { log.info("connect websocket success......." ); users.add(session); } @Override public void handleMessage (WebSocketSession session, WebSocketMessage<?> message) throws Exception { Gson gson = new Gson (); Map<String, Object> msg = gson.fromJson(message.getPayload().toString(), new TypeToken <Map<String, Object>>() {}.getType()); log.info("handleMessage......." +message.getPayload()+"..........." +msg); String content = message.getPayload().toString(); TextMessage textMessage = new TextMessage (content, true ); sendMsgToAllUsers(textMessage); } @Override public void handleTransportError (WebSocketSession session, Throwable exception) throws Exception { log.warn("handleTransportError" ); users.remove(session); } @Override public void afterConnectionClosed (WebSocketSession session, CloseStatus closeStatus) throws Exception { log.info("connect websocket closed......." ); users.remove(session); } public void sendMsgToAllUsers (WebSocketMessage<?> message) throws Exception{ for (WebSocketSession user : users) { user.sendMessage(message); } } @Override public boolean supportsPartialMessages () { return false ; } }
以下对主要方法进行说明:
afterConnectionEstablished(..)
连接建立后调用,常用于记录用户的连接标识,便于后面信息发送.
handleTextMessage(..)
对消息进行处理.
handleTransportError(..)
连接异常处理.需要关闭出错会话连接
afterConnectionClosed(..)
连接关闭处理
此处我们对消息的处理很简单,即在接受消息后发送给所有连接的用户,类似一个匿名群聊室.
注册 完成了WebSocket处理类,还需要对其进行注册生效.这里有两种方式,择其一即可.
创建配置类,并通过注解注册 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Configuration @EnableWebMvc @EnableWebSocket public class MyWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer { @Override public void registerWebSocketHandlers (WebSocketHandlerRegistry registry) { registry.addHandler(myWebSocketHandler(),"/websocket" ).addInterceptors(new HandshakeInterceptor ()).setAllowedOrigins("*" ); registry.addHandler(myWebSocketHandler(), "/sockjs/websocket" ).addInterceptors(new HandshakeInterceptor ()) .withSockJS(); } @Bean public WebSocketHandler myWebSocketHandler () { return new MyWebSocketHandler (); } }
同时还需要配置上文提到过的Spring扫描配置类.
1 2 <context:component-scan base-package ="com.lhalcyon.king.controller,com.lhalcyon.king.socket" />
通过xml配置注册 applicationContext-websocket.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:websocket ="http://www.springframework.org/schema/websocket" xsi:schemaLocation =" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.0.xsd" > <bean id ="myHandler" class ="com.lhalcyon.king.socket.MyWebSocketHandler" /> <bean id ="myInterceptor" class ="com.lhalcyon.king.socket.HandshakeInterceptor" /> <websocket:handlers > <websocket:mapping path ="/websocket" handler ="myHandler" /> <websocket:handshake-interceptors > <ref bean ="myInterceptor" /> </websocket:handshake-interceptors > </websocket:handlers > <websocket:handlers > <websocket:mapping path ="/websocket" handler ="myHandler" /> <websocket:handshake-interceptors > <ref bean ="myInterceptor" /> </websocket:handshake-interceptors > <websocket:sockjs /> </websocket:handlers > </beans >
以上服务端代码实现基本完成,接下来对客户端测试页面做一个简单的实现.
客户端页面 index.jsp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 <%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE HTML> <html> <head> <title>首页</title> <meta charset="utf-8" > <meta name="viewport" content="width=device-width, initial-scale=1.0" > <meta name="renderer" content="webkit" > <!-- 引入 JQuery --> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js" ></script> <!-- 引入 sockJS --> <script type="text/javascript" src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js" ></script> <script type="text/javascript" > $(function() { var websocket; var url = 'ws://' + window.location.host + '/word-king/websocket' ; if ('WebSocket' in window) { websocket = new WebSocket (url); } else if ('MozWebSocket' in window) { websocket = new MozWebSocket (url); } else { url = "http://" + window.location.host +"/word-king/sockjs/websocket" ; websocket = new SockJS (url); } websocket.onopen = function(evnt) { console.log(" websocket.onopen " ); }; websocket.onmessage = function(evnt) { $("#msg" ).append("<p>(<font color='red'>" + evnt.data + "</font>)</p>" ); console.log(" websocket.onmessage " ); }; websocket.onerror = function(evnt) { alert("onerror" ); console.log(" websocket.onerror " ); }; websocket.onclose = function(evnt) { console.log(" websocket.onclose " ); alert("onclose" ); }; $("#TXBTN" ).click(function(){ var text = $("#tx" ).val(); if (text == null || text == "" ){ alert(" content can not empty!!" ); return false ; } var msg = { msgContent: text, postsId: 1 }; websocket.send(JSON.stringify(msg)); }); }); </script> </head> <body> <!-- 最外边框 --> <div style="margin: 20px auto; border: 1px solid blue; width: 300px; height: 500px;" > <!-- 消息展示框 --> <div id="msg" style="width: 100%; height: 70%; border: 1px solid yellow;overflow: auto;" ></div> <!-- 消息编辑框 --> <textarea id="tx" style="width: 100%; height: 20%;" ></textarea> <!-- 消息发送按钮 --> <button id="TXBTN" style="width: 100%; height: 8%;" >发送数据</button> </div> </body> </html>
需要注意的是此处引入JQuery
时,如果是本地的文件,可能存在无效的情况,需要去设置静态资源映射路径,可自行🔍解决.
本文采用的是引入在线地址
1 2 3 <script type ="text/javascript" src ="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.5/jquery.min.js" > </script >
地址写入后需Download下来
代码不高亮警告后即能生效.
最后我们来看看效果
参考: http://blog.csdn.net/mybook201314/article/details/70173674