java NIO原理和代码实践
一,先说java IO
1,线程阻塞:当线程调用write()
或read()
时,线程会被阻塞,直到有一些数据可用于读取或数据被完全写入。
2,面向流: 我们需要从流中读取一个或多个字节。它使用流来在数据源/槽和java程序之间传输数据。从源进入 Java对象成为“input” , 从Java 对象 写出称为“ouput” .
二,同步非阻塞IO:当内核数据没有准备好的情况下,并不会一直阻塞等待,而是立即返回。随后多次发起系统调用,轮询过程。
优点: 不会等待阻塞,失败立即返回。
缺点: 多次发起调用,轮询过程消耗资源。
三,多路复用IO:select/epoll系统调用,单个线程不断的轮询select/epoll系统调用所负责的成百上千的socket连接,当某个或者某些socket网络连接有数据到达了,就返回这些可 以读写的连接。 在 Linux 下,实现高并发网络编程时都是以 IO 复用模型模式为主。这是同步非阻塞的升级。
优点:之前是一个线程对应一个连接。现在是一个线程对应多个连接。 适合高并发场景;系统不必创建线程,也不必维护这些线程,从而大大减小了系统的开销。
缺点:需要不断的进行select/epoll轮询,查找出可以进行IO操作的连接;select/epoll系统调用,属于同步IO,也是阻塞IO。也就是说这个读写过程是阻塞的。
二,java NIO 原理
就是多路复用原理。
三 , NIO 简单例子
package org.example;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class NIOServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open(); // 开启选择器
ServerSocketChannel SocketChannel = ServerSocketChannel.open();
InetSocketAddress addr = new InetSocketAddress("localhost", 1111);
SocketChannel.bind(addr); //服务器端建立channel ,绑定到端口
SocketChannel.configureBlocking(false); // 配置channel阻塞模式
int ops = SocketChannel.validOps();
SelectionKey selectKy = SocketChannel.register(selector, ops, null);
// 无限循环,保持服务器运行
while (true) {
log("我是服务器,我在等待连接... ");
selector.select(); // 从很多key中选择 已IO准备好的channel
Set<SelectionKey> Keys = selector.selectedKeys(); //从选择器中选择“已准备好的通道(对应的key)”
Iterator<SelectionKey> Iterator = Keys.iterator();
while (Iterator.hasNext()) {
SelectionKey myKey = Iterator.next();
//测试通道是否可以接收socket 连接
if (myKey.isAcceptable()) {
SocketChannel socketChannel = SocketChannel.accept();
// 修改通道阻塞模式
socketChannel.configureBlocking(false);
// 为读操作进行配置
socketChannel.register(selector, SelectionKey.OP_READ);
log("成功连接到: " + socketChannel.getLocalAddress() + "\n");
// 测试通道是否可以读
} else if (myKey.isReadable()) {
SocketChannel socketChannel = (SocketChannel) myKey.channel();
ByteBuffer Buffer = ByteBuffer.allocate(256);
socketChannel.read(Buffer);
String result = new String(Buffer.array()).trim();
log("接收到消息: " + result);
if (result.equals(".com")) {
socketChannel.close();
log("\n接收到最后一个companie name,可以关闭连接 ''");
log("\n服务器保持运行,可以继续重启客户端建立新连接进行测试。 ");
}
}
Iterator.remove();
}
}
}
private static void log(String str) {
System.out.println(str);
}
}
public class NIOClient {
public static void main(String[] args) throws IOException, InterruptedException {
InetSocketAddress addr = new InetSocketAddress("localhost", 1111);
// 建立数据报通道,并绑定地址
SocketChannel socketChannel = SocketChannel.open(addr); //channel 绑定到端口。
log("连接到服务器,端口1111...");
ArrayList<String> companyDetails = new ArrayList<String>(); //构造传输的数据
companyDetails.add("Facebook");
companyDetails.add("Twitter");
companyDetails.add("IBM");
companyDetails.add("Google");
companyDetails.add("alibaba.com");
companyDetails.add(".com");
for (String companyName : companyDetails) {
byte[] message = new String(companyName).getBytes();
ByteBuffer buffer = ByteBuffer.wrap(message); // 功能:定位 ,容量,
socketChannel.write(buffer); //将Buffer中的数据读入通道
log("发送消息: " + companyName);
buffer.clear();
Thread.sleep(2000);
}
socketChannel.close();
}
private static void log(String str) {
System.out.println(str);
}
}
运行结果:
---一------步-----一 ------个-----脚--------印----------