最近在学习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初学者都会碰到此类问题吧,希望这篇文章能对大家有点帮助。