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

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

1.1 Channel

我们首先来看一下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 serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        serverChannel.bind(new InetSocketAddress(port));
 
        Selector selector = Selector.open();    // 打开selector
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
 
        while (true) {
            int readyChannels = selector.select();  // 阻塞直到有IO事件就绪
            if (readyChannels <= 0) {
                continue;
            }
 
            Set<SelectionKey> SelectorKeySet = selector.selectedKeys(); // 得到就绪的选择键
            Iterator<SelectionKey> 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);
                    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();
            }
        }
    }

我们分析一下代码的具体含义:2-4行表示打开一个ServerSocketChannel,设置为非阻塞模式,然后绑定一个本地端口,这样客户端便可以通过这个端口连接到服务器。6-7行表示打开一个Selector,然后将ServerSocketChannel对象注册到这个Selector实例上,注意这个ServerSocketChannel关心的事件是SelectionKey.OP_ACCEPT,表示只关心接受事件即接受客户端到服务器的连接。9-32行轮询已经就绪的事件并对具体时间进行处理,注意10行的selector.select()方法,该方法会阻塞直到selecor中注册的某个事件就绪并更新SelectionKey的状态,15行调用selector.selectedKeys()得到就绪的key集合,key中保存有就绪的事件以及对应的通道,21-23对就绪的Accept事件处理即服务器接受一个客户端连接,注意23行我们将接受的客户端连接也注册到同一个Selector对象,这样24行的Read事件可以被触发,服务器读出客户端通道的数据并写入改通道,完成echo服务。我们再梳理一下对就绪事件的处理,在最开始Selector对象中只注册了ServerSocketChannel的Accept事件,所以当第一个客户端连接到服务器时,selector.select()方法不再阻塞,并执行21-23行处理客户端连接将对应通道注册到之前的Selector实例上,这样Selector实例关心的事件为:(1).Accept事件(接受新客户端的连接);(2).Read事件(读取老客户端发送给服务器的数据)。因此,当有新客户端连接时执行21-23行,当有新客户端发来数据时执行25-29行。

原文地址

猜你喜欢

转载自blog.csdn.net/jishanwang/article/details/85342197
今日推荐