在Java中 java.io 包下就是同步阻塞式IO,代表有InputStream OutputStream Reader Writer等,核心思想是面向流编程。
java.nio 包下的新IO模型,为了解决传统IO带来的同步阻塞低效问题。
但是今天不讲文件流的IO,今天面向的是网络IO,从这个角度理解下BIO到NIO的发展,以及操作系统底层epoll技术在Java的实现。
public static void main(String[] args) throws IOException {
ServerSocket bioServer = new ServerSocket(0);
while (true) {
// 当有新的TCP连接请求,封装成Socket,Socket即是一个通信的双向流,从客户端读或写数据到客户端
Socket socket = bioServer.accept();
InputStream inputStream = socket.getInputStream();
byte[] buffer = new byte[2048];
int i ;
// 关键点在这里,服务器接收到一个socket连接后代表有客户端连接请求要与服务器通信,TCP连接
// read()函数会阻塞读取客户端的数据,但是客户端什么时候发送数据时未知的
// 当有很多连接请求时怎么办?10000+个并发连接请求,主线程此时就阻塞在这里等待其中一个客户端的数据吗?
while ((i = inputStream.read(buffer)) != -1) {
// do IO operation
// read or write
}
}
}
从上面的场景可以理解为什么传统网络IO会低效,原因就在于有很多个并发socket都要处理,而read()函数是阻塞的,其中一个socket不发送数据
就会被阻塞住,所以后面的socket假设有数据到达也不能处理。基于这种IO模型,如果要处理多个socket就得开线程池,但是也没用所有线程都会阻塞在read()中。
public static void main(String[] args) throws IOException {
try (Selector selector = Selector.open();
ServerSocketChannel serverSocket = ServerSocketChannel.open();) {
serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 8888));
serverSocket.configureBlocking(false);
// 指定多个IO事件,当有新连接到达 或 某个socket有读事件发生即有数据到达,selector就会感知到
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 同步阻塞等待监听的 ServerSocketChannel 发生监听的IO事件后返回
selector.select();
// 获得文件描述符,这些key都是发生监听的IO事件后的描述符
for (SelectionKey selectionKey : selector.selectedKeys()) {
// 进一步判断当前socket是发生了什么IO事件
if (SelectionKey.OP_ACCEPT == selectionKey.readyOps()) {
ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = channel.accept();
if (null != socketChannel) {
// a new socket do something
socketChannel.configureBlocking(false);
// 新的客户端连接 监听数据读取事件
socketChannel.register(selector, SelectionKey.OP_READ);
}
}
if (SelectionKey.OP_READ == selectionKey.readyOps()) {
// do read
SocketChannel socketChannel = (SocketChannel) selectionKey.attachment();
ByteBuffer byteBuffer = ByteBuffer.allocate(2048);
int i ;
// 同步读取到达的客户端数据,同样是read()这里能保证一定要数据可读而避免了无效的阻塞浪费时间
// 将TCP连接就绪的数据从内核缓冲读到用户空间的缓冲
while ((i = socketChannel.read(byteBuffer)) != -1) {
// 这里就涉及到NIO中的 Buffer 对象操作。数据已到达用户空间的缓冲区可直接读写数据
}
}
}
}
}
}
这样一个线程就可以处理很多个并发连接请求,关键在于这个IO模型解决了不知道哪些socket有数据可读的问题。
当 serversocket 向selector注册指定的IO事件后,就可以在有指定IO事件发生后直接处理发生IO事件的socket,避免了无效的read()阻塞。
又因为read()是基于内存读取数据非常快,所以一般一个线程都可以处理多个并发连接。
NIO的处理模型就是基于IO事件监听和通知机制,底层在Linux系统上使用的epoll api实现。
java 提供了哪些IO方式 - 小南天门 - 博客园java 提供了哪些IO方式
Java网络编程 -- NIO非阻塞网络编程 - CodingDiary - 博客园Java网络编程 -- NIO非阻塞网络编程