介绍
- Channel 是一个对象,可以通过它读取和写入数据。
- 拿 NIO 与原来的 I/O 做个比较,通道就像是流。
- 所有数据都通过 Buffer 对象来处理。您永远不会将字节直接写入通道中,相反,您是将数据写入包含一个或者多个字节的缓冲区。
- 同样,您不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。
各种Channel
FileChannel
package com.yangmin.study.io.nio.channel;
import java.io.*;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @author yangmin
* @version 1.0
* @description: Channel 是一个对象,可以通过它读取和写入数据。
* 拿 NIO 与原来的 I/O 做个比较,通道就像是流。
* 所有数据都通过 Buffer 对象来处理。您永远不会将字节直接写入通道中,相反,您是将数据写入包含一个或者多个字节的缓冲区。
* 同样,您不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。
* @date 2022/5/10 14:22
*/
public class ChannelTest {
public static void main(String[] args) {
ChannelTest channelTest = new ChannelTest();
// channelTest.fileChannelRead();
// channelTest.fileChannelWrite();
channelTest.transferFrom();
}
/**
* 有时可能需要在 FileChannel 的某个特定位置进行数据的读/写操作。可以通过调用position()方法获取 FileChannel 的当前位置。
* 也可以通过调用 position(long pos)方法设置 FileChannel 的当前位置。
*
* 这里有两个例子:
*
* long pos = channel.position();//获取当前位置
* channel.position(pos +123);//设置当前位置
*
* 如果将位置设置在文件结束符之后,然后试图从文件通道中读取数据,读方法将返回-1 (文件结束标志)。
*
* 如果将位置设置在文件结束符之后,然后向通道中写数据,文件将撑大到当前位置并写入数据。这可能导致“文件空洞”,磁盘上物理文件中写入的数据间有空隙。
*
FileChannel 实例的 size()方法将返回该实例所关联文件的大小。如:
long fileSize = channel.size();
可以使用 FileChannel.truncate()方法截取一个文件。截取文件时,文件将中指定长度后面的部分将被删除。如:
channel.truncate(1024);
*/
/**
* FileChannel 从文件中读数据。
*/
void fileChannelRead() {
/*1.打开fileChannel
在使用 FileChannel 之前,必须先打开它。但是,我们无法直接打开一个FileChannel,
需要通过使用一个 InputStream、OutputStream 或RandomAccessFile 来获取一个 FileChannel 实例*/
FileInputStream fileInputStream = null;
FileChannel channel = null;
try {
fileInputStream = new FileInputStream("src/main/resources/read.text");
channel = fileInputStream.getChannel();
/*
* 调用 FileChannel.read()方法。
* 该方法将数据从 FileChannel 读取到 Buffer 中。
* read()方法返回的 int 值表示了有多少字节被读到了 Buffer 中。如果返回-1,表示到了文件末尾。*/
ByteBuffer buffer = ByteBuffer.allocate(14);
while (channel.read(buffer) != -1) {
//第一次进来的时候,buffer为:[pos=7 lim=14 cap=14]
System.out.println(buffer.toString());
System.out.println(new String(buffer.array()));
buffer.clear();
}
System.out.println(channel.position());
System.out.println(channel.size());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (channel != null) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 写数据
*/
void fileChannelWrite() {
FileOutputStream fileOutputStream = null;
FileChannel channel = null;
try {
fileOutputStream = new FileOutputStream("src/main/resources/write.text");
channel = fileOutputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.clear();
buffer.put(("注意 FileChannel.write()是在 while 循环中调用的。因为无法保证 write()方法一次能向 " +
"FileChannel 写入多少字节,因此需要重复调用 write()方法," +
"直到 Buffer 中已经没有尚未写入通道的字节。").getBytes());
System.out.println(buffer.toString());
//切换为读模式,切换后可以读取buffer中的数据
// H T T P / 1 ... ! _ _
// P(L)
// P L
buffer.flip();
System.out.println(buffer.toString());
while (buffer.hasRemaining()) { //当position==limit时,说明已经把buffer中的数据读完了
channel.write(buffer);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//用完 FileChannel 后必须将其关闭。如: inChannel.close();
if (channel != null) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* FileChannel 的 transferFrom()方法可以将数据从源通道传输到 FileChannel 中(译者注:这个方法在 JDK 文档中的解释为将字节从给定的可读取字节通道传输到此通道的文件中)。
* 下面是一个 FileChannel 完成文件间的复制的例子:
*/
void transferFrom() {
RandomAccessFile readFile = null;
RandomAccessFile writeFile = null;
try {
readFile = new RandomAccessFile("src/main/resources/read.text", "rw");
writeFile = new RandomAccessFile("src/main/resources/write.text", "rw");
FileChannel fromChannel = readFile.getChannel();
FileChannel toChannel = writeFile.getChannel();
/**
* 这会导致fromChannel中的数据被读到buffer中,导致fromChannel中没有数据
*/
/*ByteBuffer buffer = ByteBuffer.allocate(3);
fromChannel.read(buffer);*/
//设置起始/结束位置
long position = 0;
long count = fromChannel.size();
/**
* 将fromChannel中全部的数据传到toChannel中
* 方法的输入参数 position 表示从 position 处开始向目标文件写入数据,count 表示最多传输的字节数。
*
* 如果源通道的剩余空间小于 count 个字节,则所传输的字节数要小于请求的字节数。
*
* 此外要注意,在 SoketChannel 的实现中,SocketChannel 只会传输此刻准备好的数据(可能不足 count 字节)。
*
* 因此,SocketChannel 可能不会将请求的所有数据(count 个字节)全部传输到 FileChannel 中。
*/
//long l = toChannel.transferFrom(fromChannel, position, count);
/**
* transferTo()方法将数据从 FileChannel 传输到其他的 channel 中。
*/
long l = fromChannel.transferTo(position, count, toChannel);
System.out.println(l);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (readFile != null) {
readFile.close();
}
if (writeFile != null) {
writeFile.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
ServerSocketChannel
- nio服务端
- ServerSocketChannel 是一个基于通道的 socket 监听器(本身不传数据,而是一个监听器)。
- 由于 ServerSocketChannel 没有 bind()方法,因此有必要取出对等的 socket 并使用它来绑定到一个端口以开始监听连接。我们也是使用对等 ServerSocket 的 API 来根据需要设置其他的 socket 选项。
package com.yangmin.study.io.nio.channel;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
* @author yangmin
* @version 1.0
* @description: nio服务端
* ServerSocketChannel 是一个基于通道的 socket 监听器(本身不传数据,而是一个监听器)。
* 由于 ServerSocketChannel 没有 bind()方法,因此有必要取出对等的 socket 并使用它来绑定到一个端口以开始监听连接。我们也是使用对等 ServerSocket 的 API 来根据需要设置其他的 socket 选项。
* @date 2022/5/10 17:49
*/
public class ServerSocketChannelTest {
public static void main(String[] args) {
ServerSocketChannelTest serverSocketChannelTest = new ServerSocketChannelTest();
serverSocketChannelTest.test1();
}
void test1() {
try {
/**
* 打开 ServerSocketChannel
* 通过调用 ServerSocketChannel.open() 方法来打开 ServerSocketChannel.
*/
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
/**
*由于 ServerSocketChannel 没有 bind()方法,因此有必要取出对等的 socket 并使用它来绑定到一个端口以开始监听连接。
* 我们也是使用对等 ServerSocket 的 API 来根据需要设置其他的 socket 选项。
*/
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress(8888));
/**
* ServerSocketChannel 可以设置成非阻塞模式。在非阻塞模式下,accept() 方法会立刻返回,
* 如果还没有新进来的连接,返回的将是 null。 因此,需要检查返回的SocketChannel 是否是 null.
*/
serverSocketChannel.configureBlocking(false);
/**
*字节缓冲区
*/
ByteBuffer buffer = ByteBuffer.allocate(1024);
for (; ; ) {
/**
* 通过 ServerSocketChannel.accept() 方法监听新进的连接。当 accept()方法返回时候,
* 它返回一个包含新进来的连接的 SocketChannel。因此, accept()方法会一直阻塞到有新连接到达。
* 阻塞模式: SocketChannel sc = ssc.accept();这里阻塞住进程。
*
* 非阻塞模式:ServerSocketChannel 可以设置成非阻塞模式。在非阻塞模式下,accept() 方法会立刻返回,
* 如果还没有新进来的连接,返回的将是 null。 因此,需要检查返回的SocketChannel 是否是 null.
*/
//我们这里采用的是非阻塞模式
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel == null) {
System.out.println("null");
Thread.sleep(2000);
continue;
}
/**
* 读取请求传过来的信息
*/
socketChannel.read(buffer);
System.out.println("this is a data from client, "+new String(buffer.array()));
//重置position=0,进行写操作
buffer.rewind();
buffer.put("hello client, i am server".getBytes());
//切换为读操作
buffer.flip();
/**
* 向客户端响应数据
*/
socketChannel.write(buffer);
buffer.clear();
socketChannel.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
SocketChannel
nio客户端
1、SocketChannel 介绍
Java NIO 中的 SocketChannel 是一个连接到 TCP 网络套接字的通道。操作面向Buffer缓冲区
A selectable channel for stream-oriented connecting sockets,这是 Java docs 中对于 SocketChannel 的描述:SocketChannel 是一种面向流连接sockets 套接字的可选择通道。从这里可以看出:
- SocketChannel 是用来连接 Socket 套接字
- SocketChannel 主要用途用来处理网络 I/O 的通道
- SocketChannel 是基于 TCP 连接传输
- SocketChannel 实现了可选择通道,可以被多路复用的
- 用于TCP的网络I/O套接字的通道
2、SocketChannel 特征:
(1)对于已经存在的 socket 不能创建 SocketChannel
(2)SocketChannel 中提供的 open 接口创建的 Channel 并没有进行网络级联,需要使用 connect 接口连接到指定地址
(3)未进行连接的 SocketChannle 执行 I/O 操作时,会抛出NotYetConnectedException
(4)SocketChannel 支持两种 I/O 模式:阻塞式和非阻塞式
(5)SocketChannel 支持异步关闭。如果 SocketChannel 在一个线程上 read 阻塞,另一个线程对该 SocketChannel 调用 shutdownInput,则读阻塞的线程将返回-1 表示没有读取任何数据;如果 SocketChannel 在一个线程上 write 阻塞,另一个线程对该SocketChannel 调用 shutdownWrite,则写阻塞的线程将抛出AsynchronousCloseException
(6)SocketChannel 支持设定参数
SO_SNDBUF 套接字发送缓冲区大小
SO_RCVBUF 套接字接收缓冲区大小
SO_KEEPALIVE 保活连接
O_REUSEADDR 复用地址
SO_LINGER 有数据传输时延缓关闭 Channel (只有在非阻塞模式下有用)
TCP_NODELAY 禁用 Nagle 算法
package com.yangmin.study.io.nio.channel;
import jdk.nashorn.internal.ir.Block;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* @author yangmin
* @version 1.0
* @description: nio客户端
* 1、SocketChannel 介绍
* Java NIO 中的 SocketChannel 是一个连接到 TCP 网络套接字的通道。操作面向Buffer缓冲区
*
* A selectable channel for stream-oriented connecting sockets.
*
* 以上是 Java docs 中对于 SocketChannel 的描述:SocketChannel 是一种面向流连接sockets 套接字的可选择通道。从这里可以看出:
*
* SocketChannel 是用来连接 Socket 套接字
* SocketChannel 主要用途用来处理网络 I/O 的通道
* SocketChannel 是基于 TCP 连接传输
* SocketChannel 实现了可选择通道,可以被多路复用的
* 用于TCP的网络I/O套接字的通道
* 2、SocketChannel 特征:
* (1)对于已经存在的 socket 不能创建 SocketChannel
*
* (2)SocketChannel 中提供的 open 接口创建的 Channel 并没有进行网络级联,需要使用 connect 接口连接到指定地址
*
* (3)未进行连接的 SocketChannle 执行 I/O 操作时,会抛出NotYetConnectedException
*
* (4)SocketChannel 支持两种 I/O 模式:阻塞式和非阻塞式
*
* (5)SocketChannel 支持异步关闭。如果 SocketChannel 在一个线程上 read 阻塞,另一个线程对该 SocketChannel 调用 shutdownInput,则读阻塞的线程将返回-1 表示没有读取任何数据;如果 SocketChannel 在一个线程上 write 阻塞,另一个线程对该SocketChannel 调用 shutdownWrite,则写阻塞的线程将抛出AsynchronousCloseException
*
* (6)SocketChannel 支持设定参数
*
* SO_SNDBUF 套接字发送缓冲区大小
* SO_RCVBUF 套接字接收缓冲区大小
* SO_KEEPALIVE 保活连接
* O_REUSEADDR 复用地址
* SO_LINGER 有数据传输时延缓关闭 Channel (只有在非阻塞模式下有用)
*
* TCP_NODELAY 禁用 Nagle 算法
* @date 2022/5/10 18:51
*/
public class SocketChannelTest {
public static void main(String[] args) {
SocketChannelTest socketChannelTest = new SocketChannelTest();
// socketChannelTest.unBlock();
socketChannelTest.block();
}
/**
* 阻塞模式
*/
void block(){
System.out.println("connect start");
SocketChannel socketChannel = null;
try {
/**
* 创建 SocketChannel
*/
socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
//SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
/**
* 连接校验
* socketChannel.isOpen(); // 测试 SocketChannel 是否为 open 状态
* socketChannel.isConnected(); //测试 SocketChannel 是否已经被连接
* socketChannel.isConnectionPending(); //测试 SocketChannel 是否正在进行连接
* socketChannel.finishConnect(); //校验正在进行套接字连接的 SocketChannel是否已经完成连接
*/
if (socketChannel.isConnected()){
System.out.println("connect server success");
}
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("hello server\r\n".getBytes());
buffer.flip();
socketChannel.write(buffer);
System.out.println("send data to server");
buffer.clear();
socketChannel.read(buffer);
System.out.println("accept data from server :"+new String(buffer.array()));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socketChannel!=null){
try {
socketChannel.close();
System.out.println("aaaaaaaa");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 阻塞模式
*/
void unBlock(){
System.out.println("connect start");
SocketChannel socketChannel = null;
try {
socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
//设置 SocketChannel 的读写模式。false 表示非阻塞,true 表示阻塞
socketChannel.configureBlocking(false);
if (socketChannel.isConnected()){
System.out.println("connect server success");
}
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("hello server\r\n".getBytes());
buffer.flip();
socketChannel.write(buffer);
System.out.println("send data to server");
buffer.clear();
socketChannel.read(buffer);
System.out.println("accept data from server :"+new String(buffer.array()));
} catch (Exception e) {
e.printStackTrace();
} finally {
if (socketChannel!=null){
try {
socketChannel.close();
System.out.println("aaaaaaaa");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 设置和获取参数
* socketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE,Boolean.TRUE) 保持连接
* socketChannel..setOption(StandardSocketOptions.TCP_NODELAY,Boolean.TRUE); 默认缓冲区大小:8192byte。
*/
}
DatagramChannel
发包
package com.yangmin.study.io.nio.channel;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
/**
* @author yangmin
* @version 1.0
* @description: 基于UDP协议,无连接
* 向地址ip端口号发送内容;接收ip端口号发送来的内容
* <p>
* 正如 SocketChannel 对应 Socket,ServerSocketChannel 对应 ServerSocket,每一个 DatagramChannel 对象也有一个关联的 DatagramSocket 对象。
* <p>
* 正如SocketChannel 模拟连接导向的流协议(如 TCP/IP),DatagramChannel 则模拟包导向的无连接协议(如 UDP/IP)。DatagramChannel 是无连接的,每个数据报(datagram)都是一个自包含的实体,拥有它自己的目的地址及不依赖其他数据报的数据负载。与面向流的的 socket 不同,DatagramChannel 可以发送单独的数据报给不同的目的地址。
* <p>
* 同样,DatagramChannel 对象也可以接收来自任意地址的数据包。每个到达的数据报都含有关于它来自何处的信息(源地址)
* <p>
* 1、打开 DatagramChannel
* DatagramChannel server = DatagramChannel.open();
* server.socket().bind(new InetSocketAddress(10086));
* <p>
* 此例子是打开 10086 端口接收 UDP 数据包
* <p>
* 2、接收数据
* 通过 receive()接收 UDP 包
* <p>
* ByteBuffer receiveBuffer = ByteBuffer.allocate(64);
* receiveBuffer.clear();
* SocketAddress receiveAddr = server.receive(receiveBuffer);
* <p>
* SocketAddress 可以获得发包的 ip、端口等信息,用 toString 查看,格式如下
* /127.0.0.1:57126
* <p>
* 3、发送数据
* 通过 send()发送 UDP 包
* <p>
* DatagramChannel server = DatagramChannel.open();
* ByteBuffer sendBuffer = ByteBuffer. wrap ("client send".getBytes());//发送的内容
* server.send(sendBuffer, new InetSocketAddress("127.0.0.1",10086));
* <p>
* 4、连接
* UDP 不存在真正意义上的连接,这里的连接是向特定服务地址用 read 和 write 接收发送数据包。
* <p>
* client.connect(new InetSocketAddress("127.0.0.1",10086));
* int readSize= client.read(sendBuffer);
* server.write(sendBuffer);
* <p>
* read()和 write()只有在 connect()后才能使用,不然会抛NotYetConnectedException 异常。
* <p>
* 用 read()接收时,如果没有接收到包,会抛PortUnreachableException 异常。
* @date 2022/5/11 15:28
*/
public class DatagramChannelSendTest {
public static void main(String[] args) {
DatagramChannelSendTest datagramChannelTest = new DatagramChannelSendTest();
datagramChannelTest.sendData();
}
/**
* 发送数据
*/
private void sendData() {
try {
DatagramChannel datagramChannel = DatagramChannel.open();
/**
* 发送的地址
*/
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8888);
datagramChannel.send(ByteBuffer.wrap("发包".getBytes()), inetSocketAddress);
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
收包
package com.yangmin.study.io.nio.channel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
/**
* @author yangmin
* @version 1.0
* @description: 收包
* @date 2022/5/11 15:41
*/
public class DatagramChannelAcceptTest {
public static void main(String[] args) throws IOException {
DatagramChannel server = DatagramChannel.open();
InetSocketAddress socketAddress = new InetSocketAddress(8888);
server.bind(socketAddress);
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true){
buffer.clear();
server.receive(buffer);
System.out.println(new String(buffer.array()));
System.out.println("发来信息的ip地址:"+socketAddress.toString());
}
}
}