亲测!超详细的使用netty进行tcp数据通信的流程,附带完整的源代码

加粗样式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会话

NettySessionNettySessionManager类用于管理客户端会话

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
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值