SpringBoot(23) integrates socket.io server and client for communication

Posted by pixelsoul on Fri, 07 Feb 2020 18:03:22 +0100

1. Preface

What is the difference between websocket and socket.io?
websocket
  1. A technology for two-way real-time communication between client and server
  2. While mainstream browsers already support it, there may be incompatibilities when using it
  3. Suitable for client and node-based server use
socket.io
  1. Encapsulate WebSocket, AJAX, and other communication methods into a unified communication interface
  2. When using, do not worry about compatibility issues, the bottom layer will automatically choose the best way to communicate
  3. Suitable for both client and server data communication

The description of socket.io on w3cschool is as follows:

This article will implement
  1. Integrated netty-socket IO based on springboot2.1.8.RELEASE: socket.io server implemented by imitating node.js
  2. Integrated socket.io-client:socket.io client
  3. Implement communication between server and client

2. Java integrated socket.io server

1. Import required dependencies into pom.xml

Warm Tip: In order to facilitate the future needs of the client socket.io-client dependency together directly introduced oh~

<!-- netty-socketio:  Imitate`node.js`Realized socket.io Server -->
<dependency>
    <groupId>com.corundumstudio.socketio</groupId>
    <artifactId>netty-socketio</artifactId>
    <version>1.7.7</version>
</dependency>
<!-- socket.io Client -->
<dependency>
    <groupId>io.socket</groupId>
    <artifactId>socket.io-client</artifactId>
    <version>1.0.0</version>
</dependency>

2. Configure socket.io server in application.yml

# netty-socketio configuration
socketio:
  host: 127.0.0.1
  port: 8888
  # Set the maximum length of processing data per frame to prevent others from using large data to attack the server
  maxFramePayloadLength: 1048576
  # Set maximum content length for http interaction
  maxHttpContentLength: 1048576
  # Size of socket connections (e.g., listening on only one port box thread group is 1)
  bossCount: 1
  workCount: 100
  allowCustomRequests: true
  # Protocol upgrade timeout (milliseconds), default 10 seconds.HTTP handshake upgrade to ws protocol timeout
  upgradeTimeout: 1000000
  # Ping message timeout (milliseconds), default 60 seconds, within which a timeout event is sent if no heartbeat message is received
  pingTimeout: 6000000
  # Ping message interval (milliseconds), default 25 seconds.Client sends a heartbeat message interval to server
  pingInterval: 25000

3. socket.io Service-side Configuration Class

@Configuration
public class SocketIOConfig {

    @Value("${socketio.host}")
    private String host;

    @Value("${socketio.port}")
    private Integer port;

    @Value("${socketio.bossCount}")
    private int bossCount;

    @Value("${socketio.workCount}")
    private int workCount;

    @Value("${socketio.allowCustomRequests}")
    private boolean allowCustomRequests;

    @Value("${socketio.upgradeTimeout}")
    private int upgradeTimeout;

    @Value("${socketio.pingTimeout}")
    private int pingTimeout;

    @Value("${socketio.pingInterval}")
    private int pingInterval;

    @Bean
    public SocketIOServer socketIOServer() {
        SocketConfig socketConfig = new SocketConfig();
        socketConfig.setTcpNoDelay(true);
        socketConfig.setSoLinger(0);
        com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
        config.setSocketConfig(socketConfig);
        config.setHostname(host);
        config.setPort(port);
        config.setBossThreads(bossCount);
        config.setWorkerThreads(workCount);
        config.setAllowCustomRequests(allowCustomRequests);
        config.setUpgradeTimeout(upgradeTimeout);
        config.setPingTimeout(pingTimeout);
        config.setPingInterval(pingInterval);
        return new SocketIOServer(config);
    }

}

4. socket.io Server-side Service Layer

Service Class

public interface ISocketIOService {
    /**
     * Start Services
     */
    void start();

    /**
     * Out of Service
     */
    void stop();

    /**
     * Push information to specified client
     *
     * @param userId:     Client Unique Identification
     * @param msgContent: Message Content
     */
    void pushMessageToUser(String userId, String msgContent);
}

Service implementation class:

@Slf4j
@Service(value = "socketIOService")
public class SocketIOServiceImpl implements ISocketIOService {

    /**
     * Store connected clients
     */
    private static Map<String, SocketIOClient> clientMap = new ConcurrentHashMap<>();

    /**
     * Custom Event`push_data_event` for service side to client communication
     */
    private static final String PUSH_DATA_EVENT = "push_data_event";

    @Autowired
    private SocketIOServer socketIOServer;

    /**
     * Spring IoC After the container is created, start after loading the SocketIOServiceImpl Bean
     */
    @PostConstruct
    private void autoStartup() {
        start();
    }

    /**
     * Spring IoC Container closes before destroying SocketIOServiceImpl Bean to avoid restarting project service port occupancy
     */
    @PreDestroy
    private void autoStop() {
        stop();
    }

    @Override
    public void start() {
        // Listen for client connections
        socketIOServer.addConnectListener(client -> {
            log.debug("************ Client: " + getIpByClient(client) + " Connected ************");
            // Custom Events `connected` -> communicate with clients (built-in events such as Socket.EVENT_CONNECT can also be used)
            client.sendEvent("connected", "You're connected successfully...");
            String userId = getParamsByClient(client);
            if (userId != null) {
                clientMap.put(userId, client);
            }
        });

        // Listening Client Disconnect
        socketIOServer.addDisconnectListener(client -> {
            String clientIp = getIpByClient(client);
            log.debug(clientIp + " *********************** " + "Client disconnected");
            String userId = getParamsByClient(client);
            if (userId != null) {
                clientMap.remove(userId);
                client.disconnect();
            }
        });

        // Custom Event`client_info_event` ->Listen for client messages
        socketIOServer.addEventListener(PUSH_DATA_EVENT, String.class, (client, data, ackSender) -> {
            // When a client pushes a `client_info_event` event, onData accepts data, which is json data of type string here and can be Byte[], other types of object
            String clientIp = getIpByClient(client);
            log.debug(clientIp + " ************ Client:" + data);
        });

        // Start Services
        socketIOServer.start();

        // Broadcast: The default is to broadcast to all socket connections, but not to the sender himself, who needs to be sent separately if he intends to receive the message himself.
        new Thread(() -> {
            int i = 0;
            while (true) {
                try {
                    // Send broadcast message every 3 seconds
                    Thread.sleep(3000);
                    socketIOServer.getBroadcastOperations().sendEvent("myBroadcast", "Broadcast message " + DateUtil.now());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    @Override
    public void stop() {
        if (socketIOServer != null) {
            socketIOServer.stop();
            socketIOServer = null;
        }
    }

    @Override
    public void pushMessageToUser(String userId, String msgContent) {
        SocketIOClient client = clientMap.get(userId);
        if (client != null) {
            client.sendEvent(PUSH_DATA_EVENT, msgContent);
        }
    }

    /**
     * Get the userId parameter in the client url (modified here to suit individual needs and client side)
     *
     * @param client: Client
     * @return: java.lang.String
     */
    private String getParamsByClient(SocketIOClient client) {
        // Get the client url parameter (where userId is the unique identity)
        Map<String, List<String>> params = client.getHandshakeData().getUrlParams();
        List<String> userIdList = params.get("userId");
        if (!CollectionUtils.isEmpty(userIdList)) {
            return userIdList.get(0);
        }
        return null;
    }

    /**
     * Get the connected client ip address
     *
     * @param client: Client
     * @return: java.lang.String
     */
    private String getIpByClient(SocketIOClient client) {
        String sa = client.getRemoteAddress().toString();
        String clientIp = sa.substring(1, sa.indexOf(":"));
        return clientIp;
    }

}

3. Java Development socket.io Client

  1. socket.emit: Send data to server events
  2. socket.on: listen for server-side events
@Slf4j
public class SocketIOClientLaunch {

    public static void main(String[] args) {
        // Server socket.io Connection Communication Address
        String url = "http://127.0.0.1:8888";
        try {
            IO.Options options = new IO.Options();
            options.transports = new String[]{"websocket"};
            options.reconnectionAttempts = 2;
            // Time interval for failed reconnection
            options.reconnectionDelay = 1000;
            // Connection timeout (ms)
            options.timeout = 500;
            // userId: Unique identity passed to the server-side store
            final Socket socket = IO.socket(url + "?userId=1", options);

            socket.on(Socket.EVENT_CONNECT, args1 -> socket.send("hello..."));

            // Custom Event`Connected` ->Receive Server Successful Connection Message
            socket.on("connected", objects -> log.debug("Server:" + objects[0].toString()));

            // Custom Event`push_data_event` ->Receive Service-side Messages
            socket.on("push_data_event", objects -> log.debug("Server:" + objects[0].toString()));

            // Custom Event`myBroadcast` ->Receive Service-side Broadcast Messages
            socket.on("myBroadcast", objects -> log.debug("Server:" + objects[0].toString()));

            socket.connect();

            while (true) {
                Thread.sleep(3000);
                // Custom Event`push_data_event` ->Send a message to the server
                socket.emit("push_data_event", "send data " + DateUtil.now());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

4. Running tests

When the client comes online, a message is sent to the server every 3 seconds through the custom event push_data_event, as follows

A broadcast message (custom event myBroadcast) running in the server is returned to the client every 3 seconds

Broadcast events: broadcast message data to all socket connections

socketIOServer.getBroadcastOperations().sendEvent("myBroadcast", "Broadcast message " + DateUtil.now());

The logs are as follows:

Write active messaging from the server to the client interface

@RestController
@RequestMapping("/api/socket.io")
@Api(tags = "SocketIO test-Interface")
public class SocketIOController {

    @Autowired
    private ISocketIOService socketIOService;

    @PostMapping(value = "/pushMessageToUser", produces = Constants.CONTENT_TYPE)
    @ApiOperation(value = "Push information to specified client", httpMethod = "POST", response = ApiResult.class)
    public ApiResult pushMessageToUser(@RequestParam String userId, @RequestParam String msgContent) {
        socketIOService.pushMessageToUser(userId, msgContent);
        return ApiResult.ok();
    }

}

Call interface test to send helloworld...

V. Summary

socket.io Communications, Server:

1.socketIOServer.addConnectListener: Listen for client connections 2. socketIOServer.addDisconnectListener: Listen for client disconnection 3. socketIOServer.addEventListener: listens for messages transmitted by clients 4. client.sendEvent("Custom Event Name", "Message Content"): The service side sends messages to the specified clien client 5. socketIOServer.getBroadcastOperations().sendEvent("Custom Event Name", "Message Content"): The server sends broadcast messages to all clients

socket.io Communication, Client:
  1. IO.socket(url): Establishes a connection to the specified socket.io server
  2. socket.emit: Send data to server events
  3. socket.on: listen for server-side events

demo source for this case

https://gitee.com/zhengqingya/java-workspace

Topics: Programming socket Java Netty Spring