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

无                选择器

面向流与面向缓冲

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容