上两篇文章最为Netty的预备知识,主要介绍了 IO 与 NIO的相关核心知识。
相关文章
java网络编程—NIO与Netty(四)
java网络编程—NIO与Netty(三)
java网络编程—NIO与Netty(二)
java网络编程—NIO与Netty(一)
java网络编程—基石:五种IO模型及原理(多路复用\epoll)
接下来开始重点深入解析Netty
为了直观,请引入netty 依赖并先运行以下代码:
Netty 服务端(监听传入的连接):
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public static void main(String[] args) throws Exception {
new EchoServer(9099).start();
}
public void start() throws Exception {
final EchoServerHandler serverHandler = new EchoServerHandler();
// 1.创建EventLoopGroup:指定了 NioEventLoopGroup来接受和处理新的连接
EventLoopGroup group = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();// 2.创建ServerBootstrap
// 3.指定所使用的NIO传输 Channel,并且指定类型。源码中每次会通过ChannelFactory工厂创建channel,其实就是根据这个class了下反射创建对象。
b.group(group).channel(NioServerSocketChannel.class)
// 4.使用指定的 端口设置套 接字地址:服务器将绑定到这个地址以监听新的连接请求。
.localAddress(new InetSocketAddress(port))
// 5.添加一个 EchoServerHandler 到子 Channel 的 ChannelPipeline
// EchoServerHandler 是@Shareable,所 以我们可以总是使用 同样的实例
.childHandler(new ChannelInitializer() {
// ChannelInitializer。这是关键。当一个新的连接
// 被接受时,一个新的子 Channel 将会被创建,而 ChannelInitializer 将会把一个你的
// EchoServerHandler 的实例添加到该 Channel 的 ChannelPipeline 中
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(serverHandler);
}
});
// 6.异步地绑定服务器; 调用 sync()方法阻塞 等待直到绑定完成
ChannelFuture f = b.bind().sync();
System.out.println(EchoServer.class.getName() + " started and listening for connections on "
+ f.channel().localAddress());
f.channel().closeFuture().sync();// 7.获取这个channel的closeFuture,并且阻塞当前线程直到它完成channel关闭
} finally {
group.shutdownGracefully().sync();// 8.关闭 EventLoopGroup,释放所有的资源
}
}
@Sharable // 表示一个channelhandler可以被多个channel线程安全的共享
static 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);
}
// 通知ChannelInboundHandler最后一次对channelRead()的调用是当前批量读取中的最后一条消息
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// 将未决消息冲刷到远程节点,并且关闭该 Channel
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
// 在读取操作期间, 有异常抛出时会调用
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
}
Server 实现了业务逻辑:
main()方法引导了服务器。引导过程中所需要的步骤如下
Client 客户端(建立到一个或者多个进程的连接)
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() {
@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 {
new EchoClient("localhost", 9099).start();
}
@Sharable
static class EchoClientHandler extends SimpleChannelInboundHandler {
// —在到服务器的连接已经建立之后将被调用
@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();
}
}
}
注意:并非JDK源码中的channel。
能够执行 IO操作(write\read)的连接(Connection),比如文件读写连接通道、Socket等,在网络编程中通常可以理解为异步IO操作的Socket
Channel接口中定义的基本的 I/O 操作(bind()、 connect()、 read()和 write())依赖于底层网络传输所提供的原语,通过Channel接口中定义的API 降低了直接使用 Socket 类的复杂性。
结合前文Server创建的例子:b.group(group).channel(NioServerSocketChannel.class)
源码中每次会通过ChannelFactory工厂创建channel,其实就是根据这个class了下反射创建对象。
异步的(channel)IO操作的结果,是JDK中Future的子类。
在Netty中所有的IO操作都是异步的,每个 Netty 的出站 I/O 操作都将返回一个 ChannelFuture。ChannelFuture通过注册ChannelFutureListener监听器的方式在这个channelfuture对应的操作执行完成时触发响应的动作。
@Override
ChannelFuture addListener(GenericFutureListener extends Future super Void>> listener);
@Override
ChannelFuture addListeners(GenericFutureListener extends Future super Void>>... listeners);
@Override
ChannelFuture removeListener(GenericFutureListener extends Future super Void>> listener);
//示例:
ChannelFuture channelFuture = b.connect();
channelFuture.addListener(new ChannelFutureListener() {
/*这个ChannelFuture f执行完成的时候,另外一个线程会执行operationComplete逻辑*/
@Override
public void operationComplete(ChannelFuture future) throws Exception {
System.out.println("isDone" + future.isDone());
System.out.println("isSuccess" + future.isSuccess());
System.out.println("this Channel Future " + future);
}
});
另外ChannelPromise是ChannelFuture的子类,通过增加了几个set方法实现可写功能。可以理解为可写入版本的ChannelFuture.
/**
* Special {@link Future} which is writable.
*/
public interface Promise extends Future {
/**
* Marks this future as a success and notifies all
* listeners.
*
* If it is success or failed already it will throw an {@link IllegalStateException}.
*/
Promise setSuccess(V result);
/**
* Marks this future as a success and notifies all
* listeners.
*
* @return {@code true} if and only if successfully marked this future as
* a success. Otherwise {@code false} because this future is
* already marked as either a success or a failure.
*/
boolean trySuccess(V result);
/**
* Marks this future as a failure and notifies all
* listeners.
*
* If it is success or failed already it will throw an {@link IllegalStateException}.
*/
Promise setFailure(Throwable cause);
/**
* Marks this future as a failure and notifies all
* listeners.
*
* @return {@code true} if and only if successfully marked this future as
* a failure. Otherwise {@code false} because this future is
* already marked as either a success or a failure.
*/
boolean tryFailure(Throwable cause);
/**
* Make this future impossible to cancel.
*
* @return {@code true} if and only if successfully marked this future as uncancellable or it is already done
* without being cancelled. {@code false} if this future has been cancelled already.
*/
boolean setUncancellable();
............
Netty通过各种不同的事件来告诉我们某些状态的改变、操作的执行情况等,我们针对各种不同的事件去执行相应的动作。
所谓入站(Inbound)是指数据是从远端流向本地用户程序,出站(Outbound)则反之。
入站事件
出站事件
在内部,会为每个channel分配一个EventLoop,用于处理所有事件,比如:
注册感兴趣的事件;
将事件派发给 ChannelHandler;
安排进一步的动作
每个EventLoop都由一个独立的线程驱动,用来处理一个channel的所有IO事件。
一个EventLoop通常可以处理多个channel
- 一个 EventLoopGroup 包含一个或者多个 EventLoop;
- 一个 EventLoop 在它的生命周期内只和一个 Thread 绑定;
- 一个 Channel 在它的生命周期内只注册于一个 EventLoop;
- 一个 EventLoop 可能会被分配给一个或多个 Channel
- 所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理;
通常 ChannelPipeline 中的每一个 ChannelHandler 都是通过它的 EventLoop(I/O 线程)来处
理传递给它的事件的。所以至关重要的是不要阻塞这个线程,因为这会对整体的 I/O 处理产生负面的影响
ChannelHandler是一个接口族的父接口,用来接受并响应事件,netty中所有的数据处理都在channelhandler中完成。
每个channel都有个一个与之关联的channelPipeLine,这个channelPipeLine中持有一个ChannelHandler实例链。
Channelhandler充当了所有处理入站和出站数据的应用程序逻辑的容器,而ChannelPipeLine又是ChannelHandler的容器。
channel.pipeline().addLast(serverHandler)//添加一个**Handler 到子Channel的 ChannelPipeline
ChannelPipeline 提供了 ChannelHandler 链的容器,持有所有将应用于入站和出站数据以及事件的 ChannelHandler 实例
当 Channel 被创建时,它会被自动地分配到它专属的 ChannelPipeline。
当 ChannelHandler 被添加到 ChannelPipeline 时,它将会被分配一个 ChannelHandlerContext,其代表了 ChannelHandler 和 ChannelPipeline 之间的绑定
一个消息或者其他入站事件发生,这个事件会从channelPipeline的头部开始,发送给头部的channelInboundHandler,直到最末端的channelInboundHandler,到达channlPiepeline的尾部。
出站事件发送时,这个事件会从channelPipeline的尾部开始,发送给ChannelOutboundHandler直达outBoundHander链条的头部。最终出站事件的数据会到达传输层(TCP)。
两种发送消息的方式。你可以直接写到 Channel 中,也可以 写到和 ChannelHandler相关联的ChannelHandlerContext对象中。
前一种方式将会导致消息从ChannelPipeline 的尾端开始流动,
而后者将导致消息从 ChannelPipeline 中的下一个 ChannelHandler 开始流动。
ChannelPipeline中的每个ChannelHandler将负责把事件转发到链中的下一个 ChannelHandler。