也不知道说什么 也是记录一下所用到的东西 因为 我得数据板 发送得是 tcp 10进制数据 所以 在监听端口上传信息时 要进行一此转换 10 进制 转换 16进制 下面是 所用到得所有类
MyNettyServer 类
package com.sys_iot;
import com.sys_iot.system.netty.MyNettyChannelInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @author lics
* @version 1.0.0
* @date 2023/6/1 13:12
*/
public class MyNettyServer {
public void startNettyServer(int... ports) {
/*
* 配置服务端的NIO线程组
* NioEventLoopGroup 是用来处理I/O操作的Reactor线程组
* bossGroup:用来接收进来的连接,workerGroup:用来处理已经被接收的连接,进行socketChannel的网络读写,
* bossGroup接收到连接后就会把连接信息注册到workerGroup
* workerGroup的EventLoopGroup默认的线程数是CPU核数的二倍
*/
EventLoopGroup bossGroup = new NioEventLoopGroup(3);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// ServerBootstrap 是一个启动NIO服务的辅助启动类
ServerBootstrap serverBootstrap = new ServerBootstrap();
// 设置group,将bossGroup, workerGroup线程组传递到ServerBootstrap
serverBootstrap = serverBootstrap.group(bossGroup, workerGroup);
// ServerSocketChannel是以NIO的selector为基础进行实现的,用来接收新的连接,这里告诉Channel通过NioServerSocketChannel获取新的连接
serverBootstrap = serverBootstrap.channel(NioServerSocketChannel.class);
// 初始化通道,主要用于网络I/O事件,记录日志,编码、解码消息
serverBootstrap = serverBootstrap.childHandler(new MyNettyChannelInitializer());
// 绑定端口,同步等待成功
ChannelFuture[] channelFutures;
ChannelFuture futureTcp;
if (ports.length > 0) {
channelFutures = new ChannelFuture[ports.length];
int i = 0;
for (Integer port : ports) {
// 绑定端口,同步等待成功 绑定的服务器
futureTcp = serverBootstrap.bind(port).sync();
channelFutures[i++] = futureTcp;
futureTcp.addListener(future -> {
if (future.isSuccess()) {
System.out.println("netty server 启动成功!" + port);
} else {
System.out.println("netty server 启动失败!" + port);
}
});
}
for (ChannelFuture future : channelFutures) {
// 等待服务器监听端口关闭
future.channel().closeFuture().sync();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
RuoYiApplication 启动类
package com.sys_iot;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.Async;
/**
* 启动程序
*
* @author ruoyi
*/
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class RuoYiApplication implements CommandLineRunner
{
public static void main(String[] args)
{
// System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication.run(RuoYiApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ 若依启动成功 ლ(´ڡ`ლ)゙ \n" +
" .-------. ____ __ \n" +
" | _ _ \\ \\ \\ / / \n" +
" | ( ' ) | \\ _. / ' \n" +
" |(_ o _) / _( )_ .' \n" +
" | (_,_).' __ ___(_ o _)' \n" +
" | |\\ \\ | || |(_,_)' \n" +
" | | \\ `' /| `-' / \n" +
" | | \\ / \\ / \n" +
" ''-' `'-' `-..-' ");
SpringApplication app = new SpringApplication(RuoYiApplication.class);
app.run(args);
}
@Async
@Override
public void run(String... args) {
/*
* 使用异步注解方式启动netty服务端服务
* 8001 监听数据版 上传数据
* 8002 监听屏幕设备数据链接
* 8003 监听设备板子 发送控制
*/
new MyNettyServer().startNettyServer(8003, 8001);
}
}
Cpmpone 类
package com.sys_iot.system.netty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
/**
*
*/
public class Cpmpone {
// @Autowired
// private TcpRequestHandler tcpRequestHandlers;
// @Autowired
// public Cpmpone(TcpRequestHandler tcpRequestHandlers) {
// this.tcpRequestHandlers = tcpRequestHandlers;
// }
public void ryMultipleParams(String command) {
try {
// tcpRequestHandlers s是nukl
// tcpRequestHandlers.distributionControl(command);
} catch (Exception e) {
e.printStackTrace();
}
}
}
MyDecoder 类
package com.sys_iot.system.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
public class MyDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
String HEXES = "0123456789ABCDEF";
byte[] req = new byte[msg.readableBytes()];
msg.readBytes(req);
final StringBuilder hex = new StringBuilder(2 * req.length);
for (int i = 0; i < req.length; i++) {
byte b = req[i];
hex.append(HEXES.charAt((b & 0xF0) >> 4))
.append(HEXES.charAt((b & 0x0F)));
}
out.add(hex.toString());
}
}
MyEncoder 类
package com.sys_iot.system.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* @ClassName NettyMessageEncoder
* @Deacription TODO 自定义发送消息格式 发送16进制
* @Author LiuDaGang
* @Date 2021/3/11 19:19
* @Version 1.0
**/
public class MyEncoder extends MessageToByteEncoder<String> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, String s, ByteBuf byteBuf) throws Exception {
//将16进制字符串转为数组
byteBuf.writeBytes(hexString2Bytes(s));
}
/**
* @Title:hexString2Bytes
* @Description:16进制字符串转字节数组
* @param src 16进制字符串
* @return 字节数组
*/
public static byte[] hexString2Bytes(String src) {
int l = src.length() / 2;
byte[] ret = new byte[l];
for (int i = 0; i < l; i++) {
ret[i] = (byte) Integer.valueOf(src.substring(i * 2, i * 2 + 2), 16).byteValue();
}
return ret;
}
}
MyNettyChannelInitializer 类
package com.sys_iot.system.netty;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
/**
* 通道初始化
*
* @author lics
* @version 1.0.0
* @date 2023/6/1 13:12
*/
public class MyNettyChannelInitializer extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel channel) {
/**
* 处理TCP请求
*/
// ChannelOutboundHandler,依照逆序执行
channel.pipeline().addLast("encoder", new MyEncoder());
// 属于ChannelInboundHandler,依照顺序执行
channel.pipeline().addLast("decoder", new MyDecoder());
// 自定义TCP请求的处理器
channel.pipeline().addLast(new TcpRequestHandler());
}
}
MyTools 类 这个类里面得 writeToClient() 方法就是我们后面 服务端 像客户端发送消息得方法
package com.sys_iot.system.netty;
import com.sys_iot.common.utils.StringUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import java.util.HashMap;
import java.util.Map;
public class MyTools {
//十六进制字符转十进制
public int covert(String content){
int number=0;
String [] HighLetter = {"A","B","C","D","E","F"};
Map<String,Integer> map = new HashMap<>();
for(int i = 0;i <= 9;i++){
map.put(i+"",i);
}
for(int j= 10;j<HighLetter.length+10;j++){
map.put(HighLetter[j-10],j);
}
String[]str = new String[content.length()];
for(int i = 0; i < str.length; i++){
str[i] = content.substring(i,i+1);
}
for(int i = 0; i < str.length; i++){
number += map.get(str[i])*Math.pow(16,str.length-1-i);
}
return number;
}
public byte[] hexString2Bytes(String src) {
int l = src.length() / 2;
byte[] ret = new byte[l];
for (int i = 0; i < l; i++) {
ret[i] = (byte) Integer
.valueOf(src.substring(i * 2, i * 2 + 2), 16).byteValue();
}
return ret;
}
public void writeToClient(final String receiveStr, ChannelHandlerContext channel, final String mark) {
try {
ByteBuf bufff = Unpooled.buffer();//netty需要用ByteBuf传输
bufff.writeBytes(hexString2Bytes(receiveStr));//对接需要16进制
System.out.println(channel);
channel.writeAndFlush(bufff).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
StringBuilder sb = new StringBuilder();
if(!StringUtils.isEmpty(mark)){
sb.append("【").append(mark).append("】");
}
if (future.isSuccess()) {
System.out.println(sb.toString()+"回写成功"+receiveStr);
} else {
System.out.println(sb.toString()+"回写失败"+receiveStr);
}
}
});
} catch (Exception e) {
e.printStackTrace();
System.out.println("调用通用writeToClient()异常"+e.getMessage());
}
}
}
NettyConfig 这个类 对你们来说 没有用 就是 我用来 声明bean用得 使用了@Configuration 注解
然后 util 就是 所用的 工具类了 具体我记得当时 我是用来 在 TcpRequestHandler类 里面 获取其他类得bean 使用的 可以用 也可以不用 看自己 需要
package com.sys_iot.system.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext ac;
@Override
public void setApplicationContext(ApplicationContext arg0) throws BeansException {
ac = arg0;
}
public static ApplicationContext getApplicationContext() {
return ac;
}
/**
* 通过class获取Bean
*/
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
/**
* 如果BeanFactory包含所给名称匹配的bean返回true
* @param name
* @return boolean
*/
public static boolean containsBean(String name) {
return ac.containsBean(name);
}
/**
* 判断注册的bean是singleton还是prototype。
* 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
* @param name
* @return boolean
*/
public static boolean isSingleton(String name) {
return ac.isSingleton(name);
}
/**
* @param name
* @return Class 注册对象的类型
*/
public static Class<?> getType(String name) {
return ac.getType(name);
}
}
tool
package com.sys_iot.system.util;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
/**
* 工具类 ctrl + G 输入下方行数
* 1.字符串转换成十六进制字符串 25 行
* 2.十六进制转换字符串 49 行
* 3.bytes转换成十六进制字符串 70 行
* 4.bytes字符串转换为Byte值 88 行
* 5.String的字符串转换成unicode的String 109 行
* 6.unicode的String转换成String的字符串 134 行
* 7.获取当前时间 返回字符串 467 行
*/
public class Tool {
/**
* 字符串转换成十六进制字符串
* @param String str 待转换的ASCII字符串
* @return String 每个Byte之间空格分隔,如: [61 6C 6B]
*/
public static String str2HexStr(String str)
{
char[] chars = "0123456789ABCDEF".toCharArray();
StringBuilder sb = new StringBuilder("");
byte[] bs = str.getBytes();
int bit;
for (int i = 0; i < bs.length; i++)
{
bit = (bs[i] & 0x0f0) >> 4;
sb.append(chars[bit]);
bit = bs[i] & 0x0f;
sb.append(chars[bit]);
sb.append(' ');
}
return sb.toString().trim();
}
/**
* 十六进制转换字符串
* @param String str Byte字符串(Byte之间无分隔符 如:[616C6B])
* @return String 对应的字符串
*/
public static String hexStr2Str(String hexStr)
{
String str = "0123456789ABCDEF";
char[] hexs = hexStr.toCharArray();
byte[] bytes = new byte[hexStr.length() / 2];
int n;
for (int i = 0; i < bytes.length; i++)
{
n = str.indexOf(hexs[2 * i]) * 16;
n += str.indexOf(hexs[2 * i + 1]);
bytes[i] = (byte) (n & 0xff);
}
return new String(bytes);
}
/**
* bytes转换成十六进制字符串
* @param byte[] b byte数组
* @return String 每个Byte值之间空格分隔
*/
public static String byte2HexStr(byte[] b)
{
String stmp="";
StringBuilder sb = new StringBuilder("");
for (int n=0;n<b.length;n++)
{
stmp = Integer.toHexString(b[n] & 0xFF);
sb.append((stmp.length()==1)? "0"+stmp : stmp);
sb.append(" ");
}
return sb.toString().toUpperCase().trim();
}
/**
* bytes字符串转换为Byte值
* @param String src Byte字符串,每个Byte之间没有分隔符
* @return byte[]
*/
public static byte[] hexStr2Bytes(String src)
{
int m=0,n=0;
int l=src.length()/2;
System.out.println(l);
byte[] ret = new byte[l];
for (int i = 0; i < l; i++)
{
m=i*2+1;
n=m+1;
ret[i] = Byte.decode("0x" + src.substring(i*2, m) + src.substring(m,n));
}
return ret;
}
/**
* String的字符串转换成unicode的String
* @param String strText 全角字符串
* @return String 每个unicode之间无分隔符
* @throws Exception
*/
public static String strToUnicode(String strText)
throws Exception
{
char c;
StringBuilder str = new StringBuilder();
int intAsc;
String strHex;
for (int i = 0; i < strText.length(); i++)
{
c = strText.charAt(i);
intAsc = (int) c;
strHex = Integer.toHexString(intAsc);
if (intAsc > 128)
str.append("\\u" + strHex);
else // 低位在前面补00
str.append("\\u00" + strHex);
}
return str.toString();
}
/**
* unicode的String转换成String的字符串
* @param String hex 16进制值字符串 (一个unicode为2byte)
* @return String 全角字符串
*/
public static String unicodeToString(String hex)
{
int t = hex.length() / 6;
StringBuilder str = new StringBuilder();
for (int i = 0; i < t; i++)
{
String s = hex.substring(i * 6, (i + 1) * 6);
// 高位需要补上00再转
String s1 = s.substring(2, 4) + "00";
// 低位直接转
String s2 = s.substring(4);
// 将16进制的string转为int
int n = Integer.valueOf(s1, 16) + Integer.valueOf(s2, 16);
// 将int转换为字符
char[] chars = Character.toChars(n);
str.append(new String(chars));
}
return str.toString();
}
/**
* 获取当前时间 返回字符串
*/
public static String time()
{
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd kk:mm:ss ");
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
return sdf.format(d);
}
}
最后得 最后 就是 最最最主要的类了 TcpRequestHandler 类 我所 注释得地方 均为 我当前项目需要得 你们可以用来 参考 或者 自己写写
package com.sys_iot.system.netty;
import java.net.InetSocketAddress;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import com.sys_iot.system.service.IARoomScheduleService;
import com.sys_iot.system.service.impl.MyNettySeviceImpl;
import com.sys_iot.system.util.SpringUtil;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* I/O数据读写处理类 TCP连接
* <p>
* tcp设备数据上传及下发数据
*
* @author lics
* @version 1.0.0
* @date 2023/6/1 13:12
*/
@ChannelHandler.Sharable
public class TcpRequestHandler extends ChannelInboundHandlerAdapter {
// 这个东西有点坑 依赖注入 不成功 注意下方static 通过一个 spring工具类 获取bean才可以
// @Autowired
// private static IARoomScheduleService roomScheduleService;
//
// @Autowired
// private static MyNettySeviceImpl myNettySeviceImpl;
//
// static {
// roomScheduleService = SpringUtil.getBean(IARoomScheduleService.class);
// myNettySeviceImpl = SpringUtil.getBean(MyNettySeviceImpl.class);
// }
private static ChannelHandlerContext ctx; // 使用静态变量存储
/**
* 从客户端收到新的数据时,这个方法会在收到消息时被调用
* // ctx.write(we);
* // msg 客户端发送数据
* // System.out.println("channelRead: " + msg.toString());
*
* @param ctx
* @param msg
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
MyTools myTools = new MyTools();
String masg = String.valueOf(msg);
// clientIp客户端链接ip
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
System.out.println("客户端发送数据: " + msg.toString() + clientIp);
System.out.println("客户端iP: " + clientIp);
// 192.168.1.200 为 数据下发数据板 所有 之获取这一个静态变量 用作下放得下发
if("192.168.1.200".equals(clientIp)){
TcpRequestHandler.ctx = ctx; // 存储 ChannelHandlerContext
}
// 截取报头
// String str = String.valueOf(msg).substring(0,2);
// if (!"12".equals(str) && !"27".equals(str)){
// // 回应客户端 注册信息 返回27
// myTools.writeToClient("2700AA0fA0005533221105", ctx, "回复客户端 27 ");
// // 调用等待 TimeUnit 延迟 1秒 下发信息
// this.JavaDelayExample();
// // 下发信息 客户端获取控制列表
// myTools.writeToClient("2A1A00AA00555533221105", ctx, "回复客户端获取控制列表");
// // 调用等待 TimeUnit 延迟 1秒 下发信息
// this.JavaDelayExample();
// // 下发信息 回复客户端布防发送
// myTools.writeToClient("2400AA00553322110500020001", ctx, "回复客户端布防发送");
//
// }else if ("12".equals(str)){
// // 下发信息 回复客户端布防发送
// myNettySeviceImpl.transportVersionData(msg.toString(),clientIp);
// }
}
/**
* 客户端与服务端第一次建立连接时 执行
*
* @param ctx
* @throws Exception
*/
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
// 获取客户端 ip
ctx.channel().read();
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
// //判断 ip不为空 去掉也可以 无所谓这块
// if (clientIp != null) {
// // 拿取当前 ip 去库里查询 是否存在此 ip 返回 0 则 无此ip 返回 1 则 有此 ip
// String ye = roomScheduleService.selectQueryIp(clientIp);
// if ("0".equals(ye)) {
// // 无此 ip 则 新增此 ip
// roomScheduleService.insertIp(clientIp);
// }
// }
//此处不能使用ctx.close(),否则客户端始终无法与服务端建立连接 clientIp 客户端ip
System.out.println("channelActive: " + clientIp + ctx.name());
}
/**
* 向设备发送数据
*/
public void distributionControl(String command) throws Exception {
System.out.println("调用进来了~~~~");
MyTools myTools = new MyTools();
// 延迟一秒
this.JavaDelayExample();
// 获取适当的 ChannelHandlerContext 对象,例如通过构造函数或方法参数传递
// 在其他方法中使用存储的 ChannelHandlerContext
if (TcpRequestHandler.ctx != null) {
myTools.writeToClient(command, TcpRequestHandler.ctx, "回复客户端");
}
System.out.println("正在发送设备命令:" + command);
}
/**
* TimeUnit 延迟 1秒
*/
public void JavaDelayExample() {
try {
System.out.println("Start..." + new Date());
// delay 5 seconds
TimeUnit.SECONDS.sleep(2);
System.out.println("End..." + new Date());
} catch (InterruptedException e) {
System.err.format("IOException: %s%n", e);
}
}
/** JavaDelayExample
* 下方 为暂时未使用方法
* ---------------------------------------------------------------------------------------------
*/
/**
* 从客户端收到新的数据、读取完成时调用
*
* @param ctx
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
System.out.println("channel Read Complete");
ctx.flush();
}
/**
* 当出现 Throwable 对象才会被调用,即当 Netty 由于 IO 错误或者处理器在处理事件时抛出的异常时
*
* @param ctx
* @param cause
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
System.out.println("exceptionCaught");
cause.printStackTrace();
ctx.close();//抛出异常,断开与客户端的连接
}
/**
* 客户端与服务端 断连时 执行
*
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
ctx.close(); //断开连接时,必须关闭,否则造成资源浪费,并发量很大情况下可能造成宕机
System.out.println("channelInactive: " + clientIp);
}
/**
* 服务端当read超时, 会调用这个方法
*
* @param ctx
* @param evt
* @throws Exception
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
super.userEventTriggered(ctx, evt);
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
ctx.close();//超时时断开连接
System.out.println("userEventTriggered: " + clientIp);
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) {
System.out.println("channelRegistered");
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) {
System.out.println("channelUnregistered");
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) {
System.out.println("channelWritabilityChanged");
}
}