文章目录
Channel与Buffer概念
Channel
与Buffer
常常一同使用,所以一起介绍会比较合理。
对于两个有关联关系的概念,我觉得需要先给一个总体上的认识。
二者的关系:
这个buffer不仅可以是我们说的Buffer
的实现类,比如ByteBuffer
,ShortBuffer
等,还可以是一个socket
的缓冲区,也可能是一个文件。
示例:
比如我们使用FileChannel
时,创建一个FileChannel对象,需要一个文件的输入或输出流,
//仅做示例,未写异常处理
String path = "text/a.txt";
FileChannel fc= new FileInputStream(path).getChannel();
//通过输入流获取FileChannel,FileChannel比较特殊,通过输入流获取只能读,输出流只能写。
Bytebuffer buffer = ByteBuffer.allocate(16);//获取ByteBuffer来存放我们要读进来的数据
fc.read(buffer);//从channel中把数据读到我们的ByteBuffer中
上面这个示例展示了Channel
与Buffer
的关系。
我们通过Channel
来读取文件或者某个Buffer缓冲区的数据写到我自己的Buffer
中。
或者通过Channel
把我自己ByteBuffer
中的数据写到目标文件或者Buffer中。
这种关系放到SocketChannel
上,就是把Socket
接受缓冲区中的数据(其他人通过网络传输的数据)读到我的ByteBuffer
中,方便后续处理。
或者把自己要发送的放在ByteBuffer
中的数据写到Socket
写缓冲区中发送给目标。
这就是Channel
的作用,就是一个传输的桥梁。
Buffer
则是用来存放我们读写数据的地方。
二者的关联我们梳理清了,接下来就要具体到两者各自的一些实现与使用了。
下面分别介绍Channel
与ByteBuffer
Channel
主要有
FileChannel
(文件io)
SocketChannel
(TCP读写操作)
ServerSocketChannel
(TCP连接管理)
DatagramChannel
(UDP读写操作)
这里主要说前三种的使用
FileChannel
由于重点是网络io,所以FileChannel
简单看一下即可。
首先FileChannel
只能通过 FileInputStream
、FileOutputStream
或者 RandomAccessFile
来获取。调用getChannel()
方法来获取
通过FileInputStream
输入流获取的FileChannel
只可以读。
通过FileOutputStream
输出流获取的FileChannel
只可以写。
通过 RandomAccessFile
获取的则根据其读写模式决定。
下面是基本使用,涉及创建,读,写,释放
//创建
String path = "text/a.txt";
FileChannel channel1= new FileInputStream(path).getChannel();
//读
ByteBuffer buffer1 = ByteBuffer.allocate(16);
long nums = channel1.read(buffer1);
//写
ByteBuffer buffer2 = ByteBuffer.allocate(16);
buffer2.put("abc");//存入要写的数据
buffer2.filp();//切换读模式,因为我们接下来要读出buffer里的数据。后面会详细说明buffer的读写模式
FileChannel channel2 = new FileOutputStream(path).getChannel();
while(buffer2.hasRemaining()) {//hasRemaining()buffer中还有未读取数据返回true,否则返回false
channel.write(buffer2);
}
//使用循环是因为write()方法并不能保证一次将 buffer 中的内容全部写入 channel,可能需要多次。(对于文件io并无此问题,在使用SocketChannel时要注意这个问题)
//关闭(资源释放)
//使用try-with-resources写法,把资源创建放在try后的括号中,一个语法糖
try (FileChannel a = new FileInputStream(path1).getChannel();
FileChannel b = new FileOutputStream(path2).getChannel();
) {
....//做一些读写操作
} catch (IOException e) {
e.printStackTrace();
}
//强制写入
//操作系统出于性能的考虑,会先把数据写到Page Cache中,之后在某些时机进行耍刷盘。可以调用force(true)方法将文件内容和元数据(文件的权限等信息)立刻刷盘
//又有新坑了,这个操作系统的知识可以写一篇
SocketChannel与ServerSocketChannel
二者实例化的同时都会创建socket
对象,因为底层还是通过socket
进行通信。
这个Channel
就等同于我们与socket
交流的通道,或者说对底层socket
所产生的事件做一个监听。
ServerSocketChannel
的作用是监听连接请求,创建新的 SocketChannel
对象,它本身不传输数据。也就是说它就是一个负责创建连接的监听器。它监听到的连接请求处理后,我们可以获得一个对应的 SocketChannel
用于在该连接中传输数据。
ServerSocketChannel
处理连接请求的方法是accept()
,接收到连接请求并建立起连接后,返回一个SocketChannel
对象用于连接的数据传输。
对Socket
通信我并不了解,但是可以想见到的是这两个Channel相当于隔离了原本直接使用的Socket
,等于加上了一层,让网络编程更简单可用。类似于加了一层封装的感觉,这里是一个Socket
关联一个SocketChannel
这里是个人理解,如有错误,烦请批评指正。
SocketChannel
的读写也是read()
与write()
方法。
二者均支持阻塞与非阻塞模式
二者都支持多路复用,将在Selector篇章讲解。
这两个Channel
的其他方法在下文介绍
ServerSocketChannel使用
注意下文的非阻塞模式将是配合Selector
实现多路复用的关键。关于Selector
(即NIO中第三个核心概念)与多路复用,会在下篇文章讲解。这里仅需要知道阻塞与非阻塞的区别即可。(SocketChannel的非阻塞模式同理)
ServerSocketChannel
只监听连接,不具备读写的功能
//打开ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//绑定监听端口
serverSocketChannel.bind(new InetSocketAddress(8080));
// 切换为非阻塞模式
serverSocketChannel.configureBlocking(false);
//监听连接到达
//阻塞模式下,ServerSocketChannel.accept() 会在没有连接建立时让线程暂停
//非阻塞模式则在没有连接建立时,会返回 null,继续运行
serverSocketChannel.accept();
//关闭
serverSocketChannel.close();
SocketChannel使用
通过SocketChannel
我们就可以传输数据,进行网络io的读写了。
//创建ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8080));
//获取SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
//另一种获取方式
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://google.com", 80));
//阻塞与非阻塞模式设置
socketChannel.configureBlocking(false);
//读
//阻塞模式下SocketChannel.read() 会在没有数据可读时让线程暂停
//非阻塞模式下SocketChannel.read() 在没有数据可读时,会返回 0,但线程不必阻塞,可以去执行其它 SocketChannel 的 read 或是去执行 ServerSocketChannel.accept
//读数据还要考虑buffer容量不够多次读取的情况,在下篇Selector中会讨论这个问题
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
socketChannel.read(byteBuffer);
//写
ByteBuffer buffer = ByteBuffer.allocate(16);
buffer.put("abcdefdsadasdasdaddsadasdad");
buffer.flip();//切换为读模式,从中读出数据写到其他地方
//这里使用循环的原因在上面FileChannel的示例已经提前提过
//因为write()方法并不能保证一次将 buffer 中的内容全部写入 channel,可能需要多次write(),直到都写完为止。
while(buffer.hasRemaining()) {//hasRemaining()buffer中还有未读取数据返回true,否则返回false
channel.write(buf);
}
//连接校验,这个部分课程中没咋用,看博客看到的
socketChannel.isOpen(); // 测试 SocketChannel 是否为 open 状态
socketChannel.isConnected(); //测试 SocketChannel 是否已经被连接
socketChannel.isConnectionPending(); //测试 SocketChannel 是否正在进行连接
socketChannel.finishConnect(); //校验正在进行套接字连接的 SocketChannel是否已经完成连接
//关闭
socketChannel.close();
Buffer
在下一篇文章
https://blog.youkuaiyun.com/qq_42939279/article/details/140379191?spm=1001.2014.3001.5501
演示示例
只给代码感觉不太直观,下篇Buffer的写完结尾会演示效果。
并且总结一下简单的通信流程
https://blog.youkuaiyun.com/qq_42939279/article/details/140379191?spm=1001.2014.3001.5501
结语
这部分写起来需要重新组织一下结构,因为课程给我感觉一些东西没有单独讲解,而是耦合在一起,比如SocketChannel
是在对比阻塞非阻塞时讲的,我就把它提出来单独写在Channel
部分了,对于阻塞非阻塞多路复用,应该在下篇Selector
中讲。写完三个核心概念应该直接开始Netty
了。不在NIO
多做停留。
后面关于IO模型可能会写一篇,因为前两天花了一些时间去看这部分。
写了好久好久,发现写博客能很容易的让我进入心流状态,一口气写了3个多小时。
文章如有错误,还请指出,谢谢大家观看。