【基础篇】netty 源码死磕3: 

JAVA NIO  Channel

1. Java NIO Channel

1.1. Java NIO Channel的特点

和老的OIO相比,通道和NIO流(非阻塞IO)主要有以下几点区别:

(1)OIO流一般来说是单向的(只能读或者写),通道可以读也可以写。

(2)OIO流值读写阻塞的,而通道可以异步读写。

(3)通道总是基于缓冲区Buffer来读写。

1.2. Channel类型

下面列出Java NIO中最重要的集中Channel的实现:

(1)FileChannel

(2)DatagramChannel

(3)SocketChannel

(4)ServerSocketChannel

四种通道的说明如下:

FileChannel用于文件的数据读写。

DatagramChannel用于UDP的数据读写。

SocketChannel用于TCP的数据读写。

ServerSocketChannel允许我们监听TCP链接请求,每个请求会创建会一个SocketChannel。

这个四种通道,涵盖了 UDP 和 TCP网络 IO以及文件 IO的操作。下面从通道的新建、读取、写入、关闭等四个操作,四种通道进行简单的介绍。

1.3. FileChannel

FileChannel 是操作文件的Channel,我们可以通过 FileChannel 从一个文件中读取数据,也可以将数据写入到文件中。

注意,FileChannel 不能设置为非阻塞模式。

操作一:打开 FileChannel通道

RandomAccessFile aFile     = new RandomAccessFile("test.txt","rw");

FileChannel      inChannel = aFile.getChannel();

操作二:读取数据

ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buf);

操作三:写入数据

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);

}

操作四:关闭

channel.close();

当我们对 FileChannel 的操作完成后,必须将其关闭。

操作五:强制刷新磁盘

channel.force(true);

FileChannel的force()方法将所有未写入的数据从通道刷新到磁盘中。在你调用该force()方法之前,出于性能原因,操作系统可能会将数据缓存在内存中,因此您不能保证写入通道的数据实际上写入磁盘。

1.4. SocketChannel

有两种Socket通道,一个是客户端的SocketChannel,一个是负责服务器端的Socket通道ServerSocketChannel。SocketChannel与OIO中的Socket类对应,ServerSocketChannel对应于OIO中的ServerSocket类相NIO。

两种Socket通道新增的通道都支持阻塞和非阻塞两种模式。在阻塞模式下的通道的创建、关闭、读写操作如下:

操作一:创建

SocketChannel socketChannel = SocketChannel.open();

socketChannel.connect(new InetSocketAddress("127.0.0.1",80));

这个是客户端的创建。当一个服务器端的ServerSocketChannel 接受到连接请求时,也会返回一个 SocketChannel 对象。

操作二:读取

ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = socketChannel.read(buf);

如果 read()返回 -1,那么表示连接中断了.

操作三:写入数据

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);

}

操作四:关闭

socketChannel.close();

在非阻塞模式,我们可以设置 SocketChannel 为异步模式,这样我们的 connect,read,write 都是异步的了.

操作一:连接

socketChannel.configureBlocking(false);

socketChannel.connect(new InetSocketAddress("127.0.0.1",80));

while(! socketChannel.finishConnect() ){

    //wait,or do something else...

}

在异步模式中,或许连接还没有建立,socketChannel.connect 方法就返回了,因此我们不断的自旋,检查当前是否是连接到了主机。

操作二:非阻塞读写

在异步模式下,读写的方式是一样的.

在读取时,因为是异步的,因此我们必须检查 read 的返回值,来判断当前是否读取到了数据.

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.open();

serverSocketChannel.close();

1.4.1. 监听连接

我们可以使用ServerSocketChannel.accept()方法来监听客户端的 TCP 连接请求,accept()方法会阻塞,直到有连接到来,当有连接时,这个方法会返回一个 SocketChannel 对象:

while(true){

    SocketChannel socketChannel =

            serverSocketChannel.accept();

    //do something with socketChannel...

}
1.4.2. 非阻塞模式

在非阻塞模式下,accept()是非阻塞的,因此如果此时没有连接到来,那么 accept()方法会返回null:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

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

serverSocketChannel.configureBlocking(false);

while(true){

    SocketChannel socketChannel =

            serverSocketChannel.accept();

    if(socketChannel != null){

        //do something with socketChannel...

        }

}

1.5. DatagramChannel

DatagramChannel 是用来处理 UDP 连接的.

操作一:打开

DatagramChannel channel = DatagramChannel.open();

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

操作二:读取数据

ByteBuffer buf = ByteBuffer.allocate(48);

buf.clear();

channel.receive(buf);

操作三:发送数据

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("example.com",80));

连接到指定地址

因为 UDP 是非连接的,因此这个的 connect 并不是向 TCP 一样真正意义上的连接,因此我们仅仅可以从指定的地址中读取或写入数据.

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


源码:


代码工程:  JavaNioDemo.zip

下载地址:在疯狂创客圈QQ群文件共享。


疯狂创客圈:如果说Java是一个武林,这里的聚集一群武痴, 交流编程体验心得
QQ群链接:
疯狂创客圈QQ群


无编程不创客,无案例不学习。 一定记得去跑一跑案例哦


JAVA NIO 死磕全目录


1. JAVA NIO简介
1.1. NIO 和 OIO 的对比
1.2. 阻塞和非阻塞
1.3. Channel
1.4. selector
1.5. Java NIO Buffer
2. Java NIO Buffer
2.1. Buffer类型的标记属性
2.1.1. capacity
2.1.2. position
2.1.3. limit
2.1.4. 总结:
2.2. Buffer 类型
2.3. Buffer中的方法
2.3.1. 获取allocate()方法
2.3.2. 写put()方法
2.3.3. 读切换flip()方法
2.3.4. 读get() 方法
2.3.5. 倒带rewind()方法
2.3.6. mark( )和reset( )
2.3.7. clear()清空
2.4. Buffer 的使用
2.4.1. 使用的基本步骤
2.4.2. 完整的实例代码
3. Java NIO Channel
3.1. Java NIO Channel的特点
3.2. Channel类型
3.3. FileChannel
3.4. SocketChannel
3.4.1. 监听连接
3.4.2. 非阻塞模式
3.5. DatagramChannel
4. NIO Selector
4.1. Selector入门
4.1.1. Selector的和Channel的关系
4.1.2. 可选择通道(SelectableChannel)
4.1.3. Channel注册到Selector
4.1.4. 选择键(SelectionKey)
4.2. Selector的使用流程
4.2.1. 创建Selector
4.2.2. 将Channel注册到Selector
4.2.3. 轮询查询就绪操作
4.3. 一个NIO 编程的简单实例