Netty系列整体栏目


内容链接地址
【一】深入理解网络通信基本原理和tcp/ip协议https://zhenghuisheng.blog.csdn.net/article/details/136359640
【二】深入理解Socket本质和BIOhttps://zhenghuisheng.blog.csdn.net/article/details/136549478
【三】深入理解NIO的基本原理和底层实现https://zhenghuisheng.blog.csdn.net/article/details/138451491
【四】深入理解反应堆模式的种类和具体实现https://zhenghuisheng.blog.csdn.net/article/details/140113199
【五】深入理解直接内存与零拷贝https://zhenghuisheng.blog.csdn.net/article/details/140721001
【六】select、poll和epoll多路复用的区别https://zhenghuisheng.blog.csdn.net/article/details/140795733
【七】深入理解和使用Netty中组件https://zhenghuisheng.blog.csdn.net/article/details/141166098
【八】深入Netty组件底层原理和基本实现https://zhenghuisheng.blog.csdn.net/article/details/141685088
【九】深入理解和解决tcp的粘包拆包https://zhenghuisheng.blog.csdn.net/article/details/141860959
【十】通过netty实现http请求https://zhenghuisheng.blog.csdn.net/article/details/141924516

一,通过netty实现http请求

1,数据的编解码器

在实现http请求之前,先对发送的数据和接收的数据先做一个定义,在网络开发中,为了解决在不同平台,不同系统的客户端和服务端之间实现通信,需要将客户端发送请求,服务端接收请求,服务端响应请求,客户端接收响应时所接收到的报文都能被读取和解析,因此需要定义一种公共的数据规范,就是需要将这些数据全部转化成操作系统所识别的 01 二进制类型,这样就能在不同的系统或者平台之间都能将对应的报文进行编码或者解码,形成一种特定的规范。而不管是在请求还是在响应端,都需要设置对应的数据编码器和解码器,在编码器中,可以直接使用netty封装好的编码器或者解码器。

对于客户端来说,主要就是发送请求数据时进行编码,接收响应数据时进行解码,因此主要是用下面两种方式

//发送请求数据时进行编码
new HttpRequestEncoder();
//接收响应数据时进行解码
new HttpResponseDecoder();

对于服务端来说,主要是接受请求数据、发送响应数据,因此主要是有以下两种方式

//接收服务端请求数据进行的解码
new HttpRequestDecoder()
//响应服务端数据时进行编码
new HttpResponseEncoder();

1,netty简单实现http请求

1.1,server服务类

接下来还是和前面几篇文章的编码思路一样,首先先写netty的服务端,这里定义一个 HttpNettyServer 服务类,端口号设置为8888,服务端这边设置编码器 HttpResponseEncoder和解码器HttpRequestDecoder, 然后设置一个将请求头和请求体聚合的操作HttpObjectAggregator,设置一个允许解压和压缩的操作HttpContentCompressor。在设置完基础设置之后,将一个统计请求成功数和失败数的 HttpNettyCountHandler 事件加入,以及将一个响应外部接口请求的HttpNettyHandler事件加入

public class HttpNettyServer {

    private static Integer port = 8888;

    public static void main(String[] args) {
        // 创建自定义事件组,一个线程循环的处理事件,类似与nio的selector
        EventLoopGroup loopGroup = new NioEventLoopGroup();
        final HttpNettyCountHandler httpNettyCountHandler = new HttpNettyCountHandler();
        try{
            //创建服务端主启动类
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(loopGroup)    //绑定组
                    .channel(NioServerSocketChannel.class)
                    .localAddress(port)         //绑定端口
                    .childHandler(new ChannelInitializer<SocketChannel>() { //初始化channel,将事件加入
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline()
                                    .addLast("encoder",new HttpResponseEncoder())   //设置编码器
                                    .addLast("decoder",new HttpRequestDecoder())    //设置解码器
                                    //聚合操作,将请求体和请求头等聚合
                                    .addLast("aggregator",new HttpObjectAggregator(10*1024*1024))
                                    .addLast("compressor",new HttpContentCompressor())  //允许压缩解压等操作
                                    .addLast(httpNettyCountHandler)
                                    .addLast(new HttpNettyHandler())    //handler的实际应用
                            ;
                        }
                    });
            //完成绑定,内部如果异步实现bind,因此需要阻塞拿到返回结果
            ChannelFuture future = bootstrap.bind().sync();
            //关闭future时也需要阻塞,内部也采用的是异步操作
            future.channel().closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                //处理中断异常
                loopGroup.shutdownGracefully().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在使用这个共享的handler时,直接在外部实例化即可,随后将对象变量加入到pipeline中

//外部定义
final HttpNettyCountHandler httpNettyCountHandler = new HttpNettyCountHandler();
//直接将类实例变量加入到pipeline中
socketChannel.pipeline().addLast(httpNettyCountHandler)

1.2 服务端共享Handler类

共享handler类中,主要是为了统计正确的调用了 test 接口的次数,通过 AtomicInteger 原子类进行统计并解决并发的问题。

/**
 * 错误请求统计和正确请求统计
 */

@Slf4j
@ChannelHandler.Sharable
public class HttpNettyCountHandler extends ChannelInboundHandlerAdapter {

    static AtomicInteger successCount = new AtomicInteger();
    static AtomicInteger errorCount = new AtomicInteger();

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        FullHttpRequest httpRequest = (FullHttpRequest)msg;
        String uri = httpRequest.uri();
        if ("/test".equals(uri)){
            successCount.incrementAndGet();
        }else{
            errorCount.incrementAndGet();
        }
        log.info("请求成功数为:" + successCount);
        log.info("请求失败数为:" + errorCount);
        //传递给下一个handler
        ctx.fireChannelRead(msg);
    }
}

需要注意的事项主要有两点,一个是成为共享handler的注解,需要加上以下注解

@ChannelHandler.Sharable

另一个注意事项是,需要在方法内部加一个上下文的传递方法,这样下一个handler才能接到上一个handler的数据或者事件动作。

ctx.fireChannelRead(msg);

如果没有加上传递的这段方法,那么 HttpNettyHandler 事件中的read就可能不会被触发,或者读取不到数据

.addLast(httpNettyCountHandler)
.addLast(new HttpNettyHandler())

1.3 服务端自定义HttpNettyHandler事件

在该事件中,需要集成这个出站事件的适配器ChannelInboundHandlerAdapter ,随后通过netty内部封装的接收request请求的 FullHttpRequest 类接收请求, 然后只允许 /test 路径的请求才允许被访问,否则就返回一个404状态码。

@Slf4j
public class HttpNettyHandler extends ChannelInboundHandlerAdapter {
    /**
     * 读取到的数据
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //netty中封装的http请求
        FullHttpRequest httpRequest = (FullHttpRequest)msg;
        String uri = httpRequest.uri();
        log.info("请求的路径为:" + uri);
        String body = httpRequest.content().toString(CharsetUtil.UTF_8);
        log.info("获取到的请求体的数据为:" + body);
        if ("/test".equals(uri)){
            send(ctx,HttpResponseStatus.OK,"请求成功!");
        }else{
            send(ctx,HttpResponseStatus.NOT_FOUND,"路径不存在!");
        }
    }
}

1.4 测试

随后启动服务器的main方法,然后在浏览器上输入请求

http://localhost:8888/test

得到的结果如下

2024-09-04 18:08:51--------请求成功!

如果输入的不是test路径

http://localhost:8888/test11

那么得到的结果如下

2024-09-04 18:12:43--------路径不存在!

随着请求次数的不断增加,控制台打印的数据如下,请求的成功次数和失败次数都会被统计和打印

18:13:42.928 [nioEventLoopGroup-2-1] INFO  c.r.w.c.n.h.c.HttpNettyHandler - [channelActive,21] - 客户端的地址为:/0:0:0:0:0:0:0:1:28104
18:13:42.929 [nioEventLoopGroup-2-24] INFO  c.r.w.c.n.h.c.HttpNettyCountHandler - [channelRead,31] - 请求成功数为:8
18:13:42.929 [nioEventLoopGroup-2-24] INFO  c.r.w.c.n.h.c.HttpNettyCountHandler - [channelRead,32] - 请求失败数为:12
18:13:42.930 [nioEventLoopGroup-2-24] INFO  c.r.w.c.n.h.c.HttpNettyHandler - [channelRead,35] - 请求的路径为:/test11

1.5 客户端编写

上面1.4是直接通过浏览器访问,也可以通过开启客户端的方式实现对服务端的请求访问。首先定义一个客户端的主启动类,除了正常的事件组,主启动器类,pipeline之外,还新增了 ChannelOption.SO_KEEPALIVE 长连接选项,并且设置了发送端请求数据的编码器 HttpRequestEncoder 和发送端接受响应数据的解码器HttpRequestDecoder ,最后将一个自定义的 HttpNettyClientHandler 客户端加入

public class HttpNettyClient {
    private static Integer port = 8888;
    private static String host = "127.0.0.1";
    public static void main(String[] args) {
        // 创建自定义事件组,一个线程循环的处理事件,类似与nio的selector
        EventLoopGroup loopGroup = new NioEventLoopGroup();
        try{
            //客户端只需要用bootStrap
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(loopGroup)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.SO_KEEPALIVE,true)    // 长连接
                    .remoteAddress(new InetSocketAddress(host,port))    //和服务器不一样,这里只需要连接服务器地址即可
                    .handler(new ChannelInitializer<SocketChannel>() {  //和服务端不同,服务端使用的childHandler客户端只需要具体的handler即可
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline()
                                    .addLast(new HttpRequestEncoder())          //发送数据进行编码
                                    .addLast(new HttpRequestDecoder())          //发送端接受响应解码
                                    .addLast("aggregator",                //聚合成一个完整报文
                                            new HttpObjectAggregator(10*1024*1024))
                                    .addLast("decompressor",              //允许压缩解压等操作
                                            new HttpContentDecompressor())
                                    .addLast(new HttpNettyClientHandler());
                        }
                    });
            //完成绑定,内部如果异步实现bind,因此需要阻塞拿到返回结果
            ChannelFuture future = bootstrap.connect().sync();
            //关闭future时也需要阻塞,内部也采用的是异步操作
            future.channel().closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

最后定义一个 HttpNettyClientHandler ,定义好请求uri,设置好发送消息,随后构架好对应的 DefaultFullHttpRequest 请求即可,就能将请求发送给服务端

@Slf4j
public class HttpNettyClientHandler extends ChannelInboundHandlerAdapter {
        @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        String url = "/test";
        URI uri = new URI(url);
        String msg = "abcdefghijklmnopqrstuvwxyz";
        DefaultFullHttpRequest request =
                new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
                        HttpMethod.GET,
                        uri.toASCIIString(),
                        Unpooled.wrappedBuffer(msg.getBytes("UTF-8")));

        // 构建http请求
        request.headers().set(HttpHeaderNames.HOST, HttpClient.HOST);
        request.headers().set(HttpHeaderNames.CONNECTION,
                HttpHeaderValues.KEEP_ALIVE);
        request.headers().set(HttpHeaderNames.CONTENT_LENGTH,
                request.content().readableBytes());
        // 发送http请求
        ctx.writeAndFlush(request);
    }
}

先启动服务端,后启动客户端,控制台打印的数据如下

09:37:35.732 [nioEventLoopGroup-2-2] INFO c.r.w.c.n.h.c.HttpNettyHandler - [channelActive,53] - 客户端的地址为:/127.0.0.1:42961
09:37:35.807 [nioEventLoopGroup-2-2] INFO c.r.w.c.n.h.c.HttpNettyCountHandler - [channelRead,31] - 请求成功数为:1
09:37:35.807 [nioEventLoopGroup-2-2] INFO c.r.w.c.n.h.c.HttpNettyCountHandler - [channelRead,32] - 请求失败数为:0
09:37:35.807 [nioEventLoopGroup-2-2] INFO c.r.w.c.n.h.c.HttpNettyHandler - [channelRead,29] - 请求的路径为:/test
09:37:35.807 [nioEventLoopGroup-2-2] INFO c.r.w.c.n.h.c.HttpNettyHandler - [channelRead,31] - 获取到的请求体的数据为:abcdefghijklmnopqrstuvwxyz

更多推荐