Netty系列文章 - 眼熟Nio
什么是IO?什么是NIO?
- IO:
- java.io中最为核心的概念是流(
stream
),面向流编程,java中,一个流要么是输入流,要么是输出流
- java.io中最为核心的概念是流(
- NIO:
- java.nio中拥有3个核心概念:
selector
.channel
.buffer
; java.nio中,面向块(block)或是缓冲区(buffer)编程的, - java中的原生8种基本数据类型都有各自对应的buffer类型,(除Boolean外),如
IntBuffer
,CharBuffer
,ByteBuffer
,LongBuffer
,ShortBuffer
- 所有数据的读写都是通过
buffer
来进行的,永远不会出现直接channel
中直接写入,读取数据 - 与
stream
不同的是,channel
是双向的,一个流只可能是InputStream
或是OutputStream
,channel
则是双向的,channel
打开后可以进行读又可以进行写
- java.nio中拥有3个核心概念:
3个核心概念
Buffer
Buffer属性以及相关操作.
属性 | 说明 |
---|---|
capacity 最大容量 | 它永远不可能为负数,并且是不会变化的 |
position 位置 | 下一个读或写的位置,它永远不可能为负数,并且不会大于limit |
limit 限制 | 它永远不可能为负数,并且不会大于capacity |
mark 标记 | 标记位置,用于记录某次读写的位置,可以通过reset()方法回到这里 |
public class Dome1 {
public static void main(String[] args) {
// 分配一块容量大小为8个长度的Buffer块
IntBuffer buffer = IntBuffer.allocate(5);
// 生成随机数存入buffer中
for (int i = 0; i < buffer.capacity(); i++) {
int nextInt = new Random().nextInt(20);
buffer.put(nextInt);
}
/**
*
* 反转一下
* 其实核心也就是执行了这两行代码
* limit = position;
* position = 0;
*/
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print(buffer.get()+"\t"); //输出结果: 3 1 4 5 10
}
}
}
Buffer初始化完成后图示
每put或get
一次数据``position`就会往右移动一次
调用buffer.flip();
后limit = position; position = 0;
Channel
常见的Channel实现有
FileChannel:文件读写数据通道
SocketChannel:TCP读写网络数据通道
ServerSocketChannel:服务端网络数据读写通道,可以监听TCP连接。对每一个新进来的连接都会创建一个SocketChannel: 客户端网络数据读写通道.
DatagramChannel:UDP读写网络数据通道
通道表示打开到 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。
Channel相比IO中的Stream更加高效,可以异步双向传输,但是必须和buffer一起使用。
// 读一个文件内容
public class Dome2 {
public static void main(String[] args) throws Exception {
FileInputStream fileInputStream = new FileInputStream("dome2.txt");
FileChannel channel = fileInputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
channel.read(byteBuffer);
byteBuffer.flip();
while (byteBuffer.remaining() > 0) {
System.err.println((char) byteBuffer.get());
}
fileInputStream.close();
}
}
输出结果
H
e
l
l
o
// 从一个文件读数据并写到另一个文件内!
public class Dome4 {
public static void main(String[] args) throws Exception {
FileOutputStream fileOutputStream = new FileOutputStream("dome4write.txt");
FileInputStream fileInputStream = new FileInputStream("dome4read.txt");
FileChannel channelRead = fileInputStream.getChannel();
FileChannel channelWrite = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
while (true) {
// 注意这个clear方法
byteBuffer.clear();
System.out.println(byteBuffer.position());
int readNumber = channelRead.read(byteBuffer);
System.out.println(readNumber);
if (-1 == readNumber) {
break;
}
byteBuffer.flip();
channelWrite.write(byteBuffer);
}
fileOutputStream.close();
fileInputStream.close();
}
}
Selector(选择器)(多路复用器)
1. Selector的创建
通过调用Selector.open()方法创建一个Selector对象,如下:
Selector selector = Selector.open();
2. 注册Channel到Selector
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
Channel必须是非阻塞的。
所以FileChannel不适用Selector,因为FileChannel不能切换为非阻塞模式,更准确的来说是因为FileChannel没有继承SelectableChannel。Socket channel可以正常使用。
SelectableChannel抽象类 有一个 configureBlocking() 方法用于使通道处于阻塞模式或非阻塞模式。
abstract SelectableChannel configureBlocking(boolean block)
示例代码
NIOServer端
public static void main(String[] args) throws IOException {
//得到serverSocketChannel对象 老大
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//得到Selector对象 间谍
Selector selector = Selector.open();
//绑定端口
serverSocketChannel.bind(new InetSocketAddress(9090));
//设置非阻塞式
serverSocketChannel.configureBlocking(false);
//把ServerSocketChannel注册给Selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //监听连接
//干活
while (true) {
try {
//监控客户端
if (selector.select(2000) == 0) {
System.out.println("2秒内没有客户端来连接我");
continue;
}
//得到SelectionKey对象,判断是事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey selectionKey : selectionKeys) {
if (selectionKey.isAcceptable()) { //连接事件
System.out.println("有人来连接");
//获取网络通道
SocketChannel clientSocket = serverSocketChannel.accept();
//设置非阻塞式
clientSocket.configureBlocking(false);
//连接上了 注册读取事件
clientSocket.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if (selectionKey.isReadable()) { //读取客户端数据事件
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
socketChannel.read(byteBuffer);
System.out.println(new String(byteBuffer.array()));
byteBuffer.put("你好".getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
}
if (selectionKey.isWritable()) {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
byteBuffer.put("你好".getBytes());
byteBuffer.clear();
socketChannel.write(byteBuffer);
selectionKey.interestOps(SelectionKey.OP_READ);
}
//手动从当前集合将本次运行完的对象删除
selectionKeys.remove(selectionKey);
}
} catch (Exception e) {
System.out.println("错了");
}
}
}
NIOClient客户端
public static void main(String[] args) throws Exception {
//得到一个网络通道
SocketChannel socketChannel = SocketChannel.open();
//设置非阻塞式
socketChannel.configureBlocking(false);
//提供服务器ip与端口
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 9090);
//连接服务器端
if (!socketChannel.connect(inetSocketAddress)) { //如果连接不上
while (!socketChannel.finishConnect()) {
System.out.println("nio非阻塞");
}
}
new Thread(new MyRunble(socketChannel)).start();
while (true) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read = socketChannel.read(buffer);
if (read > 0) {
System.out.println(new String(buffer.array()));
}
}
}
static class MyRunble implements Runnable {
SocketChannel socketChannel;
MyRunble(SocketChannel channel) {
this.socketChannel = channel;
}
@Override
public void run() {
while (true) {
//创建一个buffer对象并存入数据
Scanner scanner = new Scanner(System.in);
String message = scanner.nextLine();
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
//发送数据
try {
socketChannel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}