brief introduction
WebSocket is a two-way, full duplex and persistent connection between Web browser and server. Once a WebSocket connection is established, the connection will remain open until the client or server decides to close the connection.
A typical use case might be when an application involves multiple users communicating with each other, such as in a chat. We will build a simple chat client in our example.
Introduction to Http, Websocket, SockJs and Stomp
Http
HTTP hypertext transfer protocol, HTTP has several versions of 1.0, 1.1 and 2.0, from http1 Since 1, keep alive is enabled by default to maintain the continuity of the connection. In short, when a web page is opened, the TCP connection between the client and the server for transmitting HTTP data will not be closed. If the client accesses the web page on the server again, it will continue to use the established connection, which reduces the consumption of resources and optimizes performance, However, keep alive also has a time limit. There is another client that can only actively initiate a request to obtain the returned data, and can not actively receive the data pushed by the background. websocket came into being.
Websocket
It is one of the newly added features of html5. The purpose is to establish a full duplex communication mode between the browser and the server, solve the excessive resource consumption caused by http request response, and provide a new implementation mode for special scenario applications, such as chat, stock trading, games and other industries with high real-time requirements.
Both http and websocket are based on TCP (transmission control protocol). Websocket can be regarded as a supplement to http protocol.
SockJs
SockJS is a JavaScript library. In order to solve the problem that many browsers do not support WebSocket protocol, an alternative SockJS is designed. SockJS is a simulation of WebSocket technology. SockJS will correspond to the WebSocket API as much as possible, but if the WebSocket technology is not available, it will be automatically reduced to the polling mode. SockJS will preferentially select WebSocket for connection, but when the server or client does not support WebSocket, it will automatically connect in XHR stream, XDR stream, iFrame event source, iFrame HTML file, XHR polling, XDR polling, iFrame XHR polling and JSONP polling.
SockJS is a JavaScript library. In order to solve the problem that many browsers do not support WebSocket protocol, an alternative SockJS is designed. SockJS is a simulation of WebSocket technology. SockJS will correspond to the WebSocket API as much as possible, but if the WebSocket technology is not available, it will be automatically reduced to the polling mode.
The SockJS client first sends GET /info to get basic information from the server. After that, it must decide which transmission method to use. If possible, use WebSocket. If not, in most browsers, there is at least one HTTP streaming option. If not, use HTTP (long) polling.
All transport requests have the following URL structure:
http://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport}
- {server ID} is used to route requests in the cluster, but not for other purposes.
- {session ID} is associated with HTTP requests belonging to SockJS sessions.
- {transport} indicates the transport type (such as websocket, XHR streaming, and others).
Github address: https://github.com/sockjs
Stomp
STOMP is a Streaming Text Oriented Messaging Protocol. It is a part of the WebSocket communication standard and belongs to the business layer control protocol Wire protocol, Protocol specification . Based on the general publish subscribe semantics, it provides reliable message delivery through begin, commit, rollback transaction sequence and ack confirmation mechanism. Because the protocol is simple and easy to implement, almost all programming languages have the client implementation of STOMP.
STOMP protocol to add appropriate message semantics for the communication between browser and server.
STOMP provides a frame based line format layer on WebSocket to define the semantics of messages.
In terms of transmission protocol, it supports both STOMP Over WebSocket and STOMP Over SockJS.
The STOMP frame consists of a command, one or more header information, and a payload. For example, the following is a STOMP frame for sending data:
>>> SEND destination:/app/marco content-length:20 {"message":"Maeco!"}
Relationship among WebSocket, SockJs and STOMP
In short, WebSocket is the underlying protocol, SockJS is the alternative to WebSocket and the underlying protocol, and STOMP is the upper protocol based on WebSocket (SockJS).
1. HTTP protocol solves the details of the request initiated by the web browser and the response of the web server. Assuming that HTTP protocol does not exist, we can only use TCP socket to write web applications.
2. Using WebSocket (SockJS) directly is very similar to using TCP socket to write web applications. Because there is no high-level protocol, we need to define the semantics of messages sent between applications and ensure that both ends of the connection can follow these semantics;
3. Like HTTP adding request response model layer on TCP socket, STOMP provides a frame based line format layer on WebSocket to define message semantics;
Common implementation methods
maven dependency is introduced first
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
Simple Websocket
It has nothing to do with Spring and is implemented based on tomcat
The WebSocket specification supports two online data formats -- text and binary. The API supports these two formats and adds the function of processing Java objects and health check messages (ping pong) defined in the specification:
- Text: any text data (java.lang.String, primitives, or their equivalent wrapper classes)
- Binary: by Java nio. Binary data represented by ByteBuffer or byte [] (byte array) (such as audio, image, etc.)
- Java objects: API s can use native (Java object) representations in your code and use custom converters (encoders / decoders) to convert them to compatible online formats (text, binary) allowed by the WebSocket protocol
- Ping-Pong: javax. WebSocket. A pongmessage is an acknowledgement sent by a WebSocket peer in response to a health check (ping) request
Server
Relevant notes are as follows:
- @ServerEndpoint: if it is decorated with * @ ServerEndpoint, the container ensures the availability of the class as a WebSocket * server and listens to a specific URI space
- @ClientEndpoint: a class decorated with this annotation is considered a WebSocket client
- @OnOpen: when a new WebSocket connection is started, the container will call the Java method with * @ OnOpen *
- @OnMessage: a Java method annotated with * @ OnMessage to receive information from the WebSocket * container when the message is sent to the endpoint
- @OnError: call the method with * @ OnError * when there is a communication problem
- @OnClose: used to decorate a Java method that is called by the container when the WebSocket connection is closed
We declare a Java class WebSocket * server endpoint by using the * @ ServerEndpoint annotation. We also specify the URI of the deployment endpoint. The URI is defined relative to the root of the server container and must begin with a forward slash:
Step 1: inject ServerEndpointExporter. This bean will automatically register the Websocket endpoint declared with @ ServerEndpoint annotation.
@Configuration public class WebSocketEndpointConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
Step 2: handle the websocket class and add @ Component
@ServerEndpoint(value = "/chat/{username}") @Component public class ChatEndpoint { private Session session; private static Set<ChatEndpoint> chatEndpoints = new CopyOnWriteArraySet<>(); private static HashMap<String, String> users = new HashMap<>(); @OnOpen public void onOpen( Session session, @PathParam("username") String username) throws IOException { this.session = session; chatEndpoints.add(this); users.put(session.getId(), username); String message = username + "Connected!"; broadcast(message); } @OnMessage public void onMessage(Session session, String message) throws IOException { broadcast(message); } @OnClose public void onClose(Session session) throws IOException { chatEndpoints.remove(this); broadcast(users.get(session.getId()) + "Disconnected!"); } @OnError public void onError(Session session, Throwable throwable) { // Do error handling here } private static void broadcast(String message) { chatEndpoints.forEach(endpoint -> { synchronized (endpoint) { try { endpoint.session.getBasicRemote().sendText(message); } catch (IOException e) { e.printStackTrace(); } } }); } }
client
<script> if ('WebSocket' in window) { var url = 'ws://localhost:8080/chat/laker1'; var sock = new WebSocket(url); //Open WebSocket } else { alert("Your browser does not support WebSocket"); } sock.onopen = function () { //Handle connection opening events console.log('Opening'); sock.send('start'); }; sock.onmessage = function (e) { //process information e = e || event; //Get events. This is written to be compatible with IE browser console.log(e.data); }; sock.onclose = function () { //Handle connection close events console.log('Closing'); }; </script>
Spring WebSocket
Server
Processing class
public class ChatHandler extends AbstractWebSocketHandler { @Override 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); } }
Configure registration
@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(chatHandler(), "/chat").setAllowedOrigins("*"); } @Bean public ChatHandler chatHandler() { return new ChatHandler(); } }
client
<script> if ('WebSocket' in window) { var url = 'ws://localhost:8080/chat'; var sock = new WebSocket(url); //Open WebSocket } else { alert("Your browser does not support WebSocket"); } sock.onopen = function () { //Handle connection opening events console.log('Opening'); sock.send('start'); }; sock.onmessage = function (e) { //process information e = e || event; //Get events. This is written to be compatible with IE browser console.log(e.data); }; sock.onclose = function () { //Handle connection close events console.log('Closing'); }; </script>
SockJS
Server
The code above is websocketconfig Focus on enabling SockJS support on Java withSockJS();
@Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(chatHandler(), "/chat").withSockJS(); }
client
<script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script> var url = 'http://localhost:8080/chat'; // The URL processed by sockjs is http: / / or https: / /, no longer ws: / / and wss:// var sock = new SockJS(url); //var sock = new WebSocket(url); // Open websocket
The operation effect is the same, but the way of client server communication has changed greatly.
Stomp
Server
Configure registration
@Configuration @EnableWebSocketMessageBroker public class WebSocketStompConfig extends AbstractWebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { //Enable SockJS for / chatroom path registry.addEndpoint("/chatroom").setAllowedOrigins("*").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { //This indicates that messages can be sent to clients in the three domains of topic, queue and users. Server -- > client registry.enableSimpleBroker("/topic", "/queue", "/users"); //When the client sends a request to the server, it needs to be prefixed with / app. Client -- > server registry.setApplicationDestinationPrefixes("/app"); //Send a one-to-one message to the specified user with the prefix / users /. registry.setUserDestinationPrefix("/users/"); } }
WebSocketStompConfig overloads the registerstampendpoints() method and registers / chat as a STOMP endpoint.
This path is different from the previous destination path for receiving and sending messages. This is an endpoint that clients connect to before subscribing or publishing messages to the destination.
Websocketstopconfig also configures a simple message broker by overloading the configureMessageBroker() method. This method is optional. If it is not overloaded, a simple memory message agent will be automatically configured to process messages prefixed with "/ topic".
Process STOMP messages from clients
@Controller public class WebSocketCharConroller { //The destination is "/ app/chat". ("/ app" prefix is implied because we configure it as the destination prefix of the application) @MessageMapping("/chat") public void chat(String msg) { System.out.println("Received message:" + msg); } @SubscribeMapping("/subscribe") public String handleSubscribe() { return "i am ok"; } }
@The MessageMapping annotation indicates that the chat () method can handle messages arriving at the specified destination. In this case, the destination is "/ app/chat". ("/ app" prefix is implied because we configure it as the destination prefix of the application)
@SubscribeMapping annotation is similar to @ MessageMapping annotation. When a STOMP subscription message is received, the method with @ SubscribeMapping annotation will be triggered.
client
<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://localhost:8080/chatroom'; var sock = new SockJS(url); //Create a SockJS connection. var stomp = Stomp.over(sock);//Create a STOMP client instance. In fact, SockJS is encapsulated so that the STOMP message can be sent on the WebSocket connection. var payload = JSON.stringify({'message': 'Marco!'}); stomp.connect('guest', 'guest', function (frame) { stomp.send("/app/chat", {}, payload); stomp.subscribe('/app/subscribe', function (message) { }); }); </script>
Frequently asked questions
1. Does JavaScript have native methods that support sending ping/pong messages?
When connecting to the WebSocket, I found that the WebSocket was disconnected shortly after it was connected. In order to maintain the connection for a long time, I thought of the ping/pong protocol.
The browser provides a native WebSocket constructor to create a WebSocket instance. The instance only provides a send method, and the send method can only be used to send the content of Payload Data in the above protocol. The browser will automatically generate a complete frame data according to the send parameters. Therefore, it is impossible to control the frame content other than Payload Data on the browser side, that is, opcode cannot be customized. Therefore, the ping/pong protocol defined in the WebSocket specification cannot be implemented.
For details, see:
-
https://stackoverflow.com/questions/10585355/sending-websocket-ping-pong-frame-from-browser
-
https://segmentfault.com/a/1190000038816821
2.SockJS heartbeat
The SockJS protocol requires the server to send a heartbeat message to prevent the proxy from concluding that the connection is suspended. Spring SockJS is configured with a property called heartbeat time, which you can use to customize the frequency. By default, the heartbeat is sent after 25 seconds. Assuming that no other messages are sent on the connection, this 25 second value meets the following requirements for public Internet applications IETF recommendations.
When using STOMP on WebSocket and SockJS, if the STOMP client and server negotiate to exchange heartbeat, the SockJS heartbeat is disabled.
Spring SockJS support also allows you to configure the TaskScheduler to schedule heartbeat tasks. The task scheduler is supported by the thread pool. Its default setting is based on the number of available processors. You should consider customizing the settings according to your specific needs.
registry.addHandler(chatHandler(), "/chat").setAllowedOrigins("*").withSockJS().setHeartbeatTime(25000) Other configurations .withSockJS() .setStreamBytesLimit(512 * 1024) .setHttpMessageCacheSize(1000) .setDisconnectDelay(30 * 1000);
- Set the streamBytesLimit property to 512KB (the default is 128KB = > 128 * 1024).
- Set the httpMessageCacheSize property to 1000 (the default is 100).
- Set the disconnectDelay property to 30 seconds (the default is 5 seconds = > 5 * 1000).
3. Configure WebSocket engine
Each underlying WebSocket engine exposes configuration properties that control runtime characteristics, such as message buffer size, idle timeout, etc.
@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Bean public ServletServerContainerFactoryBean createWebSocketContainer() { ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); container.setMaxTextMessageBufferSize(8192); container.setMaxBinaryMessageBufferSize(8192); return container; } }
reference resources: