3、Spring WebSocket服务器端实现:
3.1 引入项目依赖
使用eclipse建立maven项目后引入相关的依赖jar包,如下:
<properties>
<java-version>1.6</java-version>
<org.aspectj-version>1.6.10</org.aspectj-version>
<org.slf4j-version>1.6.6</org.slf4j-version>
<!-- Spring -->
<spring-framework.version>4.1.6.RELEASE</spring-framework.version>
<!-- Junit -->
<junit.version>4.11</junit.version>
<!-- Jackson -->
<jackson.version>2.6.0</jackson.version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-framework.version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- Spring WebSocket -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
3.2 、WebSocket相关类:
我们需要编写三个WebSocket的处理类,这里是利用了Spring WebSocket模块,三个类分别是WebSocket处理类SocketHandler、WebSocket配置类WebSocketConfig以及WebSocket拦截器WebSocketInterceptor。
WebSocketConfig.java:
package com.accenture.socket;
import org.springframework.beans.factory.annotation.Autowired;
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.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* @desp websocket配置
* @author liulichao@ruc.edu.cn
*
*/
@Configuration
@EnableWebMvc
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer{
@Autowired
private SocketHandler socketHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
//注册处理拦截器,拦截url为socketServer的请求
registry.addHandler(socketHandler, "/socketServer").addInterceptors(new WebSocketInterceptor());
//注册SockJs的处理拦截器,拦截url为/sockjs/socketServer的请求
registry.addHandler(socketHandler, "/sockjs/socketServer").addInterceptors(new WebSocketInterceptor()).withSockJS();
}
}
如上所示,在WebSocketConfig.java中,我们为SocketHandler注册了两个url请求,并应用了我们所定义的WebSocketInterceptor()拦截器。
WebSocketInterceptor.java:
package com.accenture.socket;
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.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
/**
* @desp websocket拦截器
* @author liulichao
*
*/
public class WebSocketInterceptor implements HandshakeInterceptor{
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler handler, Exception exception) {
}
/**
* @desp 将HttpSession中对象放入WebSocketSession中
*/
@Override
public boolean beforeHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler handler,
Map<String, Object> map) throws Exception {
if(request instanceof ServletServerHttpRequest){
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession();
if(session!=null){
//区分socket连接以定向发送消息
map.put("user", session.getAttribute("user"));
}
}
return true;
}
}
如上所示,在执行客户端服务器端握手之前,也就是在beforeHandshake()方法中,我们将HttpSession中我们登录后存储的对象放到WebSocketSession中,以此实现定向发送消息。
SocketHandler.java:
package com.accenture.socket;
import java.io.IOException;
import java.util.ArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
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;
/**
* @desp Socket处理类
* @author liulichao@ruc.edu.cn
*
*/
@Service
public class SocketHandler implements WebSocketHandler{
private static final Logger logger;
private static final ArrayList<WebSocketSession> users;
static{
users = new ArrayList<WebSocketSession>();
logger = LoggerFactory.getLogger(SocketHandler.class);
}
@Override
public void afterConnectionEstablished(WebSocketSession session)
throws Exception {
logger.info("成功建立socket连接");
users.add(session);
String username = session.getAttributes().get("user").toString();
if(username!=null){
session.sendMessage(new TextMessage("我们已经成功建立soket通信了"));
}
}
@Override
public void handleMessage(WebSocketSession arg0, WebSocketMessage<?> arg1)
throws Exception {
// TODO Auto-generated method stub
}
@Override
public void handleTransportError(WebSocketSession session, Throwable error)
throws Exception {
if(session.isOpen()){
session.close();
}
logger.error("连接出现错误:"+error.toString());
users.remove(session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus arg1)
throws Exception {
logger.debug("连接已关闭");
users.remove(session);
}
@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;
}
}
}
}
3.3 SpringMVC Controller类:
SocketController.java:
package com.accenture.controller;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.socket.TextMessage;
import com.accenture.socket.SocketHandler;
/**
* @desp Socket控制器
* @author liulichao@ruc.edu.cn
* @date 2016-5-6
*
*/
@Controller
public class SocketController{
private static final Logger logger = LoggerFactory.getLogger(SocketController.class);
@Autowired
private SocketHandler socketHandler;
@RequestMapping(value="/login")
public String login(HttpSession session){
logger.info("用户登录了建立连接啦");
session.setAttribute("user", "liulichao");
return "home";
}
@RequestMapping(value = "/message", method = RequestMethod.GET)
public String sendMessage(){
socketHandler.sendMessageToUser("liulichao", new TextMessage("这是一条测试的消息"));
return "message";
}
}
如上,在SocketController中我们定义了两个请求处理方法,首先执行login()后再session中存入user对象模拟用户已登录,而sendMessage()方法则是调用了sendMessageToUser()实现向某一个用户推送消息。
web.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 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.1.xsd">
<!-- 文件编码过滤器 -->
<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>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/application-config.xml</param-value>
</context-param>
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
4、前端页面实现:
4.1 SockJS
因为部分浏览器不支持WebSocket,因此我们需要应用SockJS。SockJS 是一个浏览器上运行的 JavaScript 库,如果浏览器不支持 WebSocket,该库可以模拟对 WebSocket 的支持,实现浏览器和 Web 服务器之间低延迟、全双工、跨域的通讯通道。
GitHub:https://github.com/sockjs/sockjs-client
4.2 前端页面
模拟登录页面home.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
String wsPath = "ws://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>
Hello world! This is a WebSocket demo!
<div id="message">
</div>
</h1>
<script type="text/javascript" src="js/jquery-1.12.2.min.js"></script>
<script type="text/javascript" src="js/sockjs.min.js"></script>
<script type="text/javascript">
$(function(){
//建立socket连接
var sock;
if ('WebSocket' in window) {
sock = new WebSocket("<%=wsPath%>socketServer");
} else if ('MozWebSocket' in window) {
sock = new MozWebSocket("<%=wsPath%>socketServer");
} else {
sock = new SockJS("<%=basePath%>sockjs/socketServer");
}
sock.onopen = function (e) {
console.log(e);
};
sock.onmessage = function (e) {
console.log(e)
$("#message").append("<p><font color='red'>"+e.data+"</font>")
};
sock.onerror = function (e) {
console.log(e);
};
sock.onclose = function (e) {
console.log(e);
}
});
</script>
</body>
</html>
如上所示,我们访问login模拟登录后之后将跳到home.jsp,在页面中通过sockjs与服务器端我们注册的WebSocket访问接口实现握手建立socket连接。
推送消息页面message.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>message</title>
</head>
<body>
<h1>已经发送消息了</h1>
</body>
</html>
在访问/message过后将调用消息发送的函数实现后端消息向前端的推送。
4、测试
首先,我们访问login函数,将跳至home.jsp视图,出现如下的页面:
在这一步中,我们已经在后台模拟登录,session中保存了user的对象,紧接着,我们再在新页面中访问message函数:
这时候我们再回去看home.jsp的页面:
可以看到,页面实现了更新,同时在socketServer握手请求之后并没有发生http的请求,同时我们可以在console中看到打印出来的通信的数据:
至此,我们成功实现了服务器端向客户端的消息推送。
整个maven项目的代码在附件中提供下载
文章来源:http://irmlab.ruc.edu.cn/2016/08/15/java-spring-websocket-2.html