preface

We know thatHTTPThe protocol is stateless and connectionless, and adopts the request/response mode. The communication request can only be initiated by the client and responded by the server. This request/response mode is very weak when the client server needs continuous interaction. Before HMTL5 came out, most of the client server continuous interaction is achieved through AJAX polling, but polling efficiency is low, wasting bandwidth and server resources. So WebSocket was invented. WebSocket is a protocol provided by HTML5 for full duplex communication over a single TCP connection. Next I use Spring and WebSocket to achieve a simple chat function, I hope to be helpful to you.

Project directory

Adding a dependency package

pom.xml

<? xml version="1.0" encoding="UTF-8"? > <project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> < modelVersion > 4.0.0 < / modelVersion > < groupId > groupId < / groupId > < artifactId > nChat < / artifactId > The < packaging > pom < / packaging > < version > 1.0 - the SNAPSHOT < / version > < dependencies > <! --Sping core dependencies --> <! -- https://mvnrepository.com/artifact/org.springframework/spring-core --> <dependency> < the groupId > org. Springframework < / groupId > < artifactId > spring - the core < / artifactId > < version > 5.1.3. RELEASE < / version > </dependency> <! -- https://mvnrepository.com/artifact/org.springframework/spring-web --> <dependency> < the groupId > org. Springframework < / groupId > < artifactId > spring - web < / artifactId > < version > 5.1.3. RELEASE < / version > </dependency> <! -- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --> <dependency> < the groupId > org. Springframework < / groupId > < artifactId > spring - JDBC < / artifactId > < version > 5.1.3. RELEASE < / version > </dependency> <! -- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> < the groupId > org. Springframework < / groupId > < artifactId > spring - webmvc < / artifactId > < version > 5.1.3. RELEASE < / version > </dependency> <! -- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> < the groupId > org. Springframework < / groupId > < artifactId > spring - the context < / artifactId > < version > 5.1.3. RELEASE < / version > </dependency> <! -- https://mvnrepository.com/artifact/org.springframework/spring-context-support --> <dependency> < the groupId > org. Springframework < / groupId > < artifactId > spring - the context - support < / artifactId > < version > 5.1.3. RELEASE < / version > </dependency> <! -- https://mvnrepository.com/artifact/org.springframework/spring-aop --> <dependency> < the groupId > org. Springframework < / groupId > < artifactId > spring aop - < / artifactId > < version > 5.1.3. RELEASE < / version > </dependency> <! -- https://mvnrepository.com/artifact/org.springframework/spring-test --> <dependency> < the groupId > org. Springframework < / groupId > < artifactId > spring - test < / artifactId > < version > 5.1.3. RELEASE < / version > < scope >test</scope> </dependency> <! --Mybatis dependency --> <! -- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> The < artifactId > mybatis < / artifactId > < version > 3.4.6 < / version > < / dependency > <! -- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring --> <dependency> <groupId>org.mybatis</groupId> < artifactId > mybatis - spring < / artifactId > < version > 1.3.2 < / version > < / dependency > <! -- https://mvnrepository.com/artifact/org.springframework/spring-messaging --> <dependency> < the groupId > org. Springframework < / groupId > < artifactId > spring - messaging < / artifactId > < version > 5.1.3. RELEASE < / version > </dependency> <! -- https://mvnrepository.com/artifact/org.springframework/spring-websocket --> <dependency> < the groupId > org. Springframework < / groupId > < artifactId > spring - websocket < / artifactId > < version > 5.1.2. RELEASE < / version > </dependency> <! --MySQL connection driver --> <! -- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> < artifactId > mysql connector - Java < / artifactId > < version > 8.0.13 < / version > < / dependency > <! -- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> <dependency> <groupId>javax.servlet</groupId> < artifactId > javax.mail. The servlet API - < / artifactId > < version > 4.0.1 < / version > < scope > provided < / scope > < / dependency > </dependencies> </project>Copy the code

WebSocket implementation

Java implements WebSocket in many ways, and different vendors implement WebSocket in the same way.

Spring implemented WebSocket

Spring implements WebSocket by adding Spring’s dependency on WebSocket support

<! -- https://mvnrepository.com/artifact/org.springframework/spring-messaging --> <dependency> < the groupId > org. Springframework < / groupId > < artifactId > spring - messaging < / artifactId > < version > 5.1.3. RELEASE < / version > </dependency> <! -- https://mvnrepository.com/artifact/org.springframework/spring-websocket --> <dependency> < the groupId > org. Springframework < / groupId > < artifactId > spring - websocket < / artifactId > < version > 5.1.2. RELEASE < / version > </dependency>Copy the code

In Java import Spring WebSocket package import org. Springframework. Web. Socket. *; Spring implementation of WebSocket needs to write the following items.

  1. Configuring WebSocket There are two ways to configure WebSocket, one is to write a configuration class, the other is to write a configuration file (XML file), the function of configuring WebSocket is to add WebSocket processor, interceptor to the registry, here I use the configuration class to configure. WebSocketConfig.java
package com.nChat.websocket; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; @Configuration @EnableWebMvc @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { public void RegisterWebSocketHandlers (WebSocketHandlerRegistry WebSocketHandlerRegistry) {/ / this is used for establishing the communication connection webSocketHandlerRegistry .addHandler(new WebSocketHandler(),"/ws/socketServer")
                .addInterceptors(new WebSocketInterceptor())
                .setAllowedOrigins("*"); }}Copy the code

The purpose of the three annotations is as follows

  • @Configuration Annotation: Declare this class to configure the Spring container application context for the Configuration class (equivalent to the web.xml Configuration file) that will be loaded when the project starts.
  • @enableWebMVC note: If Spring MVC is enabled, without this, the RequestMapping in Controller is disabled, I don’t know why.
  • @enablewebsocket note: EnableWebSocket service.registerWebSocketHandlersMethod configures the WebSocket entry, domains to allow access, registers WebSocket handlers, interceptors, and so on when requesting access/ws/socketServer“, the WebSocket connection will be established.
  1. Write the processor webSockethandler.java
package com.nChat.websocket;

import org.springframework.stereotype.Service;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;


@Service
public class WebSocketHandler extends TextWebSocketHandler {

    public static final Map<Integer,WebSocketSession> USER_SOCKET_SESSION_MAP;
    static{
            USER_SOCKET_SESSION_MAP = new HashMap<Integer, WebSocketSession>();
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        int uid = Integer.parseInt(session.getAttributes().get("WEBSOCKET_UID").toString()); // Save the session in USER_SOCKET_SESSION_MAP if it is a new user connectionif(USER_SOCKET_SESSION_MAP.get(uid) == null || ! USER_SOCKET_SESSION_MAP.get(uid).isOpen()) { USER_SOCKET_SESSION_MAP.put(uid, session); } super.afterConnectionEstablished(session); } @Override public void handleMessage(WebSocketSession session, WebSocketMessage<? > message) throws Exception { super.handleMessage(session, message); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { super.handleTextMessage(session, message); } @Override protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception { super.handlePongMessage(session, message); } @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { super.handleTransportError(session, exception); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { super.afterConnectionClosed(session, status); } @Override public booleansupportsPartialMessages() {
        returnsuper.supportsPartialMessages(); } /** * @description: sends a message to the specified user * @param: [uid, message] * @return: void
     * @author: Xue 8
     * @date: 2019/1/19
     */
    public void sendMessageToUser(int uid, TextMessage message){
        WebSocketSession session = USER_SOCKET_SESSION_MAP.get(uid);
        if(session ! = null && session.isOpen()) { try { session.sendMessage(message); } catch (IOException e) { e.printStackTrace(); }}}}Copy the code

The WebSocket processor inherits TextWebSocketHandler (or BinaryWebSocketHandler), where it overwrites the corresponding methods and writes its own business code. When Spring receives a WebSocket event, it calls the corresponding method. Here I have a custom method sendMessageToUser that sends a message to the specified user. A WebSocketSession is an abstraction of a WebSocket. A WebSocketSession is like a dedicated channel between a server and a client. One WebSocketSession corresponds to one user. WebSocket operations are based on this WebSocketSession.

  1. Write the interceptor WebSocketInterceptor. Java
package com.nChat.websocket;

import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

import java.util.Collection;
import java.util.Map;

public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor {
    public WebSocketInterceptor() {
        super();
    }

    public WebSocketInterceptor(Collection<String> attributeNames) {
        super(attributeNames);
    }

    @Override
    public Collection<String> getAttributeNames() {
        return super.getAttributeNames();
    }

    @Override
    public void setCopyAllAttributes(boolean copyAllAttributes) {
        super.setCopyAllAttributes(copyAllAttributes);
    }

    @Override
    public boolean isCopyAllAttributes() {
        return super.isCopyAllAttributes();
    }

    @Override
    public void setCopyHttpSessionId(boolean copyHttpSessionId) {
        super.setCopyHttpSessionId(copyHttpSessionId);
    }

    @Override
    public boolean isCopyHttpSessionId() {
        return super.isCopyHttpSessionId();
    }

    @Override
    public void setCreateSession(boolean createSession) {
        super.setCreateSession(createSession);
    }

    @Override
    public boolean isCreateSession() {
        return super.isCreateSession();
    }

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request;
        int uid = Integer.parseInt(servletServerHttpRequest.getServletRequest().getParameter("uid"));
        System.out.println("coming " + uid);
        if(uid ! = 0) {// Intercepting the request here saves the UID to the WebSocketSession before holding the hand and lets the handler WebSocketHandler operate on the uid attributes. Put ("WEBSOCKET_UID", uid);
        }
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
        System.out.println("out"); super.afterHandshake(request, response, wsHandler, ex); }}Copy the code

WebSocket interceptor inheritance HttpSessionHandshakeInterceptor, before and after the handshake to intercept the request, the request before shaking hands interception, namely when requesting access/ws/socketServer will request to intercept, You can retrieve URL parameters, request headers, protocols, and other information in a request, and then store this information in a WebSocketSession to associate the user with the WebSocketSession.

  1. Write Spring MVC controller indexController.java
package com.nChat.controller;

import com.nChat.websocket.WebSocketHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.socket.TextMessage;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@Controller
public class IndexController {

    @Autowired
    WebSocketHandler webSocketHandler;

    @RequestMapping("/send")
    public String send(HttpServletRequest request,
                        HttpServletResponse response){
        return "send";
    }

    @RequestMapping("/doSend")
    public String doSend(HttpServletRequest request,
                       HttpServletResponse response,
                       @RequestParam(value = "uid") int uid,
                       @RequestParam(value = "messages") String messages){

        HttpSession session = request.getSession(true);
        session.setAttribute("SESSION_USERNAME", uid);
        webSocketHandler.sendMessageToUser(uid,new TextMessage(messages));
        return "send";
    }

    @RequestMapping("/register")
    public String register(HttpServletRequest request,
                        HttpServletResponse response){

        return "register"; }}Copy the code

Note that @requestMapping is different from/WS /socketServer in the WebSocket configuration class, where/WS /socketServer is used to establish WebSocket connections between clients and servers. The Controller @requestMapping is used to handle client requests.

  1. Write front-end test WebSocket establishment and send information to create a new WebSocket connection, which uses UID to represent the WebSocket connection register.jsp
<%@ page language="java" contentType="text/html; charset=utf-8"
         pageEncoding="utf-8"% > <! DOCTYPE html PUBLIC"- / / / / W3C DTD HTML 4.01 Transitional / / EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>register</title>
</head>
<body>
<script type="text/javascript" src="http://cdn.bootcss.com/jquery/3.1.0/jquery.min.js"></script>
<script type="text/javascript" src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/reconnecting-websocket/1.0.0/reconnecting-websocket.js"></script>
<script type="text/javascript">
    var websocket = null;
    function createWebSocket() {
        if ('WebSocket' in window) {
            websocket = new WebSocket("ws://localhost:8080/ws/socketServer? uid=" + $("#uid").val());
            console.log($("#uid").val())
        }
        else if ('MozWebSocket' in window) {
            websocket = new MozWebSocket("ws://localhost:8080/ws/socketServer? uid=" + $("#uid").val());
        }
        else {
            websocket = new SockJS("http://localhost:8080/ws/socketServer? uid=" + $("#uid").val());
        }

        websocket.onopen = onOpen;
        websocket.onmessage = onMessage;
        websocket.onerror = onError;
        websocket.onclose = onClose;

        function onOpen(openEvt) {
            //alert(openEvt.Data);
        }

        function onMessage(evt) {
            alert(evt.data);
        }
        function onError() {}function onClose() {

        }

        window.close=function() { websocket.onclose(); }} </script> Please enter UID: <input rows="5" cols="10" id="uid" name="uid"></input>
<button onclick="createWebSocket();"</button> </body> </ HTML >Copy the code

Send message page, send message according to UID send.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
         pageEncoding="utf-8"% > <! DOCTYPE html PUBLIC"- / / / / W3C DTD HTML 4.01 Transitional / / EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<body>
<h2>send messages</h2>
<body>

<form action="/doSend"> To whom: <inputtype="text" name="uid"/> What message is sent: <inputtype="text" name="messages"/>
  <input type="submit" value="Send"/>
</form>

</body>
</body>
</html>
Copy the code
  1. Run the tests to establish WebSocket connections with UID 1 and UID 2 respectively.

Send a message to the WebSocket with UID 1

Send a message to the WebSocket with UID 2

Tomcat implementation WebSocket

Implementing WebSocket using Tomcat is not as cumbersome as implementing WebSocket in Spring, requiring only one processor to be written. First add dependencies

<! -- https://mvnrepository.com/artifact/javax.websocket/javax.websocket-api --> <dependency> < the groupId > javax.mail. Websocket < / groupId > < artifactId > javax.mail. Websocket API - < / artifactId > < version > 1.1 < / version > <scope>provided</scope> </dependency>Copy the code

Then write the processing class


package com.nChat;
  
import java.io.IOException;  
import java.util.Map;  
import java.util.concurrent.ConcurrentHashMap;  
import javax.websocket.*;  
import javax.websocket.server.PathParam;  
import javax.websocket.server.ServerEndpoint;  
import net.sf.json.JSONObject;  
  
@ServerEndpoint("/websocket/{username}")  
public class WebSocket {  
  
    private static int onlineCount = 0;  
    private static Map<String, WebSocket> clients = new ConcurrentHashMap<String, WebSocket>();  
    private Session session;  
    private String username;  
      
    @OnOpen  
    public void onOpen(@PathParam("username") String username, Session session) throws IOException {  
  
        this.username = username;  
        this.session = session;  
          
        addOnlineCount();  
        clients.put(username, this);  
        System.out.println("Connected");  
    }  
  
    @OnClose  
    public void onClose() throws IOException {  
        clients.remove(username);  
        subOnlineCount();  
    }  
  
    @OnMessage  
    public void onMessage(String message) throws IOException {  
  
        JSONObject jsonTo = JSONObject.fromObject(message);  
          
        if(! jsonTo.get("To").equals("All")){  
            sendMessageTo("For one person.", jsonTo.get("To").toString());  
        }else{  
            sendMessageAll("For all.");  
        }  
    }  
  
    @OnError  
    public void onError(Session session, Throwable error) {  
        error.printStackTrace();  
    }  
  
    public void sendMessageTo(String message, String To) throws IOException {  
        // session.getBasicRemote().sendText(message);  
        //session.getAsyncRemote().sendText(message);  
        for (WebSocket item : clients.values()) {  
            if (item.username.equals(To) )  
                item.session.getAsyncRemote().sendText(message);  
        }  
    }  
      
    public void sendMessageAll(String message) throws IOException {  
        for (WebSocket item : clients.values()) {  
            item.session.getAsyncRemote().sendText(message);  
        }  
    }  
      
      
  
    public static synchronized int getOnlineCount() {  
        return onlineCount;  
    }  
  
    public static synchronized void addOnlineCount() {  
        WebSocket.onlineCount++;  
    }  
  
    public static synchronized void subOnlineCount() {  
        WebSocket.onlineCount--;  
    }  
  
    public static synchronized Map<String, WebSocket> getClients() {  
        returnclients; }}Copy the code

Jetty implementation WebSocket

This doesn’t seem common… Here will not demonstrate how to configure, interested in online search related articles.

conclusion

WebSocket is a full-duplex communication protocol provided by HTML5 in a single TCP connection. Manufacturers can implement their own WebSocket framework according to the WebSocket API, such as Spring WebSocket and Tomcat WebSocket. I think the relationship between WebSocket and Spring WebSocket and Tomcat WebSocket is the same as the relationship between JPA and Hibernate and Mybatis. Both WebSocket and JPA have defined standards, and each manufacturer implements its own framework according to this standard.

Complete source code: github.com/xue8/Java-D…

Original address: ddnd.cn/2019/01/19/…