Netty之NIO

------NIO简介(1)--------

NIO组件

channel,buffer,selector,pip,filelock。

Channel分为:

FileChannel

DatagramChannel

SocketChannel

ServerSocketChannel

UDP 和 TCP 网络IO,以及文件IO

Buffer分为:

ByteBuffer

CharBuffer

DoubleBuffer

FloatBuffer

IntBuffer

LongBuffer

ShortBuffer

byte, short, int, long, float, double 和 char。

结构图

                  thread

                    |

                selector

    |              |            |

channel          channel      channel

使用Selector,得向Selector注册Channel,然后调用它的select()方法。

这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回

线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等

--------channel(2)---------

channel 可以理解为pip

channel <=======> buffer

FileChannel 从文件中读写数据。

DatagramChannel 能通过UDP读写网络中的数据。

SocketChannel 能通过TCP读写网络中的数据。

ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。

对每一个新进来的连接都会创建一个SocketChannel。 

例子:

                                NIO

File  -  channel -  buffer =============== application

buf.flip() 的调用,首先读取数据到Buffer,然后反转Buffer,接着再从Buffer中读取数据

--------NIO buffer(3)------------

四个步骤:

写入数据到Buffer

调用flip()方法

从Buffer中读取数据

调用clear()方法或者compact()方法

flip()方法

反转IO  写=========》读

capacity

position


capacity /limit      -

要想获得一个Buffer对象首先要进行分配。

allocate方法。

ByteBuffer buf = ByteBuffer.allocate(48);

--------向Buffer中写数据-----

从Channel写到Buffer。

通过Buffer的put()方法写到Buffer里。

int bytesRead = inChannel.read(buf); //read into buffer.

buf.put(127);

调用flip()方法会将position设回0,并将limit设置成之前position的值。

----从Buffer中读取数据----

从Buffer读取数据到Channel。

使用get()方法从Buffer中读取数据。

int bytesWritten = inChannel.write(buf);

byte aByte = buf.get();

rewind()方法

Buffer.rewind()将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变

如果调用的是clear()方法.

position将被设回0,limit被设置成 capacity的值,Buffer 被清空了

compact()方法

如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据,

compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面

mark()与reset()方法

通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。

之后可以通过调用Buffer.reset()方法恢复到这个position。

equals()方法

有相同的类型(byte、char、int等)。

Buffer中剩余的byte、char等的个数相等。

Buffer中所有剩余的byte、char等都相同。

compareTo()方法

ompareTo()方法比较两个Buffer的剩余元素

第一个不相等的元素小于另一个Buffer中对应的元素 。

第一个Buffer的元素个数比另一个少)

--------------Scatter/Gather(4)-----------------

scatter/gather用于描述从Channel

scatter  读操作时将读取的数据写入多个buffer中

gather  写操作时将多个buffer的数据写入同一个Channel

scatter

经常用于需要将传输的数据分开处理的场合,例如传输一个由消息头和消息体组成的消息,

你可能会将消息体和消息头分散到不同的buffer中,这样你可以方便的处理消息头和消息体。

ByteBuffer header = ByteBuffer.allocate(128);

ByteBuffer body  = ByteBuffer.allocate(1024);

ByteBuffer[] bufferArray = { header, body };

channel.read(bufferArray);

read()方法按照buffer在数组中的顺序将从channel中读取的数据写入到buffer,

当一个buffer被写满后,channel紧接着向另一个buffer中写。

这也意味着它不适用于动态消息(译者注:消息大小不固定)

----Gathering---------

Gathering Writes是指数据从多个buffer写入到同一个channel。

ByteBuffer header = ByteBuffer.allocate(128);

ByteBuffer body  = ByteBuffer.allocate(1024);

ByteBuffer[] bufferArray = { header, body };

channel.write(bufferArray);

注意只有position和limit之间的数据才会被写入。

因此,如果一个buffer的容量为128byte,但是仅仅包含58byte的数据

------------(五) 通道之间的数据传输---------------------

直接将数据从一个channel传输到另外一个channel。

transferFrom()

FileChannel  ===========>  FileChannel

RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");

FileChannel      fromChannel = fromFile.getChannel();

RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");

FileChannel      toChannel = toFile.getChannel();

long position = 0;

long count = fromChannel.size();

toChannel.transferFrom(position, count, fromChannel);

transferTo()

FileChannel ===========>  Channel

-----------------------(六) Selector--------------

Selector(选择器)是Java NIO中能够检测一到多个NIO通道

一个单独的线程可以管理多个channel

Selector的创建 

Selector selector = Selector.open();

向Selector注册通道

SelectableChannel.register()

channel.configureBlocking(false);

SelectionKey key = channel.register(selector,

    Selectionkey.OP_READ);

Connect

Accept

Read

Write

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

SelectionKey

interest集合

ready集合

Channel

Selector

附加的对象(可选)

Channel  channel  = selectionKey.channel();

Selector selector = selectionKey.selector();

通过Selector选择通道

下面是select()方法:

int select()

int select(long timeout)

int selectNow()

完整的示例

Selector selector = Selector.open();

channel.configureBlocking(false);

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

while(true) {

  int readyChannels = selector.select();

  if(readyChannels == 0) continue;

  Set selectedKeys = selector.selectedKeys();

  Iterator keyIterator = selectedKeys.iterator();

  while(keyIterator.hasNext()) {

    SelectionKey key = keyIterator.next();

    if(key.isAcceptable()) {

        // a connection was accepted by a ServerSocketChannel.

    } else if (key.isConnectable()) {

        // a connection was established with a remote server.

    } else if (key.isReadable()) {

        // a channel is ready for reading

    } else if (key.isWritable()) {

        // a channel is ready for writing

    }

    keyIterator.remove();

  }

}

-------(七) FileChannel--------------

连接到文件的通道

FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。

打开FileChannel

InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");

FileChannel inChannel = aFile.getChannel();

从FileChannel读取数据

ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buf);

向FileChannel写数据

String newData = "New String to write to file..." + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(48);

buf.clear();

buf.put(newData.getBytes());

buf.flip();

while(buf.hasRemaining()) {

    channel.write(buf);

}

关闭FileChannel

channel.close();

FileChannel的某个特定位置进行数据的读/写操作

position方法

long pos = channel.position();

channel.position(pos +123);

FileChannel的size方法

long fileSize = channel.size();

FileChannel的truncate方法

截取文件,后面的部分将被删除

channel.truncate(1024);

FileChannel的force方法

FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。

channel.force(true);

---------(八) SocketChannel------------

连接到TCP网络套接字的通道

创建SocketChannel

打开一个SocketChannel并连接到互联网上的某台服务器。

一个新连接到达ServerSocketChannel时,会创建一个SocketChannel

打开 SocketChannel

SocketChannel socketChannel = SocketChannel.open();

socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

关闭 SocketChannel

socketChannel.close();

从 SocketChannel 读取数据

ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = socketChannel.read(buf);

分配一个Buffer。从SocketChannel读取到的数据将会放到这个Buffer中

返回的int值表示读了多少字节进Buffer里

写入 SocketChannel

new string - new buf - buf clear - buf put - buf flip - buf remain - channel write

非阻塞模式

设置 SocketChannel 为非阻塞模式(non-blocking mode)

异步模式下调用connect(), read() 和write()了。

connect()

socketChannel.configureBlocking(false);

socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

while(! socketChannel.finishConnect() ){

    //wait, or do something else...

}

非阻塞模式与选择器

非阻塞模式与选择器搭配会工作的更好,通过将一或多个SocketChannel注册到Selector,

可以询问选择器哪个通道已经准备好了读取,写入等

--------(九) ServerSocketChannel---------

ServerSocketChannel 是一个可以监听新进来的TCP连接的通道

例子:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.socket().bind(new InetSocketAddress(9999));

while(true){

    SocketChannel socketChannel =

            serverSocketChannel.accept();

    //do something with socketChannel...

}

打开 ServerSocketChannel

关闭 ServerSocketChannel

监听新进来的连接

ServerSocketChannel.accept() 方法监听新进来的连接

返回一个包含新进来的连接的 SocketChannel

非阻塞模式

非阻塞模式下,accept() 方法会立刻返回

如果还没有新进来的连接,返回的将是null。

需要检查返回的SocketChannel是否是null

if(socketChannel != null){

        //do something with socketChannel...

    }

--------(十) Java NIO DatagramChannel-------

DatagramChannel是一个能收发UDP包的通道

UDP是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接收的是数据包。

打开 DatagramChannel

DatagramChannel channel = DatagramChannel.open();

channel.socket().bind(new InetSocketAddress(9999));

接收数据

receive()

ByteBuffer buf = ByteBuffer.allocate(48);

buf.clear();

channel.receive(buf);

发送数据

send()方法

String newData = "New String to write to file..." + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(48);

buf.clear();

buf.put(newData.getBytes());

buf.flip();

int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));

连接到特定的地址

channel.connect(new InetSocketAddress("jenkov.com", 80));

当连接后,也可以使用read()和write()方法,就像在用传统的通道一样。

只是在数据传送方面没有任何保证。这里有几个例子:

----------(十一) Pipe--------------

管道是2个线程之间的单向数据连接

Pipe有一个source通道和一个sink通道

数据会被写到sink通道,从source通道读取。

ThreadA - sink - source - ThreadB

创建管道

Pipe pipe = Pipe.open();

向管道写数据

Pipe.SinkChannel sinkChannel = pipe.sink();

String newData = "New String to write to file..." + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(48);

buf.clear();

buf.put(newData.getBytes());

buf.flip();

while(buf.hasRemaining()) {

    sinkChannel.write(buf);

}

从管道读取数据

Pipe.SourceChannel sourceChannel = pipe.source();

ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = sourceChannel.read(buf);

------------(十二) Java NIO与IO-----------

IO                NIO

面向流            面向缓冲

阻塞IO            非阻塞IO

无                选择器

面向流与面向缓冲

你可能感兴趣的:(Netty之NIO)