顾名思义BIO(Blocking IO)也就是阻塞式IO
话不多说首先看一段传统io的代码
class Server{ SercerSocket server = new SercerSocket(port); Socket client = server.accept(); InputStream input = client.getInputStream(); byte[] buffer = new byte[1024]; int len = 0; while((len=input.read(buffer))>0){ System.out.println(new String(buffer,0,len)); } }首先启动Server,线程会阻塞在server.accept()这里,这个时候客户端连接上Server,线程往下执行,遇到input.read()又会阻塞,这就是BIO,如果客户端只是连接并没有发数据,线程就会一直阻塞。此时如果再来一个客户端请求连接到服务器,此时Server线程是被阻塞在了input.read(),没法处理新的客户端响应请求;
解决这种问题的一种方法如下:
1.首先将原Server处理Client的操作进行封装
2.Server 如果收到一个Client的连接就新建一个线程,让后续处理操作在其中执行
public class SocketHandler implements Runnable{ private Socket client; public SocketHandler(Socket client){ this.client = client ; } public void run(){ InputStream input = client.getInputStream(); byte[] buffer = new byte[1024]; int len = 0; while((len=input.read(buffer))>0){ System.out.println(new String(buffer,0,len)); } } }原来的Server改为
class Server{ SercerSocket server = new SercerSocket(port); Socket client = server.accept(); new Thread (new SocketHandler(client)).start(); }这样就解决了,就可以开启多个Client 对Server进行发数据
但是这种方式是,每一个Client的连接Server都是开一条线程进行处理
随着客户端的不断增加,服务器线程也会暴增,Server必然宕机
为解决这个问题,在jdk1.4就推出了NIO
顾名思义NIO(NO-Blocking IO或New IO)也就是非阻塞式IO
NIO的核心在于Selector和Channel
原理:现在有新的客户端请求连接,会给这个Client建立一个Channel,这个Channel会被注册进Selector ,此时指定SelectionKey为OP_ACCEPT,在register时会指定SelectionKey,SelectionKey有四种属性,Selector会监听对应的属性,比如:当SelectionKey.isAcceptable()==true时说明这个Client是可以连接的,而不用向BIO那样就死等着客户端的请求,NIO则是通过Selector进行监听,当有Client连接我才过来和你进行连接,否则我就做当前线程自己得事情,建立好连接之后将这个Channel再次注册到Selector,此时再指定指定SelectionKey为OP_READ,也是同样的原理解决的线程不会像BIO那样让Server阻塞再Read()那里,原来Read()也是死等着Client发送消息,即使Client没发数据,或者数据没有准备好时也会等着它,NIO还是通过Selector进行监听,监听到SelectionKey.isReadable()==true时说明这个客户端的数据时已经准备好了,然后此时才对你进行read()这样大大提高了效率,这样就没必要你来一个客户端我就给你开一个线程维护你,此时的NIO一条线程就足以处理你多个客户端的请求,实现了完全一样的功能,但是底层的实现是截然不同
以下是NIO Server端的代码
public void server() throws IOException{ //1.获取通道 ServerSocketChannel ssChannel=ServerSocketChannel.open(); //2.切换非阻塞式模式 ssChannel.configureBlocking(false); //3.绑定连接 ssChannel.bind(new InetSocketAddress(9898)); //4.获取选择器 Selector selector=Selector.open(); //5.将通道注册到选择器上,并且指定“监听接收事件” ssChannel.register(selector,SelectionKey.OP_ACCEPT); //6.轮询式的获取选择器上已经“准备就绪”的事件 while(selector.select()>0){ //7.获取当前选择器中所有注册的“选择键(已就绪的监听事件)” Iterator<SelectionKey> it=selector.selectedKeys().iterator(); while(it.hasNext()){ //8.获取准备“就绪”的事件 SelectionKey sk=it.next(); //9.判断具体是什么时间准备就绪 if(sk.isAcceptable()){ //10.若“接收就绪”,获取客户端连接 SocketChannel sChannel=ssChannel.accept(); //11.切换非阻塞模式 sChannel.configureBlocking(false); //12.将该通道注册到选择器上 sChannel.register(selector, SelectionKey.OP_READ); }else if(sk.isReadable()){ //13.获取当前选择器上“读就绪”状态的通道 SocketChannel sChannel=(SocketChannel)sk.channel(); //14.读取数据 ByteBuffer buf=ByteBuffer.allocate(1024); int len=0; while((len=sChannel.read(buf))>0){ buf.flip(); System.out.println(new String(buf.array(),0,len)); buf.clear(); } } //15.取消选择键SelectionKey it.remove(); } } }当然,NIO对于一个客户端如果发送过大的文件时,也是存在问题的,因为NIO时只开了一个线程来处理你所有来的客户端的请求,如果一个客户端一直就再给服务器传输数据,此时也会造成堵塞。
解决:NIO可以参照BIO处理多个客户端来访问时的解决办法,同样的NIO可以开多个线程,每个线程维护一个Selector然后让Selector再来监听一定数量的客户端
原理: 其实就是直接再内核中开辟一块区域进行文件的存储,用户程序和物理磁盘都直接引用地址即可
3 零拷贝
原理: 其实就是直接再内核中开辟一块区域进行文件的存储,用户程序和物理磁盘都直接引用地址即可
[外链图片转存中…(img-FYWGmCK4-1593788930421)]
[外链图片转存中…(img-sWeofs8D-1593788930422)]