【SpringBoot+Netty】实现点击前台页面按钮调用Client向Server发送消息

代码背景

需要实现如下功能:点击前台的页面按钮,后台便向下位机发送指令,实现控制物联网设备。
其实这个逻辑可以适用于很多场景,我用的解决方式的过程是:
在这里插入图片描述

  1. 前台使用OnClick事件绑定一个按钮
  2. 按钮触发WebSocket相关方法
  3. 前台发送WebSocket数据
  4. 后台使用Netty设计一个WebSocketServer接收这个ws数据
  5. 在ws数据的handler处理类中调用Netty Client发送数据
  6. Netty Server接收、处理并返回结果

相关代码

前台页面的onclick事件

<div class="row">
        <div class="col-md-2">
            <button type="button" class="btn btn-primary"
                    onclick="connectWS(this.value)" value="01"> 开启风扇</button>
        </div>
        <div class="col-md-2">
            <button type="button" class="btn btn-primary"
                    onclick="connectWS(this.value)" value="02"> 关闭风扇</button>
        </div>
        <div class="col-md-2">
            <button type="button" class="btn btn-primary"
                    onclick="connectWS(this.value)" value="03"> 开启风扇摇头</button>
        </div>
    </div>

前台的JS

var ws;

function connectWS(command) {

    // 当前地址
    var path = window.location.pathname;

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

    // 后台wb控制器url
    var target = "/wb/test";

    // 将http协议换成ws
    if (window.location.protocol == 'http:') {
        target = 'ws://' + hostaddress + target;
    } else {
        target = 'wss://' + hostaddress + target;
    }
    console.log('WebSocketServer地址:'+target);

    //创建一个针对控制器的 webSocket 对象
    if ('WebSocket' in window) {
        ws = new WebSocket(target);
    } else if ('MozWebSocket' in window) {
        ws = new MozWebSocket(target);
    } else {
        $.modal.confirm("您的浏览器不支持 WebSocket!");
        return;
    }

    // 如果没有ws对象 直播状态为2 设置对应按钮
    if(ws==null){
        console.log("WebSocket创建失败...")
    }

    // 开启WS
    ws.onopen = function () {
        //向后台发送指令
        startsent(command);
        console.log('发送控制命令');
    };

    // WS的返回信息
    ws.onmessage = function (event) {
        console.log('WS接收到的信息:' + event.data);
    };

    // WS关闭
    ws.onclose = function (event) {
        console.log('WS已关闭:' + event.data );
    };

}

function startsent(command){
    if (ws != null) {
        // 控制台打印
        console.log('开始发送Wb指令');
        // 推送信息
        ws.send(command);
    } else {
        $.modal.confirm("WebSocket 连接建立失败,请重新连接");
    }
}

后台的webSocketServer的config配置

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 张超 teavamc
 * @Description:添加WebSocket的配置,使其支持
 * @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已注册,WB地址:" + mapping);

    }

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

ws处理类

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 张超 teavamc
 * @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("正常日志:"+session.getRemoteAddress()+"断开连接!");
    }


    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception{
        log.info("正常日志:"+session.getRemoteAddress()+"打开连接!");

    }

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


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

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



    private void sendCMDtoSocket(String msg){
        try {
            TcpClient.sendMsg(msg);
            log.info("TCP Client发送消息" + msg);

        } catch (Exception e) {
            log.info("TCP Client发送消息失败:" + e);
        }
    }



}

被调用的TcpClient客户端

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 张超 teavamc
 * @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);

    /**
     * 初始化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;
    }

    //    连接端口
    public static final Channel getChannel(String host, int port) {
        Channel channel = null;
        try {
            channel = bootstrap.connect(host, port).sync().channel();
            log.info("TCP Client 已经在" + host + "的" + port + "端口建立Channel");
        } catch (Exception e) {
            System.out.println("连接Server(IP{},PORT{})失败"+"host:"+host+"port:"+port+"e:"+e);
            return null;
        }
        return channel;
    }

    /**
     * 发送信息
     * @author 张超 teavamc
     * @date 2019/5/2
     * @return void
     */
    public static void sendMsg(String msg) throws Exception {
        if (channel != null) {
            channel.writeAndFlush(msg).sync();
        } else {
            log.info("消息发送失败,连接尚未建立!");
        }
    }
}

Client的处理类

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 张超 teavamc
 * @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接收的消息:";

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (SUCCEED.equals(msg)){
            log.info(REC + "成功");
        }else if(FAILED.equals(msg)){
            log.info(REC + "失败");
        }else {
            log.warn(REC + "Server回传的数据异常");
        }
    }

}

TcpServer 服务端

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 张超 teavamc
 * @Description:TODO
 * @ClassName TcpServer
 * @date 2019/5/2 15:04
 **/
public class TcpServer {

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

    // 服务器地址端口
    private static final String IP = "127.0.0.1";
    private static final int PORT = 8888;

    //确定客户端的IP地址
    private final String CLIENT_IP = "127.0.0.1";
    private final int CLIENT_PORT = 3000;


    /** 用于分配处理业务线程的线程组个数 */
    protected static final int BIZGROUPSIZE = Runtime.getRuntime().availableProcessors() * 2;
    /** 业务出现线程大小 */
    protected static final int BIZTHREADSIZE = 4;

    /*
     * NioEventLoopGroup实际上就是个线程池,
     * NioEventLoopGroup在后台启动了n个NioEventLoop来处理Channel事件,
     * 每一个NioEventLoop负责处理m个Channel,
     * NioEventLoopGroup从NioEventLoop数组里挨个取出NioEventLoop来处理Channel
     */
    private static final EventLoopGroup bossGroup = new NioEventLoopGroup(BIZGROUPSIZE);
    private static final EventLoopGroup workerGroup = new NioEventLoopGroup(BIZTHREADSIZE);

    //    线程内容
    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是对发送的信息进行编码、
//                @param maxFrameLength  帧的最大长度
//                @param lengthFieldOffset length字段偏移的地址
//                @param lengthFieldLength length字段所占的字节
//                @param lengthAdjustment 修改帧数据长度字段中定义的值,
//                可以为负数 因为有时候我们习惯把头部记入长度,若为负数,则说明要推后多少个字段
//                @param initialBytesToStrip 解析时候跳过多少个长度
                pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,
                        0,
                        4,
                        0,
                        4));

//                Encode是对接收的信息进行解码
                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());
            }
        });

        //异步绑定端口
        b.bind(IP, PORT).sync();
        log.info("TCP Server端口:" + PORT);
    }

    //关闭端口
    public static void shutdown() {
        workerGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
    }

}

服务端处理类

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 张超 teavamc
 * @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 打印接收到的内容,并回传
        * @author 张超 teavamc
        * @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收到开启风扇的指令:" + msg);
            ctx.channel().writeAndFlush(SUCCEED);
        }else if(CLOSEFAN.equals(msg)){
            log.info("TCP Server收到开启洒水的指令:" + msg);
            ctx.channel().writeAndFlush(SUCCEED);
        }else if(OPENAOTUFAN.equals(msg)){
            log.info("TCP Server收到开启摇头的指令:" + msg);
            ctx.channel().writeAndFlush(SUCCEED);
        }else {
            log.info("不明指令:" + msg);
            ctx.channel().writeAndFlush(FAILED);
        }
    }

    /**
        * @Description 
        * @author 张超 teavamc
        * @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();
    }

}

实现效果

在测试页面点击开启风扇的按钮,可见发送了一个数据
在这里插入图片描述
看console控制台的输出
在这里插入图片描述
看IDEA的控制台输出
在这里插入图片描述
实现了前台按钮向Server端发送数据

发布了104 篇原创文章 · 获赞 264 · 访问量 54万+

猜你喜欢

转载自blog.csdn.net/teavamc/article/details/89855364
今日推荐