netty(十五)Netty提升 - 协议设计以及常见协议介绍

作者: 我犟不过你 | 来源:发表于2021-11-16 14:12 被阅读0次

    一、什么是协议?

    在计算机网络与信息通信领域里,人们经常提及“协议”一词。互联网中常用的具有代表性的协议有IP、TCP、HTTP等。

    简单来说,协议就是计算机与计算机之间通过网络实现通信时事先达成的一种“约定”。这种“约定”使那些由不同厂商的设备、不同的CPU以及不同的操作系统组成的计算机之间,只要遵循相同的协议就能够实现通信。

    二、协议的必要性

    通常,我们发送一封电子邮件、访问某个主页获取信息时察觉不到协议的存在,只有在我们重新配置计算机的网络连接、修改网络设置时才有可能涉及协议。

    这就好比两个人使用不同国家的语言说话,怎么也无法相互理解。也可以比喻成没有断句的话,有多种不同的解释,双方可能会产生分歧。协议可以分为很多种,每一种协议都明确地界定了它的行为规范。两台计算机之间必须能够支持相同的协议,并遵循相同协议进行处理,这样才能实现相互通信。

    TCP/IP 中消息传输基于流的方式,没有边界。

    协议的目的就是划定消息的边界,制定通信双方要共同遵守的通信规则。

    三、常见协议示例

    3.1 redis协议

    比如我们要发送消息给redis,需要遵从其协议。
    比如我们要发送set key value 形式的命令,则我们首先要发送整个命令的长度,然后分别发送命令每个位置的长度,如下所示:

    set name Tom
    

    则需要按照如下的协议发送:

    *3 //命令长度
    $3 //set长度
    set //命令内容
    $4 //key的长度
    name //key的内容
    $3 //value的长度
    Tom //value的内容
    

    我们使用如下代码连接redis并发送上面的内容:

    创建一个客户端,连接本地redis:

    public class RedisProtocol {
        // 回车换行
        static final byte[] LINE = {13, 10};
    
        public static void main(String[] args) {
            NioEventLoopGroup worker = new NioEventLoopGroup();
    
            try {
                ChannelFuture channelFuture = new Bootstrap().group(worker)
                        .channel(NioSocketChannel.class)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel socketChannel) {
                                socketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                                    //连接建立后触发以下方法,发送内容
                                    @Override
                                    public void channelActive(ChannelHandlerContext ctx) {
                                        ByteBuf buf = ctx.alloc().buffer();
                                        buf.writeBytes("*3".getBytes());
                                        buf.writeBytes(LINE);
                                        buf.writeBytes("$3".getBytes());
                                        buf.writeBytes(LINE);
                                        buf.writeBytes("set".getBytes());
                                        buf.writeBytes(LINE);
                                        buf.writeBytes("$4".getBytes());
                                        buf.writeBytes(LINE);
                                        buf.writeBytes("name".getBytes());
                                        buf.writeBytes(LINE);
                                        buf.writeBytes("$3".getBytes());
                                        buf.writeBytes(LINE);
                                        buf.writeBytes("Tom".getBytes());
                                        buf.writeBytes(LINE);
                                        ctx.writeAndFlush(buf);
                                    }
                                    //接收redis返回值
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                        ByteBuf byteBuf = (ByteBuf) msg;
                                        System.out.println(byteBuf.toString(StandardCharsets.UTF_8));
                                    }
                                });
                            }
                        }).connect("127.0.0.1", 6379);
    
                channelFuture.sync();
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                worker.shutdownGracefully();
            }
        }
    }
    

    验证:

    127.0.0.1:0>get name
    "Tom"
    

    3.2 http协议

    下面我们演示下netty当中解析http请求的示例代码。因为http协议我们自己实现太过复杂,所以我们直接使用netty提供的http编解码器:HttpServerCodec

    public final class HttpServerCodec extends CombinedChannelDuplexHandler<HttpRequestDecoder, HttpResponseEncoder> implements SourceCodec
    

    如上图所示,我们发现这个类继承自CombinedChannelDuplexHandler,同时实现了编码和解码的功能。

    下面我们提供个一个服务端代码,通过浏览器访问,看看最终能收到浏览器请求内容是什么样的:

    public class HttpProtocolServer {
    
        public static void main(String[] args) {
            NioEventLoopGroup boss = new NioEventLoopGroup(1);
            NioEventLoopGroup worker = new NioEventLoopGroup();
    
            try {
                ChannelFuture channelFuture = new ServerBootstrap()
                        .group(boss, worker)
                        .channel(NioServerSocketChannel.class)
                        .childHandler(new ChannelInitializer<NioSocketChannel>() {
                            @Override
                            protected void initChannel(NioSocketChannel nioSocketChannel) {
                                nioSocketChannel.pipeline().addLast(new HttpServerCodec());
                                nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                        System.out.println(msg);
                                        System.out.println("****************");
                                        System.out.println(msg.getClass());
                                        super.channelRead(ctx, msg);
                                    }
                                });
                            }
                        }).bind(8080);
                channelFuture.sync();
                channelFuture.channel().closeFuture().sync();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                boss.shutdownGracefully();
                worker.shutdownGracefully();
            }
        }
    }
    

    浏览器访问http://localhost:8080/index.html,得到如下结果:

    DefaultHttpRequest(decodeResult: success, version: HTTP/1.1)
    GET / HTTP/1.1
    Host: localhost:8080
    Connection: keep-alive
    Cache-Control: max-age=0
    sec-ch-ua: "Chromium";v="92", " Not A;Brand";v="99", "Google Chrome";v="92"
    sec-ch-ua-mobile: ?0
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
    Sec-Fetch-Site: none
    Sec-Fetch-Mode: navigate
    Sec-Fetch-User: ?1
    Sec-Fetch-Dest: document
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
    ****************
    class io.netty.handler.codec.http.DefaultHttpRequest
    EmptyLastHttpContent
    ****************
    class io.netty.handler.codec.http.LastHttpContent$1
    

    分析如上内容,发现得到了很多http请求的参数内容,以及共接收到两个类型的类,分别是:DefaultHttpRequest 和 LastHttpContent,也就是说,http请求会给我们两部分的内容,分别是请求和请求体,这里无论是get还是post,都是如此。

    所以,当我们想要处理一个http请求的话,需要对这两个请求进行判断去分别处理:

    if (msg instanceof HttpRequest){
         // TODO do something...
    }else if(msg instanceof HttpContent){
       // TODO do something...
    }
    

    也可以使用netty提供的SimpleChannelInboundHandler的针对单一类型的入站处理器去处理请求:

     nioSocketChannel.pipeline().addLast(new SimpleChannelInboundHandler<HttpRequest>() {
          @Override
           protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpRequest httpRequest) {
    
           }
    });
    

    处理处理请求,一个正常的http请求还需要返回响应,我们用DefaultFullHttpResponse最为响应。这个类需要需要指定请求协议的version,以及请求的状态。

    除此之外,我们给请求增加上响应内容:Hello World!

    完整代码如下所示:

    public class HttpProtocolServer {
    
        public static void main(String[] args) {
            NioEventLoopGroup boss = new NioEventLoopGroup(1);
            NioEventLoopGroup worker = new NioEventLoopGroup();
    
            try {
                ChannelFuture channelFuture = new ServerBootstrap()
                        .group(boss, worker)
                        .channel(NioServerSocketChannel.class)
                        .childHandler(new ChannelInitializer<NioSocketChannel>() {
                            @Override
                            protected void initChannel(NioSocketChannel nioSocketChannel) {
                                nioSocketChannel.pipeline().addLast(new HttpServerCodec());
                                nioSocketChannel.pipeline().addLast(new SimpleChannelInboundHandler<HttpRequest>() {
                                    @Override
                                    protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpRequest httpRequest) {
                                        System.out.println(httpRequest.uri());
    
                                        //创建响应
                                        DefaultFullHttpResponse response =
                                                new DefaultFullHttpResponse(httpRequest.protocolVersion(), HttpResponseStatus.OK);
    
                                        byte[] bytes = "<h1>Hello World!<h1>".getBytes(StandardCharsets.UTF_8);
                                        //添加响应内容长度,否则会浏览器会一直处于加载状态
                                        response.headers().setInt(CONTENT_LENGTH, bytes.length);
                                        //添加响应内容
                                        response.content().writeBytes(bytes);
    
                                        //写入响应
                                        channelHandlerContext.channel().writeAndFlush(response);
                                    }
                                });
    //
    //                            nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
    //                                @Override
    //                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    //                                    System.out.println(msg);
    //                                    System.out.println("****************");
    //                                    System.out.println(msg.getClass());
    //
    //                                    if (msg instanceof HttpRequest) {
    //                                        // TODO do something...
    //                                    } else if (msg instanceof HttpContent) {
    //                                        // TODO do something...
    //                                    }
    //                                    super.channelRead(ctx, msg);
    //                                }
    //                            });
                            }
                        }).bind(8080);
                channelFuture.sync();
                channelFuture.channel().closeFuture().sync();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                boss.shutdownGracefully();
                worker.shutdownGracefully();
            }
        }
    }
    

    浏览器请求http://localhost:8080/index.html,查看结果:

    /index.html
    /favicon.ico
    
    image.png

    相关文章

      网友评论

        本文标题:netty(十五)Netty提升 - 协议设计以及常见协议介绍

        本文链接:https://www.haomeiwen.com/subject/jcxhtrtx.html