前言:最近開發(fā)公司的一個會員卡,酒店預(yù)訂房間的功能,考慮到要實現(xiàn)后臺管理的訂單提示,所以在項目中引用websocket,事實上在spring boot中引入webSocket 還是非常簡單的,但是我還是爬了很多坑,在此記錄一下希望可以幫助一些可能同樣會爬坑的同學(xué)。
1:jar包的引入,只引這個的前提是使用tomcat7.0以上的版本,我用的是tomcat8,所以用這個就夠了,若是低版本的還需引入javaee-api。
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId>
2:配置文件,這里要注意一下,使用springboot的內(nèi)置tomcat運行和打war包發(fā)布到外部Tomcat運行時配置是不一樣的。
1.先創(chuàng)建一個WebSocketConfig.java
2.使用spring boot內(nèi)置tomcat運行時的配置。
import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.springframework.web.socket.server.standard.ServerEndpointExporter; public class WebSocketConfig { public ServerEndpointExporter serverEndpointExporter(){ return new ServerEndpointExporter();
這里的配置是ServerEndpointExporter的注入,配置該出會為我們后面使用到@ServerEndPoint注解的地方自動注冊Websocket endpoint。
3.使用外部的tomcat發(fā)布時 WebSocketConfig.java 中就不需要 ServerEndpointExporter 的注入,因為這是它是由外部容器自己提供和管理的,如果你在使用外部容器發(fā)布時注入這個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; public class WebSocketConfig { // public ServerEndpointExporter serverEndpointExporter(){ // return new ServerEndpointExporter();
3:上面都配置好后我們就來看websocket使用的重頭戲@ServerEndpoint的使用,前面有說。之前的配置就是為了是項目能在使用了注解的地方自動注冊Websocket endpoint,在這里我們就能實現(xiàn)websocket的鏈接、關(guān)閉、發(fā)送和接收消息的操作,這文件看起來有點像controller,但又有些不同,所以我就把他放在service層了這個有參考慕課網(wǎng)廖師兄的操作。
1.創(chuàng)建一個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; @ServerEndpoint(value = "/webSocket") private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<>(); public void onOpen(Session session){ log.info("[WebSocket消息]有新的連接,總數(shù):{}", webSocketSet.size()); public void onClose(Session session){ webSocketSet.remove(this); log.info("[WebSocket消息]連接斷開,總數(shù):{}", webSocketSet.size()); public void onMessage(String message){ if("123456789".equals(message)){ log.info("[WebSocket消息]接收到客戶端的消息:{}", message); public void sendMessage(String message){ for (WebSocket webSocket:webSocketSet){ log.info("【websocket消息】廣播消息,message=:{}",message ); webSocket.session.getBasicRemote().sendText(message);
其實看注解名字也能猜到各個方法的操作的是什么。
@OnOpen:在前端訪問websocket時開一個新的連接。這里有用一個集合來記錄連接,方便查看管理;
@OnClose:斷開連接;
@OnMessage:接收消息接口;
sendMessage:發(fā)送消息的接口;
到目前為止服務(wù)端的準(zhǔn)備就做好了。
現(xiàn)在開始前端調(diào)用,這里使用的是原生Html5來調(diào)用如下:
var lockReconnect = false;//避免重復(fù)連接 var wsUrl = "ws://localhost:8008/webSocketTest/webSocket"; function createWebSocket() { ws = new WebSocket(wsUrl); ws.onclose = function () { console.log('鏈接關(guān)閉'); ws.onerror = function() { console.log('發(fā)生異常了'); ws.onopen = function () { ws.onmessage = function (event) { if(event.data!="123456789"){ console.log('收到消息:'+event.data); $('#myModal').modal('show'); document.getElementById('notice').play(); //拿到任何消息都說明當(dāng)前連接是正常的 window.onbeforeunload = function () { var lockReconnect = false;//避免重復(fù)連接 function reconnect(url) { //沒連接上會一直重連,設(shè)置延遲避免請求過多 tt = setTimeout(function () { this.timeoutObj && clearTimeout(this.timeoutObj); this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj); this.timeoutObj = setTimeout(function(){ //這里發(fā)送一個心跳,后端收到后,返回一個心跳消息, //onmessage拿到返回的心跳就說明連接正常 self.serverTimeoutObj = setTimeout(function() {
其實這里和上面ServerEndpoint一樣,想必大家也是見文知意。
無非是初始化一個Websocket,創(chuàng)建連接,關(guān)閉連接,接收消息,發(fā)送消息,這些就不多說了,主要在這里說一下幾個需要注意的點。
1:初始化時 new WebSocket(wsUrl ); 這個url就是請求連接@ServerEndpoint配置的地方,所以說它的使用時候是不是有點像Controller的使用,拿上面的請求路徑做個說明:
var wsUrl = "ws://localhost:8008/webSocketTest/webSocket";
ws:是websocket的協(xié)議,websocket用的不是http協(xié)議,這里我就不對這兩個協(xié)議的區(qū)別做詳細(xì)的說明,有興趣自己搜一下。需要注意的是如果請求頭的是http的就用ws,若請求的是https則使用wss。
localhost:就是服務(wù)的ip或域名。
8008:端口號。
webSocketTest:發(fā)布的項目名。
webSocket:@ServerEndpoint中配置的路徑。
2.websocket長連接有默認(rèn)的超時時間(proxy_read_timeout),就是超過一定的時間沒有發(fā)送任何消息,連接會自動斷開。所以我們要想保持長連接可以使用心跳包,定時像服務(wù)器發(fā)送心跳保持通信,上面的js中就有使用,當(dāng)發(fā)生錯誤或斷開連接時我們可以重新連接。
3.最后再提醒一下,如果項目部署到線上是使用了Nginx代理,一定要記得在Nginx配置websocket,前面有說過wesocket使用的不是http協(xié)議,如果你用了Nginx又沒有配置websocket的話,前端初始化websocket就會報404。
下面引用其他博主的一個解決方案,具體參考:springboot + websocket + linux服務(wù)器(nginx)404問題解決
配置nginx反向代理響應(yīng)webSocket請求 需要在代理的請求配置中加入下面的配置:
proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";1
Nginx配置
root /usr/java/myproject/sell; index index.html index.htm; 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";
|