46°

SpringBoot整合WebSocket

什么是WebSocket?

20180510225115144.png

WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。

为什么需要 WebSocket?

初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?

  • 答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起,HTTP 协议做不到服务器主动向客户端推送信息。 20180510223926952.png

举例来说,我们想要查询当前的排队情况,只能是页面轮询向服务器发出请求,服务器返回查询结果。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此WebSocket 就是这样发明的。

操作

话不多说,直接上代码

maven依赖

<?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>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.5.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.lifeng</groupId>
	<artifactId>boot-websocket</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>boot-websocket</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-websocket</artifactId>
		</dependency>
		<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-log -->
		<dependency>
			<groupId>org.jboss.logging</groupId>
			<artifactId>jboss-logging</artifactId>
		</dependency>
	</dependencies>
    
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
    
</project>

WebSocket配置

使用WebSocker需要开启配置,新建一个配置类

/**
 * webSocket开启配置
 */
@Configuration
public class WebSocketConfig {
	@Bean  
	public ServerEndpointExporter serverEndpointExporter() {  
		return new ServerEndpointExporter();  
	}
}

WebSocket服务配置

因为WebSocket是类似客户端服务端的形式(采用ws协议),那么这里的WebSocketServer其实就相当于一个ws协议的Controller 直接@ServerEndpoint("/websocket"),@Component启用即可,然后在里面实现@OnOpen,@onClose,@onMessage等方法

package com.lifeng.server;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.springframework.stereotype.Component;

@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketServer {
	static Log log=LogFactory.getLog(WebSocketServer.class);
	//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
	private static int onlineCount = 0;
	//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
	private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();

	//与某个客户端的连接会话,需要通过它来给客户端发送数据
	private Session session;

	//接收sid
	private String sid="";
	/**
	 * 连接建立成功调用的方法*/
	@OnOpen
	public void onOpen(Session session,@PathParam("sid") String sid) {
		this.session = session;
		webSocketSet.add(this); //加入set中
		addOnlineCount(); //在线数加1
		log.info("有新窗口开始监听:"+sid+",当前在线人数为" + getOnlineCount());
		this.sid=sid;
		try {
			sendMessage("连接成功");
		} catch (IOException e) {
			log.error("websocket IO异常");
		}
	}

	/**
	 * 连接关闭调用的方法
	 */
	@OnClose
	public void onClose() {
		webSocketSet.remove(this); //从set中删除
		subOnlineCount(); //在线数减1
		log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
	}

	/**
	 *
	 * @param session
	 * @param error
	 */
	@OnError
	public void onError(Session session, Throwable error) {
		log.error("发生错误");
		error.printStackTrace();
	}
	/**
	 * 实现服务器主动推送
	 */
	public void sendMessage(String message) throws IOException {
		this.session.getBasicRemote().sendText(message);
	}

	/**
	 * 自定义消息
	 * */
	public static void sendInfo(String message,@PathParam("sid") String sid) throws IOException {
		log.info("推送消息到窗口"+sid+",推送内容:"+message);
		for (WebSocketServer item : webSocketSet) {
			try {
				//这里可以设定只推送给这个sid的,为null则全部推送
				if(sid==null) {
					item.sendMessage(message);
				}else if(item.sid.equals(sid)){
					item.sendMessage(message);
				}
			} catch (IOException e) {
				continue;
			}
		}
	}

	public static synchronized int getOnlineCount() {
		return onlineCount;
	}

	public static synchronized void addOnlineCount() {
		WebSocketServer.onlineCount++;
	}

	public static synchronized void subOnlineCount() {
		WebSocketServer.onlineCount--;
	}
}

消息推送

至于推送新信息,可以再自己的Controller写个方法调用WebSocketServer.sendInfo();即可

package com.lifeng.controller;

import java.io.IOException;

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

import com.lifeng.server.WebSocketServer;

/**
 * 消息推送
 */
@Controller
public class WebSockerController {

	//推送数据接口
	@ResponseBody
	@RequestMapping("/socket/push/{cid}")
	public String pushToWeb(@PathVariable String cid,String message) {
		try {
			WebSocketServer.sendInfo(message,cid);
		} catch (IOException e) {
			e.printStackTrace();
			return cid+"#"+e.getMessage();
		}
		return "发送信息给:"+cid+"成功!信息内容:"+message;
	}
}

页面发起socket请求

然后在页面用js代码调用socket,当然,太古老的浏览器是不行的,一般新的浏览器或者谷歌浏览器是没问题的。还有一点,记得协议是ws的哦

  • 新增一个html页面
<!DOCTYPE html>
<html>
<head>
<base href="<%=basePath%>">
<meta charset="UTF-8">
<meta name="viewport"
	content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>弹窗</title>


</head>
<body>
	<h2>网页1</h2>
	<input id="start">
	<input id="message">
	<iframe id="my_iframe" style="display: none;"></iframe>
	
	var socket;
	if(typeof(WebSocket) == "undefined") {
		console.log("您的浏览器不支持WebSocket");
	}else{
		console.log("您的浏览器支持WebSocket");
		//实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接
		socket = new WebSocket("ws://localhost:8080/websocket/1");
		//打开事件
		socket.onopen = function() {
			console.log("Socket 已打开");
			$("#start").val("Socket 已打开")
			//socket.send("这是来自客户端的消息" + location.href + new Date());
		};
		//获得消息事件
		socket.onmessage = function(msg) {
			console.log(msg.data);
			$("#message").val(msg.data)
			//发现消息进入 开始处理前端触发逻辑
		};
		//关闭事件
		socket.onclose = function() {
			console.log("Socket已关闭");
			$("#close").val("Socket已关闭");
		};
		//发生了错误事件
		socket.onerror = function() {
			alert("Socket发生了错误");
		}
	}
	
</body>
</html>

启动

我们开始启动SpringBoot应用,然后浏览器访问http://localhost:8080

后台也有相应监听日志

1.png

开始使用postman工具进行请求测试一下

2.png

后台日志

3.png


全部评论: 0

    我有话说: