WebSocket

An overview of the

The WebSocket protocol provides full-duplex communication through a socket. Among other things, it enables asynchronous communication between the Web browser and the server. Full duplex means that the server can send messages to the browser and the browser can send messages to the server.

Use Spring’s low-level WebSocketAPI

In its simplest form, WebSocket is simply a channel for communication between two applications. The application on one side of the WebSocket sends the message and the other side receives the message. Because it is full-duplex, each end can send and process messages.WebSocket communication can be applied to any type of application, but the most common application scenario of WebSocket is the communication between servers and browser-based applications.Write a simple WebSocket sample (an endless “Marco Polo” game based on JavaScript client and server)

To use lower-level apis to process messages in Spring, we must write a class that implements WebSocketHandler. WebSocketHandler.java

public interface WebSocketHandler {

	
	void afterConnectionEstablished(WebSocketSession session) throws Exception;

	
	void handleMessage(WebSocketSession session, WebSocketMessage
        message) throws Exception;

	
	void handleTransportError(WebSocketSession session, Throwable exception) throws Exception;

	
	void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception;

	
	boolean supportsPartialMessages(a);

}
Copy the code

But a simpler way is to extend AbstractWebSocketHandler, which is an abstract implementation of WebSocketHandler. MarcoHandler.java

public class MarcoHandler extends AbstractWebSocketHandler {

 protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
  System.out.println("Received message: " + message.getPayload());
  Thread.sleep(2000);
  session.sendMessage(new TextMessage("Polo!"));
 }
 
 @Override
 public void afterConnectionEstablished(WebSocketSession session) {
  System.out.println("Connection established!");
 }
 
 @Override
 public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
  System.out.println("Connection closed. Status: " + status);
 }
Copy the code

Although AbstractWebSocketHandler is an abstract class, it does not require us to override any particular method. Instead, it lets us decide which method to override. In addition to overriding the five methods defined in WebSocketHandler, we can also override the three methods defined in AbstractWebSocketHandler:

  • handleBinaryMessage()
  • handlePongMessage()
  • handleTextMessage()

These three methods are just instantiations of the handleMessage() method, each corresponding to a particular type of message. So methods that are not overloaded are performed as null operations by AbstractWebSocketHandler. This means that MarcoHandler can handle binary and Pong messages as well, it just doesn’t do anything with them.

Another way we can extend TextWebSocketHandler, which is a subclass of AbstractWebSocketHandler, is to refuse to process binary messages. It overloads the handleBinaryMessage() method to close the WebSocket connection if a binary message is received. Similarly, BinaryWebSocketHandler, which is a subclass of AbstractWebSocketHandler, overloads the handleTextMessage() method to close the connection if a text message is received.

public class MarcoHandler extends TextWebSocketHandler {... }public class MarcoHandler extends BinaryWebSocketHandler{... }Copy the code

WebSocketConfig.java

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer{
 
 @Override
 public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
  registry.addHandler(marcoHandler(), "/marco"); // Register the information manager and map the MarcoHandler to "/ Marco"
 }
 
 @Bean
 public MarcoHandler marcoHandler(a) {
  return newMarcoHandler(); }}Copy the code

WebAppInitializer.java

@Override
 protectedClass<? >[] getServletConfigClasses() {return newClass<? >[] {WebSocketConfig.class}; }Copy the code

JavaScript client code

<script> var url = 'ws://' + window.location.host + '/yds(your project name)/ Marco '; var sock = new WebSocket(url); // open WebSocket sock.onopen = function() {// handle the connection Opening event console.log('Opening'); sock.send('Marco! '); }; Sock.onmessage = function(e) {// Handle Message console.log('Received Message: ', e.ata); setTimeout(function() { sayMarco() }, 2000); }; Sock. Onclose = function() {// Handle the connection Closing event console.log('Closing'); }; Function sayMarco() {console.log('Sending Marco! '); sock.send('Marco! '); } </script>Copy the code

In this case, the URL uses the WS :// prefix, indicating that this is a basic WebSocket connection. For secure Websockets, the protocol prefix would be WSS ://.Note: the jar package must be correctly generated, I am using Spring5.0, jackson2.9.3. Some older jar packages are always throwing NoSuchMethodException, or Spring is not compatible with Jackson

WebSocket simple example

Personally, I feel that the above one is too complicated. If it is just a simple communication, it can be written like this:

<script> if('WebSocket' in window) { var url = 'ws://' + window.location.host + '/TestsWebSocket (project name)/webSocket (endpoint defined by the server) '; var sock = new WebSocket(url); // Open WebSocket}else {alert(" your browser does not support WebSocket"); } sock. Onopen = function() {// handle the connection Opening event console.log('Opening'); sock.send('start'); }; Sock. Onmessage = function (e) {/ / process information e = e | | event; // Get the event, which is written to be compatible with IE console.log(e.ata); }; Sock. Onclose = function() {// Handle the connection Closing event console.log('Closing'); }; </script>Copy the code
import java.io.IOException;
import java.util.Date;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint(value = "/websocket")    // Declare that this is a Socket service
public class MyWebSocket {
	// Session is a connection session that needs to be used to send data to the client
	private Session session;
 
	/** * Connection established successfully called method *@paramSession This parameter is optional@throws Exception 
	 */
	@OnOpen
	public void onOpen(Session session) throws Exception {
		this.session = session;
		System.out.println("Open");
	}
 
	/** * Close the connection to call the method *@throws Exception 
	 */
	@OnClose
	public void onClose(a) throws Exception {		
		System.out.println("Close");
	}
 
	/** * The method to be called when the message is received@paramMessage Indicates the message sent by the client *@paramSession This parameter is optional@throws Exception 
	 */
	@OnMessage
	public void onMessage(String message, Session session) throws Exception {
		if(message ! =null) {switch (message) {        	
				case "start":
					System.out.println("Data received"+message);
					sendMessage("Ha ha ha ha ha ha ha");
					break;				
				case "question":					
				case "close":
					System.out.println("Close connection");
					onClose();
				default:
						break; }}}/** * call * when an error occurs@param session
	 * @param error
	 */
	@OnError
	public void onError(Session session, Throwable error) {
		error.printStackTrace();
	}
 
	/** * Send message method. *@param message
	 * @throws IOException
	 */
	public void sendMessage(String message) throws IOException {
		this.session.getBasicRemote().sendText(message);   // Send data to the client}}Copy the code

When running, the output of browser and server is shown in figure:

SockJS

An overview of the

WebSocket is a relatively new specification that has not been consistently supported across Web browsers and application servers. So we need an alternative to WebSocket. This is what SockJS is good at. SockJS is a simulation of WebSocket technology. On the surface, it mirrors the WebSocket API as much as possible, but underneath it is very intelligent. If the WebSocket technology is not available, an alternative communication mode is chosen.

Using SockJS

WebSocketConfig.java

 @Override
 public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
  registry.addHandler(marcoHandler(), "/marco").withSockJS();
 }
Copy the code

Simply add the withSockJS() method to declare that we want to use the SockJS function. If WebSocket is not available, the SockJS alternative will come into play. For JavaScript client code to use SockJS on the client side, ensure that the SockJS client library is loaded.

< script SRC = "https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js" > < / script >Copy the code

In addition to loading the SockJS client library, only two lines of code are required to use SockJS:

var url = 'marco'; var sock = new SockJS(url); //SockJS handles urls that are http:// or https://, instead of ws:// and WSS :// // using relative urls. For example, If you include JavaScript page located under the path of "http://localhost:8080/websocket" / / Then a given path "Marco" would be formed links to "http://localhost:8080/websocket/marco"Copy the code

It works the same, but the way the client-server communication works has changed a lot.

Use STOMP messages

An overview of the

STOMP provides a frame-based line format layer on top of WebSocket to define the semantics of messages. A STOMP frame consists of a command, one or more head information, and a payload. For example, here is a STOMP frame that sends data:

>>> SEND destination:/app/marco content-length:20 {"message":"Maeco!" }Copy the code

In this simple example, the STOMP command is SEND, indicating that something will be sent. This is followed by two headers: one to indicate the destination to which the message is to be sent, and the other to contain the size of the payload. Then, followed by a blank line, the STOMP frame ends with the payload content. The most interesting part of STOMP frames is the destination header. It indicates that STOMP is a messaging protocol. The message is posted to a destination that may actually have a message broker behind it. On the other hand, the message handler can also listen to these destinations to receive the messages it sends.

Enable STOMP messaging

WebSocketStompConfig.java

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketStompConfig extends AbstractWebSocketMessageBrokerConfigurer{

	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) {
		
		registry.addEndpoint("/marcopolo").withSockJS();// Enable SockJS for the /marcopolo path
	}
	@Override
	public void configureMessageBroker(MessageBrokerRegistry registry)
	{
		
		// Indicates that the topic, queue, and Users domains can send messages to clients.
		registry.enableSimpleBroker("/topic"."/queue"."/users");
        // When the client sends a request to the server, the prefix must be /app.
		registry.setApplicationDestinationPrefixes("/app");
        // Send one-to-one messages to specified users with the prefix /users/.
		registry.setUserDestinationPrefix("/users/"); }}Copy the code
 @Override
 protectedClass<? >[] getServletConfigClasses() {return newClass<? >[] {WebSocketStompConfig.class,WebConfig.class}; }Copy the code

WebSocketStompConfig overloads the registerStompEndpoints () method to register/Marcopolo as a STOMP endpoint. This path is different from the previous destination paths for receiving and sending messages. This is the endpoint to which the client connects before subscribing or publishing messages to the destination. WebSocketStompConfig also configures a simple message broker by overloading the configureMessageBroker () method. This method is optional, and if not overridden, a simple in-memory message broker will be automatically configured to handle messages prefixed with “/topic”.

Processing STOMP messages from clients

testConroller.java

@Controller
public class testConroller {
	@MessageMapping("/marco")
    public void handleShout(Shout incoming) 
    {
	System.out.println("Received message:"+incoming.getMessage());
    }
    
    @SubscribeMapping("/subscribe")
    public Shout handleSubscribe(a) 
    {
	Shout  outing = new Shout();
	outing.setMessage("subscribes");
	returnouting; }}Copy the code

The @messagemapping annotation indicates that the handleShout () method can process messages arriving at the specified destination. In this example, the destination is “/app/ Marco”. (The “/app” prefix is implicit because we configured it as the destination prefix for the application.) The @SubscribeMapping annotation, similar to the @Messagemapping annotation, when a STOMP subscription message is received, Methods with the @SubscribeMapping annotation are triggered.

Shout.java

public class Shout {
private String message;

public String getMessage(a) {
	return message;
}

public void setMessage(String message) {
	this.message = message; }}Copy the code

Client-side JavaScript code

< 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/stomp.js/2.3.3/stomp.js" > < / script > < script > var url = 'http://'+window.location.host+'/yds/marcopolo'; var sock = new SockJS(url); // Create a SockJS connection. var stomp = Stomp.over(sock); // Create a STOMP client instance. It actually encapsulates SockJS so you can send STOMP messages over WebSocket connections. var payload = JSON.stringify({'message':'Marco! '}); stomp.connect('guest','guest',function(frame){ stomp.send("/app/marco",{},payload); stomp.subscribe('/app/subscribe', function(message){ }); }); </script>Copy the code

Received message:Marco!

Send a message to the client

If you want to send a message in the response as well as receive the message, all you need to do is return the content.

@MessageMapping("/marco")	
public Shout handleShout(Shout incoming) {
	System.out.println("Received message:"+incoming.getMessage());
	Shout  outing = new Shout();
	outing.setMessage("Polo");
	return outing;
}
Copy the code

When the method indicated by the @Messagemapping annotation has a return value, the returned object is transformed (via the message converter) and placed in the payload of the STOMP frame, which is then sent to the message broker. By default, the frame is sent to the same destination as the one that triggered the handler method, but prefixed with “/topic.”

Stomp.subscribe ('/topic/ Marco ', function(message){ });Copy the code

However, we can override the destination by annotating the @sendto method:

@MessageMapping("/marco")
@SendTo("/queue/marco")
public Shout handleShout(Shout incoming) {
	System.out.println("Received message:"+incoming.getMessage());
	Shout  outing = new Shout();
	outing.setMessage("Polo");
	return outing;
}
Copy the code
stomp.subscribe('/queue/marco', function(message){ 
});
Copy the code

Send messages anywhere in the application

Spring’s SimpMessagingTemplate can send messages anywhere in the application, without even needing to receive a message in the first place. The simplest way to use SimpMessagingTemplate is it (or its interface SimpMessageSendingOperations) automatic assembly to the desired object.

 @Autowired
 private SimpMessageSendingOperations simpMessageSendingOperations;


@RequestMapping("/test")
	public void sendMessage(a)
	{
		simpMessageSendingOperations.convertAndSend("/topic/test"."Test SimpMessageSendingOperations");
	}
Copy the code

After accessing /test:

Send a message to the target user

Use the @Sendtouser annotation to indicate that its return value is to be sent as a message to a client that authenticates the user.

    @MessageMapping("/message")
	@SendToUser("/topic/sendtouser")
	public Shout message(a)
	{
		Shout  outing = new Shout();
		outing.setMessage("SendToUser");
		return outing;
	}
Copy the code
Stomp.subscribe ('/users/topic/sendtouser', function(message){// send a one to one message to the specified user with the prefix /users/. });Copy the code

This destination is prefixed by /users. Destinations prefixed by /users will be treated in a special way. / users to prefix the message will be handled UserDestinationMessageHandler.Is the main task of the UserDestinationMessageHandler reroute to a user’s unique user information to destination. When processing subscriptions, it removes the /users prefix from the target address and adds a suffix based on the user’s session.

Sends a message for the specified user

SimpMessagingTemplate also provides the convertAndSendToUser() method. The convertAndSendToUser() method lets us send a message to a specific user.

SimpMessageSendingOperations. ConvertAndSendToUser (" 1 ", "/ message", "test convertAndSendToUser");Copy the code
stomp.subscribe('/users/1/message', function(message){ 

});
Copy the code

The subject of one-to-one messages received by clients is “/users/”+usersId+”/message”, where the user Id can be a normal string, as long as each client uses its own Id and the server knows each user’s Id.

The above is just my notes for study, please correct any mistakes. Thanks!!