当前位置 熊猫博客 JAVA 正文 下一篇:

Netty总结

最近工作中要用到socket编程,将某一个接口由webservice改为socket,于是想到了Netty框架,网上大部分教程都不是很友好,在掘金小册中通读了一边闪电侠编写的《Netty入门与实战》,是个不错的netty入门教程,这里做一下总结。

为什么要使用Netty

传统的JAVA IO是阻塞的,并且会浪费大量的系统资源,使用起来极其繁琐。Netty封装了JDK NIO,让NIO和socket编程更加简单,官方描述是:Netty 是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。

Netty服务端启动流程

创建引导类->绑定线程模型->绑定IO模型->连接读写处理逻辑->绑定端口启动,这样一个NIO的服务就启动了。

package com.venustech;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(20);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup(20);

        // 创建引导类
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        // 指定线程模型
        serverBootstrap.group(bossGroup, workerGroup)
                // 指定IO模型
                .channel(NioServerSocketChannel.class)
                // 设置工作线程组逻辑处理器(读写和业务处理等等)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        nioSocketChannel.pipeline().addLast(new ServerHandler2());
                    }
                })
                // 指定队列大小
                .option(ChannelOption.SO_BACKLOG, 1024)
                // 开启心跳检测
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                // 禁用Nagle算法
                .childOption(ChannelOption.TCP_NODELAY, true);
        serverBootstrap.bind(8888).addListener(future -> {
            if (future.isSuccess()) {
                System.out.println("启动成功");
            } else {
                System.err.println("启动失败");
            }
        });
    }
}

bossGroup和workerGroup对应传统IO模型中的两大线程组,bossGroup负责监测端口,接受新连接,workerGroup表示具体工作的线程,就像工厂中老板负责接单,员工负责制作。

ServerBootstrap是引导类,引导server端的启动工作。

  • 通过.group()配置两大线程组
  • 通过.channel()指定IO模型,一般是NIO,毕竟用Netty就是为了NIO
  • 通过.handler()可以设置服务端启动过程中的一些逻辑处理(不常用)
  • 通过.childHandler()定义每条连接的读写处理逻辑
  • 通过.option()设置bossGroup参数
  • 通过.childOption()指定每条连接的配置
  • 通过.bind()绑定端口,这是一个异步方法,可以用lambda表达式判断端口是否绑定成功或者后处理

Netty客户端启动流程

创建引导类->绑定线程模型->绑定IO模型->连接读写处理逻辑->连接host和port启动,就成功启动了一个netty客户端。

package com.venustech;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

public class ClientServer {
    public static void main(String[] args) {
        NioEventLoopGroup workerGroup = new NioEventLoopGroup(20);

        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(workerGroup)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .option(ChannelOption.TCP_NODELAY, true)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        nioSocketChannel.pipeline().addLast(new ClientHandler2());
                    }
                });
        bootstrap.connect("127.0.0.1", 8888).addListener(future -> {
            if (future.isSuccess()) {
                System.out.println("连接服务器成功");
            } else {
                System.err.println("连接失败");
            }
        });
    }
}

服务端和客户端流程对比

  • 都需要创建引导类、绑定线程模型和IO模型,连接读写处理器,服务端启动和客户端连接都是异步的,可以增加异步判断是否成功或者后处理。
  • 服务端引导类使用ServerBootstrap,客户端使用Bootstrap
  • 服务端需要绑定两个线程模型,一个接受新请求,另一个负责工作,客户端只需要绑定一个
  • 服务端指定IO模型为NioServerSocketChannel,客户端为NioSocketChannel
  • 服务端option中不能设置ChannelOption.SO_KEEPALIVE和ChannelOption.TCP_NODELAY,会报无效参数,这两个参数要被工作组或者客户端设置

客户端和服务端双向通讯

客户端和服务端相关的读写逻辑都是通过Bootstrap.handler()来实现数据交互,服务端用.childHandler(),客户端则使用.handler()。在双向通讯中,依我个人理解,其实不分客户端或是服务端,这种分类是在逻辑上的,在代码上看不出差异。在逻辑上,客户端可以主动联系服务端,服务端也可以主动推送数据到客户端。

  • 新建Handler,继承自ChannelInboundHandlerAdapter
    • 重写channelActive()方法,这个方法会在连接建立成功之后被调用
      • 获取ByteBuf,ByteBuf是netty封装的数据载体,所有的数据交互都必须用它
      • 数据处理逻辑……
      • 调用.writeAndFlush()发送数据
    • 重新channelRead()方法,这个方法会在收到数据后被调用
      • 获取ByteBuf
      • 数据处理逻辑……
      • 调用.writeAndFlush()发送数据
package com.venustech;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.nio.charset.Charset;

public class ClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ByteBuf byteBuf = ctx.alloc().buffer();
        byteBuf.writeBytes("你好,我是客户端".getBytes(Charset.forName("utf-8")));
        ctx.writeAndFlush(byteBuf);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("服务端的回复:");
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println(byteBuf.toString(Charset.forName("utf8")));
    }
}
package com.venustech;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.nio.charset.Charset;

public class ServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("客户端发来的消息是:" + byteBuf.toString(Charset.forName("utf-8")));
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ByteBuf byteBuf = ctx.alloc().buffer();
        byteBuf.writeBytes("客户端你好,我已收到你的消息".getBytes(Charset.forName("utf8")));
        ctx.writeAndFlush(byteBuf);
    }
}

然后写好的Handler分别绑定到服务端和客户端的Bootstrap即可。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部