Java基础-NIO(2)Channel

Channel是什么

Java NIO中的所有I/O操作都基于Channel对象,就像流操作都要基于Stream对象一样,因此很有必要先了解Channel是什么。以下内容摘自JDK 1.8的文档。

A channel represents an open connection to an entity such as a hardware device, a file, a network socket, or a program component that is capable of performing one or more distinct I/O operations, for example reading or writing.

从上述内容可知,一个Channel(通道)代表和某一实体的连接,这个实体可以是文件、网络套接字等。也就是说,Channel是Java NIO提供的一座桥梁,用于我们的程序和操作系统底层I/O服务进行交互。这和传统IO中的Stream有点类似,并且Channel根据不同的I/O操作对应有不同的实现,常用的有如下几个:

  • FileChannel:读写文件
  • DatagramChannel: UDP协议网络通信
  • SocketChannel:TCP协议网络通信
  • ServerSocketChannel:监听Socket

Channel和Stream相比有如下几个特性:

  1. Channel是双向的,既可以用来进行读操作也可以进行写操作,而Stream是单向的(所以分InputStreamOutputStream)。
  2. Channel本身并不能访问数据,因为它只是一个通道,Channel的读和写都需要配和Buffer来操作,可以从Channel里读取数据到Buffer中,也可以把Buffer中的数据写入Channel。
  3. Channel可以异步地读写。

Channel的使用

获取通道有如下三种方式:

  1. 对支持通道的对象调用getChannel() 方法。
    支持通道的类如下:
    本地 IO:

    `FileInputStream`/`FileOutputStream`
    `RandomAccessFile`
    

    网络IO:

    `Socket`
    `ServerSocket`
    `DatagramSocket`
    
  2. 在 JDK 1.7 中的 NIO.2 针对各个通道提供了静态方法open()打开并返回指定通道。

  3. 在 JDK 1.7 中的 NIO.2 的 Files 工具类的静态方法newByteChannel()可以创建FileChannel实例。

如下是FileChannel的使用示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public class FileChannalTest {

//利用通道完成文件的复制(非直接缓冲区)
@Test
public void test1() {

FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
fis = new FileInputStream("C:/Users/liaosi/Desktop/1.jpg");
fos = new FileOutputStream("C:/Users/liaosi/Desktop/2.jpg");

//获取通道
inChannel = fis.getChannel();
outChannel = fos.getChannel();

//分配指定大小的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//将通道中的数据读取写到缓冲区中
while (inChannel.read(buffer) != 1) {
//切换缓冲区为读模式
buffer.flip();
//将缓冲区中的数据写入通道中
outChannel.write(buffer);
//清空缓冲区
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (inChannel != null) {
inChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}

try {
if (outChannel != null) {
outChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}

try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}

try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

//通道之间直接传输数据(直接缓冲区)
@Test
public void test3() throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("C:/Users/liaosi/Desktop/1.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("C:/Users/liaosi/Desktop/4.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

//数据从一个通道传输到另外一个通道
inChannel.transferTo(0, inChannel.size(), outChannel);

inChannel.close();
outChannel.close();

}

}

SocketChannel

SocketChannel是一个连接到TCP网络套接字的通道。可以通过以下2种方式创建SocketChannel:

  • 调用SocketChannelopen()方法。
  • ServerSocketChannel接受到一个连接后,会创建一个SocketChannel。

连接socket

SocketChannel可以通过configureBlocking方法设置为阻塞和非阻塞两种方式,默认是阻塞的。
SocketChannel通过connect方法连接到一个socket,如果是阻塞的SocketChannelconnect方法会一直阻塞直到连接建立或者发生IO异常,如果是非阻塞的SocketChannel,这个方法会立即返回,如果连接已经成功建立则返回true,否则返回false,后续可以再调用finishConnect()来完成连接操作。

建立连接之后,SocketChannel可以通过write向Buffer中写入数据,通过read方法从 Buffer 中读取数据,read()方法返回的int值表示读了多少字节进Buffer里。如果返回的是-1,表示已经读到了末尾。

ServerSocketChannel

前面的SocketChannel是可以主动发起连接到socket的通道,ServerSocketChannel则是用来监听到socket的连接和接收连接的通道。
ServerSocketChannel可以被无参的open()方法创建。但是改方法只是创建了一个ServerSocketChannel对象,并没有进行绑定操作,仍需要调用bind()方法进行绑定,使之监听指定端口上的套接字。未进行绑定的ServerSocketChannel调用accept(),将会抛出NotYetBoundException异常

1
2
3
4
5
6
7
8
//1.创建ServerSocketChannel
ServerSocketChannel channel = ServerSocketChannel.open();

//2.绑定到本机网络接口
channel.bind(new InetSocketAddress(8091));

//3.SocketChannel socketChannel = channel.accept();
SocketChannel socketChannel = channel.accept();

accept方法返回SocketChannel套接字通道,用于读取请求数据和写入响应数据。ServerSocketChannel可以通过configureBlocking方法设置成阻塞或非阻塞状态,ServerSocketChannel的阻塞和非阻塞体现在:

  • 阻塞模式:在调用accept方法后,将阻塞直到有新的socket连接时返回SocketChannel对象,代表新建立的套接字通道。
  • 非阻塞模式:在调用accept方法后,如果无连接建立,则返回null;如果有连接,则返回SocketChannel。

起始Java NIO中Channel最大的用处是在网络端,特别是服务端,可以通过非阻塞的方式增加应用程序的并发量,ServerSocketChannel一般会配合Selector使用以获取更好的并发性能,具体的使用方式在Selector那一节中再做介绍。

------ 本文完 ------