Cross platform communication based on websocket -- iPhone/iPad/Mac control raspberry Pie: Springboot backend construction

Train of thought / interface description

The back end is developed using the Springboot framework;

Since cross platform and multi language development is involved, in order to avoid frequent deployment caused by frequent changes to the back-end in the future, the back-end is designed to be simpler, and the data processing is handed over to the terminal and equipment.

The back-end code refers to the article of the big blogger:

Spring boot + websocket to build an online chat room (group chat + single chat)

Interface to send data to the back end

URL: /websocket/{device}
The path parameter device is the name of the device connecting to the server

Parameters:

{
	"type": 1,
	"toPlatform":["MacBook", "WM7"],
	"msgType": "MasterControl",
	"msg": "ABBA ABBA"
}

type: the mode of data transmission. It is a parameter I reserve. It doesn't need to be ignored. I set it to 1;
toPlatform: an array of String class, which is the array of device names to which the information needs to be sent;
msg: json string converted from a data class;
msgType: the name of the original class of msg. The device receiving data uses this name to resolve msg through factory mode.

Back end sending data format

Parameters:

{
	"fromPlatform": "Raspberry Pi",
	"msgType": "MasterControl",
	"msg": "ABBA ABBA"
}

From platfrom: indicates that the data is sent by the device with this name;
msgType and msg are the same as above.

realization

Project creation and other configurations

I use IDEA for development.

Select the Spring Initializr project;

Depending on your own needs, you must choose WebSocket;

I use application.properties for configuration;

Modify the parameters such as server.port, application.name, url and password as needed;
Here, I select the dependency of Mysql Server. A database connection must be configured, otherwise it cannot be deployed; It is also convenient to expand database related functions later.

Import pom.xml into fastjason: (it can be replaced with another JSON parsing library)

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.76</version>
</dependency>

Config file

Because I use websocket dependency and FastJson Library (which can be replaced by other JSON parsing libraries), and these two libraries have painful bug s, two config files need to be configured:

WebSocketConfig.java

// package com.wmiii.wmsocket.config; 	 Change to your own package path

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

FJsonConfig.java

// package com.wmiii.wmsocket.config; 	 Change to your own package path

import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

@Configuration
public class FJsonConfig {

    @Bean
    public HttpMessageConverter configureMessageConverters() {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig config = new FastJsonConfig();
        config.setSerializerFeatures(
                // Leave empty fields
                SerializerFeature.WriteMapNullValue,
                // Convert null of String type to ''
                SerializerFeature.WriteNullStringAsEmpty,
                // Convert null of type Number to 0
                SerializerFeature.WriteNullNumberAsZero,
                // Convert null of List type to []
                SerializerFeature.WriteNullListAsEmpty,
                // Convert null of Boolean type to false
                SerializerFeature.WriteNullBooleanAsFalse,
                // Avoid circular references
                SerializerFeature.DisableCircularReferenceDetect);

        converter.setFastJsonConfig(config);
        converter.setDefaultCharset(Charset.forName("UTF-8"));
        List<MediaType> mediaTypeList = new ArrayList<>();
        // To solve the problem of Chinese garbled code is equivalent to adding an attribute products = "application / JSON" to @ RequestMapping on the Controller
        mediaTypeList.add(MediaType.APPLICATION_JSON);
        converter.setSupportedMediaTypes(mediaTypeList);
        return converter;
    }
}

Parameter class code

(I took the class name randomly

Back end receive information parameter class

BaseMsg.java

// package com.wmiii.wmsocket.msg; 	 Change to your own package path

import lombok.Data;	// Remember to import lombok dependencies
import java.util.ArrayList;

@Data
public class BaseMsg {
    Integer type;       // 1 is the designated sending object, and the rest are tentatively broadcast test
    ArrayList<String> toPlatform;
    String msgType;
    String msg;         // For msg in json format, the back end does not need to care about the specific content
}

Back end sending information parameter class

ToMsgParam.java

// package com.wmiii.wmsocket.param; 	 Change to your own package path
import lombok.Data;

@Data
public class ToMsgParam {
    String fromPlatform;
    String msgType;
    String msg;     // JSON formatted data
}

WebSocket business processing code

// Omit import

@ServerEndpoint(value = "/websocket/{device}")
@Component
public class WmWebSocket {
    
    @OnOpen
    public void onOpen(Session session, @PathParam("device") String device) {

    }
    /**
     * Method called for connection closure
     */
    @OnClose
    public void onClose() {
        
    }
    /**
     * Method of calling after receiving client message
     *
     * @param message Messages sent by the client*/
    @OnMessage
    public void onMessage(String message, Session session, @PathParam("device") String device) {
        
    }
    /**
     * Called when an error occurs
     *te
     */
    @OnError
    public void onError(Session session, Throwable error) {
        
    }
}

For a class with @ ServerEndpoint annotation, it will be regarded as a component to handle the websocket business of the corresponding url; four annotation methods need to be implemented:

Methods with @ OnOpen annotation: called when a new websocket connection is created;

Methods with @ OnClose annotation: called when the websocket connection is disconnected;

Methods with @ OnMessage annotation: called when a message is received;

Methods with @ OnError annotation: called when an error occurs;

finished product

(just look at the notes. I'm too lazy to type otherwise

// Omit import

@ServerEndpoint(value = "/websocket/{device}")
@Component
public class WmWebSocket {
    //It is used to store the WmWebSocket object corresponding to each client.
    private static CopyOnWriteArraySet<WmWebSocket> webSocketSet = new CopyOnWriteArraySet<WmWebSocket>();

    //The connection session with a client needs to send data to the client through it
    private Session session;

    private String device;

    //It is used to record the platform name and bind the session
    private static Map<String,Session> deviceMap = new HashMap<String, Session>();

    @OnOpen
    public void onOpen(Session session, @PathParam("device") String device) {
        this.session = session;
        this.device = device;

        deviceMap.put(device, session);

        webSocketSet.add(this);     // Add to set
        System.out.println("equipment" + device +"join, The current number of devices is" + webSocketSet.size());
        this.session.getAsyncRemote().sendText(device+"Successfully connected to WebSocket(sessionId: "+session.getId()+")-->Current number of online devices: "+webSocketSet.size());
    }
    /**
     * Method called for connection closure
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  // Delete from set
        deviceMap.remove(device);

        System.out.println("equipment" + this.device +"Connection closed! Number of devices currently online: " + webSocketSet.size());
    }
    /**
     * Method of calling after receiving client message
     *
     * @param message Messages sent by the client*/
    @OnMessage
    public void onMessage(String message, Session session, @PathParam("device") String device) {
        System.out.println(device + ": " + message);
        BaseMsg baseMsg;

        try {
            baseMsg = JSON.parseObject(message, BaseMsg.class);

            switch (baseMsg.getType()) {
                case 1:
                    ToMsgParam toMsgParam = new ToMsgParam();
                    toMsgParam.setFromPlatform(device);
                    toMsgParam.setMsgType(baseMsg.getMsgType());
                    toMsgParam.setMsg(baseMsg.getMsg());
                    String toMsg = JSON.toJSONString(toMsgParam);

                    Session fromSession = deviceMap.get(device);
                    Session toSession;
                    // Get data target device list
                    ArrayList<String> toList = baseMsg.getToPlatform();
					// The target device used to store data transmission failure is temporarily useless;
                    ArrayList<String> failed = new ArrayList<>();
                    // Query the session map one by one to send data
                    for(String toPlatform: toList) {
                        toSession = deviceMap.get(toPlatform);
                        try {
                            toSession.getAsyncRemote().sendText(toMsg);
                        } catch (Exception e) {
                        	// If the data transmission of the target platform fails, it will be added to the transmission failure list, which is temporarily useless;
                            failed.add(toPlatform);
                        }
                    }
                    break;

                default:
                    System.out.println("default");
            }

        } catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * Called when an error occurs
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("An error occurred");
        error.printStackTrace();
    }
}

deploy

Use pagoda panel deployment and jar package deployment.
Select Maven on the right side of IDEA and run package under Lifecycle:

Then find a generated. jar file in the target folder under the project root directory and upload it to the ECS;

Remember to open the set project port on the server (mine is 8880).

Run under the path of uploading jar package:

nohup java -jar xxx.jar &

xxx here is the name of your jar package, and the deployment is completed.

test

Recently (when writing this article), postman updated the test of WebSocket interface; click New on the right side of Workspace and select WebSocket Request.


(finally, you don't have to write your own HTML test)

Tags: Spring Boot Back-end websocket

Posted on Mon, 01 Nov 2021 06:32:38 -0400 by hostcord