加粗样式Netty 是一个强大的网络框架,能够高效处理 TCP 数据通讯,适用于各种高并发、高性能的网络应用。
使用 TCP 通信的优点
可靠性:TCP 是面向连接的协议,保证数据包的顺序到达和数据的完整性。
流量控制:TCP 提供了流量控制和拥塞控制机制,能够适应不同的网络条件。
广泛应用:适用于各种需要高可靠性的数据传输场景,如文件传输、电子邮件、Web 服务等。
双向通信:TCP 支持全双工通信,允许客户端和服务器同时发送和接收数据。
1. 确认通讯数据帧格式(十六进制)
| 格式 | 帧头 | 帧长 | 校验码 | 循环码 | 通讯渠道 | 设备id码 | 数据方向 | 设备类型 | 时间 | 命令数据 | 帧尾 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 字节数 | 2byte | 2byte | 2byte | 4byte | 1byte | 12byte | 1byte | 1byte | 12byte | 2byte+nbyte | 2byte |
| 注释 | 通知对方本帧数据包开始传输 | 表示本帧数据包(不含帧头)长度为0x0042 | CRC16的校验码,0x54为高字节,0x36为低字节 | 表示是开机以来第0x00000005次帧包发送 | 表示是通过以太网渠道 | 设备ID码(3个WORD)是0x01020304,0x05060708,0x090a0b0c | 数据方向是设备上传到平台 | 表示是行人过街设备 | 表示2024年1月8日,9时12分36秒 | 见指令概述部分 | 通知对方本帧数据包结束传输 |
之前讨论过似乎这种数据帧的格式不合理,指出帧长应该放在帧尾处…具体情况具体分析,该通讯数据帧应该软硬件协商一致后进行代码开发。
2. 建立服务端对象
2.1 导入netty依赖
<!-- netty -->
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.25.Final</version>
</dependency>
</dependencies>
2.2 配置文件指定测试端口和正式端口
netty:
port: xxxxx
2.3 创建netty服务端
在 Spring Boot 应用启动的同时,异步启动一个 Netty 服务器
@SpringBootApplication
public class SentryServiceApplication implements CommandLineRunner {
@Value("${netty.port}")
private int nettyPort;
@Override
public void run(String... args) throws Exception {
new Thread(() -> {
try{
new NettyServer(nettyPort).run();
} catch (Exception e){
e.printStackTrace();
}
}).start();
}
}
NettyServer 类的主要职责是启动和配置 Netty 服务器,包括创建线程组、配置服务器引导、绑定端口等。该类封装了 Netty 服务器的启动逻辑和生命周期管理。
/**
* netty服务器,主要用于与客户端通讯
*/
public class NettyServer {
private final int port; //监听端口
//连接map
public static Map<String, ChannelHandlerContext> map = new HashMap<String, ChannelHandlerContext>();
public NettyServer(int port) {
this.port = port;
}
//编写run方法,处理客户端的请求
public void run() throws Exception {
//创建两个线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(); //8个NioEventLoop
try {
ServerBootstrap b = new ServerBootstrap();
/*将线程组传入*/
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)/*指定使用NIO进行网络传输*/
.option(ChannelOption.SO_BACKLOG, 128) // 设置线程的连接个数
.childOption(ChannelOption.SO_KEEPALIVE, true)
/*服务端每接收到一个连接请求,就会新启一个socket通信,也就是channel,
所以下面这段代码的作用就是为这个子channel增加handle*/
.childHandler(new NettyServerInitializer());
System.out.println("netty服务器启动成功(port:" + port + ")......");
/*异步绑定到服务器,sync()会阻塞直到完成*/
ChannelFuture channelFuture = b.bind(port).sync();
//监听关闭 /*阻塞直到服务器的channel关闭*/
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
/**关闭线程组*/
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
3.管理Netty会话
NettySession和NettySessionManager类用于管理客户端会话
NettySession
public class NettySession {
/**
*channel id
*/
private String channelId;
/**
* 业务关联的key
*/
private String bizKey;
/**
* 真正的连接对象
*/
private Channel channel = null;
/**
* 连接上下文
*/
private ChannelHandlerContext ctx = null;
private LocalDateTime createTime;
private LocalDateTime lastTime;
/**
* 消息流水号 word(2字节) 按发送顺序从 0 开始循环累加
*/
private int currentFlowId = 0;
public static NettySession build(String bizKey, ChannelHandlerContext ctx) {
NettySession session = new NettySession();
Channel channel = ctx.channel();
session.bizKey = bizKey;
session.channelId = channel.id().asLongText();
session.ctx = ctx;
session.channel = channel;
session.createTime = LocalDateTime.now();
session.lastTime = session.createTime;
return session;
}
public boolean isActive() {
if (ctx == null) {
return false;
}
if (channel == null) {
return false;
}
return channel.isActive();
}
public boolean isWritable() {
if (ctx == null) {
return false;
}
if (channel == null) {
return false;
}
return channel.isWritable();
}
public boolean writeAndFlush(byte[] bytes) {
if (bytes == null) {
return false;
}
if (bytes.length == 0) {
return false;
}
if (!isActive()) {
return false;
}
if (!isWritable()) {
return false;
}
ByteBuf retBuf = channel.alloc().buffer(bytes.length);
retBuf.writeBytes(bytes);
channel.writeAndFlush(retBuf);
return true;
}
public boolean writeAndFlush(String msg){
if(StrUtil.isBlank(msg)) {
return false;
}
if (msg.length() == 0) {
return false;
}
if (!isActive()) {
return false;
}
if (!isWritable()) {
return false;
}
channel.writeAndFlush(msg);
return true;
}
/**
* 关闭连接
*/
public void close() {
try {
ctx.close();
} catch (Exception ex) {
log.error(ex.getLocalizedMessage(), ex);
}
}
}
NettySessionManager
@Slf4j
public class NettySessionManager {
private static Map<String, NettySession> sessionMap = new ConcurrentHashMap<>();
private static Map<String, Set<String>> bizMap = new ConcurrentHashMap<>();
public static void addSession(NettySession session) {
if (session == null) {
return;
}
String bizKey = session.getBizKey();
if (StrUtil.isBlank(bizKey)) {
return;
}
String sessionId = session.getChannelId();
if (StrUtil.isBlank(sessionId)) {
return;
}
sessionMap.put(sessionId, session);
log.info("-------------新增客户端连接【addSession】,sessionId:{},session:{}----------------", sessionId, session);
bizMap.compute(bizKey, (key, sessionIdSet) -> {
if (sessionIdSet == null) {
sessionIdSet = ConcurrentHashMap.newKeySet();
}
sessionIdSet.add(sessionId);
log.info("-------------新增客户端连接【addSession】,bizMap:{},bizKey:{},sessionIdSet:{}----------------", bizMap, key, sessionIdSet);
return sessionIdSet;
});
}
public static void removeSession(NettySession session) {
if (session == null) {
return;
}
removeSession(session.getChannel());
}
public static void removeSession(ChannelHandlerContext ctx) {
if (ctx == null)


1万+

被折叠的 条评论
为什么被折叠?



