前后端全双工通信 —— 基于Stomp Over Websocket

WebSocket协议

概念:

WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。

HTTP 协议做不到服务器主动向客户端推送信息。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用轮询。轮询效率低,且很浪费资源。

而WebSocket协议最大的特点就是服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器端推送技术的一种。

特点:

(1)建立在 TCP 协议之上,服务器端的实现比较容易。

(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

(3)数据格式比较轻量,性能开销小,通信高效。

(4)可以发送文本,也可以发送二进制数据。

(5)没有同源限制,客户端可以与任意服务器通信。

(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

用法:

html5中提供了WebSocket对象,

// Create WebSocket connection.
const socket = new WebSocket('ws://localhost:8080');

// Connection opened
socket.addEventListener('open', function (event) {
    socket.send('Hello Server!');
});

// Listen for messages
socket.addEventListener('message', function (event) {
    console.log('Message from server ', event.data);
});

  mdn接口说明

 

Stomp协议

websocket没有规范payload格式,可以是文本数据,也可以发送二进制数据,需要我们自己定义。而我们可以使用stomp协议去规范传输数据格式标准。

什么是payload

通常在传输数据时,为了使数据传输更可靠,要把原始数据分批传输,并且在每一批数据的头和尾都加上一定的辅助信息,比如数据量的大小、校验位等,这样就相当于给已经分批的原始数据加一些外套,这些外套起标示作用,使得原始数据不易丢失,一批数据加上“外套”就形成了传输通道的基本传输单元,叫做数据帧或数据包,而其中的原始数据就是payload

什么是stomp

STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,简单(流)文本定向消息协议 。它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。

更多stomp over Websocket细节 参考官方文档   ||  翻译文档

SockJS

概念:

SockJS是一个提供类似websocket的对象的浏览器JavaScript库。SockJS为您提供了一个连贯的、跨浏览器的Javascript API,它在浏览器和web服务器之间创建了一个低延迟、全双工、跨域通信通道。

在幕后,SockJS首先尝试使用本机WebSockets。如果失败,它可以使用各种特定于浏览器的传输协议,并通过类似websocket的抽象来呈现它们。

SockJS旨在为所有现代浏览器和不支持WebSocket协议的环境(例如,受限的企业代理)工作。

(我们可以在github上找到SockJS更详细的使用文档)

 

stomp.js使用

创建stomp客户端

在web浏览器中使用原生的WebSocket

var url = "ws://localhost:61614/stomp";
var client = Stomp.client(url);

 使用sockejs包装的websocket



连接stomp服务端

client.connect(login, passcode, connectCallback); 
client.connect(login, passcode, connectCallback, errorCallback);
client.connect(login, passcode, connectCallback, errorCallback, host);
client.connect(headers, connectCallback); // headers为map结构
client.connect(headers, connectCallback, errorCallback);

发送消息

client.send(destination, headers, body); //destination 为地址 如 '/queue/default'

订阅消息

var subscription = client.subscribe(destination,callback,headers);

取消订阅

subscription.unsubscribe();

事务

// start the transaction
var tx = client.begin();
// send the message in a transaction
client.send("/queue/test", {transaction: tx.id}, "message in a transaction");
// commit the transaction to effectively send the message
tx.commit(); 
//or tx.abort();

 

 

Spring WebSocket服务

 


		
			org.springframework.boot
			spring-boot-starter-websocket
		

 

服务端

创建一个消息处理控制类

@Controller
public class GreetingController {

    @Autowired
    private UserService userService;

    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;
    
    //服务端映射地址,还需要加上代理地址前缀
    @MessageMapping("/hello")
    //@SendTo("/topic/greetings")
    public void greeting(User user) throws  Exception{
        TimeUnit.SECONDS.sleep(2);
        //手动去发送数据到客户端
        simpMessagingTemplate.convertAndSend("/topic/greetings","hello spring stomp");
    }
   

}

配置spring的stomp消息服务,spring的websocket服务实现,实际上是一个简易版的消息队列

@Configuration
@EnableWebSocketMessageBroker
public class SpringWebSocketConfig implements WebSocketMessageBrokerConfigurer {

    /**
     * 定义消息代理,设置消息连接的各种规范请求信息
     * @param registry
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        //启动一个简单基于内存的消息代理  返回客户端消息前缀/topic
        registry.enableSimpleBroker("/topic");
        //发送个服务端目的地前缀 /app 开头的数据会被@MessageMapping拦截 进入方法体
        registry.setApplicationDestinationPrefixes("/app");
        //点对点用户
        registry.setUserDestinationPrefix("/user");
    }

    /**
     * 添加一个服务端点,来接收客户端的连接。
     * @param registry
     */
   @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
       //添加一个/gs-guide-websocket 端点,客户端通过这个端点进行连接, withSockJS 开启对SockJS支持
        registry.addEndpoint("/gs-guide-websocket").withSockJS();
    }


}

对于地址“/app”,发送到这类目的地址的消息在到达broker之前, 会先路由到由应用写的某个方法. 相当于对进入broker的消息进行一次拦截@MessageMapping, 目的是针对消息做一些业务处理.

对于地址“/topic”,发送到这类目的地址的消息会直接跳转到broker,不会被应用拦截

前后端全双工通信 —— 基于Stomp Over Websocket_第1张图片

客户端




    
    Title
    
    
    
    
    






前后端全双工通信 —— 基于Stomp Over Websocket_第2张图片

 

STOMP消息的body必须为字符串

前端作为客户端,发送或订阅某个目的地地址。

var stompClient = null;

/**
 * 建立连接并订阅消息
 * @returns {*}
 */
function connect(){
    var subscription;
    var socket = new SockJS('/gs-guide-websocket');
    stompClient = Stomp.over(socket);
    stompClient.connect({},function (message) {
        if(message.body){
            console.log("got message with body " + message.body);
        }else {
            console.log("get empty message");
        }
        //订阅一个目的地
        //STOMP消息的body必须为字符串
        subscription = stompClient.subscribe('/topic/greetings',function(msg){
            console.log("接受到java后台发送的消息," + msg.body);
        });
        
    })

    return subscription;
}

/**
 * 中止订阅消息
 */
function unsubscription(subscription){
    if(subscription){
        subscription.unsubscribe();
    }
}

/**
 * 断开连接
 */
function  disconnect() {
    if (stompClient !== null) {
        stompClient.disconnect();
    }
    console.log("Disconnected");
}

function sendName(){
    //STOMP消息的body必须为字符串
    stompClient.send('/app/hello',{},JSON.stringify({'username':$("#name").val()}));
    //直接发送数据,不被拦截
    //stompClient.send('/topic/greeting',{},JSON.stringify({'username':$("#name").val()}));
}

$(function(){
    $( "#connect" ).click(function() { connect(); });
    $( "#disconnect" ).click(function() { disconnect(); });
    $( "#send" ).click(function() { sendName(); });

})

测试


建立连接时的日志

Opening Web Socket...
stomp.js:134 Web Socket Opened...
stomp.js:134 >>> CONNECT
accept-version:1.1,1.0
heart-beat:10000,10000

<<< CONNECTED
version:1.1
heart-beat:0,0

connected to server undefined

get empty message

>>> SUBSCRIBE
id:sub-0
destination:/topic/greetings

 发送消息时的日志。客户端向服务端发送的数据文本为{"username":"food"},服务端返回的数据文本为 hello spring stomp

>>> SEND
destination:/app/hello
content-length:19

{"username":"food"}

<<< MESSAGE
destination:/topic/greetings
content-type:text/plain;charset=UTF-8
subscription:sub-0
message-id:qz2tzbed-0
content-length:18

hello spring stomp

接受到java后台发送的消息,hello spring stomp


参考: spring 官网文档 using websocket

           stomp over websocket 文档翻译

           github sockjs

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(html,spring,boot,stomp,websocket,springboot)