zoukankan      html  css  js  c++  java
  • Netty章节二:Hello world/基本的http服务

    功能实现:使用netty构建一个类似于tomcat的web服务器,服务端监听8899端口,当访问8899端口的时候,服务器端给客户端hello world的响应。

    服务端代码

    启动主程序

    public class TestServer {
        public static void main(String[] args) throws Exception {
            /*
                定义两个事件循环组
                NioEventLoopGroup 就是一个死循环,不断接受客户端发起的连接并处理,和tomcat这种服务器一样
                bossGroup 用于接受客户端的连接但是不做处理,会把连接转给workerGroup
                workerGroup 会对连接进行处理,进行对应的业务处理,最后把结果返回给客户端
            */
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            try {
                //ServerBootstrap用于启动服务端
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                /*
                    group() 事件循环组
                    channel() 用到的管道 使用反射的方式创建的
                    childHandler() 子处理器,自己编写的处理器,
                        请求到来之后由我们自己编写的处理器进行真正的处理
                */
                serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                        .childHandler(new TestServerInitializer());
                //绑定端口
                ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
                //关闭的监听
                channelFuture.channel().closeFuture().sync();
            }finally {
                //关闭
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    }
    

    初始化器 (Initializer)

    客户端与服务端一旦连接之后,TestServerInitializer就会被创建 initChannel() 方法就会被调用

    public class TestServerInitializer extends ChannelInitializer<SocketChannel> {
    
        /**
         * 连接(Channel)一旦被注册之后该方法就会被调用
         * 回调方法
         */
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            //一个管道,一个管道当中可以有多个ChannelHandler(拦截器),
            // 每个拦截器做的事情就是针对自己本身的请求或业务情况,完成相应的处理
            ChannelPipeline pipeline = ch.pipeline();
            pipeline.addLast("httpServerCodec",new HttpServerCodec());
            pipeline.addLast("testHttpServerHandler",new TestHttpServerHandler());
        }
    }
    

    自定义处理器 (Handler)

    public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
    
        /**
         * channelRead0 读取客户端发过来的请求,并且向客户端返回响应的方法
         */
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
    
            System.out.println(msg.getClass());
    
            //打印出远程的地址,/0:0:0:0:0:0:0:1: 52434,本地线程的49734端口的线程和netty进行通信
            System.out.println(ctx.channel().remoteAddress());
            //Thread.sleep(8000);   用于测试持续连接(keepalive)
            if(msg instanceof HttpRequest){
    
                HttpRequest httpRequest = (HttpRequest)msg;
                System.out.println("请求方法名:"+httpRequest.method().name()); 
    
                URI uri = new URI(httpRequest.uri());
                //使用浏览器访问localhost:8899会发送二次请求,
                //其中有一次是localhost:8899/favicon.ico 这个url请求访问网站的图标
                if("/favicon.ico".equals(uri.getPath())){
                    System.out.println("请求favicon.ico");
                    return;
                }
                
                //向客户端返回的响应内容
                ByteBuf content =
                        Unpooled.copiedBuffer("Hello World", CharsetUtil.UTF_8);
                //构建一个响应
                //DefaultFullHttpResponse(http协议的版本,返回的状态码,响应内容);
                FullHttpResponse response = new DefaultFullHttpResponse(
                        HttpVersion.HTTP_1_1, HttpResponseStatus.OK,content);
                //设置response相应的头信息
                response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
                response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());
    
                //返回客户端
                ctx.writeAndFlush(response);
                //手动关闭连接 如果要测试持续连接需要注掉 ctx.channel().close();
                //其实更合理的close连接应该判断是http1.O还是1.1来进行判断请求超时时间来断开channel连接。
                ctx.channel().close();
            }
        }
    
    
        /**通道注册时调用*/
        @Override
        public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
            System.out.println("channel registered");
            super.channelRegistered(ctx);
        }
    
        /**通道活跃时调用*/
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("channel active");
            super.channelActive(ctx);
        }
    
        /**处理程序已添加时调用*/
        @Override
        public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
            System.out.println("handler added");
            super.handlerAdded(ctx);
        }
        /**通道停止活跃时调用*/
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("channel inactive");
            super.channelInactive(ctx);
        }
    
        /**通道取消注册时调用*/
        @Override
        public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
            System.out.println("channel unregistered");
            super.channelUnregistered(ctx);
        }
    
        /**连接断开之后*/
        @Override
        public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
            super.handlerRemoved(ctx);
        }
    }
    

    请求测试

    curl访问

    ❯ curl 'localhost:8899'                                                         
    Hello World% 
    

    服务端显示

    handler added	//通道添加
    channel registered	//通道注册
    channel active	//通道活跃
    class io.netty.handler.codec.http.DefaultHttpRequest
    /0:0:0:0:0:0:0:1:46082
    请求方法名:GET
    class io.netty.handler.codec.http.LastHttpContent$1
    /0:0:0:0:0:0:0:1:46082
    channel inactive	//通道不活跃
    channel unregistered	//通道取消注册
    

    使用curl工具请求服务端,当请求结束结果返回之后通道/连接马上就被close掉了,服务端使用http1.1 同样如此,curl只是一个单次请求响应的工具,并没有使用到http1.1的keepalive 持续连接特性

    Google浏览器访问

    请输入图片描述

    服务器终端显示

    //第一次请求
    handler added
    handler added
    channel registered
    channel registered
    channel active
    channel active
    class io.netty.handler.codec.http.DefaultHttpRequest
    /0:0:0:0:0:0:0:1:46500
    请求方法名:GET
    class io.netty.handler.codec.http.LastHttpContent$1
    /0:0:0:0:0:0:0:1:46500
    class io.netty.handler.codec.http.DefaultHttpRequest
    /0:0:0:0:0:0:0:1:46500
    请求方法名:GET
    请求favicon.ico
    class io.netty.handler.codec.http.LastHttpContent$1
    /0:0:0:0:0:0:0:1:46500
    
    //第二次请求
    class io.netty.handler.codec.http.DefaultHttpRequest
    /0:0:0:0:0:0:0:1:46502
    请求方法名:GET
    class io.netty.handler.codec.http.LastHttpContent$1
    /0:0:0:0:0:0:0:1:46502
    channel inactive	//第一个通道因为在保持时间内没有第二个请求复用该连接,被close
    channel unregistered	//第一个通道因为在保持时间内没有第二个请求复用该连接,被close
    class io.netty.handler.codec.http.DefaultHttpRequest
    /0:0:0:0:0:0:0:1:46502
    请求方法名:GET
    请求favicon.ico
    class io.netty.handler.codec.http.LastHttpContent$1
    /0:0:0:0:0:0:0:1:46502
    

    Google浏览器的访问使用到了持续连接的特性,第一次请求 浏览器分别请求了localhost:8899 和localhost:8899/favicon.ico两个路经,所以上面开头就添加/注册了两次通道

    • 不是持续连接吗?为什么会添加/注册两次通道,个人理解是第一个通道还没添加注册成为可用通道状态之前,第二个连接就到达了,就又创建了一个新的通道,可见输出信息也是这样

    第一次请求的localhost:8899和localhost:8899/favicon.ico两个请求的通道/连接用的都是同一个建立在46500端口之上的通道,第二次请求的全部操作都使用了另一个通道(46502)

    lsof查看端口绑定关系

    请求之前

    ❯ lsof -i:8899                                                                 
    COMMAND   PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
    java    21617 sakura  241u  IPv6 530665      0t0  TCP *:ospf-lite (LISTEN)	//程序本身
    

    第一次请求之后

    ❯ lsof -i:8899                                                                 
    COMMAND   PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
    chrome   1858 sakura   47u  IPv6 516947      0t0  TCP localhost:46500->localhost:ospf-lite (ESTABLISHED)
    chrome   1858 sakura   53u  IPv6 516948      0t0  TCP localhost:46502->localhost:ospf-lite (ESTABLISHED)
    java    21617 sakura  241u  IPv6 530665      0t0  TCP *:ospf-lite (LISTEN)
    java    21617 sakura  243u  IPv6 527749      0t0  TCP localhost:ospf-lite->localhost:46500 (ESTABLISHED)
    java    21617 sakura  244u  IPv6 524734      0t0  TCP localhost:ospf-lite->localhost:46502 (ESTABLISHED)
    

    第二次请求之后

    ❯ lsof -i:8899                                                                           
    COMMAND   PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
    chrome   1858 sakura   53u  IPv6 516948      0t0  TCP localhost:46502->localhost:ospf-lite (ESTABLISHED)
    java    21617 sakura  241u  IPv6 530665      0t0  TCP *:ospf-lite (LISTEN)
    java    21617 sakura  244u  IPv6 524734      0t0  TCP localhost:ospf-lite->localhost:46502 (ESTABLISHED)
    

    请输入图片描述

  • 相关阅读:
    在markdown中使用html
    乘车路线
    渔民的烦恼
    GEDIT外部工具
    模板匹配,以图找图(九)
    SpringBoot起飞系列-国际化(六)
    [Lyndon分解] HDU 6761 Minimum Index
    [数论]HDU 6750 Function 百度之星2020初赛第一场H题
    【雅思】【口语】描述一个可笑的场合
    面试回答数据库优化问题-数据库优化思路八点
  • 原文地址:https://www.cnblogs.com/mikisakura/p/12983460.html
Copyright © 2011-2022 走看看