一、什么是BIO
BIO:又叫阻塞io,例如我们进行socket网络编程的,当服务端没有接到客户端的连接时,这时服务端就回处于阻塞状态,当处于阻塞状态时,我们无法在对程序进行操作。
二、BIO存在的问题
当我们的程序阻塞时,会对服务端造成什么影响呢?—— 当阻塞时,回影响并发请求,无法处理多个请求!
为了解决这个问题这时,我们就需要引入多线程来解决这个问题 —— 当进行io操作的时候,每个io请求都需要去创建一个线程,来独立完成这个操作,就算这个线程被阻塞,也不会影响其他线程正常执行。
但这时使用多线程,就回带来另一个问题 —— 当我们进行频繁的创建线程的时候,会占用大量的资源,并且创建完线程后,需要对线程进行销毁,也需要占用资源,并且我们无法无节制的创建线程!这时我们就需要在引入一个解决方案 —— 线程池。
我们可以使用线程池,来帮助我们对线程进行管理,无需花费过多的资源去创建和销毁线程!但是当我们的io阻塞线程时,会占用线程,毕竟线程池中的线程数量有限,当并发量的时候,将导致线程池中的线程都被阻塞,而无线程可用,导致请求相应时间过长,甚至拒绝服务。
为了解决上诉问题,从而产生NIO的。
三、什么是NIO
NIO:也就是new io,非阻塞IO,他可以解决bio阻塞不足的问题,并且它可以以阻塞和非阻塞两种方式工作;在非阻塞模式下,可以使用少量线程来处理大量io连接。
NIO的工作流程图:
NIO的三要素:Selector选择器、Channel通道、Buffer缓冲区。
Selector选择器:非阻塞模式下,一个选择器可检测多个SelectableChannel,获得为读写等操作准备好的通道。就不需要我们自己去循环判断了。通过Selector,一个线程就可以处理多个Channel,可极大减少线程数。
Selector的用法:
1.创建Selector
Selector selector = Selector.open();
2.将要交给Selector检测的SelectableChannel注册进来
channel.configureBlocking(false);//设置成非阻塞模式
SelectionKey key = channel.register(selector,SelectionKey.OP_READ);
其中:channel.register方法的第二个参数指定要selector帮忙监听的就绪操作,可选参数为:
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
3.通过Selector来选择就绪的Channel,有三个select方法
·int select —— 阻塞直到有就绪的Channel
·int select(long timeout) —— 阻塞最长多久
·int selectNow() —— 不阻塞
三个方法返回值是就绪的Channel数量
int n = selector.select();
4.获得就绪的SelectionKey集合(当有就绪的Channel时)
Set<SelectionKey> selectedKeys = selector.selectedKeys();
5.处理selectedKeys set
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()){
SelectionKey key = keyIterator.next();
if(key.isAcceptable){
}else if(key.isConnectable){
}else if(key.isReadable){
}else if(key.isWritable){
}
keyIterator.remove();//处理了,一定要从selectedKey集中移除
}
Channel的用法:
Channel通道是数据的来源或去向目标
Channel的API方法:
open():创建通道
read(Buffer):从通道中读取数据放入到buffer
write(Buffer):将buffer中的数据写给通道
Buffer缓冲区:
1.含义:数据的临时存放区。
2.各类Buffer包括:ByteBuffer、MappedByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer
3.Buffer的基本调用步骤:
·调用xxxBuffer.allocate(int)创建Buffer
ByteBuffer buf = ByteBuffer.allocate(48);
CharBuffer buf = CharBuffer.allocate(1024);
·调用put方法往Buffer中写数据
int bytesRead = inChannel.read(buf); //read into buffer
buf.put(127);
·调用buffer.flip()将buffer转为读模式
buf.flip(); //转为读模式 position变为0
·读取buffer中的数据
int bytesWritten = inChannel.write(buf);
byte aByte = buf.get();
·读完后,调用clear()或compact()为下次写做好准备
buf.clear(); //position=0 limit=capacity
buf.compact(); //整理,将未读的数据移动到头部
4.Buffer的三个重要属性:capacity、 position、 limit
四、代码演示NIO过程
package com.hu.controller;
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.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NIOServer {
private static Charset charset = Charset.forName("UTF-8");
private static CharsetDecoder decoder = charset.newDecoder();
public static void main(String[] args) throws IOException {
//1.创建一个selector
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
int port = 9000;
ssc.bind(new InetSocketAddress(port));
//2.注册到selector
ssc.configureBlocking(false); //设置为非阻塞
//ssc向selector 注册,监听连接到来
ssc.register(selector,SelectionKey.OP_ACCEPT);
//连接计数
int connectionCount = 0;
//创建连接池
int threads = 3;
ExecutorService pool = Executors.newFixedThreadPool(threads);
while (true) {
//阻塞等待就绪的事件
int readyChannelsCount = selector.select();
if (readyChannelsCount == 0) {
continue;
}
//得到就绪的channel的key
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
//接入连接
SocketChannel socketChannel = serverSocketChannel.accept();
//设置非阻塞
socketChannel.configureBlocking(false);
//向seletor注册
socketChannel.register(selector,SelectionKey.OP_READ,++connectionCount);
} else if (key.isConnectable()) {
} else if (key.isReadable()) {
//交给连接池去处理数据读
pool.execute(new SocketProcess(key));
//取消Selector注册,防止线程池处理不及时,重复选择
key.cancel();
} else if (key.isWritable()) {
}
keyIterator.remove(); //从selectedKey中移除
}
}
}
static class SocketProcess implements Runnable{
SelectionKey key;
public SocketProcess(SelectionKey key) {
super();
this.key = key;
}
@Override
public void run() {
try {
System.out.println("连接");
//连接不需要了,就关闭
key.channel().close();
} catch (IOException e) {
e.printStackTrace();
}
}
private String readFromChannel() throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel();
int bfSize = 1024;
ByteBuffer buffer = ByteBuffer.allocateDirect(bfSize);
//定义一个更大的buffer
ByteBuffer bigBuffer = null;
//读的次数计数
int count = 0;
while ((socketChannel.read(buffer)) != -1) {
count++;
ByteBuffer tempBuffer = ByteBuffer.allocateDirect(bfSize * (count + 1));
if (bigBuffer != null) {
bigBuffer.flip();
tempBuffer.put(bigBuffer);
}
bigBuffer = tempBuffer;
buffer.flip();
bigBuffer.put(buffer);
buffer.clear();
}
if (bigBuffer != null) {
bigBuffer.flip();
try {
return decoder.decode(bigBuffer).toString();
} catch (CharacterCodingException e) {
e.getStackTrace()
}
}
return null;
}
}
}