netty执行流程分析

关于连接的建立与断开

netty的连接的断开和使用的HTTP协议版本有关。
网络编程的底层有两种协议,tcp和udp,HTTP协议通常运行在tcp上,又由于HTTP是一种无状态协议,一般都是请求-响应的模式,所以最初的HTTP0.9中对于资源请求是 建立连接 断开连接,这两个过程。
TCP连接的新建成本很高,因为需要客户端和服务器三次握手,并且开始时发送速率较慢(slow start)。所以,HTTP 1.0版本的性能比较差。随着网页加载的外部资源越来越多,这个问题就愈发突出了。

HTTP1.0中引用了一个非标准的Connection字段 => Connection: keep-alive。
这个字段要求服务器不要关闭TCP连接,以便其他请求复用。服务器同样回应这个字段。
一个可以复用的TCP连接就建立了,直到客户端或服务器主动关闭连接。但是,这不是标准字段,不同实现的行为可能不一致,因此不是根本的解决办法。

1.1 版的最大变化,就是引入了持久连接(persistent connection),即TCP连接默认不关闭,可以被多个请求复用,不用声明Connection: keep-alive。解决了1.0版本的keepalive问题,1.1版本加入了持久连接,一个TCP连接可以允许多个HTTP请求;
客户端和服务器发现对方一段时间没有活动,就可以主动关闭连接。不过,规范的做法是,客户端在最后一个请求时,发送Connection: close,明确要求服务器关闭TCP连接。
目前,对于同一个域名,大多数浏览器允许同时建立6个持久连接。降低了延迟同时提高了带宽的利用率。
同时加入了管道机制,在同一个TCP连接里,允许多个请求同时发送,增加了并发性,进一步改善了HTTP协议的效率;举例来说,客户端需要请求两个资源。以前的做法是,在同一个TCP连接里面,先发送A请求,然后等待服务器做出回应,收到后再发出B请求。管道机制则是允许浏览器同时发出A请求和B请求,但是服务器还是按照顺序,先回应A请求,完成后再回应B请求。

服务端使用HTTP1.0协议

使用游览器访问 localhost:9999 ,服务端会自动断开连接,因为HTTP1.0相当于短连接,默认会断开

服务端使用HTTP1.1协议

使用游览器访问 localhost:9999 ,服务端不会自动断开,如果长时间客户端没有请求才会自动断开,因为HTTP1.1引入了持久连接。

过一段时间后才会解除连接
这里会执行两次handler是因为chrome游览器发送请求时会自动多发一个请求

这是用于获取网站图片的,所以只要是访问了localhost:9999 就会触发我们的handler

关于handler中回调方法的执行顺序

这里我们使用cmd 的 curl命令来请求,因为他只会发送一个请求。
回调函数们:

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("注册:"+ctx.name());
        super.channelRegistered(ctx);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("解除注册:"+ctx.name());
        super.channelUnregistered(ctx);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("连接活跃:"+ctx.name());
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("连接不活跃了:"+ctx.name());
        super.channelInactive(ctx);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("读取:"+ctx.name()+"内容=>");
        System.out.println("***************************");
        System.out.println(msg);
        System.out.println("***************************");
        super.channelRead(ctx, msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("读取完毕:"+ctx.name());
        super.channelReadComplete(ctx);
    }

以及在我们channelRead0最后添加一个手动关闭连接和一个结束的输出

然后开启cmd进行测试
结果如下:

在请求下,多线程运转时显示结果会不一样,我们就发一条来看下他的基本流程。
其中一开始会执行channelRegistered 进行注册
建立连接 channelActive
读取内容 channelRead
读取完毕 channelReadComplete
释放连接 channelInactive
解除注册 channelUnregistered
其中有个细节,在他执行完handler后又读取了一个 EmptyLastHttpContent,这个后面再说。

关于channel的分析

在我们的ServerInitializer类中,我们重写了initChannel方法,用来初始化我们的channel
在Netty里,Channel是通讯的载体,而ChannelHandler负责Channel中的逻辑处理。
那么ChannelPipeline是什么呢?我觉得可以理解为ChannelHandler的容器:一个Channel包含一个ChannelPipeline,所有ChannelHandler都会注册到ChannelPipeline中,并按顺序组织起来。
观察我们的代码:

@Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast("HttpServerCodec",new HttpServerCodec())
                .addLast("HelloHandler",new HelloHandler());
    }

pipeline 这个“管道容器”为我们添加了两个handler,这个地方有点像拦截器,他是按顺序执行的。在代码的他的顺序应该是 先 HttpServerCodec 再走 HelloHandler。
其中HttpServerCodec 是为我们提供了编码和解码,我们可以试下如果不要他会怎么样。
我们删除HttpServerCodec ,然后运行程序

首先头信息没有,是因为没有编码和解码,间接的导致了handler的执行没有执行到close,没有返回结果 我们的cmd也卡在那里

所以一般HttpServerCodec是要加在handler执行之前

posted @ 2020-12-17 19:05  DirtyShady  阅读(298)  评论(0)    收藏  举报