Netty 3 Bootstrap和Channel的生命周期
Bootstrap简介
Bootstrap :引导程序,将ChannelPipeline、ChannelHandler、EventLoop进行整体关联作用。
Bootstrap的结构中。定义了一个抽象父类,两个具体子类。
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel>
implements Cloneable
抽象类定义中,子类型B是父类型的一个类型参数??这样的泛型定义方式有啥好处???
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel>
public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel>
Bootstrap具体分为了两个实现,分别是服务端引导类和客户端引导类。
ServerBootstrap:用于服务端,使用一个ServerChannel来接受客户端的连接并创建出对应的子Channel。
Bootstrap:用于客户端,只需要一个单独的Channel,来与服务端进行数据交互,对应server端的子Channel。
ServerBootstrap实例程序和API
public void start(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(nettyThread);
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // (3)
.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
// server端发送的是httpResponse,所以要使用HttpResponseEncoder进行编码
ch.pipeline().addLast(new HttpResponseEncoder());
// server端接收到的是httpRequest,所以要使用HttpRequestDecoder进行解码
ch.pipeline().addLast(new HttpRequestDecoder());
ch.pipeline().addLast(new HttpObjectAggregator(nettyLength));
ch.pipeline().addLast(new HttpServerInboundHandler());
}
}).option(ChannelOption.SO_BACKLOG, 128) // (5)
.childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
|
名称
|
描述
|
| group | 设置 ServerBootstrap 要用的 EventLoopGroup。这个 EventLoopGroup 将用于 ServerChannel 和被接受的子 Channel 的 I/O 处理 |
| channel | 设置将要被实例化的 ServerChannel 类 |
| channelFactory | 如果不能通过默认的构造函数 创建Channel,那么可以提供一个ChannelFactory |
| localAddress | 指定 ServerChannel 应该绑定到的本地地址。如果没有指定,则将由操作系 统使用一个随机地址。或者,可以通过 bind()方法来指定该 localAddress |
| option | 指定要应用到新创建的 ServerChannel 的 ChannelConfig 的 Channel- Option。这些选项将会通过 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中的Channel- Handler。handler()方法和 childHandler()方法之间的区别是:前者所 添加的 ChannelHandler 由接受子 Channel 的 ServerChannel 处理,而 childHandler()方法所添加的ChannelHandler将由已被接受的子Channel 处理,其代表一个绑定到远程节点的套接字 |
| clone | 克隆一个设置和原始的 ServerBootstrap 相同的 ServerBootstrap |
| bind | 绑定 ServerChannel 并且返回一个 ChannelFuture,其将会在绑定操作完 成后收到通知(带着成功或者失败的结果) |
在ServerBootstrap的创建过程中,通过channel()方法设置了需要被实例化ServerChannel类。
ServerChannel代表客户端和服务端之间的连接。不同的协议有对应不同的ServerChannel。可以从ServerChannel子接口中可以看出,对于不同的协议有不同的ServerChannel子接口。我们要使用的NioServerSocketChannel就是TCP协议的。Java nio中的形式使类似的,对于ServerSocketChannel还有对应的SocketChannel。
ServerSocketChannel和SocketChannel可以参考简单Java Nio通信的逻辑。客户端发起SocketChannel的通信,服务端通过ServerSocketChannel获取对应的SocketChannel。下面是普通Java Nio的服务端代码。
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(1234));
while (true) {
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
executor.submit(new SocketChannelThread(socketChannel));
}
}
在初始化过程中添加多个ChannelHandler。
在初始化过程中调用handler或者childHandler方法可以用来添加单个ChannelHandler。但是正常使用中,对于一个Channel需要多个Handler处理,如果把所有处理的逻辑都放在一个Handler中会导致一个臃肿的类,并且毫无扩展性可言。
在Netty提供了一个ChannelInboundHandlerAdapter子类,ChannelInitializer。通过这个可以将多个Handler添加到一个ChannelPipeline中。在添加的过程中,需要注意ChannelHandler的添加顺序。将一些数据梳理的Handler放在前面,业务处理的则放在最后。例如Http服务,需要先将Netty的TCP数据封装成对应的HttpRequest之后我们才能方便使用。
在Netty中提供了常用的ChannelHandler,具体参考ChannelHandler中。
childHandler(new ChannelInitializer<SocketChannel>() { // (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
// server端发送的是httpResponse,所以要使用HttpResponseEncoder进行编码
ch.pipeline().addLast(new HttpResponseEncoder());
// server端接收到的是httpRequest,所以要使用HttpRequestDecoder进行解码
ch.pipeline().addLast(new HttpRequestDecoder());
ch.pipeline().addLast(new HttpObjectAggregator(nettyLength));
ch.pipeline().addLast(new HttpServerInboundHandler());
}
})
服务端Channel的生命周期
Channel是服务端和客户端用于传输数据的通道,Channel服务维护套接字连接、IO操作等组件。下面以服务端的NioServerSocketChannel 和对应的NioSocketChannel作为例子。
对于TCP,甚至于所有有连接的传输层协议。都可以归纳为以下三个步骤:建立连接、数据传输、关闭连接。在服务的话需要提前监听端口,用于建立连接。
服务端ServerChannel
通过channel方法设定SeverChannel,通过代码可以看到,通过传入的clazz,构造了一个ReflectiveChannelFactory,并将其赋值到this.channelFactory中。用于后续创建ServerChannel的实例。
ServerBootstrap b = new ServerBootstrap();
....
b.channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 128)
public B channel(Class<? extends C> channelClass) {
if (channelClass == null) {
throw new NullPointerException("channelClass");
}
return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
if (channelFactory == null) {
throw new NullPointerException("channelFactory");
}
if (this.channelFactory != null) {
throw new IllegalStateException("channelFactory set already");
}
this.channelFactory = channelFactory;
return (B) this;
}
通过对ServerBootstrap中一些参数的设置,通过bind方法,开始Server端的启动。
1.在initAndRegister方法中,根据上述channel设置的channelFactory进行了实例化,创建ServerChannel用于监听。
2.创建完ServerChannel之后,需要将其注册到EventLoopGroup中,对分配到ServerChannel的EventLoop分配线程,启动监听线程。通过Selector获取TCP连接请求。
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
} catch (Throwable t) {
if (channel != null) {
channel.unsafe().closeForcibly();
}
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
服务端子Channel建立连接
1.创建子Channel,在监听线程中使用死循环的run方法,获取SelectedKey信息。并对SelectKey中不同readyOps执行不停的操作。OP_ACCEPT时候表示创建连接,使用unsafe.read()方法。
2.在unsafe.read()中通过NioServerSocketChannel中封装的sun.nio.ch.ServerSocketChannelImpl,accept方法去获取对应的子Channel,java.nio.channels.SocketChannel实例。
3.获取到一个SocketChannel列表后,将列表中的SocketChannel存放到NioServerSocketChannel中的ChannelPipeline中,并调用
io.netty.bootstrap.ServerBootstrap$ServerBootstrapAcceptor.channelRead方法。因为ServerBootstrapAcceptor是 ChannelInboundHandlerAdapter的子类
4.ServerBootstrapAcceptor将当前SocketChannel注册到childGroup中的EventLoop。后续所有对于这个SocketChannel的读写操作由分配的EventLoop去完成。
NioEventLoop.processSelectedKeys()
private void processSelectedKeys() {
if (selectedKeys != null) {
processSelectedKeysOptimized();
} else {
processSelectedKeysPlain(selector.selectedKeys());
}
}
NioEventLoop.processSelectedKey()
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {
final EventLoop eventLoop;
try {
eventLoop = ch.eventLoop();
} catch (Throwable ignored) {
// If the channel implementation throws an exception because there is no event loop, we ignore this
// because we are only trying to determine if ch is registered to this event loop and thus has authority
// to close ch.
return;
}
// Only close ch if ch is still registered to this EventLoop. ch could have deregistered from the event loop
// and thus the SelectionKey could be cancelled as part of the deregistration process, but the channel is
// still healthy and should not be closed.
// See https://github.com/netty/netty/issues/5125
if (eventLoop != this || eventLoop == null) {
return;
}
// close the channel if the key is not valid anymore
unsafe.close(unsafe.voidPromise());
return;
}
try {
int readyOps = k.readyOps();
// We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
// the NIO JDK channel implementation may throw a NotYetConnectedException.
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
// See https://github.com/netty/netty/issues/924
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
// Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
ch.unsafe().forceFlush();
}
// Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead to
a spin loop
// 在断点调试过程中,因为是服务端调试,所以并不会出现OP_CONNECT的状态,只会在客户端出现。
// 没有出现OP_WRITE的情况很奇怪,是因为读写操作交给EventLoop,或者请求方式不对??? 不是很清楚。
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
NioMessageUnsafe.read() NioMessageUnsafe是AbstractNioMessageChannel中的静态内部类。AbstractNioMessageChannel是AbstractChannel的子类,
也是所有NioXXXXChannel的父类,其中就包括了NioServerSocketChannel。
public void read() {
assert eventLoop().inEventLoop();
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline(); //从NioServerSocketChannel中获取对应的pipeline---DefaultChannelPipeline
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.reset(config);
boolean closed = false;
Throwable exception = null;
try {
//通过调用子类实现的doReadMesages(readBuf)放方法,从NioServerSocketChannel中读取出所有的SocketChannel。 对应的实例是Netty的NioSocketChannel。但是其本质是java.nio.channels.SocketChannel。
try {
do {
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
allocHandle.incMessagesRead(localRead);
} while (allocHandle.continueReading());
} catch (Throwable t) {
exception = t;
}
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
readPending = false;
//将上述获取的所有socketChannel放入NioServerSocketChannel的Channelpipeline中
pipeline.fireChannelRead(readBuf.get(i));
}
readBuf.clear();
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
if (exception != null) {
closed = closeOnReadError(exception);
pipeline.fireExceptionCaught(exception);
}
if (closed) {
inputShutdown = true;
if (isOpen()) {
close(voidPromise());
}
}
} finally {
// Check if there is a readPending which was not processed yet.
// This could be for two reasons:
// * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
// * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
//
// See https://github.com/netty/netty/issues/2254
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
NioServerSocketChannel.doReadMessages()
protected int doReadMessages(List<Object> buf) throws Exception {
//javaChannel()方法从NioServerSocketChannel中获取SelectableChannel的子类ch。 具体实现是sun.nio.ch.ServerSocketChannelImpl。
//从另一方面将就是NioServerSocketChannel封装了sun.nio.ch.ServerSocketChannelImpl来实现ServerSocketChannel的功能。
//ch的实例对象是java.nio.channels.SocketChannel。并且将SocketChannel封装到Netty的NioSocketChannel对象中。
SocketChannel ch = SocketUtils.accept(javaChannel());
try {
if (ch != null) {
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) {
........
}
return 0;
}
服务端子Channel关闭连接
public class HttpServerInboundHandler extends ChannelInboundHandlerAdapter
自定义的Http业务处理的ChannelHandler,继承自ChannelInboundhandlerAdapter.下面两个方法Netty中返回数据,或者返回文件的方法。
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
HashMap<String, String> params = new HashMap<String, String>();
if (msg instanceof FullHttpRequest) {
FullHttpRequest fullrequest = (FullHttpRequest) msg;
....
SendMessage(ctx, fullrequest, routeError);
....
}
}
public void SendMessage(ChannelHandlerContext ctx, FullHttpRequest request, ResultBean resultBean) throws IOException{
//设置返回的数据
String res = gson.toJson(resultBean.getMap());
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer(res, CharsetUtil.UTF_8));
response.headers().set(CONTENT_TYPE, "text/plain;charset=UTF-8");
response.headers().set(CONTENT_LENGTH,response.content().readableBytes());
response.headers().set("resultBean", URLEncoder.encode(gson.toJson(resultBean.getMap())));
boolean keepAlive = HttpUtil.isKeepAlive(request);
if (keepAlive) {
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
ctx.write(response);
//添加close的监听器
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
} else {
ctx.write(response).addListener(ChannelFutureListener.CLOSE);
}
//因为是继承自ChannelInboundhandlerAdapter,所以需要显示的将Request释放,防止Netty的内存泄漏。具体原因可以查看Netty ByteBuf和堆外内存。
while(!request.release());
if (response.refCnt()>0) {
while(!response.release());
}
}
public void SendFile(String fileName,String filePath, ChannelHandlerContext ctx, FullHttpRequest request, ResultBean resultBean,
final PdfParseBean pb, final String isSave) throws IOException {
File file = new File(filePath);
final RandomAccessFile raf = new RandomAccessFile(file, "r");
long fileLength = raf.length();
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
HttpUtil.setContentLength(response, fileLength);
setContentTypeHeader(response, file);
setDateAndCacheHeaders(response, file);
response.headers().set("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName));
response.headers().set("resultBean", URLEncoder.encode(gson.toJson(resultBean.getMap())));
if (HttpUtil.isKeepAlive(request)) {
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
}
ctx.write(response);
ChannelFuture sendFileFuture;
ChannelFuture lastContentFuture;
DefaultFileRegion defaultFileRegion = null;
HttpChunkedInput httpChunkedInput = null;
if (ctx.pipeline().get(SslHandler.class) == null) {
defaultFileRegion = new DefaultFileRegion(raf.getChannel(), 0, fileLength);
sendFileFuture = ctx.write(defaultFileRegion,ctx.newProgressivePromise());
lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
} else {
httpChunkedInput = new HttpChunkedInput(new ChunkedFile(raf, 0, fileLength, 8192));
sendFileFuture = ctx.writeAndFlush(httpChunkedInput,ctx.newProgressivePromise());
lastContentFuture = sendFileFuture;
}
sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
@Override
public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {
if (total < 0) { // total unknown
System.err.println(future.channel() + " Transfer progress: " + progress);
} else {
System.err.println(future.channel() + " Transfer progress: " + progress + " / " + total);
}
}
@Override
public void operationComplete(ChannelProgressiveFuture future) throws IOException {
System.err.println(future.channel() + " Transfer complete.");
raf.close();
if (!"true".equals(isSave)) {
deleteFile(pb);
}
}
});
if (!HttpUtil.isKeepAlive(request)) {
lastContentFuture.addListener(ChannelFutureListener.CLOSE);
}
while(!request.release());
if (httpChunkedInput!=null) {
try {
httpChunkedInput.close();
} catch (Exception e) {
logger.error("httpChunkedInput.close() error ",e);
}
}
}
上面面代码中提供了Netty返回文本数据和返回文件的两个方法,可以在代码中可以看到在发送数据的最后都添加了这样一条类似的代码。
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
对ChannelFuture添加Listener。
ChannelFutureListener中默认实现的Listener有以下几种CLOSE、CLOSE_ON_FAILURE、FIRE_EXCEPTION_ON_FAILURE。我们需要的是CLOSE。
从代码上看也是很简单,通过ChannelFuture获取对应的Channel(NioSocketChannel),然后对Channel进行关闭。
跟踪代码可以看到,首先通过Channel调用close,然后交由ChannelPipeLine处理,在经过ChannelHandlerContext。等等一系列操作,最后通过NioSocketChannel封装的
java.nio.channels.SocketChannel实例进行close。
public interface ChannelFutureListener extends GenericFutureListener<ChannelFuture> {
ChannelFutureListener CLOSE = new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) {
future.channel().close();
}
};
ChannelFutureListener CLOSE_ON_FAILURE = new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) {
if (!future.isSuccess()) {
future.channel().close();
}
}
};
ChannelFutureListener FIRE_EXCEPTION_ON_FAILURE = new ChannelFutureListener() {
......
};
}
NioSocketChannel
public ChannelFuture close() {
return pipeline.close();
}
DefaultChannelPipeline
AbstractChannelHandlerContext tail
public final ChannelFuture close() {
return tail.close();
}
NioSocketChannel
protected void doClose() throws Exception {
super.doClose();
javaChannel().close();
}
服务端子Channel数据传输
留坑
客户端Bootstrap代码
public void connect(String host, int port, String filePath, String tool_type, final String parse_type,String align_style,String pdf_style) throws Exception {
// final String parse_type = "txt";
EventLoopGroup workerGroup = new NioEventLoopGroup();
HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("codec", new HttpClientCodec());
pipeline.addLast("inflater", new HttpContentDecompressor());
pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
pipeline.addLast(new HttpClientInboundHandler(parse_type));
}
});
// Start the client.
ChannelFuture f = b.connect(host, port).sync(); // (5)
String uriSting = new URI("/search/v1/pdf_service/parse").toASCIIString();
ChannelFuture future = b.connect(SocketUtils.socketAddress(host, port));
Channel channel = future.sync().channel();
//不能使用FullHttpRequest
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST,uriSting);
HttpPostRequestEncoder bodyRequestEncoder =
new HttpPostRequestEncoder(factory, request, true); // true => multipart
HttpHeaders headers1 = request.headers();
headers1.set(HttpHeaderNames.HOST, host);
headers1.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
headers1.set(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP + "," + HttpHeaderValues.DEFLATE);
headers1.set(HttpHeaderNames.ACCEPT_CHARSET, "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
headers1.set(HttpHeaderNames.ACCEPT_LANGUAGE, "fr");
headers1.set(HttpHeaderNames.USER_AGENT, "Netty Simple Http Client side");
headers1.set(HttpHeaderNames.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
File file = new File(filePath);
bodyRequestEncoder.addBodyFileUpload("myfile", file, "application/x-zip-compressed", false);
bodyRequestEncoder.addBodyAttribute("tool_type", tool_type); //Jpdf Ppdf
if ("Jpdf".equals(tool_type)) {
bodyRequestEncoder.addBodyAttribute("parse_type", parse_type);
bodyRequestEncoder.addBodyAttribute("is_pagebreak", "true");
bodyRequestEncoder.addBodyAttribute("align_style", align_style); //TEXT_RIGHT TEXT_LEFT TEXT_ALLCENTERED
bodyRequestEncoder.addBodyAttribute("pdf_style", pdf_style); //CHINESE_PDF ENGLISH_PDF
}else if ("Ppdf".equals(tool_type)) {
bodyRequestEncoder.addBodyAttribute("ppdf_param", "");
}else {
}
// finalize request
bodyRequestEncoder.finalizeRequest();
// send request
channel.write(request);
// test if request was chunked and if so, finish the write
if (bodyRequestEncoder.isChunked()) {
channel.write(bodyRequestEncoder);
}
channel.flush();
// Now no more use of file representation (and list of HttpData)
bodyRequestEncoder.cleanFiles();
// Wait for the server to close the connection.
channel.closeFuture().sync();
// 发送http请求
// f.channel().write(request);
// f.channel().flush();
// f.channel().closeFuture().sync(
} finally {
workerGroup.shutdownGracefully();
}
}
| 名称 | 描述 |
| Bootstrap group(EventLoopGroup) | 设置用于处理 Channel 所有事件的 EventLoopGroup |
|
Bootstrap channel(
Class<? extends C>)
Bootstrap channelFactory(
ChannelFactory<? extends C>)
|
channel()方法指定了Channel的实现类。如果该实现类 没提供默认的构造函数 ,可以通过调用channel-
Factory()方法来指定一个工厂类,它将会被bind()方 法调用
|
| Bootstrap localAddress( SocketAddress) | 指定 Channel 应该绑定到的本地地址。如果没有指定, 则将由操作系统创建一个随机的地址。或者,也可以通过 bind()或者 connect()方法指定 localAddress |
|
<T> Bootstrap option(
ChannelOption<T> option,
T value)
|
设置 ChannelOption,其将被应用到每个新创建的 Channel 的 ChannelConfig。这些选项将会通过 bind()或者 connect()方法设置到 Channel,不管哪 个先被调用。这个方法在 Channel 已经被创建后再调用 将不会有任何的效果。支持的 ChannelOption 取决于 使用的 Channel 类型。 参见 8.6 节以及 ChannelConfig 的 API 文档,了解所 使用的 Channel 类型 |
|
<T> Bootstrap attr(
Attribute<T> 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()方法来建立连接 |