自顶向下深入分析Netty(一)--预备知识

Netty是基于Java NIO封装的网络通讯框架,只有充分理解了Java NIO才能理解好Netty的底层设计。Java NIO有几个重要的概念Channel,Buffer,Selector。NIO是基于ChannelBuffer操作的,数据只能通过Buffer写入到Channel或者从Channel读出数据到Buffer中。Selector可以监听多个通道的事件(连接打开,数据到达),这样便可以用一个线程监听多个Channel的事件,从而可以用一个线程处理多个网络连接。

1.1 Channel

首先来看Channel的类图:

自顶向下深入分析Netty(一)--预备知识_第1张图片
Channel类图

主要关注三个类: SocketChannel, ServerSocketChannel以及DataGramChannel,其中 SocketChannel是一个连接到TCP网络套接字的通道,可由两种方式创建:

  1. 打开一个SocketChannel并连接到互联网上的某台服务器。
  2. 一个新连接到达ServerSocketChannel时,会创建一个SocketChannel(即由serverSocketChannel.accept()方法返回)。

ServerSocketChannel是一个可以监听新进来的TCP连接的通道, 与标准IO中的ServerSocket类似。而DatagramChannel是一个能收发UDP包的通道。

1.2 Buffer

一个Buffer对象是固定数量的数据的容器。缓冲区的工作与通道紧密联系。通道是I/O传输发生时通过的入口,而缓冲区是这些数据传输的来源或目标。对于离开缓冲区的传输,想传递出去的数据被置于一个缓冲区,被传送到通道。对于传回缓冲区的传输,一个通道将数据复制到所提供的缓冲区中。这里我们主要关注的是ByteBuffer,因为字节是操作系统与I/O设备之间或者操作系统与应用进程之间传递数据使用的基本数据类型,并且Java NIO中的Channel只接受ByteBuffer作为参数。

1.3 selector

Selector是Java NIO中能够检测多个通道,并能够知晓通道是否为诸如读写时间做好准备的组件。当我们将1.1中的一个或多个SelectableChannel注册到一个Selector对象中时,一个表示通道和选择器关系的SelectionKey会被返回。SelectionKey将记住我们关心的通道,并且会追踪对应的通道是否有事件已经就绪。当调用一个Selector.select()方法时,相关的SelectionKey将会被更新,用来检查所有被注册到改选择器的通道。我们可以获取一个SelectionKey的集合,从而找到已经就绪的通道。通过遍历这些SelectionKey,我们可以选择出每个从上次调用select()开始直到现在已经就绪的通道。

1.4 示例

通过之前的描述,相信你已经对Java NIO有了初步的了解,但具体的细节还很模糊,没关系,记住我们的目标是自顶向下分析,也就是先整体后局部,先建立对整体的认识再不断完善局部细节。下面将通过一个简单的Java NIO echo Server实例从整体上理解Java NIO建立服务的过程。

    public static void startServer(int port) throws IOException{
        // 打开服务端ServerSocketChannel
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // 设置为非阻塞模式
        serverChannel.configureBlocking(false); 
        // 绑定一个本地端口,这样客户端便可以通过这个端口连接到服务器
        serverChannel.bind(new InetSocketAddress(port));
        
        // 打开selector
        Selector selector = Selector.open(); 
        // 注意关心的事件是OP_ACCEPT,表示只关心接受事件即接受客户端到服务器的连接
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            // select()阻塞直到注册的某个事件就绪并会更新SelectionKey的状态
            int readyChannels = selector.select();
            if (readyChannels <= 0) {
                continue;
            }

            // 得到就绪的key集合,key中保存有就绪的事件以及对应的Channel通道
            Set SelectorKeySet = selector.selectedKeys();
            Iterator iterator = SelectorKeySet.iterator();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            // 遍历选择键
            while (iterator.hasNext()) {    
                SelectionKey key = iterator.next(); 
                if (key.isAcceptable()) {   
                    // 处理accept事件
                    SocketChannel socketChannel = serverChannel.accept();
                    socketChannel.configureBlocking(false);
                    // 注意此处新增关心事件OP_READ
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    // 处理read事件
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    buffer.clear();
                    socketChannel.read(buffer);
                    buffer.flip();
                    socketChannel.write(buffer);
                }
                iterator.remove();
            }
        }
    }

其中内层while循环是事件处理的关键,以一个客户端连接到服务端并发送一段数据为例分析:selector最开始只关心OP_ACCEPT事件即等待客户端连接,当客户端连接到服务端完成后即OP_ACCEPT就绪,程序从selector.select()方法返回并进入内层循环的第一个if分支处理Accept事件。在处理过程中,会将刚接受的Channel注册到selector并关心该Channel上的OP_READ事件,完成后会清除key并回阻塞在下一轮循环的selector.select()方法上。当客户端发送一段数据到服务端后,OP_READ事件就绪,程序从阻塞处返回并进入内层while循环的第二个if分支,从而对OP_READ事件进行处理。本例中,会将客户端发来的数据写回原本Channel通道,从而完成echo服务。

你可能感兴趣的:(自顶向下深入分析Netty(一)--预备知识)