From theory to comprehensive examples, we finally understand the duplex communication protocol websocket!

Time:2021-5-4

My official account:MarkerHub, Java website:https://markerhub.com

For more selected articles, please click:Java notes.md

Read by little hub:

Websocket is a duplex communication protocol, stomp is a simple text protocol, which makes the transmission easier. Socktjs is to solve the problem that the browser does not support websocket protocol and switch to other protocols.

If you don’t understand this article, I can write an article to introduce it to you with git address.


This article takes the principle and implementation of websocket as the core to describe the use of websocket and related application scenarios.

[] (#http and websocket “HTTP and websocket”) HTTP and websocket

As we know, HTTP connection is a request > response, which must be synchronous call mode.
Websocket is a TCP connection after one connection, and the subsequent interaction between client and server is full duplex mode. The client can send messages to the server, and the server can also send messages to the client.

From theory to comprehensive examples, we finally understand the duplex communication protocol websocket!

http,websocket

This figure comes fromStudy, research and implementation of websocket protocolIn case of infringement, please inform and delete.

According to the figure above, we can roughly understand the difference between HTTP and websocket.

[] (# why use websocket “why use websocket”) why use websocket

So after understanding the difference between HTTP and websocket, why do we use websocket? What are his application scenarios?

I found a description that is more consistent with the usage scenario of websocket

“The best fit for WebSocket is in web applications where the client and server need to exchange events at high frequency and with low latency.”
In the interaction between client and server, websocket is most suitable for the exchange and processing of events in the scenario of high frequency and low latency

This passage comes fromOfficial document of spring websocket

After understanding the above knowledge, I give several common scenes:

  1. Data transmission in games
  2. Stock K-line chart data
  3. Customer service system

As mentioned above, isn’t it better for all systems to use websocket?

In fact, it is not. After websocket establishes a connection, the latter interaction is carried out by TCP protocol, so the development complexity will be higher. Of course, websocket communication itself should consider more than HTTP communication

Therefore, if there are no special requirements (that is, the application is not “high frequency and low latency”, priority should be given to whether the HTTP protocol can meet them.

For example, in the news system, the data of news is acceptable from 10 minutes to 30 minutes at night, so you can use HTTP to poll and call the rest interface.

Of course, sometimes we set up websocket communication and want to push it to a client through the rest interface provided by HTTP. At this time, we need to consider the rest interface to accept data and send it to websocket for broadcast communication.

So far, I have described the use scenarios of three interaction modes:

  1. Websocket independent use scenario
  2. HTTP independent usage scenarios
  3. Scenario of using HTTP to transfer websocket

[](#websocket “websocket”)websocket

Websocket is an HTTP handshake, and the subsequent communication is TCP protocol.

Of course, like HTTP, websocket also has some agreed communication modes. HTTP communication mode starts with HTTP, e.ghttp://xxx.com/pathThe websocket communication mode starts with WS, e.g. ws://xxx.com/path

SSL:

  1. HTTP: https://xxx.com/path
  2. WEBSOCKET: wss://xxx.com/path

From theory to comprehensive examples, we finally understand the duplex communication protocol websocket!

Websocket communication

This figure comes fromWebsocket tutorialIn case of infringement, please inform and delete.

[](#SockJS “SockJS”)SockJS

As we all know, although the websocket protocol has been formulated, many versions of browsers or browser manufacturers did not support it well at that time.

Therefore, sockjs can be understood as an alternative to websocket.

How does it define alternatives?

It generally supports the following programs:

  1. Websockets
  2. Streaming
  3. Polling

Of course, after opening and using sockjs, it will give priority to websocket protocol as the transport protocol. If the browser does not support websocket protocol, it will choose a better protocol for communication in other schemes.

Take a look at the current browser support:

From theory to comprehensive examples, we finally understand the duplex communication protocol websocket!

Supported transports, by browser

This figure comes fromgithub: sockjs-client

Therefore, if sockjs is used for communication, it will be consistent in use, and the bottom layer will choose the corresponding protocol by itself.

Sockjs can be considered as the upper protocol of websocket communication layer.

The bottom layer is transparent to developers.

[](#STOMP “STOMP”)STOMP

Stomp: Message Oriented simple text protocol

Websocket defines two types of transmission information: text and binary

Although the type is determined, their transporter is not specified.

Of course, you can write your own transport body to specify the transmission content( Of course, the complexity is very high.)

Therefore, we need to use a simple text transmission type to specify the transmission content, which can be used as the text transmission protocol in communication, that is, the advanced protocol in interaction to define the interaction information.

Stomp itself can support stream type network transport protocols: websocket protocol and TCP protocol

Its format is:

COMMAND
header1:value1
header2:value2

Body^@




SUBSCRIBE
id:sub-1
destination:/topic/price.stock.*

^@



SEND
destination:/queue/trade
content-type:application/json
content-length:44

{"action":"BUY","ticker":"MMM","shares",44}^@

Of course, stomp has been used in many message agents as a transport protocol, such as rabbitmq and ActiveMQ

We can all use stomp to interact with this kind of MQ

In addition to the agent related to stomp, a stomp.js is actually provided for the JS Library of browser client using stomp message protocol.

Let’s use stomp. JS to interact with agents related to stomp protocol

As we know, if the content transfer information of websocket uses stomp to interact, websocket is also good at interacting with the message broker (such as rabbitmq, ActiveMQ)

This provides a good integration solution of message broker.

In conclusion, the advantages of stomp are as follows:

  1. There is no need to build a custom message format
  2. The existing stomp.js client (used in browser) can be used directly
  3. It can route information to the designated message location
  4. You can directly use mature stomp agents for broadcasting, such as rabbitmq and ActiveMQ

[] (# back end technical solution selection “back end technical solution selection”) back end technical solution selection

Websocket server selection: spring websocket

Support sockjs. After opening sockjs, it can deal with the communication support of different browsers
It supports stomp transport protocol, and can seamlessly connect with message broker under stomp protocol (such as rabbitmq, ActiveMQ)

[] (# front end technology scheme selection “front end technology scheme selection”) front end technology scheme selection

Front end selection: stomp.js, sockjs.js

After the support of somp and sockjs is turned on at the back end, the corresponding JS library is supported at the front end
So choose these two libraries

[summary] summary

The logic of the above technology is as follows:

  1. Open socktjs:
    If a browser does not support websocket protocol, you can choose between the other two protocols, but for the application layer, it is the same.
    This is an alternative to support browsers that do not support websocket protocol
  2. Using stomp:
    Using stomp for interaction, the front end can use the stomp.js class library for interaction, and the message is transmitted in the stomp protocol format, which specifies the message transmission format.
    After the message enters the back end, the message can be integrated with the agent that implements stomp format.
    This is for unified message management. Load balancing deployment can be carried out during machine expansion
  3. Using spring websocket:
    Spring websocket is used because it provides not only the transport self protocol of stomp, but also the support of stockjs.
    Of course, in addition to this, spring websocket also provides the function of authority integration, as well as the seamless integration with spring family and other related frameworks.

[] (# application background “application background”) application background

In 2016, I discussed and developed the company’s internal customer service system with my colleagues in the company. Due to the lack of front-end skills and many communication problems, I was unable to debug the front-end to solve the problem myself.

Because the company’s technical architecture system used to be mainly separated from the back end, the front end can’t assist the back-end debugging, and the back end can’t assist the front-end debugging

In addition, websocket is a new protocol for the company, so few people know about it, which leads to many problems in front and back debugging.

Today, a year later, I plan to review the front end and debug the front and back end by myself to explore the problems of joint debugging

Of course, the front end, I just consider the use of stomp. JS and Sockt. JS.

[] (# code stage design “code stage design”) code stage design

[] (# role “role”) role

  1. customer service
  2. customer

[] (# login user status “login user status”) login user status

  1. go online
  2. Offline

[] (# allocation policy “allocation policy”) allocation policy

  1. After logging in, users should be assigned according to their roles

[] (# relationship saving policy “relationship saving policy”) relationship saving policy

  1. We should provide relational saving strategy: consider memory strategy (which can be used for testing), redis strategy
    Note: priority should be given to implementing the inmemory policy for testing, so that the relationship preservation policy has nothing to do with the storage platform

[] (# communication layer design “communication layer design”) communication layer design

  1. Broadcast design of classified topic (communication mode: 1-N)
  2. Single point design of classified queue (communication mode: 1-1)

[] (# code implementation “code implementation”) code implementation

[] (# role – 1 “role”) role

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
public enum Role {
  CUSTOMER_SERVICE,
  CUSTOMER;


  public static boolean isCustomer(User user) {
      Collection<GrantedAuthority> authorities = user.getAuthorities();
      SimpleGrantedAuthority customerGrantedAuthority = new SimpleGrantedAuthority("ROLE_" + Role.CUSTOMER.name());
      return authorities.contains(customerGrantedAuthority);
  }

  public static boolean isCustomerService(User user) {
      Collection<GrantedAuthority> authorities = user.getAuthorities();
      SimpleGrantedAuthority customerServiceGrantedAuthority = new SimpleGrantedAuthority("ROLE_" + Role.CUSTOMER_SERVICE.name());
      return authorities.contains(customerServiceGrantedAuthority);
  }
}

The user object in the code is the security object, that is, org. Springframework. Security. Core. Userdetails. User in spring is the implementation class of userdetails.
In the user object, a lot of basic permission information and user information after user authorization are saved.
As follows:

public interface UserDetails extends Serializable {
  Collection<? extends GrantedAuthority> getAuthorities();

  String getPassword();

  String getUsername();

  boolean isAccountNonExpired();

  boolean isAccountNonLocked();

  boolean isCredentialsNonExpired();

  boolean isEnabled();
}

Methods ᦇ iscustomer and ᦇ iscustomerservice are used to determine whether the user is a customer or a customer service.

[] (# login user status – 1 “login user status”) login user status

public interface StatesManager {

    enum StatesManagerEnum{
        ON_LINE,
        OFF_LINE
    }

    void changeState(User user , StatesManagerEnum statesManagerEnum);

    StatesManagerEnum currentState(User user);

}

When designing login state, there should be a state manager related to login state management, which is only responsible for changing user state and obtaining user state.
This kind of code partition is more helpful to the expansibility of interface oriented programming

[] (# allocation Policy-1 “allocation policy”) allocation policy

public interface DistributionUsers {
  void distribution(User user);
}

The design of assigned role interface only focuses on the incoming user, not the customer service or user. How to do it depends on the specific assignment strategy.

[] (# relationship saving policy – 1 “relationship saving policy”) relationship saving policy

public interface RelationHandler {

  void saveRelation(User customerService,User customer);

  List<User> listCustomers(User customerService);

  void deleteRelation(User customerService,User customer);

  void saveCustomerService(User customerService);

  List<User> listCustomerService();

  User getCustomerService(User customer);

  boolean exist(User user);

  User availableNextCustomerService();

}

The relationship preservation strategy only focuses on the relationship preservation, and does not care which storage medium is saved to.
It doesn’t focus on whether the implementation class is inmemory, redis or mysql.
However, it should be noted that for this relationship preservation strategy, high availability is not involved in the development of testing, and inmemory can be used for testing first.
At the same time, related colleagues will develop other media storage strategies. When testing performance and UAT related tests, they should switch to this media storage strategy and test again.

[] (# user integrated management “user integrated management”) user integrated management

For the implementation strategy of different functions, each function is implemented by itself. In use, we can only program according to the interface.
Therefore, to package all the above functions into a tool class for use, this is the so-called design pattern: facade pattern

@Component
public class UserManagerFacade {
    @Autowired
    private DistributionUsers distributionUsers;
    @Autowired
    private StatesManager statesManager;
    @Autowired
    private RelationHandler relationHandler;


    public void login(User user) {
        if (roleSemanticsMistiness(user)) {
            Throw new sessionauthenticationexception ("unclear role semantics");
        }

        distributionUsers.distribution(user);
        statesManager.changeState(user, StatesManager.StatesManagerEnum.ON_LINE);
    }
    private boolean roleSemanticsMistiness(User user) {
        Collection<GrantedAuthority> authorities = user.getAuthorities();

        SimpleGrantedAuthority customerGrantedAuthority = new SimpleGrantedAuthority("ROLE_"+Role.CUSTOMER.name());
        SimpleGrantedAuthority customerServiceGrantedAuthority = new SimpleGrantedAuthority("ROLE_"+Role.CUSTOMER_SERVICE.name());

        if (authorities.contains(customerGrantedAuthority)
                && authorities.contains(customerServiceGrantedAuthority)) {
            return true;
        }

        return false;
    }

    public void logout(User user){
        statesManager.changeState(user, StatesManager.StatesManagerEnum.OFF_LINE);
    }


    public User getCustomerService(User user){
        return relationHandler.getCustomerService(user);
    }

    public List<User> listCustomers(User user){
        return relationHandler.listCustomers(user);
    }

    public StatesManager.StatesManagerEnum getStates(User user){
        return statesManager.currentState(user);
    }

}

Three functional interfaces are injected into the user manager facade

@Autowired
private DistributionUsers distributionUsers;
@Autowired
private StatesManager statesManager;
@Autowired
private RelationHandler relationHandler;

Available:

  1. Login (#login)
  2. Log out
  3. Get the corresponding customer service (# getcustomerservice)
  4. Get the corresponding user list (# listcustomers)
  5. Current user login status (# getstates)

This design can ensure that the management of user relationship is decided by the user manager facade
For users, other internal operation classes don’t care. For development, the strategies of different functions are transparent.

[] (# communication layer design login, authorization “communication layer design login, authorization”) communication layer design login, authorization

Although spring websocket does not require authorization when connecting, because after connecting, it will distribute the session ID of websocket to the client to distinguish different clients.
But for most applications, after login authorization, websocket connection is the most reasonable. We can assign permissions and manage permissions.

In my simulation example, I used the configuration of spring security’s inmemory

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.inMemoryAuthentication().withUser("admin").password("admin").roles(Role.CUSTOMER_SERVICE.name());
      auth.inMemoryAuthentication().withUser("admin1").password("admin").roles(Role.CUSTOMER_SERVICE.name());


      auth.inMemoryAuthentication().withUser("user").password("user").roles(Role.CUSTOMER.name());
      auth.inMemoryAuthentication().withUser("user1").password("user").roles(Role.CUSTOMER.name());
      auth.inMemoryAuthentication().withUser("user2").password("user").roles(Role.CUSTOMER.name());
      auth.inMemoryAuthentication().withUser("user3").password("user").roles(Role.CUSTOMER.name());
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
      http.csrf().disable()
              .formLogin()
              .and()
              .authorizeRequests()
              .anyRequest()
              .authenticated();
  }
}

It is relatively simple to create 2 customers and 4 ordinary users.
After authentication, the authentication manager will put the authenticated user (the authenticated token) into the header of stomp
In this example, after authentication management authentication, the authentication token is org.springframework.security.authentication.usernamepasswordauthentication token,
After the token is authenticated, it will be put into the header of websocket( This is the security object (Java. Security. Principal) that will be discussed later

[] (# communication layer design websocket configuration “communication layer design websocket configuration”) communication layer design websocket configuration

@Order(Ordered.HIGHEST_PRECEDENCE + 99)
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.setApplicationDestinationPrefixes("/app");
        config.enableSimpleBroker("/topic", "/queue");

    }
}

In this configuration, there are several points to explain:
Among them, the endpoint “portfolio” is used when socktjs makes websocket connection, only for establishing connection.
“/ topic” and “/ queue” are the semantic constraints of stomp. The semantics of topic is 1-N (broadcast mechanism) and queue is 1-1 (single point mechanism)
“App”, which is the end prefix of application level mapping. It’s a bit obscure to say so. It’ll be much clearer if you look at the example later.

[] (# communication layer design – create connection “communication layer design – create connection”) communication layer design – create connection

The endpoint used to connect spring websocket is portfolio, which can be used to connect. Let’s see the specific implementation:

<script></script>
<script></script>
<script></script>

var socket = new SockJS("/portfolio");
stompClient = Stomp.over(socket);

stompClient.connect({}, function(frame) {
   Showgreeting ("login user): + frame. Headers [" user name "];
});

This establishes the connection. Other subsequent operations can be used through the stompclient handle.

[] (# communication layer design spring websocket message model “communication layer design spring websocket message model”) communication layer design spring websocket message model

See model diagram:

From theory to comprehensive examples, we finally understand the duplex communication protocol websocket!

message-flow-simple-broker

Source of this pictureSpring websocket official document

It can be seen that for the same target to be / topic / broadcast, there are two kinds of transmission channels: APP / broadcast and / topic / broadcast

If it is / topic / broadcast, the message body can be directly sent to the target (/ topic / broadcast).

If it is / APP / broadcast, it processes the message in the message handler method. The processed result is sent to the broker channel. Finally, the message body is sent to the target (/ topic / broadcast)

Of course, the app prefix mentioned here is the prefix in websocket configuration

Let’s take an example

Front end subscription:

stompClient.subscribe('/topic/broadcast', function(greeting){
    showGreeting(greeting.body);
});

Back end services:

@Controller
public class ChatWebSocket extends AbstractWebSocket{
 @MessageMapping("broadcast")
 public String broadcast(@Payload @Validated Message message, Principal principal) {
     Return "sender: + principal. Getname() +" content: + message. Tostring();
 }
}

@Data
public class Message {
    @Notnull (message = Title cannot be empty)
    private String title;
    private String content;
}

Front end sending:

function sendBroadcast() {
    stompClient.send("/app/broadcast",{},JSON.stringify({'content':'message content'}));
}

This method sends the message to the back end with @ message mapping annotation, and then after data combination, it is pushed to the front end of subscription / topic / broadcast

function sendBroadcast() {
    stompClient.send("/topic/broadcast",{},JSON.stringify({'content':'message content'}));
}

The message is sent directly to the front end of subscription / topic / broadcast without annotation.

I believe the above understanding has explained the message model diagram of spring websocket clearly

[] (# communication layer design – message mapping “communication layer design – @ message mapping”) communication layer design – @ message mapping

The method under @ controller with this annotation is exactly the processing method of transit data in websocket.
So what data can be obtained by the method under this annotation, and what is the principle?

From theory to comprehensive examples, we finally understand the duplex communication protocol websocket!

Method statement

Let me say something about it
Message,@Payload,@Header,@Headers,MessageHeaders,MessageHeaderAccessor, SimpMessageHeaderAccessor,StompHeaderAccessor
These are the basic object models for getting message headers, message bodies, or the whole message.

@DestinationVariable
This annotation is used to dynamically listen to paths, much like @ pathvariable: in rest

e.g.:

@MessageMapping("/queue/chat/{uid}")
public void chat(@Payload @Validated Message message, @DestinationVariable("uid") String uid, Principal principal) {
    String MSG = sender: + principal. Getname() + "chat";
    simpMessagingTemplate.convertAndSendToUser(uid,"/queue/chat",msg);
}

java.security.Principal

I need to focus on this object.
It is the token object generated after spring security authentication, that is, the usernamepasswordauthenticationtoken in this example

From theory to comprehensive examples, we finally understand the duplex communication protocol websocket!

Class diagram of usernamepasswordauthenticationtoken

It is not difficult to find that usernamepasswordauthenticationtoken is an implementation of principal

The principal can be directly converted into the token after authorization for operation

UsernamePasswordAuthenticationToken user = (UsernamePasswordAuthenticationToken) principal;

As mentioned in the previous design section, the whole user design is to operate on org.springframework.security.core.userdetails.user. How can I get the user object.
It’s very simple, as follows:

UsernamePasswordAuthenticationToken user = (UsernamePasswordAuthenticationToken) principal;
User user = (User) user.getPrincipal()

[] (# communication layer design – 1-1-amp-1-n “communication layer design – 1-1 & & 1-N”) communication layer design – 1-1 & & 1-N

1-n topic:
This method has been described in the above message model section, and will not be repeated here

1-1 queue:
Customer service user communication is a case of 1-1 user interaction

front end:

stompClient.subscribe('/user/queue/chat',function(greeting){
    showGreeting(greeting.body);
});

Back end:

@MessageMapping("/queue/chat/{uid}")
public void chat(@Payload @Validated Message message, @DestinationVariable("uid") String uid, Principal principal) {
    String MSG = sender: + principal. Getname() + "chat";
    simpMessagingTemplate.convertAndSendToUser(uid,"/queue/chat",msg);
}

Sender:

function chat(uid) {
    stompClient.send("/app/queue/chat/"+uid,{},JSON.stringify({'title':'hello','content':'message content'}));
}

It seems that the above transformation is not as smooth as the 1-n broadcast of topic, because the code is developed by convention, which is of course agreed by spring.

The processor for convention conversion is the userdestinationmessagehandler.

The general semantic logic is as follows:

“An application can send messages targeting a specific user, and Spring’s STOMP support recognizes destinations prefixed with “/user/“ for this purpose. For example, a client might subscribe to the destination “/user/queue/position-updates”. This destination will be handled by the UserDestinationMessageHandler and transformed into a destination unique to the user session, e.g. “/queue/position-updates-user123”. This provides the convenience of subscribing to a generically named destination while at the same time ensuring no collisions with other users subscribing to the same destination so that each user can receive unique stock position updates.”

The general meaning is: if the client subscribes to / user / queue / position updates, the user destination message handler will be converted to a subscription address based on the user session, such as / queue / position updates-user123, and then it can communicate.

In the example, we can treat uid as a user’s session, because the user’s 1-1 communication is authorized through spring security, so we can treat the session as a token after authorization
For example, the token of the login user is: usernamepasswordauthenticationtoken newtoken = new usernamepasswordauthenticationtoken (“admin”, “user”);
If the token is legal, the / user / queue / chat subscription is / queue / chat admin

When sending, if / user / admin / queue / chat is used, it will not be pushed directly through @ message mapping.
If it passes / APP / queue / chat / Admin, the message will be processed by @ message mapping annotation and finally sent to / user / admin / queue / chat endpoint

Tracking code: simpmessagingtemplate.convertandsendentouser:

@Override
public void convertAndSendToUser(String user, String destination, Object payload, Map<String, Object> headers,
    MessagePostProcessor postProcessor) throws MessagingException {

  Assert.notNull(user, "User must not be null");
  user = StringUtils.replace(user, "/", "%2F");
  super.convertAndSend(this.destinationPrefix + user + destination, payload, headers, postProcessor);
}

The last path is still / user / admin / queue / chat

[] (# communication layer design – subscribemapping “communication layer design – @ subscribemapping”) communication layer design – @ subscribemapping

@Subscribemapping annotation can complete the function of return upon subscription.
This is very similar to the HTTP request response, but the difference is that the HTTP request and response are synchronous, and each request must be responded.
And @ subscribemapping is asynchronous. It means: when subscribing, it will be processed until the response can respond.

[] (# communication layer design exception handling) communication layer design exception handling

@Message mapping supports JSR 303 verification. It supports @ validated annotation and can throw error exceptions, as follows:

@MessageMapping("broadcast")
public String broadcast(@Payload @Validated Message message, Principal principal) {
    Return "sender: + principal. Getname() +" content: + message. Tostring();
}

How to deal with the exception

@Message exception handler, which can handle exception in message layer

@MessageExceptionHandler
@SendToUser(value = "/queue/error",broadcast = false)
public String handleException(MethodArgumentNotValidException methodArgumentNotValidException) {
    BindingResult bindingResult = methodArgumentNotValidException.getBindingResult();
    if (!bindingResult.hasErrors()) {
        Return "unknown error";
    }
    List<FieldError> allErrors = bindingResult.getFieldErrors();
    Return "JSR 303 error": + allerrors. Iterator(). Next(). Getdefaultmessage();
}

Where @ sendtouser only sends messages to the current user. Of course, the current user needs to subscribe to the address of / user / queue / error.
Broadcast in the annotation indicates that the message is not propagated in multiple sessions (it is possible that a user logs in to three browsers and has three sessions). If this broadcast = false, the message is only propagated to the current session and no other sessions are propagated

In this paper, the principle and protocol of websocket, as well as content related protocols are introduced in detail.

Finally, taking an application scenario as an example, it shows the use of websocket communication function in the project from the aspects of project structure design, code strategy design, design pattern and so on.

How to achieve a certain function is not important, the important thing is to understand the theory, in-depth theory, and then develop.

I hope this article will help you understand websocket.

Recommended reading

Java notes.md

Great! This Java website has all kinds of projects! https://markerhub.com

The up Master of the B station, Java is really good!

Recommended Today

Large scale distributed storage system: Principle Analysis and architecture practice.pdf

Focus on “Java back end technology stack” Reply to “interview” for full interview information Distributed storage system, which stores data in multiple independent devices. Traditional network storage system uses centralized storage server to store all data. Storage server becomes the bottleneck of system performance and the focus of reliability and security, which can not meet […]