1.netty的组件
1.1 Channel
Channel 是 NIO 基本的结构。官方解释:它代表了一个用于连接到实体如硬件设备、文件、网络套接字或程序组件,能够执行一个或多个不同的 I/O 操作(例如读或写)的开放连接。
简单的来说Channel就是通信的载体,通过客户端和服务端通过它进行通信、读写数据。
生命周期状态
- ChannelUnregistered:Channel已经被创建,但还未注册到EventLoop
- ChannelRegistered :Channel已经被注册到了EventLoop
- ChannelActive:Channel处于活动状态(已经连接到远程节点),可以收发数据了
- ChannelInactive :Channel没有连接到远程节点
方法
- eventLoop:返回Channel的EventLoop
- pipeline:返回Channel的ChannelPipeline
- isActive:Channel是否活动的
- write:将数据写到远程节点。这个数据将被传递给ChannelPipeline,并且排队直到它被冲刷
- flush:将已写的数据冲刷到底层传输,如一个Socket
- writeAndFlush:等同于调用write()并接着调用flush()
1.2 Callback
Callback就是回调,回调在java中很常见,通常是提供给另外一个线程或者方法,在适当的时候调用,比如前面的AIO中,为了实现异步非阻塞。
Netty在内部使用了回调来处理事件,当一个事件发生时,通过回调让事件处理器处理事件。netty中的处理事件的回调需要实现ChannelHandler接口。
1.3 Future
Future可以看作是一个异步操作的结果的占位符,在操作完成后提供对结果的访问,也可以在操作完成时通知应用程序。
JDK中的Future,需要我们主动去检查操作是否完成,并不友好。netty提供了自己实现的Future-ChannelFuture,用于在执行异步操作完成时主动回调指定的方法,不用我们主动检查操作是否完成。
ChannelFuture提供了几种方法,使我们能够注册一个或者多个ChannelFutureListener实例。监听器的回调方法operationComplete(),将会在对应的操作完成时被调用。如果出错了,我们可以检索产生的Throwable。
每个Netty的I/O 操作都将返回一个ChannelFuture。
1.4 Event
Netty使用不同的事件来通知我们状态的改变或者是操作的状态。这使得我们能够基于已经发生的事件来触发适当的动作。
1.5 ChannelHandler
Netty的ChannelHandler是各种事件处理的基本抽象。其实就是一个回调,用于执行对各种事件的响应。
ChannelHandler分为:
- 入站(ChannelInboundHandler):数据时从远程主机到用户应用程序
- 出站(ChannelOutboundHandler):数据是从应用程序到远程主机则是出站
入站事件从pipelin头部开始传递,最后数据到达pipeline的尾部,此时所有处理结束。
出站事件从尾部开始传递,直到它到达头部。
ChannelHandlerContext:
当ChannelHandler被添加到ChannelPipeline,它得到一个 ChannelHandlerContext。它代表一个ChannelHandler和ChannelPipeline之间的绑定,可以被用来获得底层 Channel,它主要是用来写出站数据。
Netty发送消息有两种方式:
直接写消息给Channel和写 ChannelHandlerContext对象。区别是, Channel会导致消息从ChannelPipeline的尾部开始,而ChannelHandlerContext导致消息从 ChannelPipeline下一个处理器开始。
生命周期: - handlerAdded:ChannelHandler添加到ChannelPipeline中时被调用
- handlerRemoved:从ChannelPipeline中移除ChannelHandler时被调用
- exceptionCaught:当处理过程中在ChannelPipeline中有错误产生时被调用
ChannelInboundHandler生命周期:
- channelReadComplete:Channel读操作完成时被调用
- channelRead:从Channel读取数据时被调用
- ChannelWritabilityChanged:Channel的可写状态发生改变时被调用,确保写操作不会完成得太快,避免发生OOM。可以通过Channel的isWritable()方法来检测Channel的可写性。
- userEventTriggered:当ChannelnboundHandler.fireUserEventTriggered()方法被调用时被调用。
ChannelOutboundHandler生命周期:
- bind:Channel绑定到本地地址时被调用
- connect:当请求将Channel 连接到远程节点时被调用
- disconnect:当请求将Channel 从远程节点断开时被调用
- close:当请求关闭Channel 时被调用
- deregister:当请求将Channel从它的EventLoop 注销时被调用
- read:当请求从Channel读取更多的数据时被调用
- flush:当请求通过Channel将入队数据冲刷到远程节点时被调用
- write:当请求通过Channel将数据写到远程节点时被调用
几乎所有的方法都将 ChannelPromise 作为参数,一旦请求结束要通过 ChannelPipeline 转发的时候,必须通知此参数。
ChannelPromise vs. ChannelFuture
ChannelPromise是特殊的ChannelFuture,允许你的ChannelPromise及其操作成功或失败。所以任何时候调用例如 Channel.write(…) 一个新的ChannelPromise将会创建并且通过ChannelPipeline传递。这次写操作本身将会返回ChannelFuture, 这样只允许你得到一次操作完成的通知。Netty 本身使用 ChannelPromise 作为返回的 ChannelFuture 的通知,事实上在大多数时候就是 ChannelPromise 自身。
1.6 EVENTLOOP、EventLoopGroup
EVENTLOOP类似Selector。EventLoop分配给每个Channel来处理所有的事件,包括
注册感兴趣的事件,调度事件到ChannelHandler,安排进一步行动。
EventLoop本身只有一个线程驱动,并且在EventLoop的生命周期内不会改变,也就是一个Channel的所有IO操作一直在一个线程内部完成。同时任务(Runnable 或者Callable)可以提交给EventLoop实,以立即执行或者调度执行。但是一个EventLoop可以处理多个Channel。
EventLoopGroup可以包含一个或者读个EVENTLOOP。
1.7 BOOTSTRAP
Netty应用程序通过设置bootstrap(引导)类的开始,该类提供了一个用于应用程序网络层配置的容器。
有两种类型的引导:
之所以有两种,因为服务器和客户端的网络行为还是不同的,服务端是监听连接,客户端是发起连接。
Bootstrap:
Bootstrap用于客户端,用来连接远程主机,有1个EventLoopGroup。
ServerBootstrap:
ServerBootstrap用于服务器,用来绑定本地端口,有2个EventLoopGroup(可以是一个实例),想想前面讲的Reactor模式。
与ServerChannel相关EventLoopGroup分配一个EventLoop是负责创建Channels用于传入的连接请求。一旦连接接受,第二个EventLoopGroup分配一个EventLoop给它的 Channel。
这时候是不是又在想Reactor模式还有个工作线程池在哪。可以指定一个 EventExecutorGroup,用于获得EventExecutor。当添加ChannelHandler到ChannelPipeline。EventExecutor将执行所有的 ChannelHandler 的方法。这EventExecutor将从 I/O线程使用不同的线程,从而释放EventLoop。这样如果有阻塞操作,不会阻塞IO线程,否则影响性能。
1.8 CHANNELPIPELINE
ChannelPipeline就是ChannelHandler的容器,包含一个ChannelHandler链。每当一个新的Channel被创建了,都会建立一个新的 ChannelPipeline,并且这个新的ChannelPipeline还会绑定到Channel上。这个关联是永久性的,Channel既不能附上另一个ChannelPipeline 也不能分离当前这个。
ChannelHandler加入ChannelPipeline:
实现了ChannelHandler的抽象ChannelInitializer。ChannelInitializer子类通过ServerBootstrap进行注册。当它的方法initChannel()被调用时,这个对象将安装自定义的ChannelHandler加入到pipeline。当这个操作完成时,ChannelInitializer子类则从ChannelPipeline 自动删除自身。
方法:
- addFirst、addBefore、addAfter、addLast:将ChannelHandler加到ChannelPipeline
- remove:将ChannelHandler 从ChannelPipeline中移除
- replace:将ChannelPipeline中的ChannelHandler替换为另一个
- get:通过类型或者名称返回ChannelHandler
- context:返回和ChannelHandler绑定的ChannelHandlerContext
- names:返回ChannelPipeline中所有ChannelHandler 的名称
1.9 ChannelHandlerContext
ChannelHandlerContext是ChannelHandler和ChannelPipeline之间的关联,每当有ChannelHandler添加到ChannelPipeline,都会创建ChannelHandlerContext。ChannelHandlerContext用来管理它所关联的ChannelHandler 和在同一个ChannelPipeline中的其他ChannelHandler 之间的交互。
ChannelHandlerContext上的方法,则将从当前所关联的ChannelHandler开始传递。
方法:
- alloc:返回关联的Channel所配置的ByteBufAllocator
- bind ;绑定到给定的SocketAddress
- channel:返回绑定到这个实例的Channel
- close:关闭Channel
- connect:连接给定的SocketAddress
- deregister:从之前分配的EventExecutor注销
- disconnect:从远程节点断开
- executor:返回调度事件的EventExecutor
- fire***: 触发对下一个ChanneHandler的相同方法
- handler:返回绑定到这个实例的ChannelHandler
- isRemoved:关联的ChannelHandler 是否已经被从ChannelPipeline中移除
- name:返回这个实例的唯一名称
- pipeline:返回关联的ChannelPipeline
- read:将数据从Channel读取到第一个入站缓冲区,如果读取成功则触发channelRead事件,并在最后一个消息被读取完成后触发channelReadComplete
1.10 ChannelOption
- ChannelOption.SO_BACKLOG:服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小
- ChannelOption.SO_REUSEADDR:这个参数表示允许重复使用本地地址和端口,
- ChannelOption.SO_KEEPALIVE:当设置该选项以后,如果在两小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文。
- ChannelOption.SO_SNDBUF:接收缓冲区的大小
- ChannelOption.SO_RCVBUF:发送缓冲区的大小
- ChannelOption.SO_LINGER:在Linux内当用户调用close方法的时候,不一定保证会发生剩余的数据。使用SO_LINGER可以阻塞close()的调用时间,直到数据完全发送
- ChannelOption.TCP_NODELAY:而该参数的作用就是禁止使用Nagle算法(将小的数据包组装为更大的帧然后进行发送),即时传输。相对应的是TCP_CORK,该选项是需要等到发送的数据量最大的时候,一次性发送数据,适用于文件传输。
1.11 ByteBuf
ByteBuf有三种类型:
堆缓冲区:
将数据存储在堆。这种模式被称为支撑数组,它能在没有使用池化的情况下提供快速的分配和释放。可以由hasArray()来判断检查ByteBuf是否由数组支撑。
直接缓冲区:
直接缓冲区相对于基于堆的缓冲区,分配和释放都较为昂贵。它分配在堆区以外,通过免去中间交换的内存拷贝, 提升IO处理速度。
COMPOSITE BUFFER:
复合缓冲区就像一个列表,可以包含多个不同的ByteBuf,我们可以动态的添加和删除其中的ByteBuf,hasArray()总是返回 false,因为它可能既包含堆缓冲区,也包含直接缓冲区。
ByteBufAllocator :
Netty通过ByteBufAllocator分配ByteBuf实例。
- buffe:返回一个基于堆或者直接内存存储的ByteBuf
- heapBuffer:返回一个基于堆内存存储的ByteBuf
- directBuffer:返回一个基于直接内存存储的ByteBuf
- compositeBuffer:返回一个复合缓冲区
- ioBuffer():当所运行的环境具有sun.misc.Unsafe支持时,返回基于直接内存存储的ByteBuf,否则返回基于堆内存存储的ByteBuf
ByteBufAllocator有两种:PooledByteBufAllocator和UnpooledByteBufAllocator。前者池化了ByteBuf的实例以提高性能并最大限度地减少内存碎片。后者的实现不池化ByteBuf实例,每次它被调用时都会返回一个新的实例。
可丢弃字节
可丢弃字节的分段包含了已经被读过的字节。通过调用discardReadBytes,可以丢弃它们并回收空间。这个分段的初始大小为0,存储在readerIndex 中,会随着read 操作的执行而增加(get*操作不会移动readerIndex)。
缓冲区上调用discardReadBytes方法后,可丢弃字节分段中的空间已经变为可写的了。但是这将极有可能会导致内存复制,因为可读字节必须被移动到缓冲区的开始位置
引用计数 :
引用计数是一种通过在某个对象所持有的资源不再被其他对象引用时释放该对象所持有的资源来优化内存使用和性能的技术。Netty4中为ByteBuf引入了引用计数技术 ReferenceCounted。
1.12 官方实例
服务端:
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println(
"Usage: " + EchoServer.class.getSimpleName() +
" <port>");
return;
}
int port = Integer.parseInt(args[0]);
new EchoServer(port).start();
}
public void start() throws Exception {
NioEventLoopGroup group = new NioEventLoopGroup(); //3
try {
ServerBootstrap b = new ServerBootstrap();
b.group(group)
.channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(
new EchoServerHandler());
}
});
ChannelFuture f = b.bind().sync();
System.out.println(EchoServer.class.getName() + " started and listen on " + f.channel().localAddress());
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
}
@Sharable
public class EchoServerHandler extends
ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx,
Object msg) {
ByteBuf in = (ByteBuf) msg;
System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8));
ctx.write(in);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
客户端:
public class EchoClient {
private final String host;
private final int port;
public EchoClient(String host, int port) {
this.host = host;
this.port = port;
}
public void start() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host, port))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(
new EchoClientHandler());
}
});
ChannelFuture f = b.connect().sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.err.println(
"Usage: " + EchoClient.class.getSimpleName() +
" <host> <port>");
return;
}
final String host = args[0];
final int port = Integer.parseInt(args[1]);
new EchoClient(host, port).start();
}
}
@Sharable
public class EchoClientHandler extends
SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!",
CharsetUtil.UTF_8));
}
@Override
public void channelRead0(ChannelHandlerContext ctx,
ByteBuf in) {
System.out.println("Client received: " + in.toString(CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}