WebSocket是html5提出的一个用于通信的协议规范,该协议通过一个握手机制,在客户端和服务端之间建立一个类似于TCP的连接,从而方便客户端和服务端之间的通信。
WebSocket协议本质上是一个基于TCP的协议,是先通过HTTP/HTTPS协议发起一条特殊的HTTP请求进行握手后创建一个用于交换数据的TCP连接,此后服务端与客户端通过此TCP连接进行实时通信。客户端和服务端只需要要做一个握手的动作,在建立连接之后,服务端和客户端之间就可以通过此TCP连接进行实时通信。
websocket是建立在物理层上的连接,相比于基于网络层的长连接可以节约资源,相比于AJAX轮训可以降低服务器压力。
spring在4.0后将websocket集成进去,要使用spring的websocket的话,spring的版本要在4.0及以上。
spring使用websocket需要的jar(pom.xml)
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>4.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>4.0.6.RELEASE</version> </dependency>
web.xml中对websocket的配置
<servlet> <servlet-name>springMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:/springMVC/spring-webSocket.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>springMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
springMVC中对websocket的配置
<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"> <!-- websocket处理类 --> <bean id="msgHandler" class="com.test.websocket.MsgWebSocketHandler" /> <!-- 握手接口/拦截器 ,看项目需求是否需要--> <bean id="handshakeInterceptor" class="com.test.websocket.HandshakeInterceptor" /> <websocket:handlers> <websocket:mapping path="/websocket" handler="msgHandler" /> <websocket:handshake-interceptors> <ref bean="handshakeInterceptor" /> </websocket:handshake-interceptors> </websocket:handlers> <!-- 注册 sockJS,sockJs是spring对不能使用websocket协议的客户端提供一种模拟 --> <websocket:handlers> <websocket:mapping path="/sockjs/websocket" handler="msgHandler" /> <websocket:handshake-interceptors> <ref bean="handshakeInterceptor" /> </websocket:handshake-interceptors> <websocket:sockjs /> </websocket:handlers> </beans>
握手拦截器,继承HttpSessionHandshakeInterceptor类,做一些连接握手或者握手后的一些处理
public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor { // 握手前 @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { System.out .println("++++++++++++++++ HandshakeInterceptor: beforeHandshake ++++++++++++++" + attributes); return super.beforeHandshake(request, response, wsHandler, attributes); } // 握手后 @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) { System.out .println("++++++++++++++++ HandshakeInterceptor: afterHandshake ++++++++++++++"); super.afterHandshake(request, response, wsHandler, ex); } }
创建MsgWebSocketHandler类继承WebSocketHandler类(Spring提供的有AbstractWebSocketHandler类、TextWebSocketHandler类、BinaryWebSocketHandler类,看自己需要进行继承),该类主要是用来处理消息的接收和发送
public class MsgWebSocketHandler implements WebSocketHandler { private Logger logger = LoggerFactory.getLogger(MsgWebSocketHandler.class); //保存用户链接 private static ConcurrentHashMap<String, WebSocketSession> users = new ConcurrentHashMap<String, WebSocketSession>(); // 连接 就绪时 @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { users.put(session.getId(), session); } // 处理信息 @Override public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception { System.err.println(session + "---->" + message + ":"+ message.getPayload().toString()); } // 处理传输时异常 @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { } // 关闭 连接时 @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) { logger.debug("用户: " + session.getRemoteAddress() + " is leaving, because:" + closeStatus); } //是否支持分包 @Override public boolean supportsPartialMessages() { return false; } }
要进行发送消息的操作,自己可以写方法,利用保存的已经完成连接的WebSocketSession,通过调用sendMessage(WebScoketMessage<?> message)方法进行消息的发送,参数message是发送的消息内容,Spring提供的类型有TextMessage、BinaryMessage、PingMessage、PongMessage。
前端客户端JS
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>websocket</title> </head> <style type="text/css"> #div { color: red; } </style> <body> <h1>WEBSOCKET----TEST</h1> <div id="div"> </div> </script> <script type="text/javascript"> var div = document.getElementById(‘div‘); var socket = new WebSocket(‘ws://127.0.0.1:8088/websocket‘); socket.onopen = function(event){ console.log(event); socket.send(‘websocket client connect test‘); } socket.onclose = function(event){ console.log(event); } socket.onerror = function(event){ console.log(event); } socket.onmessage = function(event){ console.log(event) div.innerHTML += (‘ @[email protected] ‘ + event.data + ‘ ~_~ ‘); } </script> </body> </html>
其实在后台也可以进行websocket客户端的创建
@Test public void connectTest(){ WsWebSocketContainer wsWebSocketContainer = new WsWebSocketContainer(); wsWebSocketContainer.setDefaultMaxSessionIdleTimeout(300); StandardWebSocketClient client = new StandardWebSocketClient(wsWebSocketContainer); WebSocketHandler webSocketHandler = new MyWebSocketHandler(); String uriTemplate = "ws://127.0.0.1:8088/websocket?account=11111"; Object uriVars = null; ListenableFuture<WebSocketSession> future = client.doHandshake(webSocketHandler, uriTemplate, uriVars); try { WebSocketSession session = future.get(); session.sendMessage(new TextMessage("hello world")); } catch (InterruptedException | ExecutionException | IOException e) { e.printStackTrace(); } } @Test public void connectTest2(){ StandardWebSocketClient client = new StandardWebSocketClient(); WebSocketHandler webSocketHandler = new MyWebSocketHandler(); String uriTemplate = "ws://127.0.0.1:8088/websocket"; UriComponentsBuilder fromUriString = UriComponentsBuilder.fromUriString(uriTemplate); fromUriString.queryParam("account","111111"); /* * 作用同上,都是将请求参数填入到URI中 * MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>(); * params.add("account","111111"); * fromUriString.queryParams(params); * */ URI uri = fromUriString.buildAndExpand().encode().toUri(); WebSocketHttpHeaders headers = null; ListenableFuture<WebSocketSession> doHandshake = client.doHandshake(webSocketHandler, headers , uri); try { WebSocketSession session = doHandshake.get(); session.sendMessage(new TextMessage("hello world")); } catch (InterruptedException | ExecutionException | IOException e) { e.printStackTrace(); } }
这是我利用Junit进行的测试,实验证明是可以完成websocket的连接。
注意:websocket在进行连接的时候是可以类似get请求一样,将参数拼接到url中。还可以自己将参数封装进URI中,利用另一个方法进行连接的握手操作。