您的位置:首页 > Web前端 > BootStrap

Netty in action—Bootstraping

2017-09-13 22:18 295 查看
启动(bootstrapping)一个应用是配置并运行它的过程。

与其应用架构的方法一致,Netty将通过你的应用与网络层分离的方式(不需要关心网络层的实现细节)来处理启动。



启动类

启动类的结构如上图。包含一个抽象父类和两个具体启动子类。与其将这两个具体子类看成server和client的启动类,不如将它们看成是支持的不同应用程序功能。也就是,一个server提供一个父channel来接收客户端的连接,然后创建子channel来进行会话,而一个client可能只需要一个非父类的channel来进行所有的网络交互(也有可能不需要channel,比如无连接传输协议UDP)。

我们前面介绍过的几个Netty组件参与了启动过程,其中一些客户端和服务端都可以使用。这两种类型的共同启动步骤由AbstractBootstrap处理,而专用于客户端或服务端的是Bootstrap或ServerBootstrap。下面开始介绍Bootstrap。

为什么bootstrap类是Clonealbe的? 你有时需要创建多个channel,它们有相似或相同的配置。为了支持这种设计同时不需要一个新的bootstrap实例,AbstractBootstrap类被设计为Clonealbe的。在一个已经配置好的bootstrap实例调用clone()方法会返回另一个开箱即用的bootstrap实例;注意这只会创建bootstrap的EventLoopGroup的一个浅克隆,因此它会被所有克隆的channel共享。这是可接受的,因为被克隆的channel通常是生命周期很短的,一个典型的应用是HTTP请求。

AbstractBootstrap类的完整声明如下:

public abstract class AbstractBootstrap
<B extends AbstractBootstrap<B, C>, C extends Channel>


在这个签名中,子类B是超类的一个类型参数,因此可以返回一个运行时实例的引用来支持方法链接(method chaining)

子类如下定义:

public class Bootstrap extends AbstractBootstrap<Bootstrap,Channel>
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap,ServerChannel>


在客户端和无连接协议中的启动

下表给出了Bootstrap方法的总览,有很多方法是继承自AbstractBootstrap的

名称描述
Bootstrap group(EventLoopGroup)设置EventLoopGroup,这个EventLoopGroup会为channel处理所有的事件
Bootstrap channel(Class)
Bootstrap channelFactory(ChannelFactory)
channel()指定Channel的实现类。如果这个实现类没有提供一个默认的构造函数,你可以调用channelFactory()来指定一个工厂类(被bind()方法调用)
Bootstrap localAddress(SocketAddress)指定Channel要绑定的本地address。如果没有提供,OS会选择一个随机的地址(从你网卡中选择),另外,你可以通过bind()或connect()来指定address
Bootstrap option(ChannelOption option,T value)设置一个ChannelOption应用到新创建Channel的ChannelConfig(Channel创建后再调用此方法无效)
Bootstrap attr(Attribute key, T value)指定新创建的Channel的一个属性(Channel创建后再调用此方法无效)
Bootstrap handler(ChannelHandler)设置加到ChannelPipeline中的ChannelHandler来接收事件通知
Bootstrap clone()创建当前Bootstrap的一个克隆,它们共享同一个配置
Bootstrap remoteAddress(SocketAddress)设置远程地址。或者你可以在调用connect()方法时指定
ChannelFuture connect()连接到远端机器然后返回一个ChannelFuture,当连接操作完成时可以通过ChannelFuture来得到通知
ChannelFuture bind()绑定Channel然后返回一个ChannelFutre,当连接操作完成时可以通过ChannelFuture来得到通知。在这个方法之后,必须调用Channel.connect()方法来建立连接

启动一个客户端

Bootstrap负责为客户端创建channel,如下图所示:



下面的代码展示了如何启动一个客户端:

EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)//设置EventLoopGroup提供处理Channel事件的EventLoop
.channel(NioSocketChannel.class)
.handler(new SimpleChannelInboundHandler<ByteBuf>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("Received data");
}
} );
ChannelFuture future = bootstrap.connect(
new InetSocketAddress("www.baidu.com", 80));
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture)
throws Exception {
if (channelFuture.isSuccess()) {
System.out.println("Connection established");
} else {
System.err.println("Connection attempt failed");
channelFuture.cause().printStackTrace();
}
}
} );


Channel和EventLoopGroup的兼容性

下面列出了io.netty.channel包的树形结构,你可以从包名和类名前缀看出NIO和OIO传输服务都有相应的EventLoopGroup和Channel实现。

channel
├───nio
│         NioEventLoopGroup
├───oio
│         OioEventLoopGroup
└───socket
├───nio
│         NioDatagramChannel
│         NioServerSocketChannel
│         NioSocketChannel
└───oio
OioDatagramChannel
OioServerSocketChannel
OioSocketChannel


这种兼容性必须维护,你不能混合不同前缀的组件,比如NioEventLoopGroup和OioSocketChannel。下面的代码显示了一个错误的示范:

EventLoopGroup group = new NioEventLoopGroup();//指定了一个NIO EventLoopGroup实现
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(OioSocketChannel.class) //指定一个OIO Channel实现类
.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.manning.com", 80));
future.syncUninterruptibly();


这段代码会引起一个IllegalStateException

Exception in thread "main" java.lang.IllegalStateException: incompatible event loop type: io.netty.channel.nio.NioEventLoop
at io.netty.channel.AbstractChannel$AbstractUnsafe.register(AbstractChannel.java:464)
at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:80)
at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:74)
...


启动时,在你调用bind()或connect()之前,你必须调用下面的方法来设置必要的组件

* group()

* channel() 或 channnelFactory()

* handler()

如果没这么做也会导致一个IllegalStateException。handler()方法尤其重要,因为必须用它来配置ChannelPipeline。

启动服务端

ServerBootstrap类

下表列出了ServerBootstrap的方法:

名称描述
group设置ServerBootstrap使用的EventLoopGroup
channel设置要实例化的ServerChannel类
channelFactory如果Channel不能通过默认的构造函数创建,你就可以指定一个channelFactory
localAddress指定Channel要绑定的本地address。如果没有提供,OS会选择一个随机的地址(从你网卡中选择),另外,你可以通过bind()或connect()来指定address
option设置一个ChannelOption应用到新创建ServerChannel的ChannelConfig
childOption指定一个ChannelOption来应用到接收的Channel的ChannelConfig
attr指定新创建的ServerChannel的一个属性,通过bind()方法将属性设置到channel上,调用bind()后再调用此方法无效
childAttr应用一个属性来接收Channel
handler设置要加到ServerChannel的ChannelPipeline的ChannelHander
childHandler设置要添加到接收的Channel的ChannelPipeline的ChannelHander,与上一个方法的区别是,这个方法为接收的Channel(代表与远端机器绑定的socket)增加handler而handler()为ServerChannel增加handler
clone克隆ServerBootstrap来接收不同的远端机器连接。它的配置与源ServerBootstrap相同
bind绑定ServerChannel然后返回一个ChannelFutre,当连接操作完成时可以通过ChannelFuture来得到通知。

启动一个Server

childHandler(), childAttr()和childOption()方法支持server应用特有的操作。下图显示了一个ServerBootstrap通过bind()方法创建一个ServerChannel,然后ServerChannel管理了几个子Channel。



下面的代码展示了这个过程:

NioEventLoopGroup group = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();//创建一个ServerBootstrap
bootstrap.group(group)//设置EventLoopGroup来提供处理Channel事件的EventLoop
.channel(NioServerSocketChannel.class)
.childHandler(new SimpleChannelInboundHandler<ByteBuf>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx,
ByteBuf byteBuf) throws Exception {
System.out.println("Received data");
}
} );
//通过配置好的bootstrap来绑定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.err.println("Bound attempt failed");
channelFuture.cause().printStackTrace();
}
}
} );


从Channel中启动客户端

通过将EventLoop传入Bootstrap的group()方法来共享Channel的EventLoop。 因为所有Channel都分配给一个使用相同的线程EventLoop,这避免了额外的线程创建和相关的上下文切换。 这个共享解决方案下图所示:



实现EventLoop共享可以通过调用group()方法设置EventLoop来实现:

ServerBootstrap bootstrap = new ServerBootstrap();//用来创建SocketChannel
bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(
new SimpleChannelInboundHandler<ByteBuf>() {
ChannelFuture connectFuture;
@Override
public void channelActive(ChannelHandlerContext ctx)
throws Exception {
Bootstrap bootstrap = new Bootstrap();//创建一个Bootstrap来与远端建立连接
bootstrap.channel(NioSocketChannel.class).handler(
new SimpleChannelInboundHandler<ByteBuf>() {
@Override
protected void channelRead0(
ChannelHandlerContext ctx, ByteBuf in)
throws Exception {
System.out.println("Received data");
}
} );
//使用同样的Eventloop设置给接收的(accepted)channel
bootstrap.group(ctx.channel().eventLoop());
connectFuture = bootstrap.connect(
new InetSocketAddress("www.manning.com", 80));
}
@Override
protected void channelRead0(
ChannelHandlerContext channelHandlerContext,
ByteBuf byteBuf) throws Exception {
if (connectFuture.isDone()) {
// do something with the data
}
}
} );
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.err.println("Bind attempt failed");
channelFuture.cause().printStackTrace();
}
}
} );


在一个启动过程中增加多个ChannelHandler

当你的应用比较复杂时就需要增加多个handler了,比如需要支持多种传输协议的应用。

你可以部署很多个ChannelHandler到一个ChannelPipeline中。但是如果你只能设置一个ChannelHandler你该如何做呢?

Netty提供了一个ChannelInboundHandlerAdapter的特别的子类来满足这种情况。

public abstract class ChannelInitializer<C extends Channel> extends  ChannelInboundHandlerAdapter


它定义了如下的方法:

protected abstract void initChannel(C ch) throws Exception;


这个方法提供了一个简单的方式来增加多个ChannelHandler到一个ChannelPipeline中。你只需要提供你ChannelInitializer的实现,一旦Channel与它的EventLoop注册好了后,你实现的initChannel()方法就会被调用。当这个方法返回后,ChannelInitializer的实例就会把自己从ChannelPipeline中移除。

下面定义ChannelInitializerImpl类然后通过bootstrap的childHandler()来注册它:

ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializerImpl());//注册一个ChannelInitializerImpl的实例来设置ChannelPipeline
ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));
future.sync();
final class ChannelInitializerImpl extends ChannelInitializer<Channel> {
//增加handler到ChannelPipeline
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//下面增加了2个ChannelHandler到pipeline
pipeline.addLast(new HttpClientCodec());
pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
}
}

//创建一个AttributeKey以指定一个属性
final AttributeKey<Integer> id = new AttributeKey<Integer>("ID");
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(
new SimpleChannelInboundHandler<ByteBuf>() {
@Override
public void channelRegistered(ChannelHandlerContext ctx)
throws Exception {
Integer idValue = ctx.channel().attr(id).get();//获取id的值
// do something with the idValue
}
@Override
protected void channelRead0(
ChannelHandlerContext channelHandlerContext,
ByteBuf byteBuf) throws Exception {
System.out.println("Received data");
}
}
);
bootstrap.option(ChannelOption.SO_KEEPALIVE,true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
bootstrap.attr(id, 123456);//将123456保存到id
ChannelFuture future = bootstrap.connect(
new InetSocketAddress("www.manning.com", 80));
future.syncUninterruptibly();


启动DatagramChannels

下面展示无连接协议的使用(UDP)。Netty提供了不同的DatagramChannel的实现,它们唯一的区别是你不用调用connect(),只需要bind(),如下所示:

Bootstrap bootstrap = new Bootstrap();
bootstrap.group(new OioEventLoopGroup()).channel(
OioDatagramChannel.class).handler(//指定了OioDatagramChannel
new SimpleChannelInboundHandler<DatagramPacket>() {
@Override
public void channelRead0(ChannelHandlerContext ctx,
DatagramPacket msg) 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.err.println("Bind attempt failed");
channelFuture.cause().printStackTrace();
}
}
});


关闭

如果你想优雅的关闭你的应用,指的是干净的释放资源。你只需要注意几点,首先,你需要关闭EventLoopGroup,它会处理队列中的事件和任务然后释放活跃的线程。这可以通过调用
EventLoopGroup.shutdownGracefully()
来实现。这个调用会返回一个Future,当关闭操作完成时可以得到通知。注意,shutdownGracefully()方法是一个异步方法(所以会返回Future)。下面的代码展示了这个过程:

EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class);
...
Future<?> future = group.shutdownGracefully();
//调用syncUninterruptibly()来阻塞直到group关闭
future.syncUninterruptibly();
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  netty-in-action netty4