1:jar包的引入,只引这个的前提是使用tomcat7.0以上的版本,我用的是tomcat8,所以用这个就够了,若是低版本的还需引入javaee-api。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2:配置文件,这里要注意一下,使用springboot的内置tomcat运行和打war包发布到外部Tomcat运行时配置是不一样的。
1.先创建一个WebSocketConfig.java
2.使用spring boot内置tomcat运行时的配置。
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Component
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
这里的配置是ServerEndpointExporter的注入,配置该出会为我们后面使用到@ServerEndPoint注解的地方自动注册Websocket endpoint。
3.使用外部的tomcat发布时 WebSocketConfig.java 中就不需要 ServerEndpointExporter 的注入,因为这是它是由外部容器自己提供和管理的,如果你在使用外部容器发布时注入这个bean,项目启动的时候会报 javax.websocket.DeploymentException: Multiple Endpoints may not be deployed to the same path xxxx错误,别问我是怎么知道的(/"≡ _ ≡)/~┴┴。
所以这个时候的配置是这样的:
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Component
public class WebSocketConfig {
// @Bean
// public ServerEndpointExporter serverEndpointExporter(){
// return new ServerEndpointExporter();
// }
}
3:上面都配置好后我们就来看websocket使用的重头戏@ServerEndpoint的使用,前面有说。之前的配置就是为了是项目能在使用了注解的地方自动注册Websocket endpoint,在这里我们就能实现websocket的链接、关闭、发送和接收消息的操作,这文件看起来有点像controller,但又有些不同,所以我就把他放在service层了这个有参考慕课网廖师兄的操作。
1.创建一个WebSocket.java。
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.CopyOnWriteArraySet;
@Component
@ServerEndpoint(value = "/webSocket")
@Slf4j
public class WebSocket {
private Session session;
private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<>();
@OnOpen
public void onOpen(Session session){
this.session = session;
webSocketSet.add(this);
log.info("[WebSocket消息]有新的连接,总数:{}", webSocketSet.size());
}
@OnClose
public void onClose(Session session){
webSocketSet.remove(this);
log.info("[WebSocket消息]连接断开,总数:{}", webSocketSet.size());
}
@OnMessage
public void onMessage(String message){
if("123456789".equals(message)){
sendMessage(message);
}
log.info("[WebSocket消息]接收到客户端的消息:{}", message);
}
public void sendMessage(String message){
for (WebSocket webSocket:webSocketSet){
log.info("【websocket消息】广播消息,message=:{}",message );
try {
webSocket.session.getBasicRemote().sendText(message);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
其实看注解名字也能猜到各个方法的操作的是什么。
@OnOpen:在前端访问websocket时开一个新的连接。这里有用一个集合来记录连接,方便查看管理;
@OnClose:断开连接;
@OnMessage:接收消息接口;
sendMessage:发送消息的接口;
到目前为止服务端的准备就做好了。
现在开始前端调用,这里使用的是原生Html5来调用如下:
<script>
var lockReconnect = false;//避免重复连接
var wsUrl = "ws://localhost:8008/webSocketTest/webSocket";
var ws;
var tt;
createWebSocket();
function createWebSocket() {
try {
ws = new WebSocket(wsUrl);
init();
} catch(e) {
console.log('catch'+e);
reconnect(wsUrl);
}
}
function init() {
ws.onclose = function () {
console.log('链接关闭');
reconnect(wsUrl);
};
ws.onerror = function() {
console.log('发生异常了');
reconnect(wsUrl);
};
ws.onopen = function () {
console.log('建立连接');
//心跳检测重置
heartCheck.start();
};
ws.onmessage = function (event) {
console.log('接收到消息');
if(event.data!="123456789"){
console.log('收到消息:'+event.data);
//弹窗提醒, 播放音乐
$('#myModal').modal('show');
document.getElementById('notice').play();
}
heartCheck.start();
//拿到任何消息都说明当前连接是正常的
}
}
window.onbeforeunload = function () {
ws.close();
}
var lockReconnect = false;//避免重复连接
function reconnect(url) {
if(lockReconnect) {
return;
};
lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多
tt && clearTimeout(tt);
tt = setTimeout(function () {
createWebSocket(url);
lockReconnect = false;
}, 4000);
}
//心跳检测
var heartCheck = {
timeout: 60000,
timeoutObj: null,
serverTimeoutObj: null,
start: function(){
console.log('start');
var self = this;
this.timeoutObj && clearTimeout(this.timeoutObj);
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
this.timeoutObj = setTimeout(function(){
//这里发送一个心跳,后端收到后,返回一个心跳消息,
//onmessage拿到返回的心跳就说明连接正常
console.log('55555');
ws.send("123456789");
self.serverTimeoutObj = setTimeout(function() {
console.log(111);
console.log(ws);
ws.close();
// createWebSocket();
}, self.timeout);
}, this.timeout)
}
}
</script>
其实这里和上面ServerEndpoint一样,想必大家也是见文知意。
无非是初始化一个Websocket,创建连接,关闭连接,接收消息,发送消息,这些就不多说了,主要在这里说一下几个需要注意的点。
1:初始化时 new WebSocket(wsUrl ); 这个url就是请求连接@ServerEndpoint配置的地方,所以说它的使用时候是不是有点像Controller的使用,拿上面的请求路径做个说明:
var wsUrl = "ws://localhost:8008/webSocketTest/webSocket";
ws:是websocket的协议,websocket用的不是http协议,这里我就不对这两个协议的区别做详细的说明,有兴趣自己搜一下。需要注意的是如果请求头的是http的就用ws,若请求的是https则使用wss。
localhost:就是服务的ip或域名。
8008:端口号。
webSocketTest:发布的项目名。
webSocket:@ServerEndpoint中配置的路径。
2.websocket长连接有默认的超时时间(proxy_read_timeout),就是超过一定的时间没有发送任何消息,连接会自动断开。所以我们要想保持长连接可以使用心跳包,定时像服务器发送心跳保持通信,上面的js中就有使用,当发生错误或断开连接时我们可以重新连接。
3.最后再提醒一下,如果项目部署到线上是使用了Nginx代理,一定要记得在Nginx配置websocket,前面有说过wesocket使用的不是http协议,如果你用了Nginx又没有配置websocket的话,前端初始化websocket就会报404。
下面引用其他博主的一个解决方案,具体参考:springboot + websocket + linux服务器(nginx)404问题解决
配置nginx反向代理响应webSocket请求 需要在代理的请求配置中加入下面的配置:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";1
Nginx配置
server {
listen 80;
server_name localhost;
#charset koi8-r;
location / {
root /usr/java/myproject/sell;
index index.html index.htm;
}
location /sell/ {
proxy_pass http://127.0.0.1:8081/sell/;
}
location /sell/webSocket {
proxy_pass http://127.0.0.1:8081/sell/webSocket;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}