您的位置:首页 > 其它

Netty in action study

2015-11-05 14:01 288 查看

第一章 Netty介绍

Netty提供了简单易用的api从网络处理代码中解耦业务逻辑 

David John Wheeler, 计算机科学中的所有问题都可以通过间接的方法解决。计算机科学中的任何问题都可以通过加上一层逻辑层来解决

1.2.1 Callback (回调)

public interface Fetcher {
void fetchData(FetcherCallback callback);
}

public interface FetcherCallback {
void onData(Data data) throws Exception;
void onError(Throwable cause);
}

1.2.2 Futures (将来。。。要发生的)

Furures是一个抽象概念,它表示一个值,该值可能在某个点变得可用。

1.4.2 扩展的ByteBuffer

ByteBuffer允许包装一个byte[]来获得一个实例。如果希望减少内存拷贝,这种方式是非常有利的。

第二章 第一个Netty程序

2.3 编写一个应答服务器

端口,线程模式,事件循环,业务逻辑,通道类型
port, thread pool, envent loop, 
(leo:异步多是消息驱动,所以有事件循环)

2.3.1 启动服务器

ServerBootstrap
NioEventLoopGroup

语法 bootstrap( event loop group )( nio server socket channel )( localaddress & port )( channel initializer(  channel handler ))

2.3.2 实现服务器业务逻辑

Netty使用多个Channel Handler来达到对事件处理的分离
Handler所有方法中,只有channel read方法是必须要重写的。

2.3.3 捕获异常

2.4 编写应答程序客户端

语法 bootstrap( event loop group )( nio server socket channel )(
remote address & port )( channel initializer(  channel handler ))

ServerBootstrap
NioEventLoopGroup 可以理解为线程池,用来处理连接,接收数据,发送数据

疑问:为何 server & client 例子中,部分步骤调用了 sync()

可能你会问为什么在这里使用的是SimpleChannelInboundHandler而不使用ChannelInboundHandlerAdapter?

主要原因是ChannelInboundHandlerAdapter在处理完消息后需要负责释放资源。在这里将调用ByteBuf.release()来释放资源。

SimpleChannelInboundHandler会在完成channelRead0后释放消息,这是通过Netty处理所有消息的ChannelHandler实现了ReferenceCounted接口达到的 

为什么在服务器中不使用SimpleChannelInboundHandler呢?因为服务器要返回相同的消息给客户端,

在服务器执行完成写操作之前不能释放调用读取到的消息,因为写操作是异步的,一旦写操作完成,Netty中会自动释放消息

第三章 Netty核心概念

十个核心类

Bootstrap or ServerBootstrap
EventLoop
EventLoopGroup
ChannelPipeline
Channel
Future or ChannelFuture
ChannelInitializer
ChannelHandler

3.1 Netty Crash Course

一个Netty程序开始于Bootstrap类,Bootstrap类是Netty提供的一个可以通过简单配置来设置或"引导"程序的一个很重要的类。Netty中设计了Handlers来处理特定的"event"和设置Netty中的事件,从而来处理多个协议和数据。事件可以描述成一个非常通用的方法,因为你可以自定义一个handler,用来将Object转成byte[]或将byte[]转成Object;也可以定义个handler处理抛出的异常。 

(leo:网络中传输的都是字节,不要忘了网络字节序)

你会经常编写一个实现ChannelInboundHandler的类,ChannelInboundHandler是用来接收消息,当有消息过来时,你可以决定如何处理。当程序需要返回消息时可以在ChannelInboundHandler里write/flush数据。可以认为应用程序的业务逻辑都是在ChannelInboundHandler中来处理的,业务罗的生命周期在ChannelInboundHandler中。

Netty连接客户端端或绑定服务器需要知道如何发送或接收消息,这是通过不同类型的handlers来做的,多个Handlers是怎么配置的?Netty提供了ChannelInitializer类用来配置Handlers。ChannelInitializer是通过ChannelPipeline来添加ChannelHandler的,如发送和接收消息,这些Handlers将确定发的是什么消息。ChannelInitializer自身也是一个ChannelHandler,在添加完其他的handlers之后会自动从ChannelPipeline中删除自己。

所有的Netty程序都是基于ChannelPipeline。ChannelPipeline和EventLoop和EventLoopGroup密切相关,因为它们三个都和事件处理相关,所以这就是为什么它们处理IO的工作由EventLoop管理的原因。

Netty中所有的IO操作都是异步执行的,例如你连接一个主机默认是异步完成的;写入/发送消息也是同样是异步。也就是说操作不会直接执行,而是会等一会执行,因为你不知道返回的操作结果是成功还是失败,但是需要有检查是否成功的方法或者是注册监听来通知;Netty使用Futures和ChannelFutures来达到这种目的。Future注册一个监听,当操作成功或失败时会通知。ChannelFuture封装的是一个操作的相关信息,操作被执行时会立刻返回ChannelFuture。 

3.2 Channels, Events and Input/Output(IO)

Netty中的EventLoopGroup包含一个或多个EventLoop,而EventLoop就是一个Channel执行实际工作的线程。EventLoop总是绑定一个单一的线程,在其生命周期内不会改变 

leo:channel会被线程run()调用

3.3 什么是Bootstrap?为什么使用它?

      “引导”是Netty中配置程序的过程

Bootstrap和ServerBootstrap之间的差异:

Bootstrap用来连接远程主机,有1个EventLoopGroup
ServerBootstrap用来绑定本地端口,有2个EventLoopGroup
leo:Bootstrap用一个EventLoopGroup处理连接和处理业务。ServerBootstrap用一个EventLoopGroup处理连接,用一个EventLoopGroup处理业务。

3.4 Channel Handlers and Data Flow(通道处理和数据流)

Handers依赖于ChannelPipeline来决定他们执行的顺序
ChannelHandler是一段执行业务逻辑处理数据的代码,他们来来往往的通过ChannelPipeline,ChannelPipeline可以理解为用来管理ChannelHandler的一个容器。

ChannelHandler会在程序引导阶段被添加到ChannelPipeline中,并且被添加的顺序将决定处理数据的顺序。

一个事件能传递到下一个ChannelInBoundHandler或者上一个ChannelOutBoundHandler, 在ChannelPipeline中通过使用ChannelHandlerContext调用每一个方法。

Netty提供了抽象的事件基类称为ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter,每个都提供了在ChannelPipeline中通过调用相应的方法将事件传递给下一个Handler的方法的实现。

Netty中发送消息有两种方法:直接写入通道( leo: Channel ???) 或写入ChannelHandlerContext对象。区别如下:

直接写入通道导致处理消息从ChannelPipeline的尾部开始
写入ChannelHandlerContext对象导致处理消息从ChannelPipeline的下一个handler开始

3.5 编码器·解码器·和业务逻辑:细看Handlers

有不同类型的handlers,每个handler都依赖于他们的基类。Netty提供了一系列的“Adapter”类。
每个handler负责转发事件消息到ChannelPipeline的下一个handler。(leo: 那handler没有调用相应方法,那下一个、上一个不就不会处理了?)在Adapter类及子类是自动完成,我们只需要在感兴趣的Adapter中重写方法。

有几个Adapter允许自定义ChannelHandler,一般自定义ChannelHandler需要继承 编码/解码Adapter类中一个:

ChannelHandlerAdapter
ChannelInboundHandlerAdapter
ChannelOutboundHandlerAdapter

3.5.1 Encoders, Decoders

leo:Encoder,Decoder也是ChannelHandler (符合OO 抽象---> 统一)

3.5.2 业务逻辑(Domain Logic)

第四章 Transport

网络应用程序都以字节码传输。

leo:channel的概念是java.no引入的 java.nio.channel, 其实就是包装了建立了连接的socket

4.2 Transport API

传输API的核心是channel接口,它用于所有出站的操作。

每个Channel都会分配一个ChannelPipeline和ChannelConfig

ChannelPipeline实现了拦截过滤器模式

Channel提供了很多方法,如下列表:

eventLoop(),返回分配给Channel的EventLoop
pipeline(),返回分配给Channel的ChannelPipeline
isActive(),返回Channel是否激活,已激活说明与远程连接对等
localAddress(),返回已绑定的本地SocketAddress
remoteAddress(),返回已绑定的远程SocketAddress
write(),写数据到远程客户端,数据通过ChannelPipeline传输过去
Channel channel = ...
//Create ByteBuf that holds data to write
ByteBuf buf = Unpooled.copiedBuffer("your data", CharsetUtil.UTF_8);
//Write data
ChannelFuture cf = channel.write(buf);
//Add ChannelFutureListener to get notified after write completes
cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) {
//Write operation completes without error
if (future.isSuccess()) {
System.out.println(.Write successful.);
} else {
//Write operation completed but because of error
System.err.println(.Write error.);
future.cause().printStacktrace();


Channel是线程安全(thread-safe)的,它可以被多个不同的线程安全的操作,在多线程环境下,所有的方法都是安全的。正因为Channel是安全的,我们存储对Channel的引用,并在学习的时候使用它写入数据到远程已连接的客户端,使用多线程也是如此。下面的代码是一个简单的多线程例子

final Channel channel = ...
//Create ByteBuf that holds data to write
final ByteBuf buf = Unpooled.copiedBuffer("your data",CharsetUtil.UTF_8);
//Create Runnable which writes data to channel
Runnable writer = new Runnable() {
@Override
public void run() {
channel.write(buf.duplicate());
}
};
//Obtain reference to the Executor which uses threads to execute tasks
Executor executor = Executors.newChachedThreadPool();
// write in one thread
//Hand over write task to executor for execution in thread
executor.execute(writer);
// write in another thread
//Hand over another write task to executor for execution in thread
executor.execute(writer);


4.3 Netty包含的传输实现

Netty自带了一些传输协议的实现,虽然没有支持所有的传输协议,但是其自带的已足够我们来使用。

Netty应用程序的传输协议依赖于底层协议,本节我们将学习Netty中的传输协议。

Netty中的传输方式有如下几种:

NIO,io.netty.channel.socket.nio,基于java.nio.channels的工具包,使用选择器作为基础的方法。

OIO,io.netty.channel.socket.oio,基于java.net的工具包,使用阻塞流。

Local,io.netty.channel.local,用来在虚拟机之间本地通信。

Embedded,io.netty.channel.embedded,嵌入传输,它允许在没有真正网络的运输中使用ChannelHandler,可以非常有用的来测试ChannelHandler的实现。 

4.3.1 NIO - Nonblocking I/O

NIO传输是目前最常用的方式,它通过使用选择器提供了完全异步的方式操作所有的I/O,NIO从Java
1.4才被提供。NIO中,我们可以注册一个通道或获得某个通道的改变的状态,通道状态有下面几种改变:

一个新的Channel被接受并已准备好
Channel连接完成
Channel中有数据并已准备好读取
Channel发送数据出去

处理完改变的状态后需重新设置他们的状态,用一个线程来检查是否有已准备好的Channel,如果有则执行相关事件。

在这里可能只同时一个注册的事件而忽略其他的。选择器所支持的操作在SelectionKey中定义,具体如下:

OP_ACCEPT,有新连接时得到通知
OP_CONNECT,连接完成后得到通知
OP_READ,准备好读取数据时得到通知
OP_W RITE,写入数据到通道时得到通知

Netty中的NIO传输就是基于这样的模型来接收和发送数据

Netty中的NIO传输是“zero-file-copy”,也就是零文件复制,这种机制可以让程序速度更快,更高效的从文件系统中传输内容,零复制就是我们的应用程序不会将发送的数据先复制到JVM堆栈在进行处理,而是直接从内核空间操作。

4.3.3 Local - In VM transport

4.3.4 Embedded transport

一般用来测试特定的ChannelHandler的实现

4.4每种传输方式在什么时候使用?

不多加赘述,看下面列表:

OIO,在低连接数、需要低延迟时、阻塞时使用
NIO,在高连接数时使用
Local,在同一个JVM内通信时使用
Embedded,测试ChannelHandler时使用

第五章 Buffers

第六章 ChannelHandler

Netty允许用户自定义ChannelHandler的实现来处理数据。使得ChannelHandler更强大的是可以连接每个ChannelHandler来实现任务,这有助于代码的整洁和重用

6.1 ChannelPipeline 

6.4.2 ChannelInboundHandler 

自定义消息类型来解码字节,可以实现ChannelInboundHandler或ChannelInboundHandlerAdapter。

有一个更好的解决方法,使用编解码器的框架可以很容的实现。使用ChannelInboundHandler、ChannelInboundHandlerAdapter、SimpleChannelInboundhandler这三个中的一个来处理接收消息,使用哪一个取决于需求;

大多数时候使用SimpleChannelInboundHandler处理消息,使用ChannelInboundHandlerAdapter处理其他的“入站”事件或状态改变。(leo:???)

ChannelInitializer用来初始化ChannelHandler,将自定义的各种ChannelHandler添加到ChannelPipeline中。 

6.4.3 ChannelOutboundHandler 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  netty