Netty客户端

  • 建立一次通信,必然有着两个对象。发送者和接收者
  • 如我们使用微信、qq进行聊天的时候,就是一个通信的过程
  • 当我们使用java NIO实现简单的通信功能时,也必然存在着发送端(Client)和接收端(Server)
  • 接下来通过java NIO的实现,去逐步深入理解学习Netty的实现原理

Java BIO简单通信功能

  • 服务端
public class Server{
    // 首先定义一个 ServerSocket 对象
    private ServerSocket server;
    // 在构造方法中进行初始化
    public Server(int port) {
        //  传入需要监听的端口号
        server = new ServerSocket(port);
    }
    
    public void listen() {
        //  轮询
        while(true) {
            //  server.accept() 是一个阻塞(同步方法),在没有请求接收的时候—阻塞
            Socket socket = server.accept();
            //  拿到请求之后,意味着建立了一个Sokdcet连接
            //  拿到 client 发送的数据
            InputStream is = socket.getInputStream();
            byte[] buff = new byte[10];
            // 把数据写入到buff数组中去
            int length = is.read(buff);
            if(length > 0) {
                //  转化为 String 字符串
                String msg = new String(buff, 0, length);
            }
            // 关闭连接
            socket.close();
        }
    }
}
  • 客户端
public class Client {

    public static void main(String[] args) throws IOException {
        //  localhost:和谁进行通信    port:服务器端口
        Socket client = new Socket("localhost", 8080);
        //  客户端  输出  数据:创建输出流对象
        OutputStream os = client.getOutputStream();
        //  生成一个随机字符串
        String uId = UUID.randomUUID().toString();
        System.out.println("客户端准备开始发送数据:"+uId);
        System.out.println("发送数据字节长度:"+uId.getBytes().length);
        os.write(uId.getBytes());
        os.close();
        client.close();
    }
}
  • 通过简单的BIO实现,脑海中定下一个基调
  • 客户端发送,服务端接收,通过IP和端口地址进行关联
  • 不管Netty如何复杂,其思想是不变的

Netty是如何发起一次连接的

  • 首先提出问题,假如使用Netty去发出一个请求
  • 必然会和java的实现类似通过IP和端口进行连接,这点是躲不开的。

BootStrap来了

  • 找到Netty中的BootStrap.java,偷来官方注释瞧一瞧
  • 比较清晰明了,传达了两点
  • 客户端通过Bootstrap使用Channel将会很简单
  • 我们使用UDP协议的时候,可以使用bind()方法来进行绑定。使用TCP的时候,使用connect()方法进行绑定

详细注释版本代码

NIO Netty 基于java 网络通信框架 java netty 客户端_netty

NIO Netty 基于java 网络通信框架 java netty 客户端_java_02

  • 跟进connect方法,最终调用了Bootstrap的doResolveAndConnect方法
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();
    if (regFuture.isDone()) {
        if (!regFuture.isSuccess()) {
            return regFuture;
        }
        return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
    } else {
        final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
        regFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                Throwable cause = future.cause();
                if (cause != null) {
                    promise.setFailure(cause);
                } else {
                    promise.registered();
                    doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
                }
            }
        });
        return promise;
    }
}
  • 这里面有几个关键点需要我们注意
  • initAndRegister方法具体做了什么事情
  • 客户端时如何建立连接的

深入理解initAndRegister方法

  • 调用到AbstractBootstrap的 initAndRegister 方法

NIO Netty 基于java 网络通信框架 java netty 客户端_java_03


NioSocketChannel初始化

  • 这里走到 ReflectiveChannelFactory 通过反射的方式创建 Channel 对象
  • 创建Channel对象的类型 由 Bootstrap 的 channel() 方法指定,这里实际上调用到了 NioSocketChannel 的构造方法

NIO Netty 基于java 网络通信框架 java netty 客户端_客户端_04

  • 通过 NioSocketChannel 方法的构造器,深入了解
  • newSocket(privider) 方法通过 SelectorProvider 对象新建一个 SocketChannel 对象

NIO Netty 基于java 网络通信框架 java netty 客户端_客户端_05

  • 通过层层调用到 AbstractNioChannel 的构造方法
  • 这里parent属性传入为null
  • 通过 ch 设置 SocketChannel 非阻塞
  • 通过 readInterestOp 设置 SelectionKey.OP_READ 为初始状态

NIO Netty 基于java 网络通信框架 java netty 客户端_netty_06

  • 接着调用父类 AbstractChannel 的构造方法
  • 这里把 Channel 对象设置为 null
  • 为已经创建的 Channel 对象设置一个 唯一 id
  • 对 Unsafe 对象进行初始化——其中封装了Java底层Socket的操作
  • 对 Pipeline 对象进行初始化
  • NIO Netty 基于java 网络通信框架 java netty 客户端_java_07


  • 至此 我们最开始设置的 NioSocketChannel 对象就初始化完成了
  • 接下来是 初始化完成之后的 init(Channel) 方法

NioSocketChannel注册

  • 完成初始化之后调用 init(channel) 方法
  • 这里体现了 Pipeline对象 和 Channel 对象的关联
void init(Channel channel) throws Exception {
    //  这里通过 Channel 对象获取已经初始化完成的 Pipeline  对象
    ChannelPipeline p = channel.pipeline();
    //  把 handler  添加到 Pipeline 对象中
    p.addLast(config.handler());
    //  这里获取一些底层配置,如 ChannelOption.SO_KEEPALIVE 表示是否开启TCP底层心跳机制,true为开启
    final Map<ChannelOption<?>, Object> options = options0();
    //  为每一个  channel  进行配置
    synchronized (options) {
        for (Entry<ChannelOption<?>, Object> e: options.entrySet()) {
            try {
                if (!channel.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {
                    logger.warn("Unknown channel option: " + e);
                }
            } catch (Throwable t) {
                logger.warn("Failed to set a channel option: " + channel, t);
            }
        }
    }

    //  提供自定义属性功能  存储在 attrs 中,通过  channel.attr(key)方法可以获取相对应属性
    final Map<AttributeKey<?>, Object> attrs = attrs0();
    synchronized (attrs) {
        for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
            channel.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
        }
    }
}
  • 接下来会执行以下代码
ChannelFuture regFuture = config().group().register(channel);
  • 调用链时序图

NIO Netty 基于java 网络通信框架 java netty 客户端_客户端_08

  • 最终调用到 AbstractNioChannel 对象的 doRegister 方法
  • 到这里完成了注册关联

NIO Netty 基于java 网络通信框架 java netty 客户端_客户端_09

NIO Netty 基于java 网络通信框架 java netty 客户端_客户端_10

  • channel的注册过程可以总结为:和对应的EventLoop对象的Selector相关联
  • 初始化注册完成之后,接下来就是客户端连接操作了

客户端连接实现

  • 首先通过时序图观察一下客户端连接的调用过程,通过调用过程寻找关键代码

NIO Netty 基于java 网络通信框架 java netty 客户端_初始化_11

  • 最终调用到 NioSocketChannel 方法完成连接
  • javaChannel()方法获取java NIO SOcketChannel 完成连接

NIO Netty 基于java 网络通信框架 java netty 客户端_客户端_12