1.回调(Callback):
通俗的理解解释:你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。一般都是我们自己调用系统API,回调是让系统或者框架在特定的时刻调用我们实现的函数。
2.回调的缺点:
回调过程有个问题就是当你使用链式调用很多不同的方法会导致线性代码;有些人认为这种链式调用方法会导致代码难以阅读
3.实现异步常用的2种手段:
a.)回调,在特定时候会直接返回结果。
b.)Future,Future是任务周期的抽象,它使用Executor异步执行任务,不过你要的手动检查Future是否完成来得到通知,常用方法:Future.isDone()。
Netty框架以上2种实现异步的方式都用。
Channel ch = ...;
InetAddress s = InetAddress.getLocalHost();
InetSocketAddress localAddress = new InetSocketAddress(s, 8080);
ChannelFuture f =ch.bind(localAddress); ----Future异步
f.addListener(new ChannelFutureListener() {
//如果操作成功,Netty回调ChannelFuture里面的ChannelFutureListener的operationComplete方法
@Override
public void operationComplete(ChannelFuture future)throws Exception {
if(future.isSuccess()){
System.out.println("已经建立连接");
}
}
});
以上示例显示了Netty用Future来进行异步操作,并且在操作完成时回调opreationComplete()方法。
在Netty中每个出站(OutBound)IO操作都会返回一个ChannnelFuture,包括write,bind,connect()等操作。
4.Netty线程模型:
EventLoop中,循环事件:
List<Runnable> readyEvents = .....;
for(Runnable event : readyEvents){
event.run
}
由上图我们可以看出,EventLoop实现了EventExecutor,它类似一个线程池,不过这个线程池通过SingleThreadEventExecutor实现了单线程处理Event的线程模型,因此,每一个I/O操作和事件总是由EventLoop本身处理,并且分配给EventLoop(EventLoop里面有一个任务队列)的Thread(每个EventLoop只有且只有1个线程,属于某个EventLoop的Event()只能在该线程中执行)。
这是一个EventLoop的抽象类:
public abstract class SingleThreadEventLoop extends SingleThreadEventExecutor implements EventLoop:
@UnstableApi
public final void executeAfterEventLoopIteration(Runnabletask) {
ObjectUtil.checkNotNull(task,"task");
if (isShutdown()) {
reject();
}
if (!tailTasks.offer(task)) {
reject(task);
}
if (wakesUpForTask(task)) {
wakeup(inEventLoop());
}
}
public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutorimplements OrderedEventExecutor
这个抽象类也很重要,很多实现在这个类里。
而EventLoopGroup用来管理EventLoop的分配,它给不同的Channel分配EventLoop。EventLoopGroup里面的EventLoop是有限的,其实也是说创建有限的线程,进来的Channel会复用这些EventLoop(也是复用线程)。
5.ChannelPipeLine:每个Channel都有1个它自己的ChannelPipeline,这是个ChannelPipeLine的调用链,用于管理处于链上的ChannelHandler(PipeLine上有一系列管理的API),在ChannelInitializer(具体是initChannel()方法内部)上完成了将ChannelHandler添加到ChannelPipeline上的动作。
6.ChannelHandlerContext:当ChannelHandler被添加到ChannelPipeline的时候,得到一个ChannelHandlerContext,它代表了ChannelHandler和ChannelPipeline之间的绑定。可以用它来获得底层的Channel.
7.Netty发送消息的方式:
a.)写消息进入Channel,这时消息从ChannelPipeline的尾部开始。
b.)写消息到ChannelHandlerContext里,消息从ChannelPipeline调用链上的下个ChannelHandler开始。
8.每个Channel都会分配一个ChannelConfig和Channelpipeline,ChannelConfig负责设置并存储ChannelConfig的配置。Channnelpipeline实现了拦截过滤器模式。Channel是线程安全的。
9.ByteBuf是为了在pipeline中传输数据,解决了一些ByteBuffer的问题。
10.ByteBuf相关:它有readIndex和writeIndex,不同的方法管理不同的读/写索引,它分三个区域,discardable Bytes、readable Bytes、writeable Byte。clear()方法比disardReadBytes()更加低成本,因为调用disardReadBytes()(用来删除已读数据)的时候会有内存复制,因为它会使得可读字节移动到最开始的位置,而clear只是重置readIndex和writeIndex这2个索引,没有内存复制。
11.衍生缓冲区:衍生缓冲区是由ByteBuf的duplicate()、slice()、slice(int,int)、readOnly()、order(ByteOrder),通过调用这些方法,生成一个新的包含源数据的视图,它与原来的ByteBuffer是同源的,也就是说其中一个数据变更,另一个也变更。
12.ByteBuf拷贝:不同于衍生缓冲区,调用copy()和copy(int,int)方法生成一个已有缓冲区的全新副本,这个数据是独立的,其中一个的变更不影响原来的ByteBuf的内容。
13.ByteBufHolder:我们经常在ByteBuf中存储一些正常数据之外,我们有时候还需要增加一些各式各样的属性值,一个Http响应体就是一个很好的例子,除了按照字节传输过来的主体内容,还有状态码,cookie等信息
Netty提供了ByteBufHolder来处理这些常用的用户案例,ByteBufHolder还提供了Netty一些其他的先进特性,例如缓存池,缓存池可以是ByteBuf中直接“借出”获取,如果有需要,“借出”的ByteBuf还可以自动的还到池中
ByteBufHolder提供了一系列的获取底层数据和引用计数的方法,表5.6向你展示了一些常用的方法
如果你想要实现一个消息对象可以在ByteBuf中存储其有效负荷的话,使用ByteBufHolder是一个不错的选择
14.ByteBufferAllocator:2种获得方式
1)channel.alloc()
2)channelHandlerContext.alloc()
ByteBufferAllocator有2种实现,一种是PooledByteBufferAllocator,用实例池改进性能,使得内存使用达到最低。 另一种实现是不池化的情况,每次返回一个新的ByteBuf实例。
15.ChannelInboundHandler:处理进站数据和所有状态更改事件。
16.ChannelOutboundHandler:处理出站数据,允许拦截各种操作。它的另一个强大之处在于具有在请求时延迟操作和事件的能力。比如当你在写数据的过程中被意外暂停,可以延迟执行刷新操作,然后在迟些的时候继续。
17.ChannelHandlerAdapter:它主要负责推动事件到Channelpipeline里的下个ChannnelHandler中,直到Channelpipeline结束。
18.使用ChannelInboundHandler、ChannelInboundHandlerAdapter、SimpleChannelInboundhandler这三个中的一个来处理接收消息,使用哪一个取决于需求;大多数时候使用SimpleChannelInboundHandler处理消息,使用ChannelInboundHandlerAdapter处理其他的“入站”事件或状态改变。
19.在ChannelOutboundHandler中,有很多方法,比如
· bind,Channel绑定本地地址
· connect,Channel连接操作
· disconnect,Channel断开连接
· close,关闭Channel
· deregister,注销Channel
· read,读取消息,实际是截获ChannelHandlerContext.read()
· write,写操作,实际是通过ChannelPipeline写消息,Channel.flush()属性到实际通道
· flush,刷新消息到通道
他们的方法参数里都有1个ChannelPromise。
其中对于消息的处理我们要重点留意:
a.)当你的消息在1个Handler中被消费了的时候(即不会传到下一个Hander中的时候),你要在那个Handler中释放这个消息,调用ReferenceCountUtil.release(message)来释放消息资源。
b.)当你的消息被传入实际通道里的时候,消息自动写入,或者在Channel关闭的时候自动释放。
20.Channelpipeline用于拦截流经1个channel的入站和出站事件。每一次创建了新的channel,就会为它绑定一个Channelpipeline,这个是永久性的,channel即不能绑定另一个Channelpipeline也不能与当前的Channelpipeline分离。
21.一个ChannelHandlerContext使ChannelHandler与Channelpipeline和其他处理程序交互。
22.通常ChannelHandler将添加到Channelpipeline,将处理事件传递到EventLoop(I/O线程),
23. 当从接收的数据ByteBuf读取integer,若没有足够的字节可读,decode(...)会停止解码,若有足够的字节可读,则会读取数据添加到List列表中。使用ReplayingDecoder或ByteToMessageDecoder是个人喜好的问题,Netty提供了这两种实现,选择哪一个都可以。
24.解码时处理太大的帧:解码器缓存的数据是在内存中的,当你缓存的数据太大,可能会产生耗尽内存的问题,因此,Netty提供了一个TooLongFrameException,在解码器帧太长的时候抛出。 当你定义自己的解码器的时候,你可以为你的解码器指定一个字节数,如果超出,则抛出TooLongFrameException并由channelHandler.exceptionCaught()捕获,然后由用户决定如何处理。
代码如下:
class SafeByteToMessageDecoderextends ByteToMessageDecoder{
private final int MAX_SAFELENGTH = 1024;
@Override
protected void decode(ChannelHandlerContextctx, ByteBuf in, List<Object> out) throws Exception {
int length =in.readableBytes();
if(length >MAX_SAFELENGTH){
in.skipBytes(length);
throw new TooLongFrameException();
}
//正常解码
}
}
25.解码器的decodeLast()方法:它只调用1次,在Channel关闭的时候,用于”产生最后1个消息”。
26.使用编解码器来充当编码器和解码器的组合失去了单独使用编码器或解码器的灵活性,编解码器是要么都有要么都没有。可以使用 CombinedChannelDuplexHandler<ChannelInboundhandler, ChannelOutboundhandler> 类来解决这个问题