收到任务要调试板子,需要给板子发送16进制字符串指令,然后板子回馈给我指令,但是我怎么连接板子呢,最开始采用Socket方式,我需要的场景是我发送指令板子就会给我回馈信息,我才能知道我的这次指令是成功还是失败!但是Socket对这种方式感觉不太友好,想要一直接收信息就需要循环,后来辗转反侧使用了netty,感觉很好用,下面就贴出使用代码
1.添加初始化管道
package com.hs.server.tcpDemo.netty;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
/**
* @ClassName : ChannelInitializer
* @Description :
* @Author : df
* @Date: 2021-01-13 09:31
*/
public class ChannelInit extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) {
// 基于换行符号
/*channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
// 解码转String,注意调整自己的编码格式GBK、UTF-8
channel.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));
// 解码转String,注意调整自己的编码格式GBK、UTF-8
channel.pipeline().addLast(new StringEncoder(Charset.forName("GBK")));*/
//channel.pipeline().addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS));
// 在管道中添加我们自己的接收数据实现方法
channel.pipeline().addLast(new ServerHandler());
}
}
2.客户端连接,以及接收客户端的信息
package com.hs.server.tcpDemo.netty;
import cn.hutool.json.JSONObject;
import com.hs.server.enums.CommunicationEnum;
import com.hs.server.mapper.HexLabelMapper;
import com.hs.server.service.TcpPlcHelper;
import com.hs.server.utils.ByteUtils;
import com.hs.server.utils.RedisCache;
import com.hs.server.utils.spring.SpringContextUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.socket.SocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @ClassName : ServerHandler
* @Description :
* @Author : df
* @Date: 2021-01-13 09:34
*/
public class ServerHandler extends ChannelInboundHandlerAdapter {
private Logger logger = LoggerFactory.getLogger(ServerHandler.class);
/**
* 当客户端主动链接服务端的链接后,这个通道就是活跃的了。也就是客户端与服务端建立了通信通道并且可以传输数据
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
SocketChannel channel = (SocketChannel) ctx.channel();
logger.info("客户端连接上服务端");
channel.read();
}
/**
* 当客户端主动断开服务端的链接后,这个通道就是不活跃的。也就是说客户端与服务端的关闭了通信通道并且不可以传输数据
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
logger.info("客户端断开链接{}", ctx.channel().localAddress().toString());
}
/*
* @Author df
* @Description 接收客户端的信息
* @Date 18:17 2021/1/14
**/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//netty自定义的byte,需要转换byte
ByteBuf msgByteBuf = (ByteBuf) msg;
byte[] msgBytes = new byte[msgByteBuf.readableBytes()];
// msg中存储的是ByteBuf类型的数据,把数据读取到byte[]中
msgByteBuf.readBytes(msgBytes);
// 释放资源
msgByteBuf.release();
// 将接收的信息转换成16进制字符串
String tranforHexString = ByteUtils.byteArrayToHexString(msgBytes);
logger.info(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 接收消息:" + tranforHexString);
}
/**
* 心跳机制
*/
/* @Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (IdleState.READER_IDLE.equals((event.state()))) {
ctx.writeAndFlush("heartbeat").addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
}
super.userEventTriggered(ctx, evt);
}*/
/**
* 抓住异常,当发生异常的时候,可以做一些相应的处理,比如打印日志、关闭链接
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.info("异常信息:\r\n" + cause.getMessage());
}
/*@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel 通道 Read 读取 Complete 完成");
ctx.flush();
}*/
}
3.配置netty连接客户端
package com.hs.server.tcpDemo.netty;
import com.hs.server.utils.ByteUtils;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
/**
* @ClassName : NettyServer
* @Description :
* @Author : df
* @Date: 2021-01-13 09:26
*/
@Component
public class NettyServer {
private static Logger logger = LoggerFactory.getLogger(NettyServer.class);
//配置服务端NIO线程组
private final EventLoopGroup parentGroup = new NioEventLoopGroup();
//NioEventLoopGroup extends MultithreadEventLoopGroup Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
private final EventLoopGroup childGroup = new NioEventLoopGroup();
private Channel channel;
public ChannelFuture bing(InetSocketAddress address) {
ChannelFuture channelFuture = null;
try {
ServerBootstrap b = new ServerBootstrap();
// 添加boss和worker组
b.group(parentGroup, childGroup)
// 非阻塞模式
.channel(NioServerSocketChannel.class)
// 设置TCP的缓冲区
.option(ChannelOption.SO_BACKLOG, 128)
// childHandler 去绑定具体的事件处理器
.childHandler(new ChannelInit());
// 绑定端口
channelFuture = b.bind(address).syncUninterruptibly();
channel = channelFuture.channel();
} catch (Exception e) {
logger.error(e.getMessage());
} finally {
if (null != channelFuture && channelFuture.isSuccess()) {
logger.info("netty服务启动完成!");
} else {
logger.error("netty服务启动失败!");
}
}
return channelFuture;
}
public boolean destroy() {
try {
if (null == channel) return false;
channel.close();
parentGroup.shutdownGracefully();
childGroup.shutdownGracefully();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public Channel getChannel() {
return channel;
}
/*
* @Author df
* @Description 发送消息
* @Date 15:28 2021/1/14
**/
public void sendMessage(byte[] byteData) {
try {
ChannelHandlerContext ctx = ServerHandler.ctxMap.get("ctx");
// netty需要转换ByteBuf才可使用
ByteBuf bytebuf = Unpooled.buffer();
bytebuf.writeBytes(byteData);
ctx.writeAndFlush(bytebuf);
} catch (Exception e) {
e.printStackTrace();
ServerHandler.isAccess = false;
}
}
}
4.封装连接,关闭,发送指令服务
package com.hs.server.service;
import com.hs.server.domain.SixTeenHex;
import com.hs.server.tcpDemo.netty.NettyServer;
import com.hs.server.utils.AjaxResult;
import com.hs.server.utils.ByteUtils;
import com.hs.server.utils.HexEntityTranUtil;
import com.hs.server.utils.RedisCache;
import io.netty.channel.ChannelFuture;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @ClassName : TcpServiceImpl
* @Description :
* @Author : df
* @Date: 2021-01-07 16:41
*/
@Service
public class TcpServiceImpl implements TcpService {
// 硬件监听的端口号
@Value("${box.port}")
// 写上自己客户端的端口号即可
private int boxPort;
@Resource
private RedisCache redisCache;
@Resource
private NettyServer nettyServer;
ExecutorService exe = Executors.newSingleThreadExecutor();
/*
* @Author df
* @Description 打开连接
* @Date 16:44 2021/1/7
**/
@Override
public AjaxResult open() {
try {
InetSocketAddress address = new InetSocketAddress(boxPort);
exe.execute(new Runnable() {
@Override
public void run() {
ChannelFuture channelFuture = nettyServer.bing(address);
channelFuture.channel().closeFuture().syncUninterruptibly();
}
});
exe.shutdown();
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("启动操作异常!");
}
return AjaxResult.success("启动操作已通知!请等待连接通知");
}
@Override
public AjaxResult send(String data) {
try {
TcpPlcHelper helper = new TcpPlcHelper(redisCache);
// 将10进制字符串转换成byte数组
byte[] byteData = ByteUtils.hexStrToByteArrs(data);
// byte数组转换实体
SixTeenHex sixTeenHexSend = HexEntityTranUtil.bytesToHexEntity(byteData);
// 存储发送指令
helper.orderSendCache(sixTeenHexSend, data);
// 通过netty给板子发信息
nettyServer.sendMessage(byteData);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("服务器报错!");
}
return AjaxResult.success("成功发送数据!");
}
/*
* @Author df
* @Description 关闭连接
* @Date 16:55 2021/1/7
**/
@Override
public AjaxResult close() {
boolean isSucc = nettyServer.destroy();
if (isSucc) {
return AjaxResult.success("关闭成功!");
} else {
return AjaxResult.error("关闭失败!");
}
}
}
5.controller层实现
package com.hs.server.controller;
import com.hs.server.service.PlcSerialPortService;
import com.hs.server.service.TcpService;
import com.hs.server.utils.AjaxResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @ClassName : TcpServerController
* @Description :
* @Author : df
* @Date: 2021-01-07 17:15
*/
@RestController
@RequestMapping("/tcp")
public class TcpServerController {
@Resource
private TcpService tcpService;
@RequestMapping("/open")
public AjaxResult open() {
return tcpService.open();
}
@RequestMapping("/send")
public AjaxResult send(@RequestParam("data") String data) {
return tcpService.send(data);
}
@RequestMapping("/close")
public AjaxResult close() {
return tcpService.close();
}
}
6.访问开启netty接口
7.netty连接成功!
8.访问发送数据指令接口
9.最后我解析16进制转换成实体知道了我这次指令发送给我反馈的信息
10. 访问关闭接口
11.客户端关闭成功了!