netty使用NioEventLoopGroup处理连接,读,写等IO事件,使用pipeline-handler真正处理业务。
在日常开发中,我们通常都将注意力集中在业务handler的编写上,对于NioEventLoopGroup的关注相对较少,因此我们很有必要了解到底什么是pipeline,pipeline是如何工作的
handler
handler是处理读写事件的处理器,也是我们真正的业务处理器。handler主要分为两种类型:
- ChannelInboundHandler:处理入站事件
- ChannelOutboundHandler:处理出站事件
那么什么是入站和出站呢?对于服务端来说,进入服务端的数据就是入站数据,由服务端向外发出的数据就是出站。
对于客户端来说,来自服务端的数据就是入站,向服务端发送的数据就是出站。
简单来说,如果把数据比作钱,收入就是入站,支出就是出站。
以ChannelInboundHandler为例,该接口定义如下几个方法:
当有入站数据被读到时,channelRead方法会被调用,而我们就可以在该方法中做业务处理,然后通过ctx.writeAndFlush()将响应数据发送出去。
而发送出去的数据,将被ChannelInboundHandler处理后,才最终通过channel真正发送给出去
http示例
实际上,我们通常需要多个handler,除了业务处理handler,我们还常常需要一些编码和解码的handler。
以http服务器为例,要知道网络中数据是以二进制形式传输的,http协议是基于tcp之上的应用层协议,协议中有header,有body。在接收到数据时,如何将字节数据解析成http协议?在响应数据时,如何将协议数据转换成二进制?
这就需要编码器和解码器。编码器和解码器就是使用ChannelInboundHandler和ChannelOutboundHandler来实现
下图说明了使用netty搭建http服务器时,http请求的整个流程
代码示例:
netty已经为我们提供了httpRequestDecoder和HttpResponseEncoder,我们只需要编写业务处理handler即可。
业务处理HttpHandler
package com.netty.http;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
public class HttpHandler extends SimpleChannelInboundHandler<HttpObject> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
if (msg instanceof HttpRequest){
HttpRequest httpRequest = (HttpRequest)msg;
System.out.println("request url :" + httpRequest.getUri());
ByteBuf content = Unpooled.copiedBuffer("go go go", CharsetUtil.UTF_8);
FullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
httpResponse.headers().set("Content-Type", "text/plain");
httpResponse.headers().set("Content-Length", content.readableBytes());
ctx.writeAndFlush(httpResponse);
}
}
}
该业务handler比较简单,构造一个http response,状态码200,内容是“go go go”
启动服务:
package com.netty.http;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
public class HttpServer {
public static void main(String[] args) throws Exception{
int port = 8080;
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker)
.channel(NioServerSocketChannel.class)
.localAddress(port)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new HttpRequestDecoder());
ch.pipeline().addLast(new HttpResponseEncoder());
ch.pipeline().addLast(new HttpHandler());
}
});
ChannelFuture f = serverBootstrap.bind().sync();
System.out.println("wait channel close...");
f.channel().closeFuture().sync();
System.out.println("channel close...");
} finally {
boss.shutdownGracefully().sync();
worker.shutdownGracefully().sync();
}
}
}
上面比较关键的代码就是:
ch.pipeline().addLast(new HttpRequestDecoder());
ch.pipeline().addLast(new HttpResponseEncoder());
ch.pipeline().addLast(new HttpHandler());
当连接创建时,往pipeline中添加了三个handler,一个业务handler,两个http编解码的handler。
启动服务后,使用浏览器访问即可收到服务端响应的gogogo了。
pipeline
pipeline是一组handler的接口,它的数据结构是一个双向链表,不过链表的每一个元素并不是handler,而是ChannelHandlerContext,ChannelHandlerContext中持有一个handler
结构如下图:
无论是InBoundHandler还是OutBoundHandler都是在这一条双向链表上。
当有入站数据时,数据将从head节点向tail节点传递,不过只被ChannelInboundHandler处理
有数据出站时,数据将从tail节点向head节点传递,不过只被ChannelOutboundHandler处理
因此,两种类型的handler虽然在一个链上,却互不干扰。
值得注意的是handler的顺序不能弄错。
上面的例子中,HttpResponseEncoder是放在第二个位置,如果将其放到httpHandler之后,你会发现http请求无法正常响应。
ch.pipeline().addLast(new HttpRequestDecoder());
// ch.pipeline().addLast(new HttpResponseEncoder());
ch.pipeline().addLast(new HttpHandler());
// 将HttpResponseEncoder放到最后
ch.pipeline().addLast(new HttpResponseEncoder());
因为当数据经过HttpRequestDecoder和HttpHandler后,HttpHandler执行了writeAndFlush(),响应数据的流向是往head节点方向的。而HttpResponseEncoder是在tail方向,因此不会被调用到
所以ChannelOutboundHandler一定要放在前面