WebSocket
- 前后端通信方式
- 测试环境(以示例电脑为例)
- Java代码
- pom依赖
- application.yml
- Spring-boot项目入口
- 添加WebSocket配置
- 具体业务实现
- Html
- 启动项目
- 项目demo
前后端通信方式
客户端与服务端通信方式:
- Http请求
- WebSocket
想实现前后端实时推送数据实现方式:
- http:前端添加定时器(setInterval),然后定时向服务器请求数据
定时器实现–> setInterval(请求函数,间隔时间(单位毫秒))
优点:实现简单,无需修改服务器结构
缺点:前后端消息无法实时同步 - Websocket:前后端通过握手,然后保持连接,可前后端相互推送数据
优点:数据可以实时推送
缺点:需浏览器支持websocket,后端也需添加支持
测试环境(以示例电脑为例)
- jdk: JDK12
- Spring-boot: 2.2.1.RELEASE
- 浏览器:谷歌浏览器
- maven:3.6.2
Java代码
pom依赖
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.llc</groupId>
<artifactId>boot-webSocket</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>boot-webSocket Maven Webapp</name>
<url>http://maven.apache.org</url>
<!-- Spring-boot依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>12</java.version><!-- 指定项目jdk版本 -->
</properties>
<dependencies>
<!-- web项目 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入webSocket依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- junit依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>webSocket</finalName>
</build>
</project>
application.yml
server:
port: 8080
servlet:
context-path: /webSocket
spring:
mvc:
view:
suffix: .html
jmx:
default-domain: ${server.servlet.context-path}
Spring-boot项目入口
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // Spring-boot基本注解
public class SocketApplication {
public static void main(String[] args) {
SpringApplication.run(SocketApplication.class, args);
}
}
添加WebSocket配置
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* 开启Websocket
* @author linlvcao
*
*/
@Component
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
具体业务实现
package com.llc.socket.socket;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@ServerEndpoint(value = "/socket/{token}")
@Component
public class WebSocketServer {
// 加入日志
private Logger log = LoggerFactory.getLogger(getClass());
// 用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
// 存储用户唯一 可改用缓存
private static Map<String, Session> socketMap = new HashMap<String, Session>();
// 当前线程
private Session session;
// 当前用户标识
private String token;
/**
* 链接启用
*
* @param session
* @param token
*/
@OnOpen
public void onOpen(Session session, @PathParam(value = "token") String token) {
// 用户信息标识添加
this.session = session;
// 可用来当成唯一标识
this.token = token;
// 加入缓存
// 原来存在这个token用户,则剔除用户
Session temp = socketMap.get(token);
if (temp != null && temp.isOpen()) {
try {
temp.close();
} catch (IOException e) {
log.info("用户:" + token + ",剔除失败!");
e.printStackTrace();
}
}
socketMap.put(token, session);
// 更新在线人数 TODO 正式需使用线程安全记录
WebSocketServer.onlineCount ++;
log.info("有新窗口开始监听:" + token + ",当前在线人数为" + onlineCount);
}
/**
* 关闭链接
*/
@OnClose
public void onClose() {
// 从线程中移除
socketMap.remove(this.token);
// 更新在线人数 TODO 正式需使用线程安全记录
WebSocketServer.onlineCount --;
log.info("有一连接关闭!当前在线人数为" + onlineCount);
}
/**
* 当链接出现异常
*/
@OnError
public void onError(Session session, Throwable ex) {
log.error("发生错误:{},Session ID: {}", ex.getMessage(), session.getId());
}
/**
* 接收客户端发来的信息
*
* @param session
* @param message
*/
@OnMessage
public void onMessage(String message) {
log.info("收到来自窗口" + token + "的信息:" + message);
// 回复收到信息
this.SendMessage(session, "收到消息;" + message);
}
/**
* 给指定客户端发送消息
* @param session
* @param message
*/
public void SendMessage(Session session, String message) {
try {
session.getBasicRemote().sendText(String.format("%s (From Server,Session ID=%s)",message,session.getId()));
}catch (Exception e) {
log.error("发送消息出错", e);
}
}
/**
* 群发消息
* @param message
*/
public void batchSendMessage(String message) {
for (Entry<String, Session> entry : socketMap.entrySet()) {
Session session = entry.getValue();
if (session.isOpen()) {
SendMessage(session, message);
}else {// 已经关闭则移除
socketMap.remove(entry.getKey());
}
}
}
/**
* 给指定用户发送信息
* @param token
* @param message
*/
public void SendMessage(String token, String message) {
Session session = socketMap.get(token);
if (session != null) {
SendMessage(session, message);
}
}
}
Html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Socket链接测试</title>
<script type="text/javascript" src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
</head>
<body>
<div id="myApp">
<input v-model="subMsg"/>
<button @click="subData">提交</button>
<h2>接收到的消息</h2>
<p v-for="i in list">{{i}}</p>
<br>
</div>
<script type="text/javascript">
var baseURL = getRootUrl() + "/";
function getRootUrl() {
var path = window.location.pathname.substring(1);
var root = (path == '') ? '' : path.substring(0, path.indexOf('/'));
return window.location.protocol + '//' + window.location.host + '/' + root;
}
var socke=null;
var vm = new Vue({
el: "#myApp",
data(){
return {
subMsg:null,
list:[]
}
},
mounted(){
if (typeof (WebSocket) == "undefined") {
console.log("遗憾:您的浏览器不支持WebSocket");
} else {
console.log("恭喜:您的浏览器支持WebSocket");
var that = this;
// 实现化WebSocket对象
// 指定要连接的服务器地址与端口建立连接
// 注意ws、wss使用不同的端口。我使用自签名的证书测试,
// 无法使用wss,浏览器打开WebSocket时报错
// ws对应http、wss对应https。
var path = baseURL + "socket/" + "3jeioqejter";
//var path = "http://localhost:8080/socket/" + "3jeioqejter";
path = path.replace("http","ws");
socket = new WebSocket(path);
socket.onopen = function() {
console.log("Socket 已打开");
socket.send("消息发送测试(From Client)");
};
// 收到消息事件
socket.onmessage = function(msg) {
console.log(msg);
that.list.push(msg.data);
};
// 连接关闭事件
socket.onclose = function() {
console.log("Socket已关闭");
};
// 发生了错误事件
socket.onerror = function() {
alert("Socket发生了错误");
}
// 窗口关闭时,关闭连接
window.unload=function() {
socket.close();
};
}
},
methods: {
subData() {
vm.sendMsg(vm.subMsg);
},
sendMsg(data) {// 发送消息
socket.send(data);
}
}
});
</script>
</body>
</html>
启动项目
访问地址:
http://localhost:8080/webSocket/socket.html
效果