springboot集成websocket的兩種實現方式

WebSocket跟常規的http協議的區別和優缺點這裏大概描述一下javascript

1、websocket與http html

http協議是用在應用層的協議,他是基於tcp協議的,http協議創建連接也必需要有三次握手才能發送信息。http連接分爲短連接,長連接,短連接是每次請求都要三次握手才能發送本身的信息。即每個request對應一個response。長連接是在必定的期限內保持連接。保持TCP鏈接不斷開。客戶端與服務器通訊,必需要有客戶端發起而後服務器返回結果。客戶端是主動的,服務器是被動的。 
WebSocket是HTML5中的協議, 他是爲了解決客戶端發起多個http請求到服務器資源瀏覽器必需要通過長時間的輪訓問題而生的,他實現了多路複用,他是全雙工通訊。在webSocket協議下客服端和瀏覽器能夠同時發送信息。前端

2、HTTP的長鏈接與websocket的持久鏈接java

HTTP1.1的鏈接默認使用長鏈接(persistent connection),jquery

即在必定的期限內保持連接,客戶端會須要在短期內向服務端請求大量的資源,保持TCP鏈接不斷開。客戶端與服務器通訊,必需要有客戶端發起而後服務器返回結果。客戶端是主動的,服務器是被動的。git

  在一個TCP鏈接上能夠傳輸多個Request/Response消息對,因此本質上仍是Request/Response消息對,仍然會形成資源的浪費、實時性不強等問題。github

若是不是持續鏈接,即短鏈接,那麼每一個資源都要創建一個新的鏈接,HTTP底層使用的是TCP,那麼每次都要使用三次握手創建TCP鏈接,即每個request對應一個response,將形成極大的資源浪費。web

  長輪詢,即客戶端發送一個超時時間很長的Request,服務器hold住這個鏈接,在有新數據到達時返回Responsespring

websocket的持久鏈接  只需創建一次Request/Response消息對,以後都是TCP鏈接,避免了須要屢次創建Request/Response消息對而產生的冗餘頭部信息。後端

Websocket只須要一次HTTP握手,因此說整個通信過程是創建在一次鏈接/狀態中,並且websocket能夠實現服務端主動聯繫客戶端,這是http作不到的。

springboot集成websocket的不一樣實現方式:

pom添加依賴


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
 因涉及到js鏈接服務端,因此也寫了對應的html,這裏集成下thymeleaf模板,先後分離的項目這一塊全都是前端作的


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
配置文件:

server:
port: 8885

#添加Thymeleaf配置
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html
mode: HTML5
encoding: UTF-8
content-type: text/html
 

1:自定義WebSocketServer,使用底層的websocket方法,提供對應的onOpen、onClose、onMessage、onError方法

1.1:添加webSocketConfig配置類

/**
* 開啓WebSocket支持
* Created by huiyunfei on 2019/5/31.
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
1.2:添加webSocketServer服務端類

package com.example.admin.web;

/**
* Created by huiyunfei on 2019/5/31.
*/

@ServerEndpoint("/websocket/{sid}")
@Component
@Slf4j
public class WebSocketServer {
//靜態變量,用來記錄當前在線鏈接數。應該把它設計成線程安全的。
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 message 客戶端發送過來的消息*//*
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到來自窗口"+sid+"的信息:"+message);
//羣發消息
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
*/
/**
*
* @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--;
}
public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
return webSocketSet;
}
}
1.3:添加對應的controller

@Controller
@RequestMapping("/system")
public class SystemController {
//頁面請求
@GetMapping("/index/{userId}")
public ModelAndView socket(@PathVariable String userId) {
ModelAndView mav=new ModelAndView("/socket1");
mav.addObject("userId", userId);
return mav;
}
//推送數據接口
@ResponseBody
@RequestMapping("/socket/push/{cid}")
public Map pushToWeb(@PathVariable String cid, String message) {
Map result = new HashMap();
try {
WebSocketServer.sendInfo(message,cid);
result.put("code", 200);
result.put("msg", "success");
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
 1.4:提供socket1.html頁面

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"></meta>
<title>Title</title>
</head>
<body>
hello world!

</body>
<script>
var socket;
if(typeof(WebSocket) == "undefined") {
console.log("您的瀏覽器不支持WebSocket");
}else{
console.log("您的瀏覽器支持WebSocket");
//實現化WebSocket對象,指定要鏈接的服務器地址與端口 創建鏈接
//等同於
index = new WebSocket("ws://localhost:8885/websocket/2");
//socket = new WebSocket("${basePath}websocket/${cid}".replace("http","ws"));
//打開事件
index.onopen = function() {
console.log("Socket 已打開");
//socket.send("這是來自客戶端的消息" + location.href + new Date());
};
//得到消息事件
index.onmessage = function(msg) {
console.log(msg.data);
//發現消息進入 開始處理前端觸發邏輯
};
//關閉事件
index.onclose = function() {
console.log("Socket已關閉");
};
//發生了錯誤事件
index.onerror = function() {
alert("Socket發生了錯誤");
//此時能夠嘗試刷新頁面
}
//離開頁面時,關閉socket
//jquery1.8中已經被廢棄,3.0中已經移除
// $(window).unload(function(){
// socket.close();
//});
}
</script>
</html>
 總結:

瀏覽器debug訪問 localhost:8885/system/index/1跳轉到socket1.html,js自動鏈接server並傳遞cid到服務端,服務端對應的推送消息到客戶端頁面(cid區分不一樣的請求,server裏提供的有羣發消息方法)

2.1:基於STOMP協議的WebSocket

使用STOMP的好處在於,它徹底就是一種消息隊列模式,你可使用生產者與消費者的思想來認識它,發送消息的是生產者,接收消息的是消費者。而消費者能夠經過訂閱不一樣的destination,來得到不一樣的推送消息,不須要開發人員去管理這些訂閱與推送目的地以前的關係,spring官網就有一個簡單的spring-boot的stomp-demo,若是是基於springboot,你們能夠根據spring上面的教程試着去寫一個簡單的demo。

 

提供websocketConfig配置類

/**
* @Description:
registerStompEndpoints(StompEndpointRegistry registry)
configureMessageBroker(MessageBrokerRegistry config)
這個方法的做用是定義消息代理,通俗一點講就是設置消息鏈接請求的各類規範信息。
registry.enableSimpleBroker("/topic")表示客戶端訂閱地址的前綴信息,也就是客戶端接收服務端消息的地址的前綴信息(比較繞,看完整個例子,大概就能明白了)
registry.setApplicationDestinationPrefixes("/app")指服務端接收地址的前綴,意思就是說客戶端給服務端發消息的地址的前綴
* @Author:hui.yunfei@qq.com
* @Date: 2019/5/31
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

// 這個方法的做用是添加一個服務端點,來接收客戶端的鏈接。
// registry.addEndpoint("/socket")表示添加了一個/socket端點,客戶端就能夠經過這個端點來進行鏈接。
// withSockJS()的做用是開啓SockJS支持,
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/socket").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
//表示客戶端訂閱地址的前綴信息,也就是客戶端接收服務端消息的地址的前綴信息
registry.enableSimpleBroker("/topic");
//指服務端接收地址的前綴,意思就是說客戶端給服務端發消息的地址的前綴
registry.setApplicationDestinationPrefixes("/app");
}
}
 2.2:controller提供對應請求的接口

//頁面請求
@GetMapping("/socket2")
public ModelAndView socket2() {//@PathVariable String userId
ModelAndView mav=new ModelAndView("html/socket2");
//mav.addObject("userId", userId);
return mav;
}

/**
* @Description:這個方法是接收客戶端發送功公告的WebSocket請求,使用的是@MessageMapping
* @Author:hui.yunfei@qq.com
* @Date: 2019/5/31
*/
@MessageMapping("/change-notice")//客戶端訪問服務端的時候config中配置的服務端接收前綴也要加上 例:/app/change-notice
@SendTo("/topic/notice")//config中配置的訂閱前綴記得要加上
public CustomMessage greeting(CustomMessage message){
System.out.println("服務端接收到消息:"+message.toString());
//咱們使用這個方法進行消息的轉發發送!
//this.simpMessagingTemplate.convertAndSend("/topic/notice", value);(可使用定時器定時發送消息到客戶端)
// @Scheduled(fixedDelay = 1000L)
// public void time() {
// messagingTemplate.convertAndSend("/system/time", new Date().toString());
// }
//也可使用sendTo發送
return message;
}
2.3:提供socket2.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Spring Boot+WebSocket+廣播式</title>

</head>
<body onload="disconnect()">
<noscript><h2 style="color: #ff0000">貌似你的瀏覽器不支持websocket</h2></noscript>
<div>
<div>
<button id="connect" onclick="connect();">鏈接</button>
<button id="disconnect" disabled="disabled" onclick="disconnect();">斷開鏈接</button>
</div>
<div id="conversationDiv">
<label>輸入你的名字</label><input type="text" id="name" />
<button id="sendName" onclick="sendName();">發送</button>
<p id="response"></p>
</div>
</div>
<script th:src="@{/js/sockjs.min.js}"></script>
<script th:src="@{/js/stomp.min.js}"></script>
<script th:src="@{/js/jquery-3.2.1.min.js}"></script>
<script type="text/javascript">
var stompClient = null;

function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
$('#response').html();
}

function connect() {
var socket = new SockJS('/socket'); //1
stompClient = Stomp.over(socket);//2
stompClient.connect({}, function(frame) {//3
setConnected(true);
console.log('開始進行鏈接Connected: ' + frame);
stompClient.subscribe('/topic/notice', function(respnose){ //4
showResponse(JSON.parse(respnose.body).responseMessage);
});
});
}


function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}

function sendName() {
var name = $('#name').val();
stompClient.send("/app/change-notice", {}, JSON.stringify({ 'name': name }));//5
}

function showResponse(message) {
var response = $("#response");
response.html(message);
}
</script>
</body>
</html>
2.4:對應的js引用能夠去網上下載

2.5:瀏覽器debug訪問localhost:8885/system/socket2,點擊鏈接鏈接到服務器,數據內容能夠推送到服務器以及服務器消息回推。

2.6:實現前端和服務端的輪訓能夠頁面Ajax輪訓也能夠後端添加定時器

@Component
@EnableScheduling
public class TimeTask {
private static Logger logger = LoggerFactory.getLogger(TimeTask.class);

@Scheduled(cron = "0/20 * * * * ?")
public void test(){
System.err.println("********* 定時任務執行 **************");
CopyOnWriteArraySet<WebSocketServer> webSocketSet =
WebSocketServer.getWebSocketSet();
int i = 0 ;
webSocketSet.forEach(c->{
try {
c.sendMessage(" 定時發送 " + new Date().toLocaleString());
} catch (IOException e) {
e.printStackTrace();
}
});

System.err.println("/n 定時任務完成.......");
}
}
代碼在https://github.com/huiyunfei/spring-cloud.git 的admin項目裏

基於STOMP協議的廣播模式和點對點模式消息推送可參考:
https://www.cnblogs.com/hhhshct/p/8849449.html

https://www.cnblogs.com/jmcui/p/8999998.html————————————————版權聲明:本文爲CSDN博主「不偷腥的mao」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接及本聲明。原文連接:https://blog.csdn.net/huiyunfei/article/details/90719351

相關文章
相關標籤/搜索