Netty.Note1

本文深入讲解Netty框架的核心概念和技术细节,包括回调机制、异步处理、线程模型、ChannelPipeline工作原理及其组件如ChannelHandlerContext的功能,同时探讨了ByteBuf的高效使用技巧。

1.回调(Callback):

通俗的理解解释:你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。一般都是我们自己调用系统API,回调是让系统或者框架在特定的时刻调用我们实现的函数



2.回调的缺点:

回调过程有个问题就是当你使用链式调用很多不同的方法导致线性代码;有些人认为这种链式调用方法会导致代码难以阅读

 

 

 

3.实现异步常用的2种手段:

a.)回调,在特定时候会直接返回结果。

b.)FutureFuture是任务周期的抽象,它使用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("已经建立连接");

}

}

});

 

以上示例显示了NettyFuture来进行异步操作,并且在操作完成时回调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个线程,属于某个EventLoopEvent()只能在该线程中执行)。

 

这是一个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分配EventLoopEventLoopGroup里面的EventLoop是有限的,其实也是说创建有限的线程,进来的Channel会复用这些EventLoop(也是复用线程)

 

 

 

 

 

5.ChannelPipeLine:每个Channel都有1个它自己的ChannelPipeline,这是个ChannelPipeLine的调用链,用于管理处于链上的ChannelHandler(PipeLine上有一系列管理的API),在ChannelInitializer(具体是initChannel()方法内部)上完成了将ChannelHandler添加到ChannelPipeline上的动作。

 

 

6.ChannelHandlerContext:ChannelHandler被添加到ChannelPipeline的时候,得到一个ChannelHandlerContext,它代表了ChannelHandlerChannelPipeline之间的绑定。可以用它来获得底层的Channel.

 

 

 

7.Netty发送消息的方式:

a.)写消息进入Channel,这时消息从ChannelPipeline的尾部开始。

b.)写消息到ChannelHandlerContext里,消息从ChannelPipeline调用链上的下个ChannelHandler开始。

 

 

8.每个Channel都会分配一个ChannelConfigChannelpipeline,ChannelConfig负责设置并存储ChannelConfig的配置。Channnelpipeline实现了拦截过滤器模式。Channel是线程安全的。

 

 

9.ByteBuf是为了在pipeline中传输数据,解决了一些ByteBuffer的问题。

 

10.ByteBuf相关:它有readIndexwriteIndex,不同的方法管理不同的读/写索引,它分三个区域,discardable  Bytesreadable Byteswriteable Byteclear()方法比disardReadBytes()更加低成本,因为调用disardReadBytes()(用来删除已读数据)的时候会有内存复制,因为它会使得可读字节移动到最开始的位置,而clear只是重置readIndexwriteIndex2个索引,没有内存复制。

 

 

 

 

 

 

 

11.衍生缓冲区:衍生缓冲区是由ByteBufduplicate()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()

 

ByteBufferAllocator2种实现,一种是PooledByteBufferAllocator,用实例池改进性能,使得内存使用达到最低。 另一种实现是不池化的情况,每次返回一个新的ByteBuf实例。

 

 

 

 

15.ChannelInboundHandler:处理进站数据和所有状态更改事件。

 

 

16.ChannelOutboundHandler:处理出站数据,允许拦截各种操作。它的另一个强大之处在于具有在请求时延迟操作和事件的能力。比如当你在写数据的过程中被意外暂停,可以延迟执行刷新操作,然后在迟些的时候继续。

 

 

17.ChannelHandlerAdapter:它主要负责推动事件到Channelpipeline里的下个ChannnelHandler中,直到Channelpipeline结束。

 

 

18.使用ChannelInboundHandlerChannelInboundHandlerAdapterSimpleChannelInboundhandler这三个中的一个来处理接收消息,使用哪一个取决于需求;大多数时候使用SimpleChannelInboundHandler处理消息,使用ChannelInboundHandlerAdapter处理其他的入站”事件或状态改变。

 

 

 

19.ChannelOutboundHandler中,有很多方法,比如

· bindChannel绑定本地地址

· connectChannel连接操作

· disconnectChannel断开连接

· close,关闭Channel

· deregister,注销Channel

· read读取消息,实际是截获ChannelHandlerContext.read()

· write,写操作,实际是通过ChannelPipeline写消息,Channel.flush()属性到实际通道

· flush,刷新消息到通道

 

他们的方法参数里都有1ChannelPromise

其中对于消息的处理我们要重点留意:

a.)当你的消息在1Handler中被消费了的时候(即不会传到下一个Hander中的时候),你要在那个Handler中释放这个消息,调用ReferenceCountUtil.release(message)来释放消息资源。

b.)当你的消息被传入实际通道里的时候,消息自动写入,或者在Channel关闭的时候自动释放。

 

20.Channelpipeline用于拦截流经1channel的入站和出站事件。每一次创建了新的channel,就会为它绑定一个Channelpipeline,这个是永久性的,channel即不能绑定另一个Channelpipeline也不能与当前的Channelpipeline分离。

 

21.一个ChannelHandlerContext使ChannelHandlerChannelpipeline和其他处理程序交互。

 

 

22.通常ChannelHandler将添加到Channelpipeline,将处理事件传递到EventLoop(I/O线程)

 

23.  当从接收的数据ByteBuf读取integer,若没有足够的字节可读,decode(...)会停止解码,若有足够的字节可读,则会读取数据添加到List列表中。使用ReplayingDecoderByteToMessageDecoder是个人喜好的问题,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> 类来解决这个问题

Caused by: io.lettuce.core.RedisConnectionException: DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients. In this mode connections are only accepted from the loopback interface. If you want to connect from external computers to Redis you may adopt one of the following solutions: 1) Just disable protected mode sending the command 'CONFIG SET protected-mode no' from the loopback interface by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent. 2) Alternatively you can just disable the protected mode by editing the Redis configuration file, and setting the protected mode option to 'no', and then restarting the server. 3) If you started the server manually just for testing, restart it with the '--protected-mode no' option. 4) Setup a bind address or an authentication password. NOTE: You only need to do one of the above things in order for the server to start accepting connections from the outside. at io.lettuce.core.protocol.CommandHandler.onProtectedMode(CommandHandler.java:791) at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:639) at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:597) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:795) at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:480) at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:378) at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ... 1 common frames omitted
最新发布
10-16
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值