Websocket如何保證接收消息完整性
Websocket保證接收消息完整性
用springboot起了個websocket服務端,有時候客戶端發(fā)來的消息過長,無法接收完整,需要進行額外的處理
下面是處理的例子:
@ServerEndpoint("/websocket")
public class WebSocket {
@OnMessage
public void onMessage(Session session, byte[] message, boolean last) {
if (last) {
// 這是完整的消息,可以進行處理
String text = decodeMessage(message);
System.out.println("接收到完整的消息:" + text);
} else {
// 還需要接收后續(xù)消息片段
ByteBuffer buffer = ByteBuffer.allocate(message.length);
buffer.put(message);
session.getUserProperties().put("message", buffer);
}
}
private String decodeMessage(byte[] message) {
// 在這里進行消息解碼,返回解碼后的字符串
// 例如:假設消息是UTF-8編碼的字符串,可以這樣解碼
return new String(message, StandardCharsets.UTF_8);
}
}@OnMessage這也可以這樣:
@OnMessage
public void onMessageContinued(Session session, byte[] message, boolean last) {
ByteBuffer buffer = (ByteBuffer) session.getUserProperties().get("message");
buffer.put(message);
if (last) {
// 這是完整的消息,可以進行處理
String text = decodeMessage(buffer.array());
System.out.println("接收到完整的消息:" + text);
session.getUserProperties().remove("message");
}
}如果是完整的消息,直接進行處理;如果是消息的一部分,則緩存起來等待后續(xù)片段的到來
擴展
SpringBoot整合websocket實現(xiàn)消息對話的方法
前言
websockt
什么是websockt?
WebSocket是一種在Web應用程序中實現(xiàn)實時雙向通信的技術(shù)。Web應用程序通常是基于HTTP協(xié)議的,HTTP是一種請求/響應式的協(xié)議,客戶端發(fā)起請求,服務器響應請求并發(fā)送響應,客戶端收到響應后關(guān)閉連接。這意味著,如果客戶端需要不斷地從服務器獲取更新,它必須定期發(fā)送請求以檢查更新,這將導致大量的網(wǎng)絡流量和不必要的服務器負載。
WebSocket通過在客戶端和服務器之間創(chuàng)建持久化連接,允許雙向?qū)崟r通信。這意味著服務器可以主動向客戶端推送更新,而不必等待客戶端不停地請求。WebSocket連接始于HTTP握手,然后升級到WebSocket協(xié)議。在升級過程中,客戶端和服務器之間的數(shù)據(jù)傳輸方式從HTTP變成了WebSocket協(xié)議,這樣可以降低傳輸數(shù)據(jù)的延遲和提高傳輸數(shù)據(jù)的可靠性。
在WebSocket協(xié)議中,數(shù)據(jù)是以幀的形式傳輸?shù)?,幀包括?shù)據(jù)頭和數(shù)據(jù)體兩個部分。數(shù)據(jù)頭指示數(shù)據(jù)類型,數(shù)據(jù)傳輸?shù)姆较虻刃畔ⅲ瑪?shù)據(jù)體則包括實際傳輸?shù)臄?shù)據(jù)。WebSocket支持文本類型和二進制類型的數(shù)據(jù)。文本類型的數(shù)據(jù)使用UTF-8編碼,而二進制類型的數(shù)據(jù)可以包括任何二進制數(shù)據(jù),如圖像、音頻、視頻等。
WebSockets是非常適合實時通信的技術(shù)。它可以用于在線游戲、在線聊天、推送通知、實時監(jiān)控等,并且比傳統(tǒng)的輪詢技術(shù)更加高效和可靠。對于現(xiàn)代Web應用程序而言,WebSockets已經(jīng)成為不可或缺的技術(shù)之一。
websockt和Socket區(qū)別
| 區(qū)別 | WebSocket | Socket |
|---|---|---|
| 應用層協(xié)議 | 基于HTTP協(xié)議,升級為WebSocket協(xié)議 | 沒有明確的應用層協(xié)議,常用于傳輸TCP或UDP數(shù)據(jù) |
| 傳輸數(shù)據(jù)類型 | 支持文本和二進制類型的數(shù)據(jù) | 通常用于傳輸二進制數(shù)據(jù),如圖片、視頻等 |
| 客戶端和服務器通信 | 支持實時雙向通信 | 支持雙向通信,但通常需要進行輪詢或長連接 |
| 傳輸效率 | 比傳統(tǒng)的輪詢技術(shù)更加高效和可靠,不會頻繁請求服務器 | 傳輸效率較低,需要進行輪詢或者長連接來保持連接,會頻繁請求服務器 |
| 應用場景 | 適用于實時通信,如在線游戲、在線聊天、推送通知、實時監(jiān)控等 | 適用于需要傳輸大量二進制數(shù)據(jù)的場景,如文件傳輸、圖片處理等 |
代碼部分
引入maven依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
說明:默認版本為1.5.10RELEASE,可自定義版本,加入“<version">"”標簽即可
常量類
package com.dmsdbj.itoo.utils;
public class Constant {
//webSocket相關(guān)配置
//鏈接地址
public static String WEBSOCKETPATHPERFIX = "/ws-push";
public static String WEBSOCKETPATH = "/endpointWisely";
//消息代理路徑
public static String WEBSOCKETBROADCASTPATH = "/topic";
//前端發(fā)送給服務端請求地址
public static final String FORETOSERVERPATH = "/welcome";
//服務端生產(chǎn)地址,客戶端訂閱此地址以接收服務端生產(chǎn)的消息
public static final String PRODUCERPATH = "/topic/getResponse";
//點對點消息推送地址前綴
public static final String P2PPUSHBASEPATH = "/user";
//點對點消息推送地址后綴,最后的地址為/user/用戶識別碼/msg
public static final String P2PPUSHPATH = "/msg";
}接收前端消息實體
package com.dmsdbj.itoo.vo;
public class WiselyMessage {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}后臺發(fā)送消息實體
package com.dmsdbj.itoo.vo;
public class WiselyResponse {
private String responseMessage;
public WiselyResponse(String responseMessage){
this.responseMessage = responseMessage;
}
public String getResponseMessage() {
return responseMessage;
}
public void setResponseMessage(String responseMessage) {
this.responseMessage = responseMessage;
}
}配置websocket
package com.dmsdbj.itoo.config;
import com.dmsdbj.itoo.utils.Constant;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@Configuration
// @EnableWebSocketMessageBroker注解用于開啟使用STOMP協(xié)議來傳輸基于代理(MessageBroker)的消息,這時候控制器(controller)
// 開始支持@MessageMapping,就像是使用@requestMapping一樣。
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
//注冊一個Stomp的節(jié)點(endpoint),并指定使用SockJS協(xié)議。
stompEndpointRegistry.addEndpoint(Constant.WEBSOCKETPATH).withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
//服務端發(fā)送消息給客戶端的域,多個用逗號隔開
registry.enableSimpleBroker(Constant.WEBSOCKETBROADCASTPATH, Constant.P2PPUSHBASEPATH);
//定義一對一推送的時候前綴
registry.setUserDestinationPrefix(Constant.P2PPUSHBASEPATH);
//定義websoket前綴
registry.setApplicationDestinationPrefixes(Constant.WEBSOCKETPATHPERFIX);
}
}采用JPA方法進行操作數(shù)據(jù)庫DAO層
package com.dmsdbj.itoo.repository;
import com.dmsdbj.itoo.entity.TbUser;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TbUseRepository extends JpaRepository<TbUser,Long> {}sql語句
CREATE TABLE `tb_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) DEFAULT NULL COMMENT '用戶名', `password` varchar(32) DEFAULT NULL COMMENT '密碼,加密存儲', `phone` varchar(20) DEFAULT NULL COMMENT '注冊手機號', `email` varchar(50) DEFAULT NULL COMMENT '注冊郵箱', `created` datetime DEFAULT NULL, `updated` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`) USING BTREE, UNIQUE KEY `phone` (`phone`) USING BTREE, UNIQUE KEY `email` (`email`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=18333611875 DEFAULT CHARSET=utf8 COMMENT='用戶表';
TbUser實體類
package com.dmsdbj.itoo.entity;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.io.Serializable;
@Entity
@Data
public class TbUser implements Serializable {
@Id
private Long id;
private String username;
private String password;
private String phone;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}Service實現(xiàn)類
@Service
public class TbUseServiceImpl implements TbUseService {
@Autowired
TbUseRepository repository;
/**
* 從數(shù)據(jù)庫查詢用戶,給訂閱者發(fā)送消息
* @return
*/
@Override
public List<Long> send2Users() {
return repository.findAll().stream().map(s -> s.getId()).collect(Collectors.toList());
}
}Service接口類
public interface TbUseService {
List<Long> send2Users();
}Façade實現(xiàn)類
public interface TbUseFacade {
List<Long> send2Users();
}Façade接口類
public interface TbUseFacade {
List<Long> send2Users();
}Controller類
@Controller
public class SendInfoController {
@Reference
TbUseFacade tbUseFacade;
@Autowired
private SimpMessagingTemplate template;
@Resource
WebSocketService webSocketService;
@MessageMapping(Constant.FORETOSERVERPATH)//@MessageMapping和@RequestMapping功能類似,用于設置URL映射地址,瀏覽器向服務器發(fā)起請求,需要通過該地址。
// @SendTo(Constant.PRODUCERPATH)//如果服務器接受到了消息,就會對訂閱了@SendTo括號中的地址傳送消息。
public WiselyResponse say(WiselyMessage message) throws Exception {
List<Long> userId = tbUseFacade.send2Users();
userId.forEach(s->{
template.convertAndSendToUser(s.toString(),Constant.P2PPUSHPATH, new WiselyResponse(message.getName()));
});
return null;
}
}前端頁面
sendws.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>輸入要發(fā)送的消息</label><input type="text" id="name" />
<button id="sendName" onclick="sendName();">發(fā)送</button>
<p id="response"></p>
<p id="response1"></p>
</div>
</div>
<!--<script th:src="@{sockjs.min.js}"></script>
<script th:src="@{stomp.min.js}"></script>
<script th:src="@{jquery.js}"></script>-->
<script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
<script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<script th:inline="javascript">
var stompClient = null;
//此值有服務端傳遞給前端,實現(xiàn)方式?jīng)]有要求
//var userId = [[${userId}]];
var userId = 'd892bf12bf7d11e793b69c5c8e6f60fb';
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('/endpointWisely'); //1連接SockJS的endpoint是“endpointWisely”,與后臺代碼中注冊的endpoint要一樣。
stompClient = Stomp.over(socket);//2創(chuàng)建STOMP協(xié)議的webSocket客戶端。
stompClient.connect({}, function(frame) {//3連接webSocket的服務端。
setConnected(true);
console.log('開始進行連接Connected: ' + frame);
});
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendName() {
var name = $('#name').val();
console.log(name);
//通過stompClient.send()向地址為"/welcome"的服務器地址發(fā)起請求,與@MessageMapping里的地址對應。因為我們配置了registry.setApplicationDestinationPrefixes(Constant.WEBSOCKETPATHPERFIX);所以需要增加前綴/ws-push/
stompClient.send("/ws-push/welcome", {}, JSON.stringify({ 'name': name }));
}
</script>
</body>
</html>subws.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>輸入你的userId</label><input type="text" id="userId" />
<button id="subMsg" onclick="subMsg();">訂閱消息</button>
<p id="response"></p>
<p id="response1"></p>
</div>
</div>
<!--<script th:src="@{sockjs.min.js}"></script>
<script th:src="@{stomp.min.js}"></script>
<script th:src="@{jquery.js}"></script>-->
<script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
<script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<script th:inline="javascript">
var stompClient = null;
//此值有服務端傳遞給前端,實現(xiàn)方式?jīng)]有要求
//var userId = [[${userId}]];
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('/endpointWisely'); //1連接SockJS的endpoint是“endpointWisely”,與后臺代碼中注冊的endpoint要一樣。
stompClient = Stomp.over(socket);//2創(chuàng)建STOMP協(xié)議的webSocket客戶端。
stompClient.connect({}, function(frame) {//3連接webSocket的服務端。
setConnected(true);
console.log('開始進行連接Connected: ' + frame);
//4通過stompClient.subscribe()訂閱服務器的目標是'/topic/getResponse'發(fā)送過來的地址,與@SendTo中的地址對應。
/*stompClient.subscribe('/topic/getResponse', function(respnose){
showResponse(JSON.parse(respnose.body).responseMessage);
});*/
});
}
function subMsg() {
var userId = $('#userId').val();
console.log(userId);
$.ajax({
type: 'GET',
url: '/user/insert?userId='+userId,
//data: userId,
success: function (data) {
console.log(data);
//4通過stompClient.subscribe()訂閱服務器的目標是'/user/' + userId + '/msg'接收一對一的推送消息,其中userId由服務端傳遞過來,用于表示唯一的用戶,通過此值將消息精確推送給一個用戶
stompClient.subscribe('/user/' + userId + '/msg', function(respnose){
console.log(respnose);
showResponse1(JSON.parse(respnose.body).responseMessage);
});
}
});
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function showResponse1(message) {
var response = $("#response1");
response.html(message);
}
</script>
</body>
</html>實戰(zhàn)應用
①啟動springboot-dubbo-service的主啟動類“SpringbootDubboServiceApplication”②啟動springboot-dubbo-web的主啟動類“SpringbootDubboWebApplication”③訪問http://localhost:9000/sendws 發(fā)送消息方http://localhost:9000/subws 訂閱消息方④分別進行連接操作訂閱方輸入訂閱方的連接賬號userid,后期就可以接收發(fā)送端發(fā)送的消息連接后的結(jié)果展示

到此這篇關(guān)于Websocket如何保證接收消息完整性的文章就介紹到這了,更多相關(guān)Websocket接收消息內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中Map轉(zhuǎn)List及List轉(zhuǎn)Map的方法(簡單好用!)
這篇文章主要給大家介紹了關(guān)于Java中Map轉(zhuǎn)List及List轉(zhuǎn)Map的相關(guān)資料,可以使用Java 8的Stream API將Java Map轉(zhuǎn)換為List,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下2023-07-07
解決Java中SimpleDateFormat線程不安全的五種方案
SimpleDateFormat 就是一個典型的線程不安全事例,本文主要介紹了解決Java中SimpleDateFormat線程不安全的五種方案,需要的朋友們下面隨著小編來一起學習學習吧2021-05-05
詳解Spring-boot中讀取config配置文件的兩種方式
這篇文章主要介紹了詳解Spring-boot中讀取config配置文件的兩種方式,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10

