netty碎碎念------一一个简单的聊天demo

4 篇文章 0 订阅
4 篇文章 0 订阅

什么Netty?
netty是基于java NIO的异步事件驱动的网络应用框架,使用netty可以快速开发网络应用,netty提供了高层次的抽象来简化TCP合UDP服务器的编程,但是仍然可以使用底层的api
netty内部实现是很复杂的,但是netty提供了简单易用的api从网页处理代码中解耦业务逻辑。netty是王权基于NIO实现的,所以是异步的。一、Netty 简介
netty特性:
设计反面:
对各种传输协议提供统一的API(使用阻塞和非阻塞套接字时候使用的是同一个api但是设置参数不一样)
基于一个灵活、可拓展的事件模型来实现关注点清晰分离。
高度可定制的线程模型—单线程、一个或多个线程。
真正的无数据报套接字(UDP)的支持。
易用性:
完善的javadoc文档合示例代码
不需要额外的依赖,jdk5(netty3.x)或者jdk6(netty4.0)已经足够。
性能:
更好的吞吐量,更低的等待延迟
更少的资源消耗。
最小化不必要的内存拷贝(零拷贝)
安全性:
完整的SSL/TLS和StartTLS支持二、一个简单 Http 服务器
首先编写服务启动类

public class HttpServer {
    public static void main(String[] args) {
        //构造两个线程组
        EventLoopGroup bossrGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //服务端启动辅助类
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
            .channel(NioServerSocketChannel.class)
            .childHandler(new HttpServerInitializer());
            ChannelFuture future = bootstrap.bind(8080).sync();
            //等待服务端口关闭
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            // 优雅退出,释放线程池资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

在编写 Netty 程序时,一开始都会生成 NioEventLoopGroup 的两个实例,分别是 bossGroup 和 workerGroup,也可以称为 parentGroup 和 childGroup,为什么创建这两个实例,作用是什么?可以这么理解,bossGroup 和 workerGroup 是两个线程池, 它们默认线程数为 CPU 核心数乘以 2,bossGroup 用于接收客户端传过来的请求,接收到请求后将后续操作交由 workerGroup 处理。

接下来我们生成了一个服务启动辅助类的实例 bootstrap,boostrap 用来为 Netty 程序的启动组装配置一些必须要组件,例如上面的创建的两个线程组。channel 方法用于指定服务器端监听套接字通道 NioServerSocketChannel,其内部管理了一个 Java NIO 中的ServerSocketChannel实例。
channelHandler 方法用于设置业务职责链,责任链是我们下面要编写的,责任链具体是什么,它其实就是由一个个的 ChannelHandler 串联而成,形成的链式结构。正是这一个个的 ChannelHandler 帮我们完成了要处理的事情。
接着我们调用了 bootstrap 的 bind 方法将服务绑定到 8080 端口上,bind 方法内部会执行端口绑定等一系列操,使得前面的配置都各就各位各司其职,sync 方法用于阻塞当前 Thread,一直到端口绑定操作完成。接下来一句是应用程序将会阻塞等待直到服务器的 Channel 关闭。
启动类的编写大体就是这样了,下面要编写的就是上面提到的责任链了。如何构建一个链,在 Netty 中很简单,不需要我们做太多,代码如下:

public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
    protected void initChannel(SocketChannel sc) throws Exception {
        ChannelPipeline pipeline = sc.pipeline();
        //处理http消息的编解码
        pipeline.addLast("httpServerCodec", new HttpServerCodec());
        //添加自定义的ChannelHandler
        pipeline.addLast("httpServerHandler", new HttpServerHandler());
    }
}

ChannelInitializer 继承 ChannelInboundHandlerAdapter,用于初始化 Channel 的 ChannelPipeline。通过 initChannel 方法参数 sc 得到 ChannelPipeline 的一个实例。
当一个新的连接被接受时, 一个新的 Channel 将被创建,同时它会被自动地分配到它专属的 ChannelPipeline。
ChannelPipeline 提供了 ChannelHandler 链的容器,推荐读者仔细自己看看 ChannelPipeline 的 Javadoc,文章后面也会继续说明 ChannelPipeline 的内容。
Netty 是一个高性能网络通信框架,同时它也是比较底层的框架,想要 Netty 支持 Http(超文本传输协议),必须要给它提供相应的编解码器。
所以我们这里使用 Netty 自带的 Http 编解码组件 HttpServerCodec 对通信数据进行编解码,HttpServerCodec 是 HttpRequestDecoder 和 HttpResponseEncoder 的组合,因为在处理 Http 请求时这两个类是经常使用的,所以 Netty 直接将他们合并在一起更加方便使用。所以对于上面的代码:

pipeline.addLast(“httpServerCodec”, new HttpServerCodec())

通过 addLast 方法将一个一个的 ChannelHandler 添加到责任链上并给它们取个名称(不取也可以,Netty 会给它个默认名称),这样就形成了链式结构。在请求进来或者响应出去时都会经过链上这些 ChannelHandler 的处理。
最后再向链上加入我们自定义的 ChannelHandler 组件,处理自定义的业务逻辑。下面就是我们自定义的 ChannelHandler。

public class HttpServerChannelHandler0 extends SimpleChannelInboundHandler<HttpObject> {
    private HttpRequest request;
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if (msg instanceof HttpRequest) {
            request = (HttpRequest) msg;
            request.method();
            String uri = request.uri();
            System.out.println("Uri:" + uri);
        }
        if (msg instanceof HttpContent) {
            HttpContent content = (HttpContent) msg;
            ByteBuf buf = content.content();
            System.out.println(buf.toString(io.netty.util.CharsetUtil.UTF_8));
            ByteBuf byteBuf = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8);
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf);
            response.headers().add(HttpHeaderNames.CONTENT_TYPE, "text/plain");
            response.headers().add(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
            ctx.writeAndFlush(response);
        }
    }
}

对于自定义的 ChannelHandler, 一般会继承 Netty 提供的SimpleChannelInboundHandler类,并且对于 Http 请求我们可以给它设置泛型参数为 HttpOjbect 类,然后覆写 channelRead0 方法,在 channelRead0 方法中编写我们的业务逻辑代码,此方法会在接收到服务器数据后被系统调用。
Netty 的设计中把 Http 请求分为了 HttpRequest 和 HttpContent 两个部分,HttpRequest 主要包含请求头、请求方法等信息,HttpContent 主要包含请求体的信息。
所以上面的代码我们分两块来处理。在 HttpContent 部分,首先输出客户端传过来的字符,然后通过 Unpooled 提供的静态辅助方法来创建未池化的 ByteBuf 实例, Java NIO 提供了 ByteBuffer 作为它的字节容器,Netty 的 ByteBuffer 替代品是 ByteBuf。
接着构建一个 FullHttpResponse 的实例,并为它设置一些响应参数,最后通过 writeAndFlush 方法将它写回给客户端。
上面这样获取请求和消息体则相当不方便,Netty 又提供了另一个类 FullHttpRequest,FullHttpRequest 包含请求的所有信息,它是一个接口,直接或者间接继承了 HttpRequest 和 HttpContent,它的实现类是 DefalutFullHttpRequest。
因此我们可以修改自定义的 ChannelHandler 如下:

public class HttpServerChannelHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
        ctx.channel().remoteAddress();
        FullHttpRequest request = msg;
        System.out.println("请求方法名称:" + request.method().name());
        System.out.println("uri:" + request.uri());
        ByteBuf buf = request.content();
        System.out.print(buf.toString(CharsetUtil.UTF_8));
        ByteBuf byteBuf = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8);
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf);
        response.headers().add(HttpHeaderNames.CONTENT_TYPE, "text/plain");
        response.headers().add(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
        ctx.writeAndFlush(response);
    }
}

这样修改就可以了吗,如果你去启动程序运行看看,是会抛异常的。前面说过 Netty 是一个很底层的框架,对于将请求合并为一个 FullRequest 是需要代码实现的,然而这里我们并不需要我们自己动手去实现,Netty 为我们提供了一个 HttpObjectAggregator 类,这个 ChannelHandler作用就是将请求转换为单一的 FullHttpReques。
所以在我们的 ChannelPipeline 中添加一个 HttpObjectAggregator 的实例即可。

public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
    protected void initChannel(SocketChannel sc) {
        ChannelPipeline pipeline = sc.pipeline();
        //处理http消息的编解码
        pipeline.addLast("httpServerCodec", new HttpServerCodec());
        pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
        //添加自定义的ChannelHandler
        pipeline.addLast("httpServerHandler", new HttpServerChannelHandler0());
    }
}

启动程序运行,一切都顺畅了,好了,这个简单 Http 的例子就 OK 了。

三、编写 Netty 客户端
上面的两个示例中我们都是以 Netty 做为服务端,接下来看看如何编写 Netty 客户端,以第一个 Http 服务的例子为基础,编写一个访问 Http 服务的客户端。

public class HttpClient {

   public static void main(String[] args) throws Exception {
       String host = "127.0.0.1";
       int port = 8080;

       EventLoopGroup group = new NioEventLoopGroup();

       try {
           Bootstrap b = new Bootstrap();
           b.group(group)
           .channel(NioSocketChannel.class)
           .handler(new ChannelInitializer<SocketChannel>() {
               @Override
               public void initChannel(SocketChannel ch) throws Exception {
                   ChannelPipeline pipeline = ch.pipeline();
                   pipeline.addLast(new HttpClientCodec());
                   pipeline.addLast(new HttpObjectAggregator(65536));
                   pipeline.addLast(new HttpClientHandler());
               }
           });

           // 启动客户端.
           ChannelFuture f = b.connect(host, port).sync();
           f.channel().closeFuture().sync();

       } finally {
           group.shutdownGracefully();
       }
   }
}

客户端启动类编写基本和服务端类似,在客户端我们只用到了一个线程池,服务端使用了两个,因为服务端要处理 n 条连接,而客户端相对来说只处理一条,因此一个线程池足以。
然后服务端启动辅助类使用的是 ServerBootstrap,而客户端换成了 Bootstrap。通过 Bootstrap 组织一些必要的组件,为了方便,在 handler 方法中我们使用匿名内部类的方式来构建 ChannelPipeline 链容器。最后通过 connect 方法连接服务端。
接着编写 HttpClientHandler 类。

public class HttpClientHandler extends SimpleChannelInboundHandler<FullHttpResponse> {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        URI uri = new URI("http://127.0.0.1:8080");
        String msg = "Are you ok?";
        FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
                uri.toASCIIString(), Unpooled.wrappedBuffer(msg.getBytes("UTF-8")));

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

    @Override
    public void channelRead0(ChannelHandlerContext ctx, FullHttpResponse msg) {

        FullHttpResponse response = msg;
        response.headers().get(HttpHeaderNames.CONTENT_TYPE);
        ByteBuf buf = response.content();
        System.out.println(buf.toString(io.netty.util.CharsetUtil.UTF_8));

    }
}

在 HttpClientHandler 类中,我们覆写了 channelActive 方法,当连接建立时,此方法会被调用,我们在方法中构建了一个 FullHttpRequest 对象,并且通过 writeAndFlush 方法将请求发送出去。
channelRead0 方法用于处理服务端返回给我们的响应,打印服务端返回给客户端的信息。至此,Netty 客户端的编写就完成了,我们先开启服务端,然后开启客户端就可以看到效果了。
希望通过前面介绍的几个例子能让大家基本知道如何编写 Netty 客户端和服务端,下面我们来说说 Netty 程序为什么是这样编写的,这也是 Netty 中最为重要的一部分知识,可以让你在编写 netty 程序时做到心中有数。

笔记学习来源:https://www.cnblogs.com/nanaheidebk/p/11025362.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值