websocket是Html5新增加特性之一,目的是浏览器与服务端建立全双工的通信方式,解决http请求-响应带来过多的资源消耗,同时对特殊场景应用提供了全新的实现方式,比如聊天、股票交易、游戏等对对实时性要求较高的行业领域。
Spring4开始支持WebSocket,也支持通过STOMP协议连接JMS消息服务器如ActiveMQ等。WebSocket是目前唯一真正实现全双工通信的服务器向客户端推的互联网技术,与长连接和轮询技术相比,WebSocket的优越性不言自明,长连接的连接资源(线程资源)随着连接数量的增多必会耗尽,客户端轮询会给服务器造成很大的压力,而WebSocket是在物理层非网络层建立一条客户端至服务器的长连接,以此来保证服务器向客户端的即时推送,既不耗费线程资源,又不会不断向服务器轮询请求。
1.spring 4.0及以上增加了WebSocket的支持(这里使用4.3.8.RELEASE)
2.spring 支持STOMP协议的WebSocket通信
3.应对不支持 WebSocket 的场景,许多浏览器不支持 WebSocket 协议;SockJS 是 WebSocket 技术的一种模拟。SockJS 会 尽可能对应 WebSocket API,但如果 WebSocket 技术 不可用的话,会从如下 方案中挑选最优可行方案:
XHR streaming
XDR streaming
iFrame event source
iFrame HTML file
XHR polling
XDR polling
iFrame XHR polling
JSONP polling
4.WebSocket 是发送和接收消息的 底层API,而SockJS 是在 WebSocket 之上的 API;最后 STOMP(面向消息的简单文本协议)是基于 SockJS 的高级API
5.SockJS 所处理的URL 是 “http: ” 或 “https: ” 模式
6.WebSocket 所处理的URL 是“ws: ” or “wss: ” 模式
下面我们来建立一个websocket实例
一。 建立maven工程
dom.xml 加入websocket所依赖的包
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
spring框架加入 spring-core, spring-context, spring-web ,spring-webmvc 类库,贴出完整的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.jstudioframework</groupId>
<artifactId>jstudio-webstocket1</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 使用最近较新的spring版本 -->
<spring.version>4.3.7.RELEASE</spring.version>
<slf4j.version>1.6.6</slf4j.version>
<jackson.version>2.8.4</jackson.version>
<servlet-api.version>3.1.0</servlet-api.version>
</properties>
<dependencies>
<!-- 日志处理 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- spring相关 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<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.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet-api.version}</version>
<scope>provided</scope>
</dependency>
<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>
</dependencies>
<build>
<plugins>
<!-- 配置Tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<port>8080</port>
<path>/ws</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
二。创建一个Websocket处理器
可以扩展 AbstractWebSocketHandler ,也可以扩展 TextWebSocketHandler(文本处理器),TextWebSocketHandler 继承 AbstractWebSocketHandler
package org.jstudioframework.websoket.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.ArrayList;
/**
* Websocket处理器
*/
public class WebSocketHandler extends TextWebSocketHandler {
private final static Logger LOGGER = LoggerFactory.getLogger(WebSocketHandler.class);
//已建立连接的用户
private static final ArrayList<WebSocketSession> users = new ArrayList<WebSocketSession>();
/**
* 处理前端发送的文本信息
* js调用websocket.send时候,会调用该方法
*
* @param session
* @param message
* @throws Exception
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String username = (String) session.getAttributes().get("WEBSOCKET_USERNAME");
// 获取提交过来的消息详情
LOGGER.debug("收到用户 " + username + "的消息:" + message.toString());
//回复一条信息,
session.sendMessage(new TextMessage("reply msg:" + message.getPayload()));
}
/**
* 当新连接建立的时候,被调用
* 连接成功时候,会触发页面上onOpen方法
*
* @param session
* @throws Exception
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
users.add(session);
String username = (String) session.getAttributes().get("WEBSOCKET_USERNAME");
LOGGER.info("用户 " + username + " Connection Established");
session.sendMessage(new TextMessage(username + " connect"));
session.sendMessage(new TextMessage("hello wellcome"));
}
/**
* 当连接关闭时被调用
*
* @param session
* @param status
* @throws Exception
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
String username = (String) session.getAttributes().get("WEBSOCKET_USERNAME");
LOGGER.info("用户 " + username + " Connection closed. Status: " + status);
users.remove(session);
}
/**
* 传输错误时调用
*
* @param session
* @param exception
* @throws Exception
*/
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
String username = (String) session.getAttributes().get("WEBSOCKET_USERNAME");
if (session.isOpen()) {
session.close();
}
LOGGER.debug("用户: " + username + " websocket connection closed......");
users.remove(session);
}
/**
* 给所有在线用户发送消息
*
* @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("WEBSOCKET_USERNAME").equals(userName)) {
try {
if (user.isOpen()) {
user.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
}
}
三。创建一个WebSocket握手拦截器
package org.jstudioframework.websoket.interceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
import javax.servlet.http.HttpSession;
import java.util.Map;
/**
* WebSocket握手拦截器
*/
public class WebSocketHandshakeInterceptor implements HandshakeInterceptor {
private final static Logger LOGGER = LoggerFactory.getLogger(WebSocketHandshakeInterceptor.class);
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession(false);
if (session != null) {
//使用userName区分WebSocketHandler,以便定向发送消息
String userName = (String) session.getAttribute("SESSION_USERNAME");
if (userName == null) {
userName = "system-" + session.getId();
}
attributes.put("WEBSOCKET_USERNAME", userName);
}
}
return true;
}
public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
System.out.println("After Handshake");
}
}
四。 Spring WebSocket的配置文件,采用的是注解的方式
package org.jstudioframework.websoket.config;
import org.jstudioframework.websoket.handler.WebSocketHandler;
import org.jstudioframework.websoket.interceptor.WebSocketHandshakeInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.config.annotation.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
/**
* Spring WebSocket的配置,这里采用的是注解的方式
*/
@Configuration
//@EnableWebMvc//这个标注可以不加,如果有加,要extends WebMvcConfigurerAdapter
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
//1.注册WebSocket
String websocket_url = "/websocket/socketServer.do"; //设置websocket的地址
registry.addHandler(webSocketHandler(), websocket_url). //注册Handler
addInterceptors(new WebSocketHandshakeInterceptor()); //注册Interceptor
//2.注册SockJS,提供SockJS支持(主要是兼容ie8)
String sockjs_url = "/sockjs/socketServer.do"; //设置sockjs的地址
registry.addHandler(webSocketHandler(), sockjs_url). //注册Handler
addInterceptors(new WebSocketHandshakeInterceptor()). //注册Interceptor
withSockJS(); //支持sockjs协议
}
@Bean
public TextWebSocketHandler webSocketHandler() {
return new WebSocketHandler();
}
}
五。创建Controller测试类
package org.jstudioframework.websoket.controller;
import org.jstudioframework.websoket.handler.WebSocketHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.socket.TextMessage;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* 测试类
*/
@Controller
public class PageController {
@Bean//这个注解会从Spring容器拿出Bean
public WebSocketHandler infoHandler() {
return new WebSocketHandler();
}
@RequestMapping("/login")
public void login(HttpServletRequest request, HttpServletResponse response) throws Exception {
String username = request.getParameter("username");
System.out.println(username + "登录");
HttpSession session = request.getSession();
session.setAttribute("SESSION_USERNAME", username);
response.sendRedirect("websocket.jsp");
}
@RequestMapping("/send")
@ResponseBody
public String send(HttpServletRequest request) {
String username = request.getParameter("username");
infoHandler().sendMessageToUser(username, new TextMessage("你好,欢迎测试!!!!"));
return null;
}
}
六。测试页面
1.登录页面,输入用户名称 login.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>
<body>
<h2>Wellcome</h2>
<body>
<form action="login">
登录名:<input type="text" name="username"/>
<input type="submit" value="登录"/>
</form>
</body>
</body>
</html>
2.消息发送接收页面 websocket.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>Java API for WebSocket (JSR-356)</title>
</head>
<body>
<script type="text/javascript" src="http://cdn.bootcss.com/jquery/3.1.0/jquery.min.js"></script>
<script type="text/javascript" src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.js"></script>
<script type="text/javascript">
var websocket = null;
if ('WebSocket' in window) {
//Websocket的连接
websocket = new WebSocket("ws://localhost:8080/ws/websocket/socketServer.do");//WebSocket对应的地址
}
else if ('MozWebSocket' in window) {
//Websocket的连接
websocket = new MozWebSocket("ws://localhost:8080/ws/websocket/socketServer.do");//SockJS对应的地址
}
else {
//SockJS的连接
websocket = new SockJS("http://localhost:8080/ws/sockjs/socketServer.do"); //SockJS对应的地址
}
websocket.onopen = onOpen;
websocket.onmessage = onMessage;
websocket.onerror = onError;
websocket.onclose = onClose;
function onOpen(openEvt) {
//alert(openEvt.Data);
}
function onMessage(evt) {
alert(evt.data);
}
function onError() {
}
function onClose() {
}
function doSend() {
if (websocket.readyState == websocket.OPEN) {
var msg = document.getElementById("inputMsg").value;
websocket.send(msg);//调用后台handleTextMessage方法
alert("发送成功!");
} else {
alert("连接失败!");
}
}
window.close = function () {
websocket.onclose();
}
</script>
请输入:<textarea rows="3" cols="100" id="inputMsg" name="inputMsg"></textarea>
<button οnclick="doSend();">发送</button>
</body>
</html>
七。spring的配置
<?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: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-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<context:annotation-config />
<mvc:annotation-driven />
<context:component-scan base-package="org.jstudioframework.websoket" />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
八。web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app 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="3.1">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/*.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<async-supported>true</async-supported>
<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>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
所有的servlet和filter都要加<async-supported>true</async-supported>
九。测试
工程代码实例下载地址
http://download.csdn.net/download/zsg88/9933921