Introduction to websocket and its application in java

Keywords: Java xml Database Firefox

Introduction to websocket

Scenario: Background management of a mall system, to achieve if there is customer order in the foreground, the background will receive messages, so as to ship processing as soon as possible.

To achieve these functions, there are several alternatives.

Scenario 1. Use ajax short polling, such as requesting the server every 1 minute, letting the server go to the database to query for new unprocessed orders, and then return to the client.

Scenario 2. Long polling, the principle of long polling is similar to the above, but a blocking response method is used, that is, as long as the server does not respond, the request will continue until the server responds.

ajax short polling is like a client making a phone call to the server every once in a while, and the server responds to the client with or without data, hangs up the phone when the response is complete, and then starts over the next week.

Long polling is when the client makes a call and waits until the server responds. If the server does not respond, the request hangs there.

 

Differences:

Option 1: Poll the server at regular intervals. This time is critical. It is too long, the instantaneity is not guaranteed, and it is too short, which may result in a waste of server performance (mainly cpu). Assuming that there is no order within an hour, but the client sends requests every minute uninterrupted, which is a waste.

Scenario 2: Scenario 2 was originally designed to solve the waste caused by blindly and indeterminately sending requests to Solution 1. However, it has its own drawbacks. First, it uses blocking to force connections to be maintained for a long time. For servers, the number of connections that can be processed at the same time (called program concurrency) is limited and long polling takes placeIt is easy for the server to reach the concurrency limit because it does not release connections as quickly as short polling.

Their common drawbacks are:

Each request and response handles data that is really useful. The server and client also exchange a bunch of request headers, response headers, and so on. Information exchange is not efficient.In fact, it can be said that long polling is a pseudo-long connection, but it also needs to follow the rules of http connections: client requests--server responses--to release the connection and exchange some of the same useless information in passing.(Causes bandwidth waste)

The principle of WebSockets is a protocol in html 5 that upgrades long connections to html

1. First, a websocket connection only needs to be established once, and on the first connection, the client and server exchange the necessary information, as shown below.

You can see that the status code returned after a successful websocket connection is 101. At the request header, the connection type passed in is keep_alive, upgrade.This means an upgraded version of keep_alive.First keep_alive falls under the category of the http1.1 protocol.In general, in the http1.0 era, building a connection was a request-response process.In the 1.1 era, keep-alive was added, which allows us to maintain the lifetime of the connection (by setting the keep-alive-timeout parameter on servers such as nginx). The benefit is that you can customize the lifetime of a connection so that it can handle multiple requests instead of a single request.After setting keepalive-timeout, when a request ends, wait so long for keepalive-timeout that if there are no new requests, the connection is closed.

Speaking of http1.1, let's look at a request header and a response header for an HTTP connection.

The difference is that upgrade.A websocket is also a long connection.But because it's an upgrade

Advantage:

1. That is, it only needs to establish a connection, pass the necessary request and response header information once, and then pass the data without exchanging it.Save bandwidth.

2. The websocket is bidirectional, which is the biggest difference between the two other methods. Either ajax or long polling, they send requests through the client and exchange information in the form of server response, in which the server is in a passive role.This problem does not exist with websockets. Once a link to a websocket is established, both the server and the client can extrapolate information from each other.

  

Application cases in java

Scenarios: such as the mall system, divided into client and background, client for customer browsing, order, purchase, background main management of goods, processing orders, shipping, and so on.To achieve the function is, when the client has a customer to place an order, and after the payment is completed, actively push the message to the background, so that the background people know, to handle issues such as shipping.

First, a websocket is a connection between the client (page) and the server, so it is done in two parts, the server code and the client code.

1. First, introduce the following jar packages in pom.xml.

<!-- websocket -->
        <dependency>
            <groupId>org.java-websocket</groupId>
            <artifactId>Java-WebSocket</artifactId>
            <version>1.3.0</version>
        </dependency>

2. A websocket is a connection between a client and a server. When a connection is established, a websocket object is generated that can be used to perform send, receive, and other operations.But this is only a link that exists between the client and the server. In other words, the system can only recognize which page (browser) this websocket connection corresponds to, and which user (user in the database, or no user at all, that is, not logged in, just a visitor) we can't get from this wObtained in the ebsocket object.So we need to create a Map object to associate the websocket object with the actual user object, which paves the way for us to subsequently push messages to specific users.

To do this, we create a WsPool, a class of websocket connection pools used to manage associations between real-world users and websocket objects.The code is as follows.

package com.xdx.websocket;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.java_websocket.WebSocket;

public class WsPool {
    private static final Map<WebSocket, String> wsUserMap = new HashMap<WebSocket, String>();

    /**
     * Get their corresponding users through a websocket connection
     * 
     * @param conn
     * @return
     */
    public static String getUserByWs(WebSocket conn) {
        return wsUserMap.get(conn);
    }

    /**
     * Get the WebSocket from userName, this is a list, here is the first
     * Because it is possible to have multiple WebSockets corresponding to one userName (but generally only one, because in the close method, we remove the invalid websocket connection)
     * 
     * @param user
     */
    public static WebSocket getWsByUser(String userName) {
        Set<WebSocket> keySet = wsUserMap.keySet();
        synchronized (keySet) {
            for (WebSocket conn : keySet) {
                String cuser = wsUserMap.get(conn);
                if (cuser.equals(userName)) {
                    return conn;
                }
            }
        }
        return null;
    }

    /**
     * Add Connection to Connection Pool
     * 
     * @param inbound
     */
    public static void addUser(String userName, WebSocket conn) {
        wsUserMap.put(conn, userName); // add connections
    }

    /**
     * Get all users in the connection pool, as set is not allowed to be duplicated, so you can get an array of users without duplication
     * 
     * @return
     */
    public static Collection<String> getOnlineUser() {
        List<String> setUsers = new ArrayList<String>();
        Collection<String> setUser = wsUserMap.values();
        for (String u : setUser) {
            setUsers.add(u);
        }
        return setUsers;
    }

    /**
     * Remove connections from connection pool
     * 
     * @param inbound
     */
    public static boolean removeUser(WebSocket conn) {
        if (wsUserMap.containsKey(conn)) {
            wsUserMap.remove(conn); // Remove Join
            return true;
        } else {
            return false;
        }
    }

    /**
     * Send data to a specific user
     * 
     * @param user
     * @param message
     */
    public static void sendMessageToUser(WebSocket conn, String message) {
        if (null != conn && null != wsUserMap.get(conn)) {
            conn.send(message);
        }
    }

    /**
     * Send a message to all users
     * 
     * @param message
     */
    public static void sendMessageToAll(String message) {
        Set<WebSocket> keySet = wsUserMap.keySet();
        synchronized (keySet) {
            for (WebSocket conn : keySet) {
                String user = wsUserMap.get(conn);
                if (user != null) {
                    conn.send(message);
                }
            }
        }
    }

}

3. Next, write the main program class for websockets, which manages the lifecycle of websockets.This class inherits from WebSocketServer, which is a class that implements the runnable interface. Its constructor needs to pass in a port, so it needs to specify a port for the websocket service. This class has four overloaded methods, the onOpen() method is called after the connection is created successfully, the onClose method is called after the connection is closed, and the onError method is called when the connection fails (1)The onError is triggered after a general connection error, followed by the onClose method.The onMessage method is triggered when a message from the client is received.Messages from clients can be handled in this method.

package com.xdx.websocket;

import java.net.InetSocketAddress;

import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;

public class WsServer extends WebSocketServer {
    public WsServer(int port) {
        super(new InetSocketAddress(port));
    }

    public WsServer(InetSocketAddress address) {
        super(address);
    }

    @Override
    public void onOpen(WebSocket conn, ClientHandshake handshake) {
        // Code triggered when ws connects, we don't do anything in onOpen

    }

    @Override
    public void onClose(WebSocket conn, int code, String reason, boolean remote) {
        //Trigger code when disconnected
        userLeave(conn);
        System.out.println(reason);
    }

    @Override
    public void onMessage(WebSocket conn, String message) {
        System.out.println(message);
        if(null != message &&message.startsWith("online")){
            String userName=message.replaceFirst("online", message);//User name
            userJoin(conn,userName);//User Join
        }else if(null != message && message.startsWith("offline")){
            userLeave(conn);
        }

    }

    @Override
    public void onError(WebSocket conn, Exception ex) {
        //Code triggered on error
        System.out.println("on error");
        ex.printStackTrace();
    }
    /**
     * Remove broken websocket links
     * @param conn
     */
    private void userLeave(WebSocket conn){
        WsPool.removeUser(conn);
    }
    /**
     * Add websocket to user pool
     * @param conn
     * @param userName
     */
    private void userJoin(WebSocket conn,String userName){
        WsPool.addUser(userName, conn);
    }

}

In the onMessage() method above, a message is received from the client, and the corresponding websocket connection from the client is also passed along as a parameter. The information carried in the message is used to determine what the message corresponds to. If it starts with online, it means that it is an online message, so the websocket and its correspondingThe userName of the websocket is stored in the ws connection pool. If it starts with offline, the websocket is disconnected. It is not necessary to maintain the map key-value pair of the websocket, just remove it.

4. How to open this socket on the server side.As mentioned above, the parent WebSocketServer of WsServer implements a runnable method, so we need to run this WsServer in a thread. In fact, WebSocketServer has a start() method with the following source code.

 

    public void start() {
        if( selectorthread != null )
            throw new IllegalStateException( getClass().getName() + " can only be started once." );
        new Thread( this ).start();;
    }

 

Clearly, it opens a thread.So we can start a websocket thread in the following way.(This method is only for normal java projects, if a web project needs to run the websocket thread at project startup, as described in point 7 below)

    public static void main(String args[]){
        WebSocketImpl.DEBUG = false;
        int port = 8887; // port
        WsServer s = new WsServer(port);
        s.start();
    }

When you run this main method, the server for the websocket is turned on.

So far, how do we test that we have a websocket server?There is a free test tool available online.The test address is as follows: http://www.blue-zero.com/WebSocket/

Click in and write down our websocket service address.Click Connect.As shown in the diagram.

 

If the connection is successful, he will show "The connection is established, waiting for data..."

Let's type onlinexdx into the text box and click Enter to try.

 

This simulates the client sending an onlinexdx request to the server, which triggers the onMessage method on the server to see the server console.In addition to the onlinexdx message you'd like to see, there are also @heart that triggered this method.

@heart is a heart detection package sent from this free page to keep the websocket connected.

5. Next we will finish the client part.This is accomplished by performing a websocket connection when the background user logs in and enters the background home page (similar to clicking the Connection button on the free page in the previous step), then sending the message "online+userName" to the server to trigger the server's onMessage method, which allows the userName to be added to the connection pool.We encapsulate these codes in js.

 

var websocket = '';
var ajaxPageNum = 1;
var last_health;
var health_timeout = 10;
var tDates = [], tData = [];
var rightIndex;
if ($('body').attr('userName') != '' && $('body').attr('ws') == 'yes') {
    var userName = $('body').attr('userName');
    if (window.WebSocket) {
        websocket = new WebSocket(
                encodeURI('ws://' + document.domain + ':8887'));
        websocket.onopen = function() {
            console.log('Connected');
            websocket.send("online"+userName);
            heartbeat_timer = setInterval(function() {
                keepalive(websocket)
            }, 60000);
        };
        websocket.onerror = function() {
            console.log('Connection error');
        };
        websocket.onclose = function() {
            console.log('Disconnected');
            initWs();
        };
        // messages receiving
        websocket.onmessage = function(message) {
            console.log(message)
            showNotice("New Order", "You have a new futures order, please handle it in time!")
        };
    } else {
        alert("The browser does not support placing reminders.<br/>Higher versions of browsers are recommended.<br/>as IE10,Firefox, Google, Sogou, etc.");
    }

}
var initWs = function() {
    if (window.WebSocket) {
        websocket = new WebSocket(
                encodeURI('ws://' + document.domain + ':8887'));
        websocket.onopen = function() {
            console.log('Connected');
            websocket.send("online"+userName);
            heartbeat_timer = setInterval(function() {
                keepalive(websocket)
            }, 60000);
        };
        websocket.onerror = function() {
            console.log('Connection error');
        };
        websocket.onclose = function() {
            console.log('Disconnected');
            initWs();
        };
        // messages receiving
        websocket.onmessage = function(message) {
            console.log(message)
            showNotice("New Order", "You have a new futures order, please handle it in time!")
        };
    } else {
        alert("The browser does not support placing reminders.<br/>Higher versions of browsers are recommended.<br/>as IE10,Firefox, Google, Sogou, etc.");
    }
}
var vadioTimeOut;
function showNotice(title, content) {
    if (!title && !content) {
        title = "New Order";
        content = "You have a new order,Please handle it in time!";
    }
    var iconUrl = "http://www.wonyen.com/favicon.ico";
    $("#myaudio")[0].play();//message play voice
    var playTime = 1;
    var audio = document.createElement("myaudio");
    clearTimeout(vadioTimeOut);
    audio.addEventListener('ended', function() {
        vadioTimeOut = setTimeout(function() {
            playTime = playTime + 1;
            playTime < 3 ? audio.play() : clearTimeout(vadioTimeOut);
        }, 500);
    })
    if (Notification.permission == "granted") {
        var notification = new Notification(title, {
            body : content,
            icon : iconUrl
        });

        notification.onclick = function() {
            notification.close();
        };
    }

}

// Heartbeat
function keepalive(ws) {
    var time = new Date();
    if (last_health != -1 && (time.getTime() - last_health > health_timeout)) {

        // ws.close();
    } else {
        if (ws.bufferedAmount == 0) {
            ws.send('~HC~');
        }
    }
}

The main code of the page is as follows.The first is to introduce the above js. This JS must be placed at the end of the page because it needs to load the page to get the attr of the body.

<!-- websocket -->
<script src="./static/js/OtherJs/ws.js" type="text/javascript"></script>

Next, add the userName and ws attributes to the body of the page.Passed into js as a parameter.You also need to add voice attachments.

<body  userName=${adminName} ws="yes">
<!-- Message Tip Sound -->
   <audio id="myaudio" src="./static/new_order.wav"></audio>

The js code above clearly shows the life cycle of the websocket on the page. It is important to note that in the onopen() method, we first send online+userName to the server for online processing, and then start calling the heartbeat package to avoid the websocket being idle for a long time and failing.

In the onmessage method, messages pushed by the server are processed to alert the client with voice and pop-up windows.By encapsulating the message parameter, the server can turn it into a json object and give the json object an msgType attribute, so that different front-end codes can be executed depending on the msgType, such as msgType=newOrder for new orders, code for arrival of new orders, msgType=newUser for new user registrations, and then new user registrations are executed.Code.I don't make a distinction here, because I only send messages about user purchase orders on the server, so I execute showNotice("New Order", "You have new futures orders, please handle them in time!") This method.

6. Finally, write a way to send messages from the service side to the client side.Once an order arrives, we send a message to all background users.We can simulate this action by writing a controller method that calls the sendMessageToAll method of WsPool.

@ResponseBody
    @RequestMapping("sendWs")
    public String sendWs(String message) {
        WsPool.sendMessageToAll(message);
        return message;
    }

7. If you are a web project, you also need to open the websocket server-side thread at project startup. You can put the startup action in a filter and configure the filter in web.xml to run at project startup.

package com.xdx.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.java_websocket.WebSocketImpl;

import com.xdx.websocket.WsServer;


public class StartFilter implements Filter {

    public void destroy() {

    }

    public void doFilter(ServletRequest arg0, ServletResponse arg1,
            FilterChain arg2) throws IOException, ServletException {

    }

    public void init(FilterConfig arg0) throws ServletException {
        this.startWebsocketInstantMsg();
    }

    /**
     * Start the instant chat service
     */
    public void startWebsocketInstantMsg() {
        WebSocketImpl.DEBUG = false;
        WsServer s;
        s = new WsServer(8887);
        s.start();
    }
}

Configure in web.xml.

<!-- filter -->
    <filter>
        <filter-name>startFilter</filter-name>
        <filter-class>com.xdx.filter.StartFilter</filter-class>
    </filter>

So far, all the code has been completed.

8. Test, run the project first.Then when you log in, go to the background home page and you can see that you have successfully connected.

SendWs is then executed by sending a message from the server to the background.Enter http://192.168.1.185:8080/warrior/sendWs in the browser? Message=xxx

The voice plays and a prompt pops up.It proved successful.

The above is a simple application of websocket, on which you can do many extensions, such as chat rooms, real-time stock price display, and so on.

Posted by dark_mirage on Fri, 10 May 2019 10:33:04 -0700