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);
    }
}
在Spring Boot中,使用Netty接收数据转发数据可以通过以下步骤完成: 1. 首先,我们需要在Spring Boot项目中引入Netty的依赖。可以通过在pom.xml文件中添加以下代码来实现: ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.32.Final</version> </dependency> ``` 2. 接下来,我们需要创建一个Netty服务器来接收数据。可以创建一个新的类来实现服务器的启动和配置。例如,我们可以创建一个名为NettyServer的类: ```java @Component public class NettyServer { private final int port = 8080; // 启动Netty服务器 @PostConstruct public void startServer() { EventLoopGroup bossGroup = new NioEventLoopGroup(); // 接收进来的连接 EventLoopGroup workerGroup = new NioEventLoopGroup(); // 处理已接收连接的流量 try { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // 使用NIO传输 .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new NettyServerHandler()); // 处理网络IO事件 } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture future = bootstrap.bind(port).sync(); // 绑定端口,开始接收进来的连接 future.channel().closeFuture().sync(); // 关闭服务器通道 } catch (Exception e) { e.printStackTrace(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } } ``` 3. 然后,我们需要创建一个Netty服务器处理程序来处理接收到的数据并进行相应的转发操作。例如,我们可以创建一个名为NettyServerHandler的类: ```java public class NettyServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 接收到数据后的处理逻辑 // 在这里可以根据实际需求进行数据转发操作或其他处理 // 示例:将接收到的数据转发给其他服务器 String data = (String) msg; String forwardedData = forwardData(data); ctx.writeAndFlush(forwardedData); } private String forwardData(String data) { // 实现数据转发的逻辑 // 可以使用其他网络客户端或第三方API进行转发操作 return "Forwarded data: " + data; } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } } ``` 4. 最后,我们可以编写用于接收和处理Netty服务器返回结果的代码。可以在Spring Boot中的任何组件中使用Netty的客户端来处理转发后的数据。例如,我们可以在控制器中编写以下代码: ```java @RestController public class NettyClientController { @GetMapping("/forwardData") public String forwardData() { // 创建Netty客户端并发送数据服务器 EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 接收服务器返回的转发数据 String forwardedData = (String) msg; // 处理转发后的数据 } }); } }); ChannelFuture future = bootstrap.connect("localhost", 8080).sync(); // 连接到服务器 future.channel().writeAndFlush("Data to be forwarded"); // 发送需要转发数据 future.channel().closeFuture().sync(); // 关闭客户端通道 } catch (Exception e) { e.printStackTrace(); } finally { group.shutdownGracefully(); } return "Data forwarded successfully"; } } ``` 通过以上步骤,我们可以在Spring Boot中使用Netty接收数据并进行转发操作。根据实际需求,可以在Netty服务器处理程序中添加更多的逻辑来实现更复杂的数据转发操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值