最近在学习Netty框架,对着教程上写了个简单的netty应用,可是死活调试不成功,对着程序跟教程上看了几遍也找不到原因,后来又重新写了一遍,服务端程序终于调试成功,原因出在了那个@Skip注释上了,代码如下:
package com.chris.netty; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandler.Skip; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPromise; import io.netty.channel.EventLoopGroup; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.example.discard.DiscardServerHandler; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.util.ReferenceCountUtil; import java.net.SocketAddress; import java.sql.Date; /** * @author Chris * @date 2015-4-12 */ public class NettyTimerServer { public void bind(int port) throws Exception{ EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChildChannelHandler()); System.out.println("server bind 8888"); ChannelFuture f = b.bind(port).sync(); System.out.println("finish bind"); f.channel().closeFuture().sync(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } } private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{ /* (non-Javadoc) * @see io.netty.channel.ChannelInitializer#initChannel(io.netty.channel.Channel) */ @Override protected void initChannel(SocketChannel arg0) throws Exception { System.out.println("server initChannel"); arg0.pipeline().addLast(new TimeServerHandler()); //arg0.pipeline().addl } } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub try { new NettyTimerServer().bind(8888); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class TimeServerHandler extends ChannelHandlerAdapter { @Override @Skip public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); } @Override @Skip public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf)msg; byte[] bytes = new byte[buf.readableBytes()]; buf.readBytes(bytes); String body = new String(bytes,"UTF-8"); System.out.println("the server receive order:"+body); String currentTIme = "QUERY CURRENT TIME".equalsIgnoreCase(body)?(new Date(System.currentTimeMillis())).toString():"receive error order"; ByteBuf resp = Unpooled.copiedBuffer(currentTIme.getBytes()); ctx.write(resp); } @Override @Skip public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override @Skip public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception { // TODO Auto-generated method stub super.connect(ctx, remoteAddress, localAddress, promise); } }
这个实现类的每个方法上都有一个@Skip注释,去掉注释之后,程序调试成功,使用netty开发的服务端程序可以正常接收和处理客户端连接。
被这个注释坑了一天了,于是特地去看了netty的源码,以下是关于@Skip源码的说明:
/** * Indicates that the annotated event handler method in {@link ChannelHandler} will not be invoked by * {@link ChannelPipeline}. This annotation is only useful when your handler method implementation * only passes the event through to the next handler, like the following: * * <pre> * {@code @Skip} * {@code @Override} * public void channelActive({@link ChannelHandlerContext} ctx) { * ctx.fireChannelActive(); // do nothing but passing through to the next handler * } * </pre> * * {@link #handlerAdded(ChannelHandlerContext)} and {@link #handlerRemoved(ChannelHandlerContext)} are not able to * pass the event through to the next handler, so they must do nothing when annotated. * * <pre> * {@code @Skip} * {@code @Override} * public void handlerAdded({@link ChannelHandlerContext} ctx) { * // do nothing * } * </pre> * * <p> * Note that this annotation is not {@linkplain Inherited inherited}. If you override a method annotated with * {@link Skip}, it will not be skipped anymore. Similarly, you can override a method not annotated with * {@link Skip} and simply pass the event through to the next handler, which reverses the behavior of the * supertype. * </p> */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @interface Skip { // no value }
大概意思就是说@Skip注释用来在实现了Handler的实现类中的方法上,程序运行过程中如果某个handler实现中的方法被@Skip注释了,则此方法不会被 ChannelPipeline 对象调用,所以,这就是为什么我的服务端程序死活调试不成功的原因。我们可以看看netty内部执行过程中是如何处理@Skip注释的,通过对源码文件全文扫苗,找到了对@Skip注释的处理都集中在了AbstractChannelHandlerContext中,下面贴出处理@Skip相关的方法源码:
/** * Returns an integer bitset that tells which handler methods were annotated with {@link Skip}. * It gets the value from {@link #skipFlagsCache} if an handler of the same type were queried before. * Otherwise, it delegates to {@link #skipFlags0(Class)} to get it. */ static int skipFlags(ChannelHandler handler) { WeakHashMap<Class<?>, Integer> cache = skipFlagsCache.get(); Class<? extends ChannelHandler> handlerType = handler.getClass(); int flagsVal; Integer flags = cache.get(handlerType); if (flags != null) { flagsVal = flags; } else { flagsVal = skipFlags0(handlerType); cache.put(handlerType, Integer.valueOf(flagsVal)); } return flagsVal; }
/** * Determines the {@link #skipFlags} of the specified {@code handlerType} using the reflection API. */ static int skipFlags0(Class<? extends ChannelHandler> handlerType) { int flags = 0; try { if (isSkippable(handlerType, "handlerAdded")) { flags |= MASK_HANDLER_ADDED; } if (isSkippable(handlerType, "handlerRemoved")) { flags |= MASK_HANDLER_REMOVED; } if (isSkippable(handlerType, "exceptionCaught", Throwable.class)) { flags |= MASK_EXCEPTION_CAUGHT; } if (isSkippable(handlerType, "channelRegistered")) { flags |= MASK_CHANNEL_REGISTERED; } if (isSkippable(handlerType, "channelUnregistered")) { flags |= MASK_CHANNEL_UNREGISTERED; } if (isSkippable(handlerType, "channelActive")) { flags |= MASK_CHANNEL_ACTIVE; } if (isSkippable(handlerType, "channelInactive")) { flags |= MASK_CHANNEL_INACTIVE; } if (isSkippable(handlerType, "channelRead", Object.class)) { flags |= MASK_CHANNEL_READ; } if (isSkippable(handlerType, "channelReadComplete")) { flags |= MASK_CHANNEL_READ_COMPLETE; } if (isSkippable(handlerType, "channelWritabilityChanged")) { flags |= MASK_CHANNEL_WRITABILITY_CHANGED; } if (isSkippable(handlerType, "userEventTriggered", Object.class)) { flags |= MASK_USER_EVENT_TRIGGERED; } if (isSkippable(handlerType, "bind", SocketAddress.class, ChannelPromise.class)) { flags |= MASK_BIND; } if (isSkippable(handlerType, "connect", SocketAddress.class, SocketAddress.class, ChannelPromise.class)) { flags |= MASK_CONNECT; } if (isSkippable(handlerType, "disconnect", ChannelPromise.class)) { flags |= MASK_DISCONNECT; } if (isSkippable(handlerType, "close", ChannelPromise.class)) { flags |= MASK_CLOSE; } if (isSkippable(handlerType, "deregister", ChannelPromise.class)) { flags |= MASK_DEREGISTER; } if (isSkippable(handlerType, "read")) { flags |= MASK_READ; } if (isSkippable(handlerType, "write", Object.class, ChannelPromise.class)) { flags |= MASK_WRITE; } if (isSkippable(handlerType, "flush")) { flags |= MASK_FLUSH; } } catch (Exception e) { // Should never reach here. PlatformDependent.throwException(e); } return flags; }
@SuppressWarnings("rawtypes") private static boolean isSkippable( Class<?> handlerType, String methodName, Class<?>... paramTypes) throws Exception { Class[] newParamTypes = new Class[paramTypes.length + 1]; newParamTypes[0] = ChannelHandlerContext.class; System.arraycopy(paramTypes, 0, newParamTypes, 1, paramTypes.length); return handlerType.getMethod(methodName, newParamTypes).isAnnotationPresent(Skip.class); }
相信不少netty初学者都会碰到此类问题吧,希望这篇文章能对大家有点帮助。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/118912.html