channelHandler
channelHandler
自己本来没有很多方法,但是你通常必须实现其中一个子类型。
- ChannelInboundHandler 通常用处理入站(inbound)的IO事件类型
- ChannelOutboundHandler 通常用处理出站(outbound)的IO事件类型
另外,下边的一些适配器类,给你提供了便利。
- ChannelInboundHandlerAdapter 通常用处理入站的IO事件类型
- ChannelOutboundHandlerAdapter 通常用处理出站的IO事件类型
- ChannelDuplexHandler 同时处理进入和出去的事件处理器
The context object
ChannelHandler 提供了一个ChannelHandlerContext对象,
ChannelHandler应该通过上下文对象(context object)与它所属的ChannelPipeline进行交互。ChannelHandler可以通过事件upstream和downstream,使用这个上下文对象(context object)动态的修改pipeline或者使用AttributeKey存储每个handler的特殊信息。
一个channelHandler 通常需要存储一些有状态的(stateful)信息。最简单和推荐的方法是使用成员变量
public interface Message {
// your methods here
}
public class DataServerHandler extends SimpleChannelInboundHandler<Message> {
private boolean loggedIn;
@Override
public void channelRead0(ChannelHandlerContext ctx, Message message) {
if (message instanceof LoginMessage) {
authenticate((LoginMessage) message);
loggedIn = true;
} else (message instanceof GetDataMessage) {
if (loggedIn) {
ctx.writeAndFlush(fetchSecret((GetDataMessage) message));
} else {
fail();
}
}
}
...
}
由于处理程序实例有一个专用于一个连接的状态变量,因此必须为每个新通道创建一个新的处理程序实例,以避免未经身份验证的客户端可以获取机密信息的竞争条件:
// Create a new handler instance per channel.
// See ChannelInitializer.initChannel(Channel).
public class DataServerInitializer extends ChannelInitializer<Channel> {
@Override
public void initChannel(Channel channel) {
channel.pipeline().addLast("handler", new DataServerHandler());
}
}
注:A、B连个用户都与netty服务器建立连接。因为DataServerHandler声明loggedIn全局变量,为了防止冲突。每个channelPipeline通道都需要创建一个DataServerHandler对象,用来标记连接通道的用户是否登录。所以DataServerHandler不能共享(Sharable)。
使用 AttributeKey
尽管建议使用成员变量来存储处理程序的状态,但出于某些原因,您可能不想创建许多处理程序实例。在这种情况下,可以使用ChannelHandlerContext提供的AttributeKeys:
public interface Message {
// your methods here
}
@Sharable
public class DataServerHandler extends SimpleChannelInboundHandler<Message> {
private final AttributeKey<Boolean> auth =
AttributeKey.valueOf("auth");
@Override
public void channelRead(ChannelHandlerContext ctx, Message message) {
Attribute<Boolean> attr = ctx.attr(auth);
if (message instanceof LoginMessage) {
authenticate((LoginMessage) o);
attr.set(true);
} else (message instanceof GetDataMessage) {
if (Boolean.TRUE.equals(attr.get())) {
ctx.writeAndFlush(fetchSecret((GetDataMessage) o));
} else {
fail();
}
}
}
...
}
既然处理程序的状态已附加到ChannelHandlerContext,那么可以将同一处理程序实例添加到不同的管道中:
public class DataServerInitializer extends ChannelInitializer<Channel> {
private static final DataServerHandler SHARED = new DataServerHandler();
@Override
public void initChannel(Channel channel) {
channel.pipeline().addLast("handler", SHARED);
}
}
注:多个ChannelPipeline可以共享的变量,底层 看 AtomicReferenceArray : Object[] array = new Object[];
每个特殊元素的key,计算出所在的位置。DefaultAttributeMap.attr 方法添加元素
private static int index(AttributeKey<?> key) {
return key.id() & MASK;
}
@Sharable 注解
在上面使用AttributeKey的示例中,您可能注意到了@Sharable注释。
如果ChannelHandler使用@Sharable注释进行了注释,这意味着您可以只创建一次处理程序的实例,然后将其多次添加到一个或多个ChannelPipelines中,而不需要竞争条件。
如果未指定此注释,则每次将其添加到管道时都必须创建一个新的处理程序实例,因为它具有非共享状态(如成员变量)。
与JCIP注释一样,此注释是为文档目的而提供的。
其他值得一读的资源
请参阅ChannelHandler和ChannelPipeline以了解有关入站和出站操作的更多信息,它们有哪些基本区别,它们如何在管道中流动,以及如何在应用程序中处理操作。