小码问答,有问必答!

为什么每次通过这个send方法只能发送一次数据,再发送就阻塞了

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.util.CharsetUtil;
import lombok.SneakyThrows;
import java.net.InetSocketAddress;
public class BootNettyUdpClient {
    private int port;
    private String server;
    public BootNettyUdpClient(String server,int port){
          this.server = server;
          this.port = port;

    }

    @SneakyThrows
    public void send(String data) {
        data = AES.encryptAES(data);
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {
            Bootstrap clientBootstrap = new Bootstrap();
            clientBootstrap = clientBootstrap.group(eventLoopGroup);
            clientBootstrap = clientBootstrap.channel(NioDatagramChannel.class);
            clientBootstrap = clientBootstrap.option(ChannelOption.SO_BROADCAST, true);
            clientBootstrap = clientBootstrap.handler(new BootNettyUdpClientSimpleChannelInboundHandler());
            Channel channel = clientBootstrap.bind(0).sync().channel();
            channel.writeAndFlush(new DatagramPacket(
                    Unpooled.copiedBuffer(data, CharsetUtil.UTF_8),
                    new InetSocketAddress(server,port)));
            System.out.println("channnel id = "+channel.id().toString());
            channel.closeFuture().await();
        } catch (Exception e) {
            // TODO: handle exception
        } finally {
            System.out.println("netty client udp close!");
            eventLoopGroup.shutdownGracefully();
        }
    }

}


JavaEE

收藏

1个回答

我要回答

  • author
    牛叔叔 2024-12-05 14:44

    在你提供的 BootNettyUdpClient 类中,send 方法每次调用都会创建一个新的 EventLoopGroupBootstrap 实例,并且绑定一个新的 Channel 到一个临时的本地端口(通过 bind(0)),然后发送数据包。这种设计有几个问题,其中最主要的是每次发送都会启动一个新的 Netty 客户端实例,并且在发送完数据后等待这个客户端实例关闭(channel.closeFuture().await();)。

    这就是为什么你只能发送一次数据,并且再次调用 send 方法时会阻塞:

    1. 资源消耗:每次发送都创建和销毁大量的 Netty 组件,这是非常低效的。

    2. 阻塞:channel.closeFuture().await(); 会阻塞当前线程,直到这个 Channel 关闭。由于你在发送完数据后立即关闭了 Channel,所以这个方法调用会等待直到 Netty 完成所有的清理工作。这意味着在 send 方法返回之前,不会有其他数据被发送。

    3. 无法重用连接:UDP 本身是无连接的协议,但 Netty 的 DatagramPacket 允许你在同一个 Channel 上发送多个数据包到不同的目标地址。然而,你的代码每次都会创建一个新的 Channel,因此无法重用之前的连接(尽管 UDP 实际上没有“连接”的概念,但这里指的是 Netty 的 Channel 实例)。

    为了解决这个问题,你可以考虑以下方案:

    • 重用 EventLoopGroup 和 Channel:将 EventLoopGroup 和 Channel 的创建移到 BootNettyUdpClient 类的构造函数或某个初始化方法中,然后在 send 方法中只使用这些已经创建好的资源来发送数据。

    • 移除 channel.closeFuture().await();:由于你不需要等待客户端关闭,可以直接发送数据后返回。Netty 会自动管理 Channel 的生命周期,特别是在使用无连接的 UDP 协议时。

    下面是一个简化的示例,展示了如何修改你的代码以重用 EventLoopGroup 和 Channel

    java复制代码


    public class BootNettyUdpClient {

    private final int port;

    private final String server;

    private final EventLoopGroup eventLoopGroup;

    private final Channel channel;



    public BootNettyUdpClient(String server, int port) throws InterruptedException {

    this.server = server;

    this.port = port;

    this.eventLoopGroup = new NioEventLoopGroup();



    Bootstrap clientBootstrap = new Bootstrap();

    clientBootstrap.group(eventLoopGroup)

    .channel(NioDatagramChannel.class)

    .option(ChannelOption.SO_BROADCAST, true)

    .handler(new BootNettyUdpClientSimpleChannelInboundHandler());



    this.channel = clientBootstrap.bind(0).sync().channel();

    }



    @SneakyThrows

    public void send(String data) {

    data = AES.encryptAES(data);

    channel.writeAndFlush(new DatagramPacket(

    Unpooled.copiedBuffer(data, CharsetUtil.UTF_8),

    new InetSocketAddress(server, port)));

    System.out.println("Data sent to " + server + ":" + port);

    }



    public void shutdown() throws InterruptedException {

    channel.close().sync();

    eventLoopGroup.shutdownGracefully().sync();

    System.out.println("netty client udp close!");

    }

    }

    在这个修改后的版本中,EventLoopGroup 和 Channel 只在构造函数中创建一次,并且提供了一个 shutdown 方法来优雅地关闭这些资源。现在你可以多次调用 send 方法来发送数据,而不会遇到阻塞或资源耗尽的问题。记得在不再需要客户端时调用 shutdown 方法来释放资源。