netty实战笔记 第八章 引导
8.1 bootstrap类
先看下引导类的层次结构
服务器端使用一个父Channel来接受来自客户端的连接,并创建子Channel用于他们之间的通信。客户端将最可能只需要一个单独的,没有父Channel的Channel来用于所有的网络交互。
- 为什么引导类是Cloneable的?
AbstractBootStrap类的完成声明:
public abstract class AbstractBootstrap <B extends AbstractBootstrap<B,C> , C extends Channel>
在这个签名中,子类型B 是其父类型的一个类型参数,因此可以返回到运行时实例的引用以支持方法的链式调用(也就是所谓的流式语法)。
其子类的声明如下:
public class Bootstrap
ext ends AbstractBootstrap<Bootstrap,Channel>
和
public class ServerBootstrap
ext ends AbstractBootstrap<ServerBootstrap,ServerChannel>
8.2 引导客户端 和 无连接协议。
我觉得应该用客户端引导。引导是名词。
Bootstrap类,被用于客户端或者使用了无连接的的应用程序中。
Bootstrap的API如下:
名 称 | 描 述 |
---|---|
Bootstrap group(EventLoopGroup) | 设置用于处理Channel 所有事件的EventLoopGroup |
Bootstrap channel(Class<? extends C>) / Bootstrap channelFactory(ChannelFactory<? extends C>) | channel()方法指定了Channel的实现类。如果该实现类没提供默认的构造函数,可以通过调用channelFactory()方法来指定一个工厂类,它将会被bind()方法调用Bootstrap localAddress(SocketAddress)指定Channel 应该绑定到的本地地址。如果没有指定,则将由操作系统创建一个随机的地址。或者,也可以通过bind()或者connect()方法指定localAddress |
Bootstrap option(ChannelOption option,T value) | 设置ChannelOption,其将被应用到每个新创建的Channel 的ChannelConfig。这些选项将会通过bind()或者connect()方法设置到Channel,不管哪个先被调用。这个方法在Channel 已经被创建后再调用将不会有任何的效果。支持的ChannelOption 取决于使用的Channel 类型。参见8.6 节以及ChannelConfig 的API 文档,了解所使用的Channel 类型 |
Bootstrap attr(Attribute key, T value) | 指定新创建的Channel 的属性值。这些属性值是通过bind()或者connect()方法设置到Channel 的,具体取决于谁最先被调用。这个方法在Channel 被创建后将不会有任何的效果。参见8.6 节Bootstrap handler(ChannelHandler)设置将被添加到ChannelPipeline 以接收事件通知的ChannelHandler |
Bootstrap clone() | 创建一个当前Bootstrap 的克隆,其具有和原始的Bootstrap 相同的设置信息 |
Bootstrap remoteAddress(SocketAddress) | 设置远程地址。或者,也可以通过connect()方法来指定它ChannelFuture connect() 连接到远程节点并返回一个ChannelFuture,其将会在连接操作完成后接收到通知 |
ChannelFuture bind() | 绑定Channel 并返回一个ChannelFuture,其将会在绑定操作完成后接收到通知,在那之后必须调用Channel.connect()方法来建立连接 |
8.2 引导客户端
Bootstrap类负责为客户端和使用无连接协议的应用创建Channel。
package com.moyang;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;
public class BootstrapDemo {
public static void main (String[] args) {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new SimpleChannelInboundHandler<ByteBuf>() {
protected void channelRead0 (ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
System.out.println("received data ...");
}
});
// 连接到远程主机
ChannelFuture future = bootstrap.connect(new InetSocketAddress("127.0.0.1", 80));
future.addListener(new ChannelFutureListener() {
public void operationComplete (ChannelFuture channelFuture) throws Exception {
if(channelFuture.isSuccess()) {
System.out.println("operation success !");
} else {
System.out.println("operation failed!");
}
}
});
}
}
8.3 引导服务器
8.3.1 引导服务器的API
名 称 | 描 述 |
---|---|
group() | 设置ServerBootstrap 要用的EventLoopGroup。这个EventLoopGroup将用于ServerChannel 和被接受的子Channel 的I/O 处理 |
channel | 设置将要被实例化的ServerChannel 类channelFactory 如果不能通过默认的构造函数创建Channel,那么可以提供一个ChannelFactory |
localAddress | 指定ServerChannel 应该绑定到的本地地址。如果没有指定,则将由操作系统使用一个随机地址。或者,可以通过bind()方法来指定该localAddress |
option | 指定要应用到新创建的ServerChannel 的ChannelConfig的ChannelOption。这些选项将会通过bind()方法设置到Channel。在bind()方法被调用之后,设置或者改变ChannelOption 都不会有任何的效果。所支持的ChannelOption 取决于所使用的Channel 类型。参见正在使用的ChannelConfig 的API 文档 |
childOption | 指定当子Channel 被接受时,应用到子Channel 的ChannelConfig 的ChannelOption。所支持的ChannelOption 取决于所使用的Channel 的类型。参见正在使用的ChannelConfig 的API 文档 |
attr | 指定ServerChannel 上的属性,属性将会通过bind()方法设置给Channel。在调用bind()方法之后改变它们将不会有任何的效果 |
childAttr | 将属性设置给已经被接受的子Channel。接下来的调用将不会有任何的效果 |
handler | 设置被添加到ServerChannel 的ChannelPipeline 中的ChannelHandler。更加常用的方法参见childHandler() |
childHandler | 设置将被添加到已被接受的子Channel 的ChannelPipeline 中的ChannelHandler。handler()方法和childHandler()方法之间的区别是:前者所添加的ChannelHandler 由接受子Channel 的ServerChannel 处理,而childHandler()方法所添加的ChannelHandler 将由已被接受的子Channel处理,其代表一个绑定到远程节点的套接字 |
clone | 克隆一个设置和原始的ServerBootstrap 相同的ServerBootstrap |
bind | 绑定ServerChannel 并且返回一个ChannelFuture |
8.3.2 引导服务器
package com.moyang;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class ServerBootstrapDemo {
public static void main (String[] args) {
ServerBootstrap bootstrap = new ServerBootstrap();
EventLoopGroup group = new NioEventLoopGroup();
bootstrap.group(group)
.channel(NioServerSocketChannel.class)
// 设置用于处理子Channel 的IO数据的ChannelInboundHandler
.childHandler(new SimpleChannelInboundHandler<ByteBuf>() {
protected void channelRead0 (ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
System.out.println("received data !");
}
});
ChannelFuture future = bootstrap.bind(80);
future.addListener(new ChannelFutureListener() {
public void operationComplete (ChannelFuture channelFuture) throws Exception {
if(channelFuture.isSuccess()){
System.out.println("server bound !");
}else{
System.out.println(" faild !");
}
}
});
}
}
ServerChannel的实现服务创建子Channel。这些子Channel代表已经接受的连接。
8.4 从Channel引导客户端
需求: 假设你的服务器正在处理一个客户端的请求,这个请求需要它充当第三方系统的客户端。当一个应用程序(如一个代理服务器)必须要和组织现有的系统(如Web 服务或者数据库)集成时,就可能发生这种情况。在这种情况下,将需要从已经被接受的子Channel 中引导一个客户端Channel。
解决方案:
通过将已被接受的子Channel 的EventLoop 传递给Bootstrap 的group()方法来共享该EventLoop。因为分配给EventLoop 的所有Channel 都使用同一个线程,所以这避免了额外的线程创建,以及前面所提到的相关的上下文切换
解决方案图例:
一般准则: 尽可能地重用EventLoop,以减少线程创建所带来的开销
代码如下:
package com.moyang;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;
/**
* 需求:
* 服务器端接收客户端的请求: 需要服务端充当第三方系统的客户端。
* <p>
* 解决方案:
* 通过将已接受的子channel的EventLoop传递给我Bootstrap的group()方法来共享该EventLoop。
* 这个方案: 反映了netty的一般准则。尽可能的重用EventLoop,以减少线程创建带来的开销。
*/
public class BootDemo {
public static void main (String[] args) {
final ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new SimpleChannelInboundHandler<ByteBuf>() {
ChannelFuture connectFuture;
protected void channelRead0 (ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
ChannelHandlerContext ctx;
if(connectFuture.isDone()) {
System.out.println("has received all data ....");
}
}
@Override
public void channelActive (ChannelHandlerContext ctx) throws Exception {
// 新建一个客户端用以连接到目标地址。
Bootstrap bs = new Bootstrap();
bs.channel(NioSocketChannel.class)
.handler(new SimpleChannelInboundHandler<ByteBuf>() {
protected void channelRead0 (ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
System.out.println("received data..");
}
});
// 使用的服务端的eventLoop。
bs.group(ctx.channel().eventLoop());
connectFuture = bs.connect(new InetSocketAddress("www.manning.com", 80));
}
});
ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));
future.addListener(new ChannelFutureListener() {
public void operationComplete (ChannelFuture channelFuture) throws Exception {
if(channelFuture.isSuccess()) {
System.out.println("server bound!");
} else {
System.out.println(" bind attempt failed");
channelFuture.cause().printStackTrace();
}
}
});
}
}
8.5 在引导过程中添加多个ChannelHandler
当一个要支持很多协议的应用程序将会有很多的CHannelHandler,那么仅仅使用Handler或者childHandler方法就不够了。
netty提供了一个ChannelInboundHandlerAdapter子类:public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter
. 它提供了方法: protected abstract void initChannel(C ch) throws Exception
.这个方法可以将多个ChannelHandler添加到一个ChannelPipeline中的简便方法。
一旦Channel被注册到它的EventLoop之后,就会电泳initChannel版本。在方法返回之后,ChannelInitializer实例将会从ChannelPipeline中移除自己。
来个栗子吧。
package com.moyang2;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
public class AddMutilChannelHandler {
public static void main (String[] args) throws InterruptedException {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<Channel>() {
protected void initChannel (Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new HttpClientCodec());
pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
}
});
ChannelFuture fu = serverBootstrap.bind(8080).sync();
// fu.addListener(....)
if(fu.isSuccess()){
// doSomething..
}
}
}
8.6 使用Netty的ChannelOption的属性
可以通过option()方法来将ChannelOption应用到引导所创建的所有Channel。ChannelOption包括了底层连接的详细信息。
有时chanel这样的组件可能会在Netty生命周期之外被使用。在某些常用的属性和数据不可用时,netty提供了AttributeMap抽象(一个由Channel和引导类提供的集合)以及AttributeKey(一个用于掺入和获取属性值的泛型类)。通过这些工具,便可以安全的将任何类型的数据项和客户端的服务器Channel(包含ServerChannel和子Channel)相关联了。
demo:
package com.moyang2;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.AttributeKey;
public class ChannelOptaionDemo {
public static void main (String[] args) throws InterruptedException {
final AttributeKey<Integer> attributeKey = AttributeKey.newInstance("ID");
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.handler(new SimpleChannelInboundHandler<ByteBuf>() {
@Override
public void channelRegistered (ChannelHandlerContext ctx) throws Exception {
// 使用AttributeKey检索属性以及它的值。
System.out.println(ctx.channel().attr(attributeKey).get());
}
protected void channelRead0 (ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
// doSomethings...
}
})
// 存储属性值
.attr(attributeKey,123456);
ChannelFuture future = bootstrap.bind("127.0.0.1", 8080);
future.syncUninterruptibly();
}
}
8.7 引导DatagramChannel(数据报文Channel)
之前的实例都是基于TCP协议的SOcketChannel。BootStrap也常用于无连接的协议。Netty提供了DatagramChannel的实现。
唯一的区别是,不需要调用connect()方法,而是只调用bind()方法。
package com.moyang2;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.oio.OioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.oio.OioDatagramChannel;
public class DatagramChannelDemo {
public static void main (String[] args) throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
ChannelFuture future = bootstrap.group(new OioEventLoopGroup())
.channel(OioDatagramChannel.class)
.handler(new SimpleChannelInboundHandler<DatagramPacket>() {
protected void channelRead0 (ChannelHandlerContext channelHandlerContext, DatagramPacket datagramPacket) throws Exception {
//dosomethings
}
}).bind(0).sync();
}
}
8.8 关闭
你需要关闭EventLoopGroup,他将处理人和挂起的事件。并且随后释放所有活动的线程。你可以使用EventLoopGroup.shutdownGracefully()方法的作用。这个方法调用将会返回一个Future,这个Future 将在关闭完成时接收到通知。需要注意的是,shutdownGracefully()方法也是一个异步的操作,所以你需要阻塞等待直到它完成,或者向所返回的Future 注册一个监听器以在关闭完成时获得通知。
最后
如果你觉得写的还不错,就关注下公众号呗,关注后,有点小礼物回赠给你。
你可以获得5000+电子书,java,springCloud,adroid,python等各种视频教程,IT类经典书籍,各种软件的安装及破解教程。
希望一块学习,一块进步!