案例
Spring+Websocket实现消息的推送
步骤
1、用户登录后建立websocket连接,默认选择websocket连接,如果浏览器不支持,则使用sockjs进行模拟连接
2、建立连接后,服务端返回该用户的未读消息
3、服务端进行相关操作后,推送给某一个用户或者所有用户新消息
相关环境
spring4.0+ , tomcat7+
maven包
<!-- spring websocket库 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
Websocet服务端实现
1 @Configuration
2 @EnableWebMvc
3 @EnableWebSocket
4 // 注册websocket server实现类,设置访问websocket,Sockjs的地址
5 public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
6 @Override
7 public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
8
9 //允许连接的域,只能以http或https开头
10 String[] allowsOrigins = {"http://127.0.0.1:8080"};
11
12 // 支持websocket 的访问链接
13 registry.addHandler(systemWebSocketHandler(), "/echo.send").setAllowedOrigins(allowsOrigins).
14 addInterceptors(handshakeInterceptor());
15
16 // 不支持websocket的访问链接
17 registry.addHandler(systemWebSocketHandler(), "/sockjs/echo.send").setAllowedOrigins(allowsOrigins).
18 addInterceptors(handshakeInterceptor()).withSockJS();
19 }
20
21 @Bean
22 public SystemWebSocketHandler systemWebSocketHandler(){
23 return new SystemWebSocketHandler();
24 }
25
26 @Bean
27 public HandshakeInterceptor handshakeInterceptor(){
28 return new HandshakeInterceptor();
29 }
30 }
SystemWebSocketHandler
1 public class SystemWebSocketHandler extends TextWebSocketHandler{
2
3 private static final Logger logger = Logger.getLogger(SystemWebSocketHandler.class);
4
5 private static final ArrayList<WebSocketSession> users = new ArrayList<>();
6
7 @Autowired
8 private WebSocketService webSocketService;
9
10 // 初次链接成功执行
11 @Override
12 public void afterConnectionEstablished(WebSocketSession session) throws Exception {
13 logger.debug("链接成功......");
14 users.add(session);
15 Integer staffId = (Integer) session.getAttributes().get("staffId");
16 String exitTime = (String) session.getAttributes().get(staffId+"_exitTime");
17 logger.debug("staffId:" + staffId);
18 if (staffId != null && staffId != 0) {
19 // 这块会实现自己业务,比如,当用户登录后,会把离线消息推送给用户
20 // 查询未读消息
21 //TextMessage returnMessage = webSocketService.findCheckInfoCount(exitTime);
22 //session.sendMessage(returnMessage);
23 session.sendMessage(new TextMessage("xxxx"));
24 }
25 }
26
27 /**
28 * 接受消息处理消息 js用websocket.send()时候,会调用该方法
29 */
30 @Override
31 public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
32
33 }
34
35 @Override
36 public void handleTransportError(WebSocketSession session, Throwable throwable) throws Exception {
37 if (session.isOpen()) {
38 session.close();
39 }
40 logger.info("链接出错,关闭链接......");
41 users.remove(session);
42 }
43
44 @Override
45 public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
46 Integer staffId = (Integer) session.getAttributes().get("staffId");
47 webSocketService.setLogoutTime(staffId);
48 users.remove(session);
49 logger.info("链接关闭......" + closeStatus.toString()+",staffId:"+staffId);
50 }
51
52 @Override
53 public boolean supportsPartialMessages() {
54 return false;
55 }
56
57
58 /**
59 * 给所有在线用户发送消息
60 *
61 * @param message
62 */
63 public void sendMessageToUsers(TextMessage message) {
64 for (WebSocketSession user : users) {
65 try {
66 if (user.isOpen()) {
67 user.sendMessage(message);
68 }
69 } catch (IOException e) {
70 logger.error(e, e);
71 }
72 }
73 }
74
75 /**
76 * 给某个用户发送消息
77 *
78 * @param userName
79 * @param message
80 */
81 public void sendMessageToUser(int staffId, TextMessage message) {
82 for (WebSocketSession user : users) {
83 if ((Integer) user.getAttributes().get("staffId") == staffId) {
84 try {
85 if (user.isOpen()) {
86 user.sendMessage(message);
87 }
88 } catch (IOException e) {
89 logger.error(e, e);
90 }
91 break;
92 }
93 }
94 }
95
96 /**
97 * 给某个权限集合的用户发送消息
98 *
99 * @param roleCode
100 * 角色代码 例如:|2|
101 * @param message
102 */
103 public void sendMessageToRole(String roleCode, TextMessage message) {
104 for (WebSocketSession user : users) {
105 if (((String) user.getAttributes().get("roleCode")).contains(roleCode)) {
106 try {
107 if (user.isOpen()) {
108 user.sendMessage(message);
109 }
110 } catch (IOException e) {
111 logger.error(e, e);
112 }
113 break;
114 }
115 }
116 }
117 }
WebSocketHandshakeInterceptor
// 这个的主要作用是取得当前请求中的用户名,并且保存到当前的WebSocketHandler中,以便确定WebSocketHandler所对应的用户
public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor {
private static final Logger logger = Logger.getLogger(HandshakeInterceptor.class);
// 进入hander之前的拦截
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> attributes) throws Exception {
logger.info("beforeHandshake...start");
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession(false);
//HttpSession session = ((ServletServerHttpRequest) request).getServletRequest().getSession();
if (session != null) {
// 使用userName区分WebSocketHandler,以便定向发送消息 如: String userName = (String) session.getAttribute("WEBSOCKET_USERNAME");
//attributes.put("staffId", session.getAttribute("staffId"));
//attributes.put("roleCode", session.getAttribute("roleCode"));
//attributes.put(session.getAttribute("staffId") + "_exitTime", session.getAttribute(session.getAttribute("staffId") + "_exitTime"));
}
}
logger.info("beforeHandshake...end");
return true;
}
@Override
public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
logger.debug("afterHandshake...start");
super.afterHandshake(serverHttpRequest, serverHttpResponse, webSocketHandler, e);
logger.debug("afterHandshake...end");
}
}
socketHelper.js
1 var websocket;
2 $(window).load(function() {
3 socketInit();
4 });
5
6 function socketInit(){
7 if ('WebSocket' in window) {
8
9 websocket = new WebSocket("ws://127.0.0.1:8080/echo.send");
10 } else if ('MozWebSocket' in window) {
11
12 websocket = new MozWebSocket("ws://127.0.0.1:8080/echo.send");
13 } else {
14
15 websocket = new SockJS("http://127.0.0.1:8080/echo.send");
16 }
17
18 websocket.onopen = function(event) {
19 console.log('Info: connection opened.');
20 };
21 websocket.onmessage = function(event) {
22 console.log('Received.message: ' + event.data); //处理服务端返回消息
23 };
24 websocket.onerror = function(event) {
25 alert('Received.error: ' + event.data);
26 };
27 websocket.onclose = function(event) {
28 console.log('Info: connection closed.');
29 console.log(event);
30 }
31 }
如果使用注解方式则添加相应注解 也可使用xml配置方式(注解的我没弄好使 错误代码:403 估计也是下面说的访问源问题)
更新applicationContext.xml(默认配置文件,根据自己情况修改即可)的xmlns
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd"
配置如下:
<websocket:handlers allowed-origins="*">
<websocket:mapping handler="handler" path="/echo.send"/>
<websocket:handshake-interceptors>
<bean class="com.zhuo.console.core.interceptor.HandshakeInterceptor"/>
</websocket:handshake-interceptors>
</websocket:handlers>
<websocket:handlers allowed-origins="*">
<websocket:mapping handler="handler" path="/echo.send"/>
<websocket:handshake-interceptors>
<bean class="com.zhuo.console.core.interceptor.HandshakeInterceptor"/>
</websocket:handshake-interceptors>
<websocket:sockjs/>
</websocket:handlers>
<bean id="handler" class="com.zhuo.console.websocket.SystemWebSocketHandler" />
注意: allowed-origins="*" spring javadoc的说明是默认情况下,允许所有来源访问,但我跑下来发现不配置allowed-origins的话总是报403错误。 有的说sockjs是不允许有后缀的,否则将无法匹配,这个还没试过等试过后再确认
web.xml中 web-app 的 version 和 xsi:schemaLocation 必须要3.0+
servlet与filter需要加 <async-supported>true</async-supported>
(上面的说必须加我也没试过不加行不行)
配置nginx支持websocket,默认情况下,nginx不支持自动升级至websocket协议,否则js中会出现连接时异常"Error during WebSocket handshake: Unexpected response code: 400",需在恰当的位置加上如下设置:
server {
listen 8020;
location / {
proxy_pass http://websocket;
proxy_set_header Host $host:8020; #注意, 原host必须配置, 否则传递给后台的值是websocket,端口如果没有输入的话会是80, 这会导致连接失败
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
}
upstream websocket {
server 192.168.100.10:8081;
}
经上述调整后,websocket就可以同时支持通过nginx代理的https协议,结合MQ机制,可以做到B端实时推送、B端/C端实时通信。
nginx的https(自动跳转http->https)+nginx+websocket的完整配置可参考http://www.cnblogs.com/zhjh256/p/6262620.html。