Netty客户端抛出UnsupportedOperationException问题排查及修复
问题
我正在学习Netty教程,编写了服务端与客户端代码。系统中EPOLL不可用,服务端运行正常,但客户端启动时抛出java.lang.UnsupportedOperationException异常,堆栈信息指向io.netty.channel.socket.nio.NioServerSocketChannel.doConnect(NioServerSocketChannel.java:178)。我的代码与教程完全一致,使用Netty版本4.1.82.Final、Java 17(openjdk 17.0.3-oracle)。
服务端代码:
package yt.ingrim.p01; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollServerSocketChannel; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; public class Server { public static class MySimpleChannelInboundHandler extends SimpleChannelInboundHandler<Object> { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); System.out.println("channel Connected --> " + ctx.channel()); } @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { } } public static final boolean EPOLL = Epoll.isAvailable(); Class channelClazz = EPOLL? EpollServerSocketChannel.class : NioServerSocketChannel.class; public Server() throws Exception { EventLoopGroup eventLoopGroup = EPOLL? new EpollEventLoopGroup(): new NioEventLoopGroup(); try { new ServerBootstrap() .group(eventLoopGroup) .channel(channelClazz) .childHandler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel ch) throws Exception { ChannelPipeline cpl = ch.pipeline(); cpl.addLast("default_channel_handler", new MySimpleChannelInboundHandler()); } }).bind(8000).sync().channel().closeFuture().syncUninterruptibly(); } finally { eventLoopGroup.shutdownGracefully().await(); } } public static void main(String[] args) throws Exception { new Server(); } }
客户端代码:
package yt.ingrim.p01; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollServerSocketChannel; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import java.net.InetSocketAddress; public class Client { public static final boolean EPOLL = Epoll.isAvailable(); Class channelClazz = EPOLL? EpollServerSocketChannel.class : NioServerSocketChannel.class; public Client() throws Exception { EventLoopGroup eventLoopGroup = EPOLL? new EpollEventLoopGroup(): new NioEventLoopGroup(); try { new Bootstrap() .group(eventLoopGroup) .channel(channelClazz) .remoteAddress(new InetSocketAddress("127.0.0.1", 8000)) .handler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel ch) throws Exception { } }).connect().sync().channel().closeFuture().syncUninterruptibly(); } finally { eventLoopGroup.shutdownGracefully().sync(); } } public static void main(String []args) throws Exception { System.out.println(EPOLL); new Client(); } }
异常堆栈:
Exception in thread "main" java.lang.UnsupportedOperationException
at io.netty.channel.socket.nio.NioServerSocketChannel.doConnect(NioServerSocketChannel.java:178)
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.connect(AbstractNioChannel.java:248)
at io.netty.channel.DefaultChannelPipeline$HeadContext.connect(DefaultChannelPipeline.java:1342)
at io.netty.channel.AbstractChannelHandlerContext.invokeConnect(AbstractChannelHandlerContext.java:548)
at io.netty.channel.AbstractChannelHandlerContext.connect(AbstractChannelHandlerContext.java:533)
at io.netty.channel.AbstractChannelHandlerContext.connect(AbstractChannelHandlerContext.java:517)
at io.netty.channel.DefaultChannelPipeline.connect(DefaultChannelPipeline.java:978)
at io.netty.channel.AbstractChannel.connect(AbstractChannel.java:265)
at io.netty.bootstrap.Bootstrap$3.run(Bootstrap.java:250)
at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:174)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:167)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:470)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:569)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:829)
问题原因
客户端代码错误使用了服务端专属的Channel类型:NioServerSocketChannel和EpollServerSocketChannel。这两类Channel的设计目的是监听TCP端口、接受客户端连接,不支持主动发起连接的connect操作,调用该方法时必然抛出UnsupportedOperationException。
修复方案
将客户端的Channel类型替换为对应的客户端Channel:
- 当EPOLL可用时,使用
EpollSocketChannel - 当EPOLL不可用时,使用
NioSocketChannel
修改后的客户端代码:
package yt.ingrim.p01; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollSocketChannel; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import java.net.InetSocketAddress; public class Client { public static final boolean EPOLL = Epoll.isAvailable(); // 修正Channel类型为客户端版本 Class channelClazz = EPOLL ? EpollSocketChannel.class : NioSocketChannel.class; public Client() throws Exception { EventLoopGroup eventLoopGroup = EPOLL ? new EpollEventLoopGroup() : new NioEventLoopGroup(); try { new Bootstrap() .group(eventLoopGroup) .channel(channelClazz) .remoteAddress(new InetSocketAddress("127.0.0.1", 8000)) .handler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel ch) throws Exception { } }).connect().sync().channel().closeFuture().syncUninterruptibly(); } finally { eventLoopGroup.shutdownGracefully().sync(); } } public static void main(String []args) throws Exception { System.out.println(EPOLL); new Client(); } }
内容的提问来源于stack exchange,提问作者user51

