SpringBoot集成WebSocket

导读:本篇文章讲解 SpringBoot集成WebSocket,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

SpringBoot集成WebSocket


前言

案例github地址(如果有用点个star呗) https://github.com/chenxiban/BlogCaseSet.git


WebSocket 介绍

WebSocket 是一种网络通信协议。WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通
讯的协议。


WebSocket的引入

了解计算机网络协议的人,应该都知道:HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采
用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。

这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。

这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程
序将通过频繁的异步JavaScript和XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须
不停连接,或者 HTTP 连接始终打开)。如图:

在这里插入图片描述

因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。WebSocket 连接允许客户
端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需
要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。 如
图:

在这里插入图片描述


WebSocket对象

以下 API 用于创建 WebSocket 对象。

var Socket = new WebSocket(url, [protocol]);

以上代码中的第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。


WebSocket属性

以下是WebSocket对象的属性,假定我们使用了以上代码创建了WebSocket对象。

属性 描述
Socket.readyState 只读属性 readyState表示连接状态,可以是以下值:0 – 表示连接尚未建立。1 – 表示连接已建立,可以进行通信。2 – 表示连接正在进行关闭。3 – 表示连接已经关闭或者连接不能打开。
Socket.bufferedAmount 只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。

WebSocket事件

以下是 WebSocket对象的相关事件。假定我们使用了以上代码创建了 Socket 对象:

事件 事件处理程序 描述
open Socket.onopen 连接建立时触发
message Socket.onmessage 客户端接收服务端数据时触发
error Socket.onerror 通信发生错误时触发
close Socket.onclose 连接关闭时触发

WebSocket方法

以下是 WebSocket对象的相关方法。假定我们使用了以上代码创建了 Socket 对象:

方法 描述
Socket.send() 使用连接发送数据
Socket.close() 关闭连接

SpringBoot整合WebSocket

SpringBoot对WebSocket提供了很好的支持,具体集成过程如下:

添加依赖

springboot的高级组件会自动引用基础的组件,像spring-boot-starter-websocket就引入了spring-boot-starter-web和spring-boot-starter,所以不要重复引入 。

<dependency> 
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
    <version>1.3.5.RELEASE</version>
</dependency>

项目结构

搭建项目的整体结构,如下图:

在这里插入图片描述

WebSocket客户端实现

客户端首先创建WebSocket对象,与服务器端建立连接,接下来开始通信,主要代码如下:(只是个栗子)

<script type="text/javascript">
		var wsurl = "ws://localhost:800/myWebsocket";
		//WebSocket服务器地址
		var sockurl = "ws:localhost:800/myWebsocket/sockjs";
		//基本没用
		var websocket; //声明一个WebSocket客户端
		//检测浏览器兼容性
		if ('WebSocket' in window) {
			websocket = new
			WebSocket(wsurl); //当前所有浏览器全部都支持
			console.log("当前所有浏览器全部都支持 WebSocket in window =>" + websocket);
		} else if ('MozWebSocket' in window) {
			websocket = new MozWebSocket(wsurl); //基本没用
			console.log("基本没用 MozWebSocket in window =>" + websocket);
		} else {
			websocket = new SockJS(sockurl); //基本没用
			console.log("基本没用 else new SockJS=>" + websocket);
		}
		//********* 成功连接服务器*********
		websocket.onopen = function(event) {
			console.log("WebSocket:成功连接服务器 ");
		};
		//********* 接收服务器消息 *********
		websocket.onmessage = function(event) {
			console.log("wesocket接收服务器数据event=>" + event);
			console.log("wesocket接收服务器数据event.data=>" + event.data);
			// var mage=JSON.parse(event.data);
			//event.data是JSON格式字符串,转为JS对象
			// console.log("wesocket接收服务器data=>"+mage);
		};
		//********* 服务器发生异常错误 *********
		websocket.onerror = function(event) {
			console.log(event);
		};
		//********* 服务器关闭 *********
		websocket.onclose = function(event) {
			console.log("WebSocket:已关闭");
			console.log("WebSocket:服务器发生异常错误 ");
			console.log(event);
		};
	</script>
  • onopeng事件,当与服务端成功创建连接时触发。
  • onmessage事件,接收服务器发送来的消息。
  • onerror事件,当服务器端发生异常时触发。
  • onclose事件,当服务器端关闭时触发。
  • send方法,向服务器端发送信息。

编写index.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>WebSocket客户端</title>
		<script type="text/javascript" src="jquery.min.js" ></script>
	</head>
	<body>
		
		<input type="text" id="msg" /><button onclick="sendMsg()">发给服务器</button>
		
		
		<script type="text/javascript">
			//6. 主动发送数据给服务器
			function sendMsg(){
				websocket.send( $('#msg').val() );
			}
			//7. 主动关闭通信
			function close(){
				websocket.close();
			}
		</script>
		
		
		<script type="text/javascript">
			//WebSocket服务器地址
			var wsurl = "ws://localhost:8080/SpringBootWebSocket?token=cocococococcoc&param=张三";
			//1. 与服务器建立websocket连接
			var websocket = new WebSocket(wsurl);
			
			// 2. 连接成功
			websocket.onopen = function(event){
				console.log("成功与服务器建立连接!!!");
				console.log(event);
			}
			//3. 接收服务器数据
			websocket.onmessage = function(event){
				console.log("wesocket接收服务器数据event.data=>"+event.data);
				console.log(event);
			}
			
			//4. 服务器发生异常错误 
			websocket.onerror = function(event) {
				console.log("WebSocket:服务器发生异常错误 ");
				console.log(event);
			};
			//5.  服务器关闭 
			websocket.onclose = function(event) {
				console.log("WebSocket:已关闭");
				console.log(event);
			};
			
			
		</script>
		
	</body>
</html>

WebSocket服务器端实现

  1. 编写通信握手拦截器。

该拦截器的作用就是在连接成功前和成功后增加一些额外的功能,此处可记录当前建立连接的用户信息,用
于定向信息发送的动能前提。代码如下:

编写WebSocket通信 握手拦截器 它只拦截握手SpringBootHandshakeInterceptor实现HandshakeInterceptor

package com.ysd.boot.websocket;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;
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.HandshakeInterceptor;

/**
 * WebSocket通信 握手拦截器 它只拦截握手
 *
 */
@Component
public class SpringBootHandshakeInterceptor implements HandshakeInterceptor {

	/**
	 * 握手之前 ServerHttpRequest : 请求对象 ServerHttpResponse : 响应对象 WebSocketHandler :
	 * WebSocket服务处理类,在这里指的是SpringBootWebSocketHandler attributes :
	 * WebSocketSession.getAttributes()
	 */
	@Override
	public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse,
			WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
		ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) serverHttpRequest;
		HttpServletRequest request = servletServerHttpRequest.getServletRequest();// 得到Http协议的请求对象
		HttpSession session = request.getSession(true);
		System.out.println("握手拦截器^^^^^^^^^^^^  request.getParameter(\"param\")=>" + request.getParameter("param"));
		System.out.println("握手拦截器^^^^^^^^^^^^  request.getParameter(\"token\")=>" + request.getParameter("token"));
		System.out.println("握手拦截器^^^^^^^^^^^^ HttpSession.getAttribute(\"user\")=>" + session.getAttribute("user"));

		// 数据中转 可以把http协议的会话对象数据中转到ws协议的会话对象中
		attributes.put("param", request.getParameter("param"));
		// 非前后端分离架构:把HttpSession中的数据中转到WebSocketSession中
		if (session.getAttribute("user") != null)
			attributes.put("user", session.getAttribute("user"));
		// 如果是前后端分离架构:把Http协议中的token令牌中转到ws协议的WebSocketSession中
		attributes.put("token", request.getParameter("token"));

		return true;
	}

	/**
	 * 握手之后
	 */
	@Override
	public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
			Exception exception) {
		// TODO Auto-generated method stub

	}

}

该拦截器需要实现HandshakeInterceptor接口,其中方法的作用如下:

  • beforeHandshake:在调用handler前处理方法。常用在注册用户信息,绑定WebSocketSession,在
    handler里根据用户信息获取WebSocketSession发送消息。
  • afterHandshake:在握手之后执行该方法. 无论是否握手成功都指明了响应状态码和相应头

编写WebSocket服务配置类SpringBootWebSocketConfigurer实现WebMvcConfigurer, WebSocketConfigurer

由于这个类是配置类,即 WebSocket的入口,用于给WebSocket服务配置URL,比如绑定客户端url,所以
需要在spring mvc配置文件中加入对这个类的扫描,第一个addHandler是对正常连接的配置,第二个是如果
浏览器不支持websocket,使用socketjs模拟websocket的连接。该类需要实现WebSocketConfigurer接口,
重写registerWebSocketHandlers方法,这是一个核心实现方法,配置websocket入口,允许访问的域、注
HandlerSockJs支持和拦截器。参考代码如下:

package com.ysd.boot.websocket;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
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;

/**
 * WebSocket服务配置类
 *
 */
// @Component //非单例模式bean
@Configuration // 单例模式bean
@EnableWebSocket // 启动WebSocket服务器
//public class SpringBootWebSocketConfigurer extends WebMvcConfigurerAdapter implements WebSocketConfigurer{ //SpringBoot1.0系列用法
public class SpringBootWebSocketConfigurer implements WebMvcConfigurer, WebSocketConfigurer { // SpringBoot2.0系列用法

	@Autowired
	private SpringBootWebSocketHandler handler;
	@Autowired
	private SpringBootHandshakeInterceptor handshakeInterceptor;

	@Override
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
		// -------------------- 允许跨域访问WebSocket ------------------------
		String[] allowsOrigins = { "*" };// 允许连接的域,只能以http或https开头
		/**
		 * http://localhost:8080 http://localhost:8080/index.html
		 */
		// 7. 设置websocket服务器地址 ws://localhost:8080/SpringBootWebSocket
		registry.addHandler(handler, "/SpringBootWebSocket").addInterceptors(handshakeInterceptor)
				.setAllowedOrigins(allowsOrigins);
//		registry.addHandler(handler, "/SpringBootWebSocket/sockjs").addInterceptors(handshakeInterceptor).setAllowedOrigins(allowsOrigins).withSockJS();//兼容以前老版本的sockJS

	}

}

其中的方法含义解析如下:

  • registry.addHandler():注册和路由的功能,当客户端发起websocket连接,把/path交给对应的
    handler处理,而不实现具体的业务逻辑,可以理解为收集和任务分发中心。
  • setAllowedOrigins(String[] domains),允许指定的域名或IP(含端口号)建立长连接,如果只允许自家域
    名访问,这里轻松设置。如果不限时使用”*”号,如果指定了域名,则必须要以http或https开头。
  • addInterceptors,顾名思义就是为handler添加拦截器,可以在调用handler前后加入我们自己的逻辑
    代码。

若需要注册路由,给WebSocket服务配置URL,则在registerWebSocketHandlers方法中添加如下代码
即可

//兼容以前老版本的sockJS
// registry.addHandler(handler, "/SpringBootWebSocket/sockjs").addInterceptors(handshakeInterceptor).setAllowedOrigins(allowsOrigins).withSockJS();

编写自定义实现WebSocket服务处理类SpringBootWebSocketHandler实现WebSocketHandler

该类用于处理与客户端的通话过程。整个通话过程主要有以下几个阶段:

  • 客户端成功连接服务器(@OnOpen):当连接成功的时候,会触发onopen方法,这里对应的方法为
  • afterConnectionEstablished。
    接收客户端消息(@OnMessage)
    :接收文本消息,并发送出去。当UI客户端在用js调用
    websocket.send()时候,会调用该方法 。在客户端通过Websocket的API发送的消息会经过这里,然后
    进行相应的处理。
  • 客户端发生异常错误(@OnError):消息传输抛出异常时执行,当消息传输错误时进行处理。
  • 客户端关闭(@OnClose): 客户端关闭连接后执行。
    具体代码实现案例如下所示:
package com.ysd.boot.websocket;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

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;

/**
 * WebSocket服务处理类
 */
@Component
public class SpringBootWebSocketHandler implements WebSocketHandler {

	// 存储所有客户端的会话WebSocketSession,key使用客户端的唯一标识方便存取
	private static Map<String, WebSocketSession> allWebSocketSession = new HashMap<String, WebSocketSession>();

	/**
	 * 1. 客户端成功建立连接时触发
	 */
	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		System.out.println("客户端成功建立连接=>" + session.getId());
		// 在ws服务类中任何位置都可以使用以下取值
		System.out.println("WebSocketSession.getAttributes().get(\"param\")" + session.getAttributes().get("param"));
		System.out.println("WebSocketSession.getAttributes().get(\"user\")" + session.getAttributes().get("user"));
		System.out.println("WebSocketSession.getAttributes().get(\"token\")" + session.getAttributes().get("token"));
		// 存储所有客户端的WebSocketSession
		allWebSocketSession.put((String) session.getAttributes().get("param"), session);

	}

	/**
	 * 2. 接收客户端数据时触发
	 */
	@Override
	public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
		System.out.println("接收客户端数据=>" + message.getPayload().toString());

		// 给客户端回一句话意思意思,随便给客户端发数据
		this.send(session, "&&&&&&服务器回复的&&&&&&" + message.getPayload().toString());

	}

	/**
	 * 3. 通信异常时触发
	 */
	@Override
	public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
		System.out.println("通信异常=>" + session.getId());

	}

	/**
	 * 4.客户端关闭连接时触发
	 */
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
		System.out.println("客户端关闭连接=>" + session.getId());
		// 移除通行关闭的客户端
		allWebSocketSession.remove((String) session.getAttributes().get("param"));

	}

	/**
	 * 是否支持分段传输
	 */
	@Override
	public boolean supportsPartialMessages() {
		// TODO Auto-generated method stub
		return false;// 一次输出完毕
	}

	/**
	 * 5.服务器主动发送数据
	 * 
	 * @param webSocketSession
	 * @param msg
	 */
	public void send(WebSocketSession webSocketSession, String msg) {
		try {
			webSocketSession.sendMessage(new TextMessage(msg));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 6. 服务器主动关闭通信
	 * 
	 * @param webSocketSession
	 * @param msg
	 */
	public void close(WebSocketSession webSocketSession, String msg) {
		try {
			webSocketSession.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

除了以上各阶段方法外,WebSocket处理类中还有一些其他处理方法,如supportsPartialMessages()方法用
于WebSocketHandler是否处理部分消息。如果这个标志被设置为true,而底层的WebSocket服务器支持部
分消息,那么一个大的WebSocket消息,或者一个未知的大小可能会被拆分,并且可能会收到多个调用的
handleMessage(WebSocketSession,WebSocketMessage)。标记WebSocketMessage.isLast()表示该消息是
否是部分消息,以及它是否是最后一部分:

/**
	 * 是否支持分段传输
	 */
	@Override
	public boolean supportsPartialMessages() {
		// TODO Auto-generated method stub
		return false;// 一次输出完毕
	}

服务器发送消息(当然也可以写成向某个用户发送)

/**
	 * 5.服务器主动发送数据
	 * 
	 * @param webSocketSession
	 * @param msg
	 */
	public void send(WebSocketSession webSocketSession, String msg) {
		try {
			webSocketSession.sendMessage(new TextMessage(msg));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

编写controller控制器,代码如下:

package com.ysd.boot.controller;

import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class WelcomeController {
	
	
	/**
	 * http://localhost:8080
	 * @return
	 */
	@RequestMapping("/")
	public String welcome(HttpSession session) {
		session.setAttribute("user", "我是HttpSession中的数据");
		return "index.html";
	}

}

注意:配置spring容器,在需要使用时可自动注入,我们可以通过这种方式把想用的工具类实例对象先放入Spirng容器中,在需要使用到的时候自动注入即可。例如配置一个ObjectMapper实例对象和SimpleDateFormat实例对象,参考代码如下:


最后

  • 更多参考精彩博文请看这里:《陈永佳的博客》

  • 喜欢博主的小伙伴可以加个关注、点个赞哦,持续更新嘿嘿!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/97604.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!