需要实现如下功能:点击前台的页面按钮,后台便向下位机发送指令,实现控制物联网设备。
其实这个逻辑可以适用于很多场景,我用的解决方式的过程是:
<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>
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 连接建立失败,请重新连接");
}
}
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();
}
}
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);
}
}
}
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("消息发送失败,连接尚未建立!");
}
}
}
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回传的数据异常");
}
}
}
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端发送数据