参考:https://www.cnblogs.com/ostenant/p/9695183.html
通道(Channel):
由java.nio.channels包定义的,Channel表示IO源与目标打开的连接,Channel类似于传统的“流”,只不过Channel本身不能直接访问数据,Channel只能与Buffer进行交互。通道主要用于传输数据,从缓冲区的一侧传到另一侧的实体(如文件、套接字…),反之亦然;通道是访问IO服务的导管,通过通道,我们可以以最小的开销来访问操作系统的I/O服务;顺便说下,缓冲区是通道内部发送数据和接收数据的端点。
在标准的IO当中,都是基于字节流/字符流进行操作的,而在NIO中则是是基于Channel和Buffer进行操作,其中的Channel的虽然模拟了流的概念,实则大不相同。
Channel的分类广义上来说通道可以被分为两类:文件I/O和网络I/O,也就是文件通道和套接字通道。如果分的更细致一点则是:
- FileChannel:从文件读写数据;
- SocketChannel:通过TCP读写网络数据;
- ServerSocketChannel:可以监听新进来的TCP连接,并对每个链接创建对应的SocketChannel
- DatagramChannel:通过UDP读写网络中的数据。
通道可以以多种方式创建。Socket通道有可以直接创建新socket通道的工厂方法。但是一个FileChannel对象却只能通过在一个打开的RandomAccessFile、FileInputStream或FileOutputStream对象上调用getChannel( )方法来获取。你不能直接创建一个FileChannel对象。
1、 FileChannelJava NIO中的FileChannel是一个连接到文件的通道,可以通过文件通道读写文件。文件通道总是阻塞式的,因此FileChannel无法设置为非阻塞模式。
文件读写操作
代码实例:
package com.example.dtest.nio.channel.fileChannel;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelTest01 {
public static void main(String[] args) {
// 测试写:
System.out.println("Start to write");
// 通过FileChannel写入数据
testWriteOnFileChannel();
// 测试读:
System.out.println("Start to read");
// 通过FileChannel读取数据
testReadOnFileChannel();
}
// 文件写操作
public static void testWriteOnFileChannel(){
try {
RandomAccessFile randomAccessFile = new RandomAccessFile("D:\test.txt", "rw");
FileChannel fileChannel = randomAccessFile.getChannel();
byte[] bytes = new String("Java Non-blocking IO").getBytes();
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
// 将缓冲区中的字节写入文件通道中
fileChannel.write(byteBuffer);
// 强制将通道中未写入磁盘的数据立刻写入到磁盘
fileChannel.force(true);
// 清空缓冲区,释放内存
byteBuffer.clear();
fileChannel.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// 文件读操作:
public static void testReadOnFileChannel(){
try {
FileInputStream fileInputStream = new FileInputStream(new File("D:\test.txt"));
FileChannel fileChannel = fileInputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
// 不断地写入缓冲区,写一次读一次
while (fileChannel.read(byteBuffer) != -1){
// 缓冲区从写模式切换为读模式
byteBuffer.flip();
// 开始读取
while (byteBuffer.hasRemaining()){
// 从一个字节一个字节地读取,并向后移动position地位置
System.out.println((char) byteBuffer.get());
}
// 缓冲区不会被自动覆盖,需要主动调用该方法(实际上还是覆盖)
byteBuffer.clear();
//
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}
}
}
测试结果:
(一). transferFrom()的使用
(二). transferTo()的使用
FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中。
transferTo()方法将数据从FileChannel传输到目标channel中。下面是一个简单的例子:
package com.example.dtest.nio.channel.transferfromOrTo;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
public class TransferFromOrTo {
public static void main(String[] args) {
testTransferFrom();
testTransferTo();
}
// FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中
public static void testTransferFrom(){
try {
RandomAccessFile fromFile = new RandomAccessFile("D://file1.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("D://file2.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
toChannel.transferFrom(fromChannel, position, count);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// transferTo()方法将数据从FileChannel传输到目标channel中
public static void testTransferTo() {
try {
RandomAccessFile fromFile = new RandomAccessFile("D://file1.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("D://file3.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
fromChannel.transferTo(position, count, toChannel);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
测试结果:
file1里面的内容都会写入到file2和file3里面去
Java NIO中的ServerSocketChannel是一个可以监听新进来的TCP连接的通道。它类似ServerSocket,要注意的是和DatagramChannel和SocketChannel不同,ServerSocketChannel本身不具备传输数据的能力,而只是负责监听传入的连接和创建新的SocketChannel。
(一). 创建ServerSocketChannel
通过ServerSocketChannel.open()方法来创建一个新的ServerSocketChannel对象,该对象关联了一个未绑定ServerSocket的通道。通过调用该对象上的socket()方法可以获取与之关联的ServerSocket。
ServerSocketChannel socketChannel = ServerSocketChannel.open();
(二). 为ServerSocketChannel绑定监听端口号
在JDK 1.7之前,ServerSocketChannel没有bind()方法,因此需要通过他关联的的socket对象的socket()来绑定。
// JDK1.7之前
serverSocketChannel.socket().bind(new InetSocketAddress(25000));
从JDK1.7及以后,可以直接通过ServerSocketChannel的bind()方法来绑定端口号。
// JDK1.7之后
serverSocketChannel.bind(new InetSocketAddress(25000));
设置ServerSocketChannel的工作模式
ServerSocketChannel底层默认采用阻塞的工作模式,它提供了一个configureBlocking()方法,允许配置ServerSocketChannel以非阻塞方式运行。
代码演示:
阻塞模式:
package com.example.dtest.nio.channel.serversocketChannel.block;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class ServerSocketChannelBlock {
public static void main(String[] args) {
try {
blockingTest();
} catch (IOException e) {
e.printStackTrace();
}
}
// 阻塞模式
public static void blockingTest() throws IOException{
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(25000));
System.out.println("ServerSocketChannel listening on 25000...");
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (true){
SocketChannel socketChannel = serverSocketChannel.accept();
// 新连接没到达之前,后面的程序无法继续执行
InetSocketAddress remoteAddress = (InetSocketAddress) socketChannel.getRemoteAddress();
System.out.println("Remote address: " + remoteAddress.getHostString());
StringBuilder stringBuilder = new StringBuilder();
while (socketChannel.read(byteBuffer) != -1){
byteBuffer.flip();
while (byteBuffer.hasRemaining()){
stringBuilder.append((char)byteBuffer.get());
// System.out.println((char)byteBuffer.get());
}
System.out.println(stringBuilder.toString());
byteBuffer.clear();
}
}
}
}
非阻塞模式:
package com.example.dtest.nio.channel.serversocketChannel.noblock;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class ServerSocketChannelNONBlock {
public static void main(String[] args) {
try {
nonBlockingTest();
} catch (IOException e) {
e.printStackTrace();
}
}
// 非阻塞模式
public static void nonBlockingTest() throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(25001));
System.out.println("ServerSocketChannel listening on 25001...");
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
StringBuilder stringBuilder = new StringBuilder();
while (true){
SocketChannel socketChannel = serverSocketChannel.accept();
// 新连接没到达之前,后面程序一直循环,直到检测到socketChannel不为null时进入真正的执行逻辑
System.out.println("SocketChannel: " + socketChannel);
if(socketChannel != null){
InetSocketAddress remoteAddress = (InetSocketAddress) socketChannel.getRemoteAddress();
System.out.println("Remote address: " + remoteAddress.getHostString());
while (socketChannel.read(byteBuffer) != -1){
byteBuffer.flip();
while (byteBuffer.hasRemaining()){
stringBuilder.append((char) byteBuffer.get());
// System.out.println((char) byteBuffer.get());
}
byteBuffer.clear();
System.out.println(stringBuilder.toString());
}
}
}
}
}
socket是用来连接服务器端的,所以要服务器端开放才行,他自己不是服务,这里可以配合serversocketChannel(只做服务端)联调测试
Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道,它是Socket类的对等类。
通常SocketChannel在客户端向服务器发起连接请求,每个SocketChannel对象创建时都关联一个对等的Socket对象。同样SocketChannel也可以运行在非阻塞模式下。
SocketChannel的用法
SocketChannel创建的方式有两种:
客户端主动创建:客户端打开一个SocketChannel并连接到某台服务器上;
服务端被动创建:一个新连接到达ServerSocketChannel时,服务端会创建一个SocketChannel。
(一). 创建SocketChannel
通过SocketChannel的静态方法open()创建SocketChannel对象。此时通道虽然打开,但并未建立连接。此时如果进行I/O操作会抛出NotYetConnectedException异常。
SocketChannel socketChannel = SocketChannel.open();
(二). 连接指定服务器
通过SocketChannel对象的connect()连接指定地址。该通道一旦连接,将保持连接状态直到被关闭。可通过isConnected()来确定某个SocketChannel当前是否已连接。
阻塞模式:
如果在客户端的SocketChannel阻塞模式下,即服务器端的ServerSocketChannel也为阻塞模式:
socketChannel.connect(new InetSocketAddress("127.0.0.1", 25000));
// connect()方法调用以后,socketChannel底层的连接创建完成后,才会执行后面的打印语句
System.out.println("连接创建完成...");
非阻塞模式:
两点需要注意:其一,SocketChannel需要通过configureBlocking()设置为非阻塞模式;其二,非阻塞模式下,connect()方法调用后会异步返回,为了确定连接是否建立,需要调用finishConnect()的方法。
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("127.0.0.1", 25001));
// connect()方法调用以后,异步返回,需要手动调用finishConnect确保连接创建
while(!socketChannel.finishConnect()){
// 检测到还未创建成功则睡眠10ms
TimeUnit.MILLISECONDS.sleep(10);
}
System.out.println("连接创建完成...");
(三). 从SocketChannel读数据
利用SocketChannel对象的read()方法将数据从SocketChannel读取到Buffer。
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了,所以需要关注它的int返回值。
while (socketChannel.read(byteBuffer) != -1) {
byteBuffer.flip();
while (byteBuffer.hasRemaining()) {
System.out.println((char) byteBuffer.get());
}
byteBuffer.clear();
}
(四). 向SocketChannel写数据
利用SocketChannel对象的write()将Buffer的数据写入SocketChannel。
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put("Client Blocking SocketChannel".getBytes());
// byteBuffer.put("Client Non-Blocking SocketChannel".getBytes());
byteBuffer.flip();
// 非阻塞模式下,write()方法在尚未写出任何内容时可能就返回了。所以需要在循环中调用write()
while (byteBuffer.hasRemaining()) {
socketChannel.write(byteBuffer);
}
// 保持睡眠,观察控制台输出
TimeUnit.SECONDS.sleep(20000);
socketChannel.close();
代码实例:
阻塞:
package com.example.dtest.nio.channel.socketchannel.block;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.TimeUnit;
public class SocketChannelBlockTest {
public static void main(String[] args) {
try {
blockingWrite();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void blockingWrite() throws Exception{
try {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1",25000));
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put("Client Blocking SocketChannel".getBytes());
byteBuffer.flip();
while (byteBuffer.hasRemaining()){
socketChannel.write(byteBuffer);
}
TimeUnit.SECONDS.sleep(2000);
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
非阻塞:
package com.example.dtest.nio.channel.socketchannel.nonblock;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.TimeUnit;
public class SocketChannelNonBlockTest {
public static void main(String[] args) {
try {
nonBlockingWrite();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void nonBlockingWrite() throws Exception{
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("127.0.0.1",25001));
while (!socketChannel.finishConnect()){
TimeUnit.MICROSECONDS.sleep(10);
}
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put("Client Non-Blocking SocketChannel".getBytes());
byteBuffer.flip();
while (byteBuffer.hasRemaining()){
socketChannel.write(byteBuffer);
}
TimeUnit.SECONDS.sleep(20000);
socketChannel.close();
}
}
启动服务端1(25000):ServerSocketChannelBlock:
启动服务端2(25001):ServerSocketChannelNONBlock:
启动客户端1(25000):SocketChannelBlockTest
查看服务端1:
启动客户端2(25001):
查看服务端2:
测试成功!
若是没启动服务端就启动客户端会报错:
java.net.ConnectException: Connection refused: connect5、DatagramChannel
Java NIO中的DatagramChannel是一个能收发UDP包的通道,其底层实现为DatagramSocket + Selector。DatagramChannel可以调用socket()方法获取对等DatagramSocket对象。
DatagramChannel对象既可以充当服务端(监听者),也可以充当客户端(发送者)。如果需要新创建的通道负责监听,那么该通道必须绑定一个端口(或端口组):
DatagramChannel的完整示例:
数据报发送方:
package com.example.dtest.nio.channel.datagramchannel.send;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
public class DatagramchannelSendTest {
public static void main(String[] args) {
try {
DatagramChannel datagramChannel = DatagramChannel.open();
ByteBuffer byteBuffer = ByteBuffer.wrap("DatagramChannel Sender".getBytes());
int byteSent = datagramChannel.send(byteBuffer, new InetSocketAddress("127.0.0.1",50020));
System.out.println("Byte sent is: " + byteSent);
} catch (IOException e) {
e.printStackTrace();
}
}
}
数据报接收方:
package com.example.dtest.nio.channel.datagramchannel.get;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
public class DatagramchannelGetTest {
public static void main(String[] args) {
DatagramChannel datagramChannel;
{
try {
datagramChannel = DatagramChannel.open();
datagramChannel.socket().bind(new InetSocketAddress(50020));
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
datagramChannel.receive(byteBuffer);
byteBuffer.flip();
StringBuilder stringBuilder = new StringBuilder();
while (byteBuffer.hasRemaining()) {
// System.out.println((char) byteBuffer.get());
stringBuilder.append((char) byteBuffer.get());
}
System.out.println(stringBuilder.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
首先启动数据接收方:DatagramchannelGetTest:
然后启动数据发送方:
查看数据接收方:
数据接收成功!!!



