Netty的复习【3】Buffer、NIO

Buffer

ByteBuffer可以理解为放在内存中的一个数组。Buffer是一个抽象类,它的具体实现有
ByteBuffer,CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer。为什么没有string,用char代替了。
在这些类型中,使用最多的是ByteBuffer,可以在网络上进行传输。
1540950201907080101436121149491136.png

该图描述的是Buffer的分配、读写和常用操作
原生JDK网络编程- Buffer

capacity

作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”.你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。

position

当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1.

当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。

limit

在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。

当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)。

Buffer本质上是一个读取和写入数据的内存,被JDK包装成了一个对象,方便我们访问这块内存。

Buffer的分配

要想获得一个Buffer对象首先要进行分配。 每一个Buffer类都有allocate方法(可以在堆上分配,也可以在直接内存上分配)。

分配48字节capacity的ByteBuffer的例子:ByteBuffer buf = ByteBuffer.allocate(48);

分配一个可存储1024个字符的CharBuffer:CharBuffer buf = CharBuffer.allocate(1024);

wrap方法:把一个byte数组或byte数组的一部分包装成ByteBuffer:

ByteBuffer wrap(byte [] array)

ByteBuffer wrap(byte [] array, int offset, int length)

写数据到buffer的两个方式

读取channel写到buffer

channel.read(buffer)
这里为什么说channel.read()是一个写操作呢?
这里其实当中发生的操作是,从channel中读取出数据,然后写入到buffer中。

通过buffer的 put 方法写

buffer.put(...);

flip()方法 从写切换到读

从写模式改变成读模式,这个方法在发生的实际操作其实是:

把position的位置变为0,把limit的位置变到position之前的位置上

从buffer中读数据的方式

从buffer读到channel,channel.write(buffer);

这个方法中发生的操作是从buffer读取出来,然后写入channel中,所以这个操作对于buffer其实是一个读操作。

buffer的get方法

使用buffer比较常见的步骤

1.写入数据到buffer
2.调用flip()方法
3.从buffer读数据
4.清空buffer

NIO

转载:原生JDK网络编程- NIO

什么是NIO?

NIO 库是在 JDK 1.4 中引入的。NIO 弥补了原来的 I/O 的不足,它在标准 Java 代码中提供了高速的、面向块的 I/O。NIO翻译成 no-blocking io 或者 new io都说得通。

1540950201907080104026731586124068.png

和BIO的主要区别

面向流与面向缓冲

Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

阻塞与非阻塞IO

Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。

Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

选择器(Selectors)

Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。

NIO主要有三个核心部分组成:

buffer缓冲区、Channel管道、Selector选择器

Selector

Selector的英文含义是“选择器”,也可以称为为“轮询代理器”、“事件订阅器”、“channel容器管理机”都行。

应用程序将向Selector对象注册需要它关注的Channel,以及具体的某一个Channel会对哪些IO事件感兴趣。Selector中也会维护一个“已经注册的Channel”的容器。

Channels

通道,被建立的一个应用程序和操作系统交互事件、传递内容的渠道(注意是连接到操作系统)。那么既然是和操作系统进行内容的传递,那么说明应用程序可以通过通道读取数据,也可以通过通道向操作系统写数据,而且可以同时进行读写。

  • 所有被Selector(选择器)注册的通道,只能是继承了SelectableChannel类的子类。
  • ServerSocketChannel:应用服务器程序的监听通道。只有通过这个通道,应用程序才能向操作系统注册支持“多路复用IO”的端口监听。同时支持UDP协议和TCP协议。
  • ScoketChannel:TCP Socket套接字的监听通道,一个Socket套接字对应了一个客户端IP:端口 到 服务器IP:端口的通信连接。

通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。

操作类型 SelectionKey

在向Selector对象注册感兴趣的事件时,JAVA NIO共定义了四种作:OP_READ、OP_WRITE、OP_CONNECT、OP_ACCEPT(定义在SelectionKey中),分别对应读、写、请求连接、接受连接等网络Socket操作。

ServerSocketChannel和SocketChannel可以注册自己感兴趣的操作类型,当对应操作类型的就绪条件满足时OS会通知channel,下表描述各种Channel允许注册的操作类型,Y表示允许注册,N表示不允许注册,其中服务器SocketChannel指由服务器ServerSocketChannel.accept()返回的对象。
image.png
服务器启动ServerSocketChannel,关注OP_ACCEPT事件,

客户端启动SocketChannel,连接服务器,关注OP_CONNECT事件

服务器接受连接,启动一个服务器的SocketChannel,这个SocketChannel可以关注OP_READ、OP_WRITE事件,一般连接建立后会直接关注OP_READ事件

客户端这边的客户端SocketChannel发现连接建立后,可以关注OP_READ、OP_WRITE事件,一般是需要客户端需要发送数据了才关注OP_READ事件

连接建立后客户端与服务器端开始相互发送消息(读写),根据实际情况来关注OP_READ、OP_WRITE事件。

我们可以看看每个操作类型的就绪条件。
image.png

# Netty 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×