BIO,是指Blocking IO阻塞的io,是针对socket编程从网络上读写操作而言的。
对于传统的IO,在socket编程中,read,write,connect服务端的accept都会产生阻塞。
因此,在jdk1.4之前,如果遇到并发请求。那么就需要开启多个线程,每个线程处理一个网络请求。
这就会造成问题
线程的增多,会造成cpu压力增大
线程增大,会导致线程栈增多,因此会占用大量的内存。
然而这些线程,并不是都在请求,所以会造成cpu,内存的浪费。
利用线程池确实可以解决,但是线程池也是有数量的。假设有5个线程,那如果这5个线程同时阻塞了。那么另外的线程就又在等待了。
基于上诉问题。NIO出现了。
NIo首先设计了Channel,使得socket的accept方法不在阻塞,然后将所有的通道(这里是socket在NIO中是通道)注册在一个多路复用器中,Selector。这个多路复用器会阻塞调用select方法。这个方法会返回所有有事件的通道个数,如果个数为0,则阻塞。直到有活跃通道。这里的事件是指,连接,读,写等等。然后再根据事件,处理通道。因此,这个可以单线程处理所有客户端。第一,服务器监听客户端连接,不在阻塞。第二,采用了事件触发。只有当有事件的通道,cpu才会去执行。没有事件,就一直轮训。这里的事件包括客户端连接服务器。即accept。
但是这模式也有弊端。第一,如果有长连接,单线程下,在处理这个长连接的同时,是不能处理其他通道的读写事件和监听事件的。如果通道的连接处理时间过长,仍然会产生阻塞。这时就产生了多线程NIO,将通道处理的逻辑放在线程池中去做。
这种方法仍然有弊端,就是select轮询所有的注册通道,因为有的通道注册了,但是并没有事件,如果连接数很多,依然会造成性能浪费。依然不能最大化的压榨服务器性能。所以产生了另外的react方式。
bio是传统的阻塞,等待通信,实现多客户端需要采用循环监听加多线程读写。子线程阻塞不影响主线程监听。主线程监听阻塞不影响子线程读写。否则,监听的阻塞,在没监听到客户端连接的情况下,程序阻塞,将无法读取已监听的客户端的读写。或者在读写已经连接的客户端时候,程序阻塞,因此无法监听新的客户端
nio是采用了事件触发模式。首先监听不在阻塞。此时在没有监听到客户端连接的情况下,不影响已连接客户端的通信。第二,把客户端通道设为非阻塞,在客户端没有读到内容时,将不再阻塞,因此程序会继续循环监听。所以,如果直接读取内容,一旦客户端没有发送数据,程序将继续进行,不在阻塞等待。此时如果另外的客户端连接上来,那么前一个通道将被覆盖。因此,需要不断的循环监听同时,将连接到的客户端放入List。然后轮询list,每当拿出一个channel,就read,如果读到内容就处理,读不到就继续下一个,因为是非阻塞的。这时就实现了多路复用。
上面的多路复用是我们自己实现的。效率比较低。jvm采用了poll,select,epoll来让操作系统实现这个轮询。一旦发现有读写操作的channel,就会返回活跃的通道个数,并且有selectedkeys返回活跃通道集合,然后我们根据通道注册的事件,来进行下一步的处理。就不用轮询所有的channel了。
package NIO;
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.Charset;
public class ServerTest {
private Selector selector=null;
private int port=9999;
public void init(){
try {
selector=Selector.open();
System.out.println("多路复用器准备就绪。。。");
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务端通道已注册。。。");
} catch (IOException e) {
e.printStackTrace();
}
}
public void run(){
while (true) {
try {
if (!(selector.select()>0)) {
System.out.println("没有活跃通道!");
break;
}
System.out.println("有"+selector.select()+"条活跃通道");
for(SelectionKey sk:selector.selectedKeys()){
selector.selectedKeys().remove(sk);
if(sk.isAcceptable()){
System.out.println("服务端通道有连接事件");
SocketChannel socketChannel=((ServerSocketChannel)sk.channel()).accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector,SelectionKey.OP_READ);
System.out.println("客户端通道注册完成!");
}
if(sk.isReadable()){
System.out.println("有活跃通道读事件");
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
String content="";
try {
while (((SocketChannel) sk.channel()).read(byteBuffer) > 0) {
byteBuffer.flip();
content += Charset.defaultCharset().decode(byteBuffer);
}
System.out.println(content);
}catch (Exception e){
sk.cancel();
if(sk.channel()!=null){
sk.channel().close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ServerTest ser=new ServerTest();
ser.init();
ser.run();
}
}
代码中的
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
的open方法,






而产生provider需要调用create方法,create方法实现如下




最终调了poll0方法,poll0在操作系统中调用了Windows的select方法。这是在Windows系统下的
如果是在linux下,则会掉epoll。
这两个方法,可以将注册在多路复用器中的channel,交给操作系统内核,然后他的轮询就交由操作系统来做。因此效率比较高。
另外NIO还有一个特性是0拷贝,也是效率比较高。
上面介绍的BIO和NIO都是同步IO,因为IO分为两部分,1,程序发出IO请求。2完成实际的IO操作。对于前面的两种阻塞和非阻塞,是针对于第一步,划分的。如果发出请求会阻塞线程,则为阻塞IO,如果没有阻塞线程,则为非阻塞IO。但是同步IO与异步IO的区别在第二步,如果实际的IO操作由操作系统完成,将结果返回给应用程序,这就是异步IO。如果实际的IO需要应用程序本身区执行,会阻塞线程,那就是同步IO。BIO和NIO都是同步IO,NIO是同步非阻塞IO
异步IO是采用
AsynchronousServerSocketChannel ,
AsynchronousSocketChannel
两个通道来完成。服务端通道的accept方法有两种一种是普通,另一种需要传递
AsynchronousChannelGroup
例子如下
public class server02 {
static List<AsynchronousSocketChannel>channelList=new ArrayList<AsynchronousSocketChannel>();
public static void main(String[] args) {
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
ExecutorService executorService= Executors.newFixedThreadPool(80);
try {
AsynchronousChannelGroup channelGroup=AsynchronousChannelGroup.withThreadPool(executorService);
AsynchronousServerSocketChannel serverSocketChannel=
AsynchronousServerSocketChannel.open(channelGroup);
serverSocketChannel.bind(new InetSocketAddress(8888));
System.out.println("开启监听。。。。");
serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel sc, Object o) {
System.out.println("连接成功,接收数据。。。");
channelList.add(sc);
serverSocketChannel.accept(null,this);
sc.read(byteBuffer, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer integer, Object o) {
byteBuffer.flip();
System.out.println("接收到数据:");
System.out.println(Charset.defaultCharset().decode(byteBuffer));
System.out.println("发送给其他客户端");
for (var ch:channelList
) {
try {
ch.write(byteBuffer).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
byteBuffer.clear();
sc.read(byteBuffer,null,this);
}
@Override
public void failed(Throwable throwable, Object o) {
System.out.println("读取数据失败:"+throwable);
channelList.remove(sc);
}
});
}
@Override
public void failed(Throwable throwable, Object o) {
System.out.println("连接失败:"+throwable+"!");
}
});
Scanner scanner=new Scanner(System.in);
scanner.nextInt();
} catch (Exception e) {
e.printStackTrace();
}
}