Netty客户端

Netty TCP客户端实现与数据发送

Netty客户端

TCPClient

public class NettyTcpClient {

    /**
     * TCP服务端IP(MD)
     */
    @Value("${mdServer.ip}")
    private String ip;

    /**
     * TCP服务端端口号(MD)
     */
    @Value("${mdServer.port}")
    private int port;

    @Resource
    private UnvChannelInitialize channelInitialize;

    private Bootstrap bootstrap = new Bootstrap();

    private List<Channel> channelList = new ArrayList<>();

    private final AtomicInteger index = new AtomicInteger();

    /**
     * 初始化TCP客户端
     */
    public void initClient() {
        int threadNum = Runtime.getRuntime().availableProcessors();
        if (threadNum <= 0) {
            threadNum = 4;
        }
        EventLoopGroup loopGroup = new NioEventLoopGroup();
        try {
            bootstrap.group(loopGroup)
                    .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(ip, port))
                    .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    // 设置ChanelOutBoundBuffer缓冲区最低水位为64kb,最高水位为256kb
                    .option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(32 * 1024, 256 * 1024))
                    .handler(channelInitialize);
            // 创建多个连接
            for (int i = 0; i < threadNum; i++) {
                ChannelFuture channelFuture = bootstrap.connect().sync();
                channelList.add(channelFuture.channel());
            }
            log.info("{} 服务端连接成功", ip + ":" + port);
        } catch (Exception e) {
            log.error("TCP客户端连接{}服务端失败:", ip + ":" + port, e);
        }
    }

    /**
     * 获取Chanel实例
     *
     * @return channel
     */
    public Channel getNextChannel() {
        return getFirstActiveChannel(0);
    }

    /**
     * 获取Chanel实例
     *
     * @param count
     * @return channel
     */
    public Channel getFirstActiveChannel(int count) {
        Channel channel = channelList.get(Math.abs(index.getAndIncrement() % channelList.size()));
        if (!channel.isActive()) {
            // 重连
            reConnect(channel);
            if (count > channelList.size()) {
                throw new RuntimeException("暂无空闲的连接通道");
            }
            return getFirstActiveChannel(count + 1);
        }
        return channel;
    }

    /**
     * 重连
     *
     * @param channel
     */
    public void reConnect(Channel channel) {
        log.info("{} 通道连接断开,正在重连...", channel);
        synchronized (channel) {
            if (channelList.indexOf(channel) == -1) {
                return;
            }
            channelList.set(channelList.indexOf(channel), bootstrap.connect().channel());
        }
    }

ChannelInitalize

public class UnvChannelInitialize extends ChannelInitializer {

    @Resource
    private UnvHeartBeatHandler heartBeatHandler;

    @Resource
    private SendMsgHandler sendMsgHandler;

    @Override
    protected void initChannel(Channel channel) throws Exception {
        channel.pipeline()
                .addLast(new StringEncoder(Charset.forName("UTF-8")))
                .addLast(new StringDecoder(Charset.forName("UTF-8")))
                //自定义解码器支持宇视协议
                .addLast("unvMsgEnCode", new UnvEncode())
                .addLast(heartBeatHandler)
                .addLast(sendMsgHandler);
    }
}

Handler


```java
public class SendMsgHandler extends SimpleChannelInboundHandler {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (String pathKey : BlockingQueueConfig.queueMap.keySet()) {
            SendTask task = new SendTask(pathKey);
            InitServer.threadPoolExecutor.execute(task);
        }
        ctx.fireChannelActive();
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 不作处理
        ReferenceCountUtil.release(msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("通道连接异常:", cause);
        ctx.close();
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.error("{}:通道连接断开", ctx.channel());
        ctx.close();
    }

    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        super.channelWritabilityChanged(ctx);
    }
}
### 发送数据

```java
public class SendTask implements Runnable {

    private FilePathKeyProperties pathKeyProperties;

    private NettyTcpClient tcpClient;

    private Map<String, FilePathKeyProperties.PathProperties> branchPath;

    private String pathKey;

    /**
     * 宇视物联协议报文命令字
     */
    private int command;

    public SendTask(String pathKey) {
        this.pathKeyProperties = ApplicationContextUtil.getBean(FilePathKeyProperties.class);
        this.tcpClient = ApplicationContextUtil.getBean(NettyTcpClient.class);
        this.branchPath = pathKeyProperties.getBranchPath();
        this.pathKey = pathKey;
    }

    @Override
    public void run() {
        File file = null;
        while (true) {
            try {
                BlockingQueue<File> fileQueue = BlockingQueueConfig.queueMap.get(pathKey);
                log.info("{}:阻塞队列大小:{}", pathKey, fileQueue.size());
                file = fileQueue.take();
                if (null == file || !file.exists()) {
                    continue;
                }
                String fileName = file.getName();
                // 读取文件内容
                byte[] fileData = FileUtils.readFileToByteArray(file);
                // 实际文件中数据长度
                String length = fileName.split("\\.")[0].split(ConstantUtil.SPLITTER)[4];
                int fileDataLength = Integer.parseInt(length);
                // 文件内容为空或者长度为0,不发送
                if (null == fileData || fileData.length == 0) {
                    file.delete();
                    continue;
                }
                if (fileData.length < fileDataLength) {
                    log.info("读取到的数据大小:{}, 实际文件数据大小:{}", fileData.length, fileDataLength);
                    renameFile(file);
                    continue;
                }

                command = branchPath.get(pathKey).getCommand();
                byte[] sendData = UnvProtocolUtil.buildUnvMsg(fileData, command);
                log.info("{}:数据大小:{}", branchPath.get(pathKey), sendData.length);
                Channel channel = tcpClient.getNextChannel();
                if (channel.isWritable()) {
                    File tempFile = file;
                    channel.writeAndFlush(sendData).addListener(future -> {
                        if (!future.isSuccess()) {
                            log.error("{}文件数据发送数据失败,重命名为.fail文件");
                            renameFile(tempFile);
                        } else {
                            // 数据发送成功后,删除文件
                            log.debug("{}:数据发送成功", tempFile.getName());
                            if (!tempFile.delete()) {
                                log.error("{}:文件删除失败", tempFile.getPath());
                            }
                        }
                    });
                } else {
                    renameFile(file);
                }
            } catch (InterruptedException e) {
                log.error("数据出队异常:", e);
            } catch (IOException e) {
                log.error("读取文件内容异常:", e);
                renameFile(file);
            } catch (Exception e) {
                log.error("数据发送异常:", e);
                renameFile(file);
            }
        }
    }

    /**
     * 将.temp文件重命名为.fail文件
     *
     * @param file 文件
     */
    private void renameFile(File file) {

        Runnable renameFailFile = () -> {
            try {
                int times = 0;
                String filePath = file.getPath();
                filePath = filePath.replace(ConstantUtil.TEMP_SUFFIX, ConstantUtil.FAIL_SUFFIX);
                File failFile = new File(filePath);
                while (true) {
                    // 文件重命名成功,跳出循环
                    if (file.renameTo(failFile)) {
                        break;
                    }
                    // 文件重命名失败,延时0.5秒重试,失败三次后,文件删除
                    if (times >= 2) {
                        log.info("{}文件三次重命名失败,删除", filePath);
                        file.delete();
                        break;
                    }
                    times++;
                    TimeUnit.MILLISECONDS.sleep(500);
                }
                log.info("将.temp文件重命名为{}", filePath);
            } catch (Exception e) {
                log.error("{}文件重命名为fail文件异常:", e);
            }
        };
        InitServer.failFileThreadPoolExecutor.execute(renameFailFile);
    }
}
### Netty 客户端使用教程 Netty 是一个高性能、异步事件驱动的网络应用框架,适用于开发可维护的高性能协议服务端和客户端[^1]。下面介绍如何创建并运行一个基本的 Netty 客户端。 #### 创建 Netty 客户端实例 要建立一个简单的 Netty 客户端程序,首先需要引入必要的依赖项到项目的构建工具中(Maven 或 Gradle),接着编写如下所示的基础代码: ```java import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; public class SimpleClient { public static void main(String[] args) throws Exception { String host = "localhost"; int port = 8080; EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); // (1) b.group(group) // (2) .channel(NioSocketChannel.class) // (3) .handler(new ClientInitializer()); // (4) ChannelFuture f = b.connect(host, port).sync(); // (5) f.channel().closeFuture().sync(); // (6) } finally { group.shutdownGracefully(); // (7) } } } ``` 上述代码片段解释了启动过程的关键部分: - `Bootstrap` 对象用于设置客户端参数; - 设置线程池为 `NioEventLoopGroup` 类型; - 指定通道类型为基于 NIO 的套接字; - 添加初始化处理器链; - 尝试连接至指定地址的服务端; - 阻塞当前线程直到连接关闭; - 清理资源。 #### 处理异常与错误 对于可能出现的各种异常状况,比如 I/O 错误或是内存不足等问题,在 Netty 中可以通过覆盖特定的方法来进行捕获和响应。例如,当检测到远程主机已断开时,将会触发 `channelInactive()` 方法;而遇到未预见的问题,则会进入 `exceptionCaught()` 函数体内处理逻辑[^3]。 为了避免重复尝试重新连接的情况发生——即两个回调函数内均存在相同的重连操作——建议只在一个地方实施此行为,并确保其他位置不会再次发起不必要的请求[^4]。 #### 端口冲突排查指南 如果应用程序无法绑定预期使用的端口,可能是由于该端口已被另一个进程占用所致。此时应按照以下步骤进行诊断: 1. 参考官方文档确认默认监听端口; 2. 利用操作系统命令行工具查询是否存在竞争情况; 3. 调整配置文件里的设定值或终止干扰性的后台任务以解决问题[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值