netty轻松入门(三)—— pipeline与handler

netty使用NioEventLoopGroup处理连接,读,写等IO事件,使用pipeline-handler真正处理业务。

在日常开发中,我们通常都将注意力集中在业务handler的编写上,对于NioEventLoopGroup的关注相对较少,因此我们很有必要了解到底什么是pipeline,pipeline是如何工作的

handler

handler是处理读写事件的处理器,也是我们真正的业务处理器。handler主要分为两种类型:

  1. ChannelInboundHandler:处理入站事件
  2. 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一定要放在前面

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值