基于Spring Websocket+SockJS实现的长连接请求

一、 背景:

现在实时web消息推送一般会用到websocket,但是由于此技术并没有推广开来,所以各浏览器对其支持也不同,例如下图显示了各类浏览器的支持情况。

 

 

粉红色区域表示不支持Websocket。

至于IE浏览器,以及部分陈旧的桌面浏览器,可以选择Flashsocket作为替代品。

客户端如何把Websocket和Flashsocket结合在一起使用,可借鉴开源项目:web-socket-js (客户端的Websocket实现方案)

 

二、解决方法

Spring 4.0的一个最大更新是增加了websocket的支持。websocket提供了一个在web应用中的高效、双向的通讯,需要考虑到客户端(浏览器)和服务器之间的高频和低延时消息交换。一般的应用场景有:在线交易、游戏、协作、数据可视化等。同时Spring WebSocket API提供了SockJS的支持,SockJs是一个脚本框架,它可以通过提供类似websocket的编程模式以适应不同的浏览器(包括不支持websocket的浏览器)。针对不同的浏览器环境,传输的协议可以是Http Streaming,long polling等。

 

三、简单配置和应用

下面简单的搭建一个Spring Websocket+SockJS的web项目,实现在线实时聊天的功能。

web.xml配置:

<web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">

其中: version 和 web-app_3_1.xsd 这个两个版本都必须是3.0+。

 

然后在这个servlet和所有的filter中加入

<async-supported>true</async-supported>

即让程序支持异步运行。

<servlet>

<servlet-name>dispatcher</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<load-on-startup>1</load-on-startup>

<async-supported>true</async-supported>

</servlet>

pom.xml中添加相关依赖:

<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.3.3</version> </dependency>

在dispatcher-servlet.xml中添加对注册服务类的扫描:

<context:component-scan base-package="com.milanosoft.RCS.web.webSocket.config" />

<context:component-scan base-package="com.milanosoft.RCS.web.webSocket.hndler">

</context:component-scan>

<context:component-scan base-package="com.milanosoft.RCS.web.webSocket.interceptor">

</context:component-scan>

 

使用WebSocketConfigurer集中注册WebSocket服务:

package com.milanosoft.RCS.web.webSocket.config;

 

import com.milanosoft.RCS.web.webSocket.hndler.SystemWebSocketHandler;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

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;

import com.milanosoft.RCS.web.webSocket.interceptor.HandshakeInterceptor;

import org.springframework.context.annotation.Bean;

 

@Configuration

@EnableWebMvc

@EnableWebSocket

//以@Bean的方式加载bean,并且支持springmvc和websocket

public class WebSocketConfig extends WebMvcConfigurerAdapter implements

WebSocketConfigurer {

 

public WebSocketConfig() {

}

 

@Override

public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {

//提供符合W3C标准的Websocket数据,用来注册websocket server实现类,第二个参数是访问websocket的地址

registry.addHandler(systemWebSocketHandler(), "/websck").addInterceptors(new HandshakeInterceptor()).setAllowedOrigins("*");

System.out.println("registed!");

//提供符合Sockjs的数据

//.setAllowedOrigins("*")解决跨域问题

registry.addHandler(systemWebSocketHandler(), "/sockjs/websck").addInterceptors(new HandshakeInterceptor()).setAllowedOrigins("*")

.withSockJS().setClientLibraryUrl(null);

}

//websocket的实现类

@Bean

public WebSocketHandler systemWebSocketHandler() {

//return new InfoSocketEndPoint();

return new SystemWebSocketHandler();

}

}

 

@Configuration

@EnableWebMvc

@EnableWebSocket

这三个大致意思是使这个类支持以@Bean的方式加载bean,并且支持springmvc和websocket。

 

Spring WebSocket API的核心接口是WebSocketHandler,以下就是处理消息的实现类。包括client与服务端的Connect、Dieconnect和Message的处理

package com.milanosoft.RCS.web.webSocket.hndler;

 

import java.io.IOException;

import java.util.ArrayList;

 

import org.springframework.stereotype.Component;

import org.springframework.web.socket.CloseStatus;

import org.springframework.web.socket.TextMessage;

import org.springframework.web.socket.WebSocketHandler;

import org.springframework.web.socket.WebSocketMessage;

import org.springframework.web.socket.WebSocketSession;

 

@Component

public class SystemWebSocketHandler implements WebSocketHandler {

private static final ArrayList<WebSocketSession> users;

static {

users = new ArrayList<>();

}

@Override

public void afterConnectionEstablished(WebSocketSession session) throws Exception {

System.out.println("connect to the websocket success......");

System.out.println("connect user"+session.getAttributes().get("user"));

users.add(session);

session.sendMessage(new TextMessage("Server:connected OK!"));

}

//可以根据业务传入在WebSocketMessage<?>传入实体类。

@Override

public void handleMessage(WebSocketSession wss, WebSocketMessage<?> wsm) throws Exception {

TextMessage returnMessage = new TextMessage(wsm.getPayload()

+ " received at server");

System.out.println(wss.getHandshakeHeaders().getFirst("Cookie"));

sendMessageToUsers(returnMessage);

//wss.sendMessage(returnMessage);

}

 

@Override

public void handleTransportError(WebSocketSession wss, Throwable thrwbl) throws Exception {

if(wss.isOpen()){

wss.close();

}

System.out.println("websocket connection closed......");

}

@Override

public void afterConnectionClosed(WebSocketSession wss, CloseStatus cs) throws Exception {

System.out.println("websocket connection closed......");

}

@Override

public boolean supportsPartialMessages() {

return false;

}

/**

* 给所有在线用户发送消息

*

* @param message

*/

public void sendMessageToUsers(TextMessage message) {

for (WebSocketSession user : users) {

try {

if (user.isOpen()) {

user.sendMessage(message);

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

 

/**

* 给某个用户发送消息

*

* @param userName

* @param message

*/

public void sendMessageToUser(String userName, TextMessage message) {

for (WebSocketSession user : users) {

if (user.getAttributes().get("user").equals(userName)) {

try {

if (user.isOpen()) {

user.sendMessage(message);

}

} catch (IOException e) {

e.printStackTrace();

}

break;

}

}

}

}

 

监听握手操作:(在握手时可以将连接的用户信息添加到内存中,以此来统计在线人数)。

package com.milanosoft.RCS.web.webSocket.interceptor;

 

import java.util.Map;

import javax.servlet.http.HttpSession;

import org.springframework.http.server.ServerHttpRequest;

import org.springframework.http.server.ServerHttpResponse;

import org.springframework.http.server.ServletServerHttpRequest;

import org.springframework.stereotype.Component;

import org.springframework.web.socket.WebSocketHandler;

import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

 

@Component

public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor {

@Override

public boolean beforeHandshake(ServerHttpRequest request,

ServerHttpResponse response, WebSocketHandler wsHandler,

Map<String, Object> attributes) throws Exception {

//解决The extension [x-webkit-deflate-frame] is not supported问题

if(request.getHeaders().containsKey("Sec-WebSocket-Extensions")) {

request.getHeaders().set("Sec-WebSocket-Extensions", "permessage-deflate");

}

//return true;

if (request instanceof ServletServerHttpRequest) {

ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;

HttpSession session = servletRequest.getServletRequest().getSession(false);

if (session != null) {

//使用userName区分WebSocketHandler,以便定向发送消息

String userName = (String) session.getAttribute("user");

//attributes.put("username", userName);

}

}

System.out.println("Before Handshake");

return super.beforeHandshake(request, response, wsHandler, attributes);

}

@Override

public void afterHandshake(ServerHttpRequest request,

ServerHttpResponse response, WebSocketHandler wsHandler,

Exception ex) {

System.out.println("After Handshake");

super.afterHandshake(request, response, wsHandler, ex);

}

}

四、其他问题

针对于内存泄漏问题,我采用的是Spring原生的IntrospectorCleanupListener的监听器,在web 应用关闭的时候释放与掉这个web 应用相关的class loader 。

 

参考文章:

http://blog.csdn.net/hzzhoushaoyu/article/details/49407835

http://blog.csdn.net/linlzk/article/details/50153745

http://blog.csdn.net/sl543001/article/details/19343005

http://blog.csdn.net/yingxiake/article/details/51224569

https://my.oschina.net/ldl123292/blog/304360

http://www.cnblogs.com/davidwang456/p/5321413.html

 

官方文档:

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html

websocket API:

http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/socket/WebSocketHandler.html

 

基于MessageBroker的项目

http://www.jianshu.com/p/60799f1356c5

http://blog.csdn.net/xjyzxx/article/details/24182677

http://blog.csdn.net/pete_lee/article/details/51453352

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值