目录
2.3、IO多路复用(IO Multiplexin)(Java中的NIO)
2.4、AIO(Asynchronous IO)(异步IO模型)
1、IO读写的原理
Linux系统中分为内核缓冲区和用户进程缓冲区。
外部设备的读写,会导致操作系统的中断,中断发生时,操作系统需要保存进程状态和数据等信息;中断恢复时,再恢复进程数据和状态。为了减少这种消耗,就有了缓冲区。
我们常说的IO读写,是在内核缓冲区和进程缓冲区之间的读写。
内核缓冲区是操作系统自带的,当程序要读硬盘的数据时,数据先从硬盘读到内核缓冲区,再从内核缓冲区复制到用户缓冲区(程序内存)。
系统调用读写的流程:
2、4种IO模型
2.1、BIO(Blocking IO)
阻塞IO,在Java中最常见的IO操作,就是阻塞IO,当IO读写时,线程是阻塞的。
Java发起IO读操作时,线程开始阻塞,操作系统等待数据进入内核缓冲区,等数据到达后(socket),把数据复制进内核缓冲区,当内核缓冲区满后,复制到用户缓冲区,这个过程中,线程一直是挂
起的。不会耗费cpu资源。
示例代码:
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BIOServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(6666);
ExecutorService executorService = Executors.newCachedThreadPool();
while (true) {
System.out.println("等待客户端连接。。。。");
//阻塞
Socket socket = serverSocket.accept();
executorService.execute(() -> {
try {
InputStream inputStream = socket.getInputStream(); //阻塞
byte[] bytes = new byte[1024];
while (true) {
int length = inputStream.read(bytes);
if (length == -1) {
break;
}
System.out.println(new String(bytes, 0, length, "UTF-8"));
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
}
2.2、NIO(None Blocking IO)
socket连接,默认是阻塞的。在Linux系统下,可以设置socket连接为非阻塞的。
当内核缓冲区还没有数据时,系统调用立刻返回一个调用失败的信息,不会阻塞。所以需要程序不断轮询查询,耗费大量cpu资源。
当内核缓冲区有数据并开始向用户缓冲区复制时,程序是阻塞的。
这种模型很少很少使用。不适合高并发场景下。
2.3、IO多路复用(IO Multiplexin)(Java中的NIO)
Java中的NIO(New IO),其实就是应用的IO多路复用模型。
需要操作系统支持select/epoll模式,把多个socket连接,注册到选择器上(Java中的Selector)。在Linux系统中,对应的系统调用为select/epoll系统调用。通过该系统调用,一个进程可以监视多个文
件描述符,一旦某个描述符就绪(一般是内核缓冲区可读/可写),内核能够将就绪的状态返回给应用程序
一个线程不断轮询查询选择器,返回可读/可写的socket列表(这个过程是阻塞的,但是一个线程可处理多个 soccer 连接);交给另一个进程读写,内核开始复制数据到用户缓冲区。(这个过程也是阻塞的)
示例代码:
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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 SelectorDemo {
/**
* 注册事件 *
*
* @return
*/
private Selector getSelector() throws Exception { //获取selector对象
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//非阻塞
serverSocketChannel.configureBlocking(false);
//获取通道并且绑定端口
ServerSocket socket = serverSocketChannel.socket();
socket.bind(new InetSocketAddress(6677));
//注册感兴趣的事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
return selector;
}
public void listen() throws Exception {
Selector selector = this.getSelector();
while (true) {
selector.select(); //该方法会阻塞,直到至少有一个事件的发生
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
process(selectionKey, selector);
iterator.remove();
}
}
}
private void process(SelectionKey key, Selector selector) throws Exception {
//新连接请求
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel = server.accept();
//非阻塞
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
//读数据
} else if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
channel.read(byteBuffer);
System.out.println("form 客户端 " + new String(byteBuffer.array(), 0, byteBuffer.position()));
}
}
public static void main(String[] args) throws Exception {
new SelectorDemo().listen();
}
}
2.4、AIO(Asynchronous IO)(异步IO模型)
这是真正的异步IO。程序通过系统调用,向内核注册某个IO操作,内核拿到数据并复制到用户缓冲区后,再通知程序(发送某个信号或者回调程序的某个接口).
优点是完全的异步,不需要程序阻塞。
缺点是这个模型,需要操作系统支持才行。
就目前而言,Windows系统下通过IOCP实现了真正的异步IO。而在Linux系统下,异步IO模型在2.6版本才引入,目前并不完善,其底层实现仍使用epoll,与IO多路复用相同,因此在性能上没有明显的优势。
所以大多数的高并发服务器端的程序,一般都是基于Linux系统的。因而,目前这类高并发网络应用程序的开发,大多采用IO多路复用模型。netty使用的就是IO多路复用模型。
3、操作系统配置支持百万连接
在Linux系统中,单个进程能同时打开的句柄数是1024,远远不能满足要求。可通过以下配置进行调整:
在 /etc/rc.local 开机启动文件中增加
ulimit -SHn 1000000
-S是软性极限值(soft);-H是硬性极限值(hard);
也可以修改 /etc/security/limits.conf 文件来配置软硬极限值
soft nofile 1000000
hard nofile 1000000
参考:《Netty、Redis、Zookeeper 高并发实战》
欢迎留言一起讨论技术