Netty转发数据

环境:win10、jdk17、springboot3

1.Netty概述

        Netty 是一个基于 Java 的异步事件驱动的网络应用框架,用于快速开发高性能、高可靠性的网络服务器和客户端。它提供了对各种协议(如 HTTP、TCP、UDP)的支持,并通过其强大的网络编程能力,简化了网络应用的开发过程。

2.Netty使用

https://netty.io/wiki/user-guide-for-4.x.htmlicon-default.png?t=N7T8https://netty.io/wiki/user-guide-for-4.x.html

2.1 导入依赖

<!-- netty -->
<dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>

        <version>4.1.84.Final</version>
</dependency>

        如果有用到redis,记得去掉redis自带的netty

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>netty-transport</artifactId>
                    <groupId>io.netty</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>netty-handler</artifactId>
                    <groupId>io.netty</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>netty-common</artifactId>
                    <groupId>io.netty</groupId>
                </exclusion>
            </exclusions>
        </dependency>

2.2 Netty 服务器启动类

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;

@Slf4j
@Component
public class NettyServer {

    public void start(InetSocketAddress address) {
        //配置服务端的NIO线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap()
                    .group(bossGroup, workerGroup)  // 绑定线程池
                    .channel(NioServerSocketChannel.class)
                    .localAddress(address)
                    .childHandler(new NettyServerChannelInitializer())//编码解码
                    .option(ChannelOption.SO_BACKLOG, 12800)  //服务端接受连接的队列长度,如果队列已满,客户端连接将被拒绝
                    .childOption(ChannelOption.SO_KEEPALIVE, true);  //保持长连接,2小时无数据激活心跳机制
            // 绑定端口,开始接收进来的连接
            ChannelFuture future = bootstrap.bind(address).sync();
            log.info("netty服务器开始监听端口:" + address.getPort());
            //关闭channel和块,直到它被关闭
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

2.4 服务端初始化类

import io.netty.channel.ChannelInitializer;
import io.netty.channel.FixedRecvByteBufAllocator;
import io.netty.channel.socket.SocketChannel;

/**
 * @Author luobei
 **/
public class NettyServerChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) throws Exception {

        // 设置接收缓冲区大小
        channel.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(2048));

        // 添加自定义的解码器
        channel.pipeline().addLast("decoder", new Decoder());
        // 添加自定义的编码器
        channel.pipeline().addLast("encoder", new Encoder());
        // 添加处理器
        channel.pipeline().addLast(new NettyServerHandler());
    }
}

2.5 解码器

import io.netty.buffer.ByteBuf;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.ByteToMessageDecoder;

 import java.util.List;

 /**
  * 解码器
  */

 public class Decoder extends ByteToMessageDecoder {

     @Override
     protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
         //创建字节数组,buffer.readableBytes可读字节长度
         byte[] b = new byte[buffer.readableBytes()];
         //复制内容到字节数组b
         buffer.readBytes(b);
         out.add(toHexString1(b));
     }

     private String toHexString1(byte[] b) {
         StringBuffer buffer = new StringBuffer();
         for (int i = 0; i < b.length; ++i) {
             buffer.append(toHexString1(b[i]));
         }
         return buffer.toString();
     }

     private String toHexString1(byte b) {
         String s = Integer.toHexString(b & 0xFF);
         if (s.length() == 1) {
             return "0" + s;
         } else {
             return s;
         }
     }
 }

2.6 编码器

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

/**
 * 编码器
 */
public class Encoder extends MessageToByteEncoder<String> {
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, String s, ByteBuf byteBuf) throws Exception {
        //将16进制字符串转为数组
        byteBuf.writeBytes(CodeUtil.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;
    }
}

2.7 netty消息处理类

import com.nettydemo.entity.NettyChannelDto;
import com.nettydemo.service.ProtocolFactoryService;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.net.InetSocketAddress;

@Slf4j
@Component
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    private static NettyServerHandler nettyServerHandler;

    @PostConstruct
    public void init() {
        nettyServerHandler = this;
    }

    /**
     * @param ctx
     * @DESCRIPTION: 有客户端连接服务器会触发此函数
     * @return: void
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        //保存通道信息
        log.info(ctx.toString());
    }

    /**
     * @param ctx
     * @DESCRIPTION: 有客户端终止连接服务器会触发此函数
     * @return: void
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        delChannel(ctx, 1);
    }

    /**
     * @param ctx
     * @DESCRIPTION: 有客户端发消息会触发此函数
     * @return: void
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String cmd = msg.toString();
        log.info("客户端发消息:{}", cmd);
        /**
         *  下面可以解析数据,保存数据。
         */
        ThreadExecutor.execute(() -> {
            log.info(ctx+msg.toString());
            //处理数据
        });

    }

    private NettyChannelDto getNettyChannelDto(ChannelHandlerContext ctx){

        NettyChannelDto dto = NettyContextUtil.CHANNEL_MAP.get(ctx.channel().id());
        if(dto==null){
            return updateChannel(ctx);
        }
        return dto;
    }

    private NettyChannelDto updateChannel(ChannelHandlerContext ctx){
        //获取连接通道唯一标识
        ChannelId channelId = ctx.channel().id();
        NettyChannelDto dto = NettyContextUtil.CHANNEL_MAP.get(channelId);
        if(dto==null){
            InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
            String clientIp = insocket.getAddress().getHostAddress();
            int clientPort = insocket.getPort();

            //有新的连接,将连接信息写入缓存
            NettyChannelDto nettyChannelDto = new NettyChannelDto();
            nettyChannelDto.setContext(ctx);
            nettyChannelDto.setClientIp(clientIp);
            nettyChannelDto.setClientPort(clientPort);
            NettyContextUtil.CHANNEL_MAP.put(channelId, nettyChannelDto);
            log.info("客户端【" + channelId + "】连接netty服务器[IP:" + clientIp + "--->PORT:" + clientPort + "]");
            return nettyChannelDto;
        }
        return dto;
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

        String socketString = ctx.channel().remoteAddress().toString();

        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state() == IdleState.READER_IDLE) {
                log.info("Client: " + socketString + " READER_IDLE 读超时");
                ctx.disconnect();
            } else if (event.state() == IdleState.WRITER_IDLE) {
                log.info("Client: " + socketString + " WRITER_IDLE 写超时");
                ctx.disconnect();
            } else if (event.state() == IdleState.ALL_IDLE) {
                log.info("Client: " + socketString + " ALL_IDLE 总超时");
                ctx.disconnect();
            }
        }
    }

    /**
     * @param ctx
     * @DESCRIPTION: 发生异常会触发此函数
     * @return: void
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("异常", cause.getMessage());
        delChannel(ctx, 2);
        ctx.close();
    }

    /**
     * 通道失去连接时调用
     * @param ctx
     * @param type 通道断开类型:1.正常断开(正常失去连接) 2.异常断开(服务端捕获到异常,断开连接)
     */
    public void delChannel(ChannelHandlerContext ctx, int type){
        switch (type){
            case 1://服务器正常断开连接

                InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();

                String clientIp = insocket.getAddress().getHostAddress();

                ChannelId channelId = ctx.channel().id();
                //有客户端断开连接,将连接信息移除
                log.info("客户端【" + channelId + "】退出netty服务器[IP:" + clientIp + "--->PORT:" + insocket.getPort() + "]");
                break;
            case 2:
                //连接错误清除通道
        }
    }
}
import com.nettydemo.entity.NettyChannelDto;
import io.netty.channel.ChannelId;
import org.apache.logging.log4j.util.Strings;
import java.util.concurrent.ConcurrentHashMap;

public class NettyContextUtil {

    /**
     * 保存连接服务端的通道数量
     */
    public static final ConcurrentHashMap<ChannelId, NettyChannelDto> CHANNEL_MAP = new ConcurrentHashMap<>();
}

2.8 通道信息实体类

@Data
public class NettyChannelDto {

    //通道连接
    private ChannelHandlerContext context;
    //客户端ip
    private String clientIp;
    //端口
    private int clientPort;
    //回复命令
    private String cmd;
    //密码
    private String password;
}

2.9 netty客户端(用来转发消息)

package com.nettydemo.util;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class NettyClient {

    private ChannelFuture f;
    private final EventLoopGroup workerGroup = new NioEventLoopGroup();
    private final String host;
    private final int port;

    public NettyClient(String host, int port) {
        this.host = host;
        this.port = port;
        connect();
    }

    public void sendMessage(String message) throws Exception {
        if (f == null || !f.channel().isActive()) {
            log.warn("连接未建立或连接已失效,正在尝试重新连接");
            connect();
        }

        if (f != null && f.channel().isActive()) {
            f.channel().writeAndFlush(message).addListener((ChannelFuture future) -> {
                if (future.isSuccess()) {
                    log.info("消息已发送: {}", message);
                } else {
                    log.error("消息发送失败", future.cause());
                }
            }).sync();
        } else {
            log.error("无法发送消息,连接未建立");
        }
    }

    private void connect() {
        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup);
            b.channel(NioSocketChannel.class);
            b.option(ChannelOption.SO_KEEPALIVE, true);
            b.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) {
                    ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
                    ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));
                }
            });
            f = b.connect(host, port).sync();
            log.info("连接成功: {}:{}", host, port);
        } catch (Exception e) {
            log.error("连接失败", e);
            if (workerGroup != null && !workerGroup.isShutdown()) {
                workerGroup.shutdownGracefully();
            }
        }
    }

    public void shutdown() {
        if (workerGroup != null && !workerGroup.isShutdown()) {
            workerGroup.shutdownGracefully();
            log.info("工作组已关闭");
        }
    }
}

         用的地方直接调用方法(最好只创建一次nett客户端,保持一个连接)

private static NettyClient nettyClient;

@PostConstruct
public void init() {
    nettyClient = new NettyClient(HOST, PORT);
}

try {
    nettyClient.sendMessage(msg);
} catch (Exception e) {
    throw new RuntimeException(e);
}

 2.10 netty启动监听器

        在 Spring Boot 应用程序启动后自动启动 Netty 服务器

@Component
public class NettyStartListener implements ApplicationRunner {

    @Autowired
    private NettyServer nettyServer;

    @Value("${nettyserver.ip}")
    private String SERVER_IP;

    @Value("${nettyserver.port}")
    private Integer SERVER_PORT;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        InetSocketAddress address=new InetSocketAddress(SERVER_IP,SERVER_PORT);
        nettyServer.start(address);
    }
}
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值