您的位置:首页 > 编程语言 > Java开发

Spring WebSocket 初探

2017-05-11 14:36 232 查看


Spring WebSocket API

参考文档:https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html

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 实现消息功能


web.xml配置修改

<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_0.xsd" metadata-complete="true">


DispatcherServlet配置中加入
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>



pom.xm添加依赖

<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>



创建一个WebSocketHandler

1.可以扩展 AbstractWebSocketHandler 2.也可以扩展 TextWebSocketHandler(文本 WebSocket 处理器),TextWebSocketHandler 继承 AbstractWebSocketHandler;
public class TestHandler extends TextWebSocketHandler {

private final static Logger LOGGER = Logger.getLogger(TestHandler.class);

//已建立连接的用户
private static final ArrayList<WebSocketSession> users = new ArrayList<WebSocketSession>();;

/**
* 处理前端发送的文本信息
*
* @param session
* @param message
* @throws Exception
*/
@Override
protected void handleTextMessage(WebSocketSession session,
TextMessage message) throws Exception {
// 获取提交过来的消息详情
LOGGER.debug("-------handleMessage" + message.toString());
//回复一条信息,
session.sendMessage(new TextMessage("reply msg:" +message.getPayload()));

}

/**
* 当新连接建立的时候,被调用;
*
* @param session
* @throws Exception
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
LOGGER.info("Connection Established");
users.add(session);
session.sendMessage(new TextMessage("connect"));
session.sendMessage(new TextMessage("new_msg"));
super.afterConnectionEstablished(session);
}

/**
* 当连接关闭时被调用;
*
* @param session
* @param status
* @throws Exception
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception
4000
{
LOGGER.info("Connection closed. Status: " + status);
users.remove(session);
super.afterConnectionClosed(session, status);
}

/**
* 给所有在线用户发送消息
*
* @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(Constants.WEBSOCKET_USERNAME).equals(userName)) {
try {
if (user.isOpen()) {
user.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
}
}



创建WebSocketConfigurer

@Configuration
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {

@Override
public  void registerWebSocketHandlers(WebSocketHandlerRegistry registry){
// 将 TestHandler 处理器 映射到  /webSocketServer路径下.
registry.addHandler(testHandler(),"/webSocketServer")
.addInterceptors(new WebSocketHandshakeInterceptor());

// 将 SockJS的连接映射到  /webSocketServer/sockjs路径下.
registry.addHandler(testHandler(),"/webSocketServer/sockjs")
.addInterceptors(new WebSocketHandshakeInterceptor())
.withSockJS(); //开启SockJS的支持

//WebSocketHandshakeInterceptor 自定义websocket握手拦截器,在这里可以对用户名进行登记或进行用户分组,以便定向发送消息
}

@Bean
public WebSocketHandler testHandler(){
return  new TestHandler();
}
}



自定义握手拦截器

public class WebSocketHandshakeInterceptor implements HandshakeInterceptor {

private final static Logger LOGGER = Logger.getLogger(WebSocketHandshakeInterceptor.class);
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, 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(Constants.SESSION_USERNAME);
//这里是模拟测试,没有userName,用sessionId代为表示
attributes.put(Constants.WEBSOCKET_USERNAME,session.getId());
}
}
return true;
}

@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {

}
}



推送消息

private TestHandler testHandler = new TestHandler();

@RequestMapping(value = "sendMessage")
@ResponseBody
public String sendMessage(String msg){
//无关代码都省略了
testHandler.sendMessageToUsers(new TextMessage("using controller to send msg:"+msg));
return "done";
}



前端页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path ;
String serverPath = request.getServerName() + ":" + request.getServerPort() + path ;
%>
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Websocket</title>

<script src="<%=basePath%>/resources/js/jquery-2.1.4.min.js"></script>
<!--引入sockjs-->
<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

<script type="text/javascript">
var socket = null;

function connect() {
//方式一 SockJS的连接
//socket = new SockJS("<%=basePath%>/webSocketServer/sockjs");

//方式二 Websocket的连接
socket = new WebSocket('ws://<%=serverPath%>/webSocketServer');
socket.onopen = function () {
log('建立连接');
};

socket.onmessage = function (event) {
log('接收消息: ' + event.data);
$("#connect").hide();
$("#disconnect").show();
};

socket.onclose = function (event) {
log('连接关闭:'+event.data);
$("#connect").show();
$("#disconnect").hide();
};
}

function sendMsg() {
if (socket != null) {
socket.send($("#message").val());
} else {
alert('请先建立连接.');
}
}

function disconnect() {
if (ws != null) {
ws.close();
ws = null;
}
}

function log(message) {
var html = '<p style="word-wrap: break-word;">'+message+'</p>';
$("#console").append(html);
}
</script>
</head>
<body>

<div>
<div id="connect-container" style="text-align: center;margin: 40px;">
<div>
<button id="connect" onclick="connect();">连接</button>
<button id="disconnect" style="display: none" onclick="disconnect()">关闭连接</button>
<input id="message" style="width: 300px" placeholder="输入消息内容"/>
<button onclick="sendMsg();">发送消息</button>
</div>
<div id="console-container">
<div id="console"></div>
</div>
</div>
</div>
</body>
</html>



界面效果




STOMP 实现消息功能

public class Greeting {
private String content;

public Greeting(String content) {
this.content = content;
}

public String getContent() {
return content;
}
}

public class HelloMessage {
private String name;

public String getName() {
return name;
}
}

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
//启用 STOMP 代理中继功能: 并将其目的地前缀设置为 "/topic" or "/greetings" ;
// spring就能知道 所有目的地前缀为 "/topic" or "/greetings" 的消息都会发送到 STOMP 代理中;
config.enableSimpleBroker("/topic", "/greetings");

//设置应用的前缀为 "app":所有目的地以 "/app" 打头的消息(发送消息url not 连接url)都会路由到 带有 @MessageMapping 注解的方法中,而不会发布到 代理队列中;
config.setApplicationDestinationPrefixes("/app");
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/hello").withSockJS();
// 在网页上我们就可以通过这个链接 /server/hello 来和服务器的WebSocket连接
}
}

@Controller
public class GreetingController {

// @MessageMapping defines the sending addr for client.
// 消息发送地址: /app/hello
@MessageMapping("/hello")
@SendTo("/topic")
public Greeting greeting(HelloMessage message) throws Exception {
System.out.println("receiving " + message.getName());
System.out.println("connecting successfully.");
return new Greeting("Hello, " + message.getName() + "!");
}

@SubscribeMapping("/macro")
public Greeting handleSubscription() {
Greeting greeting = new Greeting("SubscribeMapping macro");
return greeting;
}

private SimpMessageSendingOperations  template;

@Autowired
public GreetingController(SimpMessageSendingOperations  template) {
this.template = template;
}

@RequestMapping(path="/send")
@ResponseBody
public String send(@RequestParam String message) {
//发送消息
this.template.convertAndSend("/topic", new Greeting(message));
return "done";
}
}

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path ;
%>
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Websocket</title>

<script src="<%=basePath%>/resources/js/jquery-2.1.4.min.js"></script>
<!--引入sockjs-->
<script src="<%=basePath%>/resources/js/sockjs-1.1.1.js"></script>
<script src="<%=basePath%>/resources/js/stomp.js"></script>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

<script type="text/javascript">

var stompClient = null;

//this line.
function connect() {
var socket = new SockJS("<%=basePath%>/hello");

ac07
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
stompClient.subscribe('/topic', function(greeting){
log("Received:"+JSON.parse(greeting.body).content);
});

stompClient.subscribe('/app/macro',function(greeting){
log("Received:"+JSON.parse(greeting.body).content);
});
});
}

function sendMsg() {
stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#message").val()}));
log("send:name="+$("#message").val());
}

function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
}

function log(message) {
var html = '<p style="word-wrap: break-word;">'+message+'</p>';
$("#console").append(html);
}
</script>
</head>
<body>

<div>
<div id="connect-container" style="text-align: center;margin: 40px;">
<div>
<button id="connect" onclick="connect();">连接</button>
<button id="disconnect" style="display: none" onclick="disconnect()">关闭连接</button>
<input id="message" style="width: 300px" placeholder="输入消息内容"/>
<button onclick="sendMsg();">发送消息</button>
</div>
<div id="console-container">
<div id="console"></div>
</div>
</div>
</div>
</body>
</html>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  WebSocket