[SpringBoot+Netty] implementation click the front page button to call the Client to send messages to the Server

Posted by subesc on Sun, 17 Nov 2019 16:51:45 +0100

Code background

The following functions need to be realized: click the page button in the foreground, and the background will send instructions to the lower computer to control the Internet of things devices.
In fact, this logic can be applied to many scenarios. The process of my solution is as follows:

  1. The foreground uses OnClick event to bind a button
  2. Button triggered WebSocket related methods
  3. Send WebSocket data to the foreground
  4. Using Netty to design a WebSocketServer to receive the ws data in the background
  5. Calling Netty Client to send data in the handler processing class of ws data
  6. Netty Server receives, processes, and returns results

Related code

onclick event of foreground page

<div class="row">
        <div class="col-md-2">
            <button type="button" class="btn btn-primary"
                    onclick="connectWS(this.value)" value="01"> Open fan</button>
        </div>
        <div class="col-md-2">
            <button type="button" class="btn btn-primary"
                    onclick="connectWS(this.value)" value="02"> Fan off</button>
        </div>
        <div class="col-md-2">
            <button type="button" class="btn btn-primary"
                    onclick="connectWS(this.value)" value="03"> Turn on the fan and shake your head</button>
        </div>
    </div>

JS at the front desk

var ws;

function connectWS(command) {

    // Current address
    var path = window.location.pathname;

    // Current host
    var hostaddress = window.location.host + path.substring(0,path.substr(1).indexOf('/')+1);

    // Background wb controller url
    var target = "/wb/test";

    // Replace http protocol with ws
    if (window.location.protocol == 'http:') {
        target = 'ws://' + hostaddress + target;
    } else {
        target = 'wss://' + hostaddress + target;
    }
    console.log('WebSocketServer Address:'+target);

    //Create a webSocket object for the controller
    if ('WebSocket' in window) {
        ws = new WebSocket(target);
    } else if ('MozWebSocket' in window) {
        ws = new MozWebSocket(target);
    } else {
        $.modal.confirm("Your browser does not support WebSocket!");
        return;
    }

    // If there is no live broadcast status of ws object, set the corresponding button to 2
    if(ws==null){
        console.log("WebSocket Create failure...")
    }

    // Open WS
    ws.onopen = function () {
        //Send instructions to the background
        startsent(command);
        console.log('Send control command');
    };

    // Return information of WS
    ws.onmessage = function (event) {
        console.log('WS Information received:' + event.data);
    };

    // WS shut down
    ws.onclose = function (event) {
        console.log('WS Closed:' + event.data );
    };

}

function startsent(command){
    if (ws != null) {
        // Console printing
        console.log('Start sending Wb instructions');
        // Push information
        ws.send(command);
    } else {
        $.modal.confirm("WebSocket Connection establishment failed, please reconnect");
    }
}

config configuration of webSocketServer in the background

package com.teavamc.transsocket.config;

import com.teavamc.transsocket.websocket.WbHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

import javax.websocket.server.ServerEndpointConfig;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.logging.StreamHandler;

/**
 * @author Zhang Chao TeamC
 * @Description:Add WebSocket configuration to support
 * @ClassName WebSocketConfig
 * @date 2019/5/4 9:54
 **/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    private  Logger log = LogManager.getLogger(WebSocketConfig.class);

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry){

        String mapping = "/wb/test";
        registry.addHandler(webSocketHandler(),mapping);
        log.info("WebSocket Registered. WB Address:" + mapping);

    }

    @Bean
    public WebSocketHandler webSocketHandler(){
        return new WbHandler();
    }
}

ws processing class

package com.teavamc.transsocket.websocket;

import com.teavamc.transsocket.client.TcpClient;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;

import java.lang.reflect.Array;
import java.util.ArrayList;


/**
 * @author Zhang Chao TeamC
 * @Description:TODO
 * @ClassName WbHandler
 * @date 2019/5/4 15:10
 **/
public class WbHandler implements WebSocketHandler {


    private static final Logger log = LogManager.getLogger(WbHandler.class);

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus arg1) throws Exception{
        log.info("Normal log:"+session.getRemoteAddress()+"Disconnect!");
    }


    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception{
        log.info("Normal log:"+session.getRemoteAddress()+"Open connection!");

    }

    @Override
    public void handleMessage(WebSocketSession conn, WebSocketMessage<?> message) throws Exception {
        log.info("Log information:"+message.getPayload());
        sendCMDtoSocket(message.getPayload().toString());
    }


    @Override
    public void handleTransportError(WebSocketSession session, Throwable arg1) throws Exception {
        if(session.isOpen()){
            session.close();
        }
        log.error( "error log: IP:" +session.getRemoteAddress()+"  "+ arg1.getMessage() );
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }



    private void sendCMDtoSocket(String msg){
        try {
            TcpClient.sendMsg(msg);
            log.info("TCP Client send message" + msg);

        } catch (Exception e) {
            log.info("TCP Client Failed to send message:" + e);
        }
    }



}

Called tcpcclient client

package com.teavamc.transsocket.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * @author Zhang Chao TeamC
 * @Description:TODO
 * @ClassName TcpClient
 * @date 2019/5/4 17:25
 **/
public class TcpClient {

    private static Logger log = LogManager.getLogger(TcpClient.class);

    public static String HOST = "127.0.0.1";
    public static int PORT = 8888;

    public static Bootstrap bootstrap = getBootstrap();
    public static Channel channel = getChannel(HOST, PORT);

    /**
     * Initialize Bootstrap
     */
    public static final Bootstrap getBootstrap() {
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(group).channel(NioSocketChannel.class);
        b.handler(new ChannelInitializer<Channel>() {
            @Override
            protected void initChannel(Channel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
                pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
                pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
                pipeline.addLast("handler", new TcpClientHandler());
            }
        });
        b.option(ChannelOption.SO_KEEPALIVE, true);
        return b;
    }

    //    Connection port
    public static final Channel getChannel(String host, int port) {
        Channel channel = null;
        try {
            channel = bootstrap.connect(host, port).sync().channel();
            log.info("TCP Client Already in" + host + "Of" + port + "Port establishment Channel");
        } catch (Exception e) {
            System.out.println("Connect Server(IP{},PORT{})fail"+"host:"+host+"port:"+port+"e:"+e);
            return null;
        }
        return channel;
    }

    /**
     * Send message
     * @author Zhang Chao TeamC
     * @date 2019/5/2
     * @return void
     */
    public static void sendMsg(String msg) throws Exception {
        if (channel != null) {
            channel.writeAndFlush(msg).sync();
        } else {
            log.info("Message sending failed,Connection not established!");
        }
    }
}

Client's processing class

package com.teavamc.transsocket.client;

import com.teavamc.transsocket.server.TcpServerHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * @author Zhang Chao TeamC
 * @Description:TODO
 * @ClassName TcpClientHandler
 * @date 2019/5/4 17:26
 **/
public class TcpClientHandler extends SimpleChannelInboundHandler<Object> {

    private static Logger log = LogManager.getLogger(TcpServerHandler.class);

    private final String SUCCEED = "1";
    private final String FAILED = "0";
    private final String REC = "TCP Client Messages received:";

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (SUCCEED.equals(msg)){
            log.info(REC + "Success");
        }else if(FAILED.equals(msg)){
            log.info(REC + "fail");
        }else {
            log.warn(REC + "Server Returned data exception");
        }
    }

}

TcpServer server

package com.teavamc.transsocket.server;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * @author Zhang Chao TeamC
 * @Description:TODO
 * @ClassName TcpServer
 * @date 2019/5/2 15:04
 **/
public class TcpServer {

    private static Logger log = LogManager.getLogger(TcpServer.class);

    // Server address port
    private static final String IP = "127.0.0.1";
    private static final int PORT = 8888;

    //Determine the IP address of the client
    private final String CLIENT_IP = "127.0.0.1";
    private final int CLIENT_PORT = 3000;


    /** Number of thread groups used to allocate processing business threads */
    protected static final int BIZGROUPSIZE = Runtime.getRuntime().availableProcessors() * 2;
    /** Business thread size */
    protected static final int BIZTHREADSIZE = 4;

    /*
     * NioEventLoopGroup It's actually a thread pool,
     * NioEventLoopGroup n nioeventloops are started in the background to handle Channel events,
     * Each NioEventLoop is responsible for processing m channels,
     * NioEventLoopGroup Take NioEventLoop from NioEventLoop array one by one to process Channel
     */
    private static final EventLoopGroup bossGroup = new NioEventLoopGroup(BIZGROUPSIZE);
    private static final EventLoopGroup workerGroup = new NioEventLoopGroup(BIZTHREADSIZE);

    //    Thread content
    public static void run() throws Exception {
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup);
        b.channel(NioServerSocketChannel.class);
        b.childHandler(new ChannelInitializer<SocketChannel>() {

            @Override
            public void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
//                Decode is to code the information sent
//                @param maxFrameLength maximum frame length
//                @param lengthFieldOffset length field offset address
//                @Bytes occupied by param lengthFieldLength length field
//                @param lengthAdjustment modifies the value defined in the frame data length field,
//                It can be a negative number because sometimes we are used to record the header in the length. If it is a negative number, it indicates how many fields should be pushed back
//                @How many lengths are skipped when parsing param initialBytesToStrip
                pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,
                        0,
                        4,
                        0,
                        4));

//                Encode is to decode the received information
                pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
                pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
                pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
                pipeline.addLast(new TcpServerHandler());
            }
        });

        //Asynchronous binding port
        b.bind(IP, PORT).sync();
        log.info("TCP Server Port:" + PORT);
    }

    //Close port
    public static void shutdown() {
        workerGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
    }

}

Server processing class

package com.teavamc.transsocket.server;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * @author Zhang Chao TeamC
 * @Description:TODO
 * @ClassName TcpServerHandler
 * @date 2019/5/2 15:05
 **/
public class TcpServerHandler extends SimpleChannelInboundHandler<Object> {

    private static Logger log = LogManager.getLogger(TcpServerHandler.class);

    private final String OPENFAN = "01";
    private final String CLOSEFAN = "02";
    private final String OPENAOTUFAN = "03";
    private final String SUCCEED = "1";
    private final String FAILED = "0";

    /**
        * @Description Print the received content and send it back
        * @author Zhang Chao TeamC
        * @date 2019/5/4
        * @Time 16:25
        * @return void
        */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (OPENFAN.equals(msg)){
            log.info("TCP Server Received command to turn on the fan:" + msg);
            ctx.channel().writeAndFlush(SUCCEED);
        }else if(CLOSEFAN.equals(msg)){
            log.info("TCP Server Receive the instruction to start watering:" + msg);
            ctx.channel().writeAndFlush(SUCCEED);
        }else if(OPENAOTUFAN.equals(msg)){
            log.info("TCP Server Receive command to turn on shaking head:" + msg);
            ctx.channel().writeAndFlush(SUCCEED);
        }else {
            log.info("Unknown instruction:" + msg);
            ctx.channel().writeAndFlush(FAILED);
        }
    }

    /**
        * @Description 
        * @author Zhang Chao TeamC
        * @date 2019/5/4
        * @Time 16:50
        * @return void
        */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.info("exceptionCaught!cause:" + cause.toString());
        ctx.close();
    }

}

Realization effect

Click the button to turn on the fan on the test page, and you can see that a data is sent

See the output of console

See the console output of IDEA

The front button sends data to the Server

Topics: Netty socket Apache log4j