Netty实战八之引导
通过前面的学习,我们可能要考虑一个问题:如何将这些部分组织起来,成为一个可实际运行的应用程序呢?
答案是引导。简单来说,引导一个应用程序是指对它进行配置,并使它运行起来的过程——尽管该过程的具体细节可能并不如它定义那样简单,尤其是对于一个网络应用程序来说。
引导是我们一直以来都在组装的完整拼图中缺失的那一块。当你把它放到正确的位置上时,你的Netty应用程序就完整了。
1、Bootstarp类
引导类的层次结构包括一个抽象的父类和两个具体的引导子类,如下图所示。 相对于将具体的引导类分别看作用于服务器和客户端的引导来说,记住它们的本意是用来支撑不同的应用程序的功能的将有所裨益。也就是说,服务器致力于使用一个父Channel来接受来自客户端的连接、并创建子Channel以用于它们之间的通信;而客户端将最可能只需要一个单独的、没有父Channel的Channel来用于所有的网络交互(这也适用于无连接的传输协议,如UDP,因为它们并不是每个连接都需要一个单独的Channel)
两种应用程序类型之间通用的引导步骤由AbstractBootstrap处理,而特定于客户端或者服务器的引导步骤则分别由Bootstrap或ServerBootstrap处理。
为什么引导类是Cloneable的?
你有时可能会需要创建多个具有类似配置或者完全相同配置的Channel。为了支持这种模式而又不需要为每个Channel都创建并配置一个新的引导类实例,AbstractBootstrap被标记为了Cloneable。在一个已经配置完成的引导类实例上调用clone()方法将返回另一个可以立即使用的引导类实例。
注意,这种方式只会创建引导类实例的EventLoopGroup的一个浅拷贝,所以,后者将在所有克隆的Channel实例之间共享。这是可以接受的,因为通常这些克隆的Channel的生命周期都很短暂,一个典型的场景是——创建一个Channel以进行一次HTTP请求。
AbstractBootstrap类的完整声明是:
public abstract class AbstractBootstrap <B extends AbstractBootstrap<B,C>,C extends Channel>
在这个签名中,子类型B是其父类型的一个类型参数,因此可以返回到运行时实例的引用以支持方法的链式调用。
其子类的声明如下:
public class Bootstrap extends AbstractBootstrap<Bootstrap,Channel>
和
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap,ServerChannel>
2、引导客户端和无连接协议
Bootstrap类被用于客户端或者使用了无连接协议的应用程序中。Bootstrap类负责为客户端和使用无连接协议的应用程序创建Channel,如下图所示。 以下代码引导了一个使用NIO TCP传输的客户端
EventLoopGroup group = new NioEventLoopGroup();
//创建一个Bootstrap类的实例以创建和连接新的客户端Channel
Bootstrap bootstrap = new Bootstrap();
//设置EventLoopGroup,提供用于处理Channel事件的EventLoop
bootstrap.group(group)
//指定要使用的Channel实现
.channel(NioSocketChannel.class)
//指定用于Channel事件和数据的ChannelInboundHandler
.handler(new SimpleChannelInboundHandler<ByteBuf>() {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf)
throws Exception {
System.out.println("Received data");
}
});
ChannelFuture future = bootstrap.connect(
//连接到远程主机
new InetSocketAddress("www.myself.com",80)
);
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (channelFuture.isSuccess()){
System.out.println("Connection established");
} else {
System.out.println("Connection attempt failed");
channelFuture.cause().printStackTrace();
}
}
});
这个示例使用了前面提到的流式语法,这些方法将通过每次方法调用所返回的对Bootstrap实例的引用链接在一起。
3、Channel和EventLoopGroup的兼容性
EventLoopGroup group = new NioEventLoopGroup();
//创建一个Bootstrap类的实例以创建和连接新的客户端Channel
Bootstrap bootstrap = new Bootstrap();
//指定一个适用于OIO的Channel实现类
bootstrap.group(group)
.channel(OioSocketChannel.class)
//设置一个用于处理Channel的I/O事件和数据的ChannelInboundHandler
.handler(new SimpleChannelInboundHandler<ByteBuf>() {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf)
throws Exception {
System.out.println("Received data");
}
});
ChannelFuture future = bootstrap.connect(
//尝试连接到远程节点
new InetSocketAddress("www.myself.com",80)
);
future.syncUninterruptibly();
这段代码将会导致IllegalStateException,因为它混用了不兼容的传输。
关于IllegalStateException的更多讨论
在引导过程中,在调用bind()或者connect()方法之前,必须调用以下方法来设置所需的组件:
——group()
——channel()或者channelFactory()
——handler()
如果不这样做,则将会导致IllegalStateException。对handler()方法的调用尤其重要,因为它需要配置好ChannelPipeline。
4、引导服务器
具体来说,ServerChannel的实现负责创建子Channel,这些子Channel代表了已被接受的连接。因此,负责引导ServerChannel的ServerBootstrap提供了这些方法,以简化将设置应用到已被接受的子Channel的ChannelConfig的任务。
下图展示了ServerBootstrap在bind()方法被调用时创建了一个ServerChannel,并且该ServerChannel管理多个子Channel。 以下代码实现了服务器的引导过程。
NioEventLoopGroup group = new NioEventLoopGroup();
//创建ServerBootstrap
ServerBootstrap bootstrap = new ServerBootstrap();
//设置EventLoopGroup,其提供了用于处理Channel事件的EventLoop
bootstrap.group(group)
//指定要使用的Channel实现
.channel(NioServerSocketChannel.class)
.childHandler(new SimpleChannelInboundHandler<ByteBuf>() {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf)
throws Exception {
System.out.println("Received data");
}
});
//通过配置好的ServerBootstrap的实例绑定该Channel
ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (channelFuture.isSuccess()){
System.out.println("Server bound");
} else {
System.out.println("Bound attempt failed");
channelFuture.cause().printStackTrace();
}
}
});
5、从Channel引导客户端
假设你的服务器正在处理一个客户端的请求,这个请求需要它充当第三方系统的客户端。当一个应用程序必须要和组织现有的系统集成时,就可能发生这种情况。在这种情况下,将需要从已经被接受的子Channel中引导一个客户端Channel。
通过将已被接受的子Channel的EventLoop传递给Bootstrap的group()方法来共享该EventLoop。因为分配给EventLoop的所有Channel都使用同一个线程,所以这避免了额外的线程创建,以及前面所提到的相关的上下文切换。这个共享的解决方案如下图所示。 实现EventLoop共享涉及通过调用group()方法来设置EventLoop,如下代码所示。
//创建ServerBootstrap以创建和绑定新的Channel
ServerBootstrap bootstrap = new ServerBootstrap();
//设置EventLoopGroup,其将提供用以处理Channel事件的EventLoop
bootstrap.group(new NioEventLoopGroup(),new NioEventLoopGroup())
//指定Channel的实现
.channel(NioServerSocketChannel.class)
//注册一个ChannelInitializerImpl的实例来设置ChannelPipeline
.childHandler(new ChannelInitializerImpl());
ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));
future.sync();*/
/*//创建一个AttributeKey以标识该属性
final AttributeKey<Integer> id = AttributeKey.valueOf("ID");
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new SimpleChannelInboundHandler<ByteBuf>() {
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
//使用AttributeKey检索属性以及它的值
Integer idValue = ctx.channel().attr(id).get();
//do something with the idValue
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf)
throws Exception {
System.out.println("Received data");
}
});
//设置ChannelOption其将在connect()或者bind()方法被调用时被设置到已经创建的Channel上
bootstrap.option(ChannelOption.SO_KEEPALIVE,true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,5000);
//存储该id属性
bootstrap.attr(id,123456);
ChannelFuture future = bootstrap.connect(new InetSocketAddress("www.myself.com",80));
future.syncUninterruptibly();
以上示例都反应了编写Netty应用程序的一个一般准则:尽可能地重用EventLoop,以减少线程创建带来的开销。
6、在引导过程中添加多个ChannelHandler
在所有我们展示过的代码示例中,我们都在引导的过程中调用了handler()或者childHandler()方法来添加单个的ChannelHandler。这对于简单的应用程序来说可能已经足够了,但是它不能满足更加复杂的需求。例如,一个必须要支持多种协议的应用程序将会有很多的ChannelHandler,而不会是一个庞大而又笨重的类。
正如你经常看到的一样,你可以根据需要,通过在ChannelPipeline中将它们链接在一起来部署尽可能多的ChannelHandler。但是,如果在引导的过程中你只能设置一个ChannelHandler,那么你应该怎么做到这一点呢?
正是针对于这个用例,Netty提供了一个特殊的ChannelInboundHandlerAdapter子类:
public abstract class ChannelInitializer extends ChannInboundHandlerAdapter
它定义了下面的方法:
protected abstract void initChannel(C ch) throws Exception
这个方法提供了一种将多个ChannelHandler添加到一个ChannelPipeline中的简便方法。你只需要简单地向Bootstrap或ServerBootstrap的实例提供你的ChannelInitializer实现即可,并且一旦Channel被注册到了它的EventLoop之后,就会调用你的initChannel()版本。在该方法返回之后,ChannelInitializer的实例将会从ChannelPipeline中移除它自己。
以下代码定义了ChannelInitializer类,并通过ServerBootstrap的childHandler()方法注册了它。
/创建ServerBootstrap以创建和绑定新的Channel
ServerBootstrap bootstrap = new ServerBootstrap();
//设置EventLoopGroup,其将提供用以处理Channel事件的EventLoop
bootstrap.group(new NioEventLoopGroup(),new NioEventLoopGroup())
//指定Channel的实现
.channel(NioServerSocketChannel.class)
//注册一个ChannelInitializerImpl的实例来设置ChannelPipeline
.childHandler(new ChannelInitializerImpl());
ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));
future.sync();
//用以设置ChannelPipeline的自定义ChannelInitializerImpl实现
final static class ChannelInitializerImpl extends ChannelInitializer<Channel>{
@Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new HttpClientCodec());
pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
}
}
7、使用Netty的ChannelOption和属性
在每个Channel创建时都手动配置它可能会变得相当乏味。幸运的是,你不必这样做。相反,你可以使用option()方法来将ChannelOption应用到引导。你所提供的值将会被自动应用到引导所创建的所有Channel。可用的ChannelOption包括了底层连接的详细信息,如keep-aliva或者超时属性以及缓冲区设置。
Netty应用程序通常与组织的专有软件集成在一起,而像Channel这样的组件可能甚至会在正常的Netty生命周期之外被使用。在某些常用的属性和数据不可用时,Netty提供了AttributeMap抽象(一个由Channel和引导类提供的集合)以及AttributeKey(一个用于插入和获取属性值的泛型类)。使用这些工具,便可以安全地将任何类型的数据项与客户端和服务器Channel(包含ServerChannel的子Channel)相关联了。
例如,考虑一个用于跟踪用户和Channel之间的关系的服务器应用程序。这可以通过将用户的ID存储为Channel的一个属性来完成。类似的技术可以被用来基于用户的ID将消息路由给用户,或者关闭活动较少的Channel。
以下代码展示了可以如何使用ChannelOption来配置Channel,以及如果使用属性来存储整型值。
//创建一个AttributeKey以标识该属性
final AttributeKey<Integer> id = AttributeKey.valueOf("ID");
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new SimpleChannelInboundHandler<ByteBuf>() {
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
//使用AttributeKey检索属性以及它的值
Integer idValue = ctx.channel().attr(id).get();
//do something with the idValue
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf)
throws Exception {
System.out.println("Received data");
}
});
//设置ChannelOption其将在connect()或者bind()方法被调用时被设置到已经创建的Channel上
bootstrap.option(ChannelOption.SO_KEEPALIVE,true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,5000);
//存储该id属性
bootstrap.attr(id,123456);
ChannelFuture future = bootstrap.connect(new InetSocketAddress("www.myself.com",80));
future.syncUninterruptibly();
8、引导DatagramChannel
前面的引导代码示例使用的都是基于TCP协议的SocketChannel,但是Bootstrap类也可以被用于无连接的协议。为此,Netty提供了各种DatagramChannel的实现。唯一区别就是,不再调用connect()方法,而是只调用bind(),如下代码所示。
//创建一个Bootstrap的实例以创建和绑定新的数据报Channel
Bootstrap bootstrap = new Bootstrap();
//设置EventLoopGroup,其提供了用以处理Channel事件的EventLoop
bootstrap.group(new OioEventLoopGroup()).channel(OioDatagramChannel.class).handler(
new SimpleChannelInboundHandler<DatagramPacket>() {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext,
DatagramPacket datagramPacket) throws Exception {
//Do something with the packet
}
}
);
//调用bind()方法,因为该协议是无连接的
ChannelFuture future = bootstrap.bind(new InetSocketAddress(0));
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (channelFuture.isSuccess()){
System.out.println("Channel bound");
} else {
System.out.println("Bind attempt faild");
channelFuture.cause().printStackTrace();
}
}
});
9、关闭
引导使你的应用程序启动并且运行起来,但是迟早你都需要优雅地将它关闭。当然你也可以让JVM在退出时处理好一切,但是这不符合优雅的定义,优雅是指干净地释放资源。关闭Netty应用程序并没有太多的魔法,但是还是有些事情需要记在心上。
最重要的是,你需要关闭EventLoopGroup,它将处理任何挂起的事件和任务,并且随后释放所有活动的线程。这就是调用EventLoopGroup.shutdownGracefully()方法的作用。这个方法调用将会返回一个Future,这个Future将在关闭完成时接收到通知。需要注意的是,所返回的Future注册一个监听器以在关闭完成时获得通知。
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class);
...
//shutdownGracefully()方法将释放所有的资源,并且关闭所有的当前正在使用中的Channel
io.netty.util.concurrent.Future future = group.shutdownGracefully();
//block until the group has shutdown
future.syncUninterruptibly();
或者,你也可以调用EventLoopGroup.shutdownGracefully()方法之前,显式地在所有活动的Channel上调用Channel.close()方法。但是在任何情况下,都请记得关闭EventLoopGroup本身。
Netty实战八之引导的更多相关文章
- 重磅!阿里P8费心整理Netty实战+指南+项目白皮书PDF,总计1.08G
前言 Netty是一款用于快速开发高性能的网络应用程序的Java框架.它封装了网络编程的复杂性,使网络编程和Web技术的最新进展能够被比以往更广泛的开发人员接触到. Netty不只是一个接口和类的集合 ...
- Python爬虫实战八之利用Selenium抓取淘宝匿名旺旺
更新 其实本文的初衷是为了获取淘宝的非匿名旺旺,在淘宝详情页的最下方有相关评论,含有非匿名旺旺号,快一年了淘宝都没有修复这个. 可就在今天,淘宝把所有的账号设置成了匿名显示,SO,获取非匿名旺旺号已经 ...
- 《Netty实战》源码运行及本地环境搭建
1.源码路径: GitHub - zzzvvvxxxd/netty-in-action-cn: Netty In Action 中文版 ,中文唯一正版<Netty实战>的代码清单 下载后 ...
- 1.Netty 实战前言
1.参考文档:Netty实战精髓篇 2.Netty介绍: Netty是基于Java NIO的网络应用框架. Netty是一个NIO client-server(客户端服务器)框架,使用Nett ...
- SpringSecurity权限管理系统实战—八、AOP 记录用户、异常日志
目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...
- 「Netty实战 02」手把手教你实现自己的第一个 Netty 应用!新手也能搞懂!
大家好,我是 「后端技术进阶」 作者,一个热爱技术的少年. 很多小伙伴搞不清楚为啥要学习 Netty ,今天这篇文章开始之前,简单说一下自己的看法: @ 目录 服务端 创建服务端 自定义服务端 Cha ...
- 1、Netty 实战入门详解
一.Netty 简介 Netty 是基于 Java NIO 的异步事件驱动的网络应用框架,使用 Netty 可以快速开发网络应用,Netty 提供了高层次的抽象来简化 TCP 和 UDP 服务器的编程 ...
- Netty实战十三之使用UDP广播事件
1.UDP的基础知识 我们将会把重点放在一个无连接协议即用户数据报协议(UDP)上,它通常用在性能至关重要并且能够容忍一定的数据报丢失的情况下. 面向连接的传输(如TCP)管理了两个网络端点之间的连接 ...
- Netty实战十二之WebSocket
如果你有跟进Web技术的最新进展,你很可能就遇到过“实时Web”这个短语,这里并不是指所谓的硬实时服务质量(QoS),硬实时服务质量是保证计算结果将在指定的时间间隔内被递交.仅HTTP的请求/响应模式 ...
随机推荐
- MacOS使用Charles抓去HTTPS数据
1.安装Charles,示例版本为4.0.1 2.Proxy->Proxy Settings 3.MacOS->Terminal->ifconfig 获取本机IP地址,如192.16 ...
- 利用正则+requests爬取猫眼电影信息
import json # from multiprocessing import Pool import requests from requests.exceptions import Reque ...
- Openvswitch手册(7): Interfaces
我们来看Interfaces ofport: OpenFlow port number for this interface. type: system: An ordinary network de ...
- “Xavier”安卓木马分析:可静默收集数据并远程代码执行
趋势科技研究发现了一款Android恶意木马——Xavier.在谷歌Play应用市场中,超过800款Android应用感染了该恶意木马,影响数百万Android用户.感染的应用范围覆盖图片编辑器,墙纸 ...
- 《JavaScript》高级程序设计第21章:Ajax和Comet,jsonp
一.创建XMLHttpRequest对象 二.XHR的用法 五.跨域资源共享 六.其他跨域技术七.安全七.安全 1. 图像Ping 2. JSONP(JSON with padding,填充式JSON ...
- 我所理解的HTTP协议
前言 对于HTTP协议,想必大家都不陌生,在工作中经常用到,特别是针对移动端和前端开发人员来说,要获取服务端数据,基本走的网络请求都是基于HTTP协议,特别是RESTFUL + JSON 这种搭配特别 ...
- OkHttp 设置 User-Agent 教程
我们知道OkHttp走的并不是原生的http请求,因此在header里面并没有真正的User-Agent,而是“okhttp/版本号”这样的字符串,因为后台需要统计信息,要求传入正确的User-Age ...
- MySQL结构
MySQL属于关系型数据库管理系统(RDBMS),使用RDBMS时,最常见的系统结构就是客户端/服务器类型(C/S类型)这种结构. 服务器指的是用来接收其他程序发出的请求,并对该请求进行相应处理的程序 ...
- 第59节:Java中的html和css语言
欢迎到我的简书查看我的文集 前言: HTML 英文: HyperText Markup Language内容 html是超文本标记语言,是网页语言的基础知识,html是通过标签来定义的语言,所有代码都 ...
- Kali学习笔记33:Linux系统缓冲区溢出实验
之前做过一个Windows应用SLmail的缓冲区溢出的实验 这次来做一个Linux平台的缓冲区溢出实验: 缓冲区溢出是什么? 学过汇编的应该知道,当缓冲区边界限制不严格时,由于变量传入畸形数据或程序 ...