netty服务端的创建(简单入门)
netty服务端创建时序图
步骤 1:创建 ServerBootstrap 实例。ServerBootstrap 是 Netty 服务端的
启动辅助类,它提供了一系列的方法用于设置服务端启动相关的参数。
步 骤 2: 设 置 并 绑 定 Reactor 线 程 池。Netty 的 Reactor 线 程 池 是
EventLoopGroup,它实际就是 EventLoop 的数组。EventLoop 的职责是处理所有注册到本线程多路复用器 Selector 上的 Channel,Selector 的轮询操作由绑定的 EventLoop 线程 run 方法驱动,在一个循环体内循环执行。
步 骤 3: 设 置 并 绑 定 服 务 端 Channel。 作 为 NIO 服 务 端, 需 要 创 建ServerSocketChannel,Netty 对 原 生 的 NIO 类 库 进 行 了 封 装, 对 应 实 现 是NioServerSocketChannel。
步骤 4:链路建立的时候创建并初始化 ChannelPipeline。ChannelPipeline
并不是 NIO 服务端必需的,它本质就是一个负责处理网络事件的职责链,负责管理和执行 ChannelHandler。
步骤 5:初始化 ChannelPipeline 完成之后,添加并设置 ChannelHandler。
ChannelHandler 是 Netty 提 供 给 用 户 定 制 和 扩 展 的 关 键 接 口。
步骤 6:绑定并启动监听端口。
步骤 7:Selector 轮询。
步 骤 8: 当 轮 询 到 准 备 就 绪 的 Channel 之 后, 就 由 Reactor 线
程 NioEventLoop 执 行 ChannelPipeline 的 相 应 方 法, 最 终 调 度 并 执 行ChannelHandler。
步 骤 9: 执 行 Netty 系 统 ChannelHandler 和 用 户 添 加 定 制 的
ChannelHandler。
编写 Echo 服务器
所有的 Netty 服务器都需要以下两部分。
至少一个 ChannelHandler—该组件实现了服务器对从客户端接收的数据的处理,即它的业务逻辑。
引导—这是配置服务器的启动代码。至少,它会将服务器绑定到它要监听连接请求的端口上。
Echo 客户端和服务器之间的交互是非常简单的;在客户端建立一个连接之后,它会向服务器发送一个或多个消息,反过来,服务器又会将每个消息回送给客户端。虽然它本身看起来好像用处不大,但它充分地体现了客户端/服务器系统中典型的请求-响应交互模式。
1.ChannelHandler 和业务逻辑
简单设计一个EchoServerHandler,它需要实现 ChannelInboundHandler 接口,用 来定义响应入站事件的方法。这个简单的应用程序只需要用到少量的这些方法,所以继承 ChannelInboundHandlerAdapter 类也就足够了,它提供了 ChannelInboundHandler 的默认实现。
除了 ChannelInboundHandlerAdapter 之外,还有很多需要学习的 ChannelHandler 的子类型和实现。
针对不同类型的事件来调用 ChannelHandler;
应用程序通过实现或者扩展 ChannelHandler 来挂钩到事件的生命周期,并且提供自定义的应用程序逻辑;
在架构上,ChannelHandler 有助于保持业务逻辑与网络处理代码的分离。这简化了开发过程,因为代码必须不断地演化以响应不断变化的需求。
针对ChannelInboundHandlerAdapter 我们感兴趣的方法是:
channelRead()—对于每个传入的消息都要调用; channelReadComplete()—通知ChannelInboundHandler最后一次对channelRead()的调用是当前批量读取中的最后一条消息; exceptionCaught()—在读取操作期间,有异常抛出时会调用。
代码如下:
@Sharable //标示一个ChannelHandler 可以被多个 Channel 安全地共享
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
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 {
//将未决消息冲刷到远程节点,并且关闭该 Channel
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
2.引导服务器
服务器本身的过程了,具体涉及以下内容:
绑定到服务器将在其上监听并接受传入连接请求的端口;
配置 Channel,以将有关的入站消息通知给 EchoServerHandler 实例。
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public void start() throws InterruptedException {
final EchoServerHandler echoServerHandler = new EchoServerHandler();
//创建EventLoopGroup
EventLoopGroup group = new NioEventLoopGroup();
//创建ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
//指定所使用的的NIO传输Channel为NioServerSocketChannel
b.group(group).channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))//使用指定的端口设置套接字地址
.childHandler(new ChannelInitializer<SocketChannel>() {//添加一个EchoServerHandler 到子Channel的 ChannelPipeline
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(echoServerHandler);//EchoServerHandler 被标注为@Shareable,所以我们可以总是使用同样的实例
}
});
ChannelFuture f = b.bind().sync();//异步地绑定服务器;调用 sync()方法阻塞等待直到绑定完成
f.channel().closeFuture().sync();//获取 Channel 的CloseFuture,并且阻塞当前线程直到完成
group.shutdownGracefully().sync();//关闭EventLoopGroup ,释放所有的资源
}
public static void main(String[] args) throws InterruptedException {
int port = 9999;
new EchoServer(port).start();
}
}
总结:创建了一个 ServerBootstrap 实例。因为你正在使用的是 NIO 传输,所以你指定了 NioEventLoopGroup 来接受和处理新的连接,并且将 Channel 的类型指定为 NioServerSocketChannel 。在此之后,你将本地地址设置为一个具有选定端口的 InetSocketAddress 。服务器将绑定到这个地址以监听新的连接请求。使用了一个特殊的类——ChannelInitializer。这是关键。当一个新的连接被接受时,一个新的子 Channel 将会被创建,而 ChannelInitializer 将会把一个你的EchoServerHandler 的实例添加到该 Channel 的 ChannelPipeline 中。正如我们之前所解释的,这个 ChannelHandler 将会收到有关入站消息的通知。
接下来你绑定了服务器 ,并等待绑定完成。(对 sync()方法的调用将导致当前 Thread阻塞,一直到绑定操作完成为止)。在 处,该应用程序将会阻塞等待直到服务器的 Channel关闭(因为你在 Channel 的 CloseFuture 上调用了 sync()方法)。然后,你将可以关闭EventLoopGroup,并释放所有的资源,包括所有被创建的线程 。
回顾一下刚完成的服务器实现中的重要步骤。下面这些是服务器的主要
代码组件:
EchoServerHandler 实现了业务逻辑;
main()方法引导了服务器;
引导过程中所需要的步骤如下:
创建一个 ServerBootstrap 的实例以引导和绑定服务器;
创建并分配一个 NioEventLoopGroup 实例以进行事件的处理,如接受新连接以及读/写数据;
指定服务器绑定的本地的 InetSocketAddress;
使用一个 EchoServerHandler 的实例初始化每一个新的 Channel;
调用 ServerBootstrap.bind()方法以绑定服务器。
至此,服务端简单实现完成,在下一节中,将探讨对应的客户端应用程序的代码。