从BIO到NIO的演变:扒扒源码

BIO and NIO

BIO:Blocking IO,同步阻塞IO
文件流/网络流
我们常常会这样做

ServerSocket serversocket=new ServerSocket(8888);
Socket clientsocket=serversocket.accept();
socket.IO-----》》》InputStream OutputStream

这段伪代码我也准备了一个例子,很简单,最基础的知识了

import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class BioServer {
 public static void main(String[] args) {
 try (ServerSocket serversocket=new ServerSocket(8888)){
  System.out.println(serversocket.getLocalSocketAddress()+"成功启动");
  //不断等待客户端的连接
     while(true)
      {
      Socket clientsocket=serversocket.accept();
      System.out.println(clientsocket.getRemoteSocketAddress()+"连接成功")try(Scanner scanner=new Scanner(clientsocket.getInputStream())){
      //针对每个客户端能够进行读写操作
        while(true)
        {
        String request=scanner.nextLine();
        if("quit".equals(request))
        break;
        clientsocket.getOutputStream().write((request+"输出"+"\n").getBytes());
      }
      }
      }
 } catch(Exception e)
 {
  e.printStackTrace();
 }
 }
}

此时的数据交互就像这样
在这里插入图片描述
运行一下程序
在这里插入图片描述
然后我们与客户端的一个交互
打开控制台ctrl+r,输入cmd
输入telnet localhost 8888
在这里插入图片描述
在这里插入图片描述
在cmd控制台输入ctrl+]
在这里插入图片描述
回车进入新窗口
输入
在这里插入图片描述
这样就完成了一个交互

那问题就来

如果此时有第二个客户端,还能成功完成交互吗?
在这里插入图片描述

当然是不能的
我们看上述代码,显然
Socket clientsocket=serversocket.accept(); //第一个阻塞
String request=scanner.nextLine(); //第二个阻塞
想想看,第一个客户端来了,服务器一直等待客户端的输入,堵塞,导致第二个客户端无法连接
在这里插入图片描述
我们将第一个客户端断开,第二个才连接成功
在这里插入图片描述
那我怎么样能让多个客户端同时与服务器进行交互呢?
没错,用多线程
每来一个客户端,开启一个线程
在这里插入图片描述

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) {
  ExecutorService exe=Executors.newFixedThreadPool(3);
 try (ServerSocket serversocket=new ServerSocket(8888)){
  System.out.println(serversocket.getLocalSocketAddress()+"成功启动");
     while(true)
      {
      Socket clientsocket=serversocket.accept();
      System.out.println(clientsocket.getRemoteSocketAddress()+"连接成功");
      exe.submit(new ClientHandler(clientsocket));
      }
 } catch(Exception e)
 {
  e.printStackTrace();
 }
 }
}

每来一个客户端,将数据的交互放在一个线程里处理
exe.submit(new ClientHandler(clientsocket));

import java.net.Socket;
import java.util.Scanner;
public class ClientHandler implements Runnable {
 public Socket clientsocket;
 public ClientHandler(Socket clientsocket)
 {
  this.clientsocket=clientsocket;
 }
@Override
public void run() {
 try(Scanner scanner=new Scanner(clientsocket.getInputStream())){
        while(true)
        {
        String request=scanner.nextLine();
        if("quit".equals(request))
        break;
        clientsocket.getOutputStream().write((request+"输出"+"\n").getBytes());
        }
     }catch(Exception e)
 {
      e.printStackTrace();
 }
}
}

显然,我们将线程池Executors.newFixedThreadPool(3);,所以可以同时有三个客户端同时连接交互
在这里插入图片描述
serversocket.accept()不停的接受客户端
String request=scanner.nextLine(); --》占用当前线程资源,不能完成其他操作
只有当所占用线程的客户端释放资源时,其他客户端才可以连入

为什么我们用threadpool呢?不用new Thread(clientSocket)呢?

那如果来100000个客户端,就要手动创建100000个线程
1、服务器会不会崩掉
2、方便管理,cpu会放在上下文的一个切换上,cpu不去处理逻辑代码,大量的时间去管理线程的切换了

多线程虽好,但仔细去想
小小的String request=scanner.nextLine()阻塞,占用线程资源合适吗?
你这个客户端数据一直不来,又不关掉
如果有数据来,我就处理你,没有数据来,我就不要这个线程
线程的数量就可以控制
就产生了这样的一个思路
每来一个客户端,我就用Map集合保存起来,并保存它的状态
Map<Socket,String> map=null;
先不创建线程
map.put(clientsocket,“Accepted”);
当客户端A想要传输数据
map.set(clientsocketA,“Readable”);
再创建线程
new Thread(…);
当客户端B想要传输数据
map.set(clientsocketB,“Readable”);
new Thread(…);
这样有效的控制,服务端的线程一定会减少,因为不是每一个客户端都要数据交互
map集合存储了所有的客户端,是不是还要用什么去监听客户端的状态呢?
在这里插入图片描述
这样一看,还是BIO呀
jdk1.4之后
java.nio xxxx类 Non-Blocking IO 同步非阻塞 IO不是阻塞的
既然IO不是阻塞的,那是不是就可以完成多个客户端数据读写的操作呢?
如何完成上面的新思路的呢?
仅仅是使用了一个类 Selector
作用就是
在这里插入图片描述
IO不是阻塞的,那就不用创建新线程了,上面的思路是不是可以改一改了
在这里插入图片描述
改成这样,也就是说IO要改成非阻塞的
其实,java应用程序一定和操作系统打交道,IO的阻塞与否决定于和操作系统打交道的一种方式:阻塞方式和非阻塞方式(废话)
阻塞方式在这里插入图片描述
主程序啥都不能做,只能等待数据传输过来
非阻塞:
不间断的给操作系统发信号,没有数据我就不要了,我去处理别的事了
在这里插入图片描述
数据阶段性的传输,就要引入一个buffer
在这里插入图片描述
数据交互的IO堵塞处理完了,那么还有个问题
客户端连接服务端的时候会不会阻塞呢?

serverSocket.accept();阻塞
我们换一种通讯方式,不用IO流通讯
用channel方式,面向通道传输–双向读写—异步读写
多路复用:
连接来了,需要数据交互,我再给你分配资源
在这里插入图片描述
看到这,没有实际操作,挺懵的,结合实际代码看就好多了

一、先看channel这个组件

在这里插入图片描述

看这一段注释
A channel represents an open connection to an entity such as a hardware
device, a file, a network socket, or a program component that is capable of
performing one or more distinct I/O operations, for example reading or
writing.
代表一种公开的连接,很多种介质都可以,硬件设备,文件,网络socket,程序组件呀,处理更多IO交互的操作
public interface Channel extends Closeable
这个接口有一些实现类,就有代表客户端和服务端的实现类ServerSocketChannel

第一条语句解读

//服务器端监听一个端口,等待多个客户端数据连接处理
ServerSocketChannel serverChannel=ServerSocketChannel.open();

看open()方法

//通过provider()获得一个服务端的channel
public static ServerSocketChannel open() throws IOException {
        return SelectorProvider.provider().openServerSocketChannel();
    }

我们再看provider()是什么

public static SelectorProvider provider() {
        synchronized (lock) {
            if (provider != null)
                return provider;
            return AccessController.doPrivileged(
                new PrivilegedAction<SelectorProvider>() {
                    public SelectorProvider run() {
                            if (loadProviderFromProperty())
                                return provider;
                            if (loadProviderAsService())
                                return provider;
                          provider=sun.nio.ch.DefaultSelectorProvider.create();
                            return provider;
                        }
                    });
        }
    }

provider=sun.nio.ch.DefaultSelectorProvider.create();
到底是怎么创建的呢?

public static Selector create()
{
return new WindowsSelectorProvider();//provider取决于操作系统
}

provider取决于操作系统,不同的操作系统进行不同获得,通过provider()打开channel

SelectorProvider.provider().openServerSocketChannel();

看下openServerSocketChannel()

 public abstract ServerSocketChannel openServerSocketChannel()

找到实现类

public  ServerSocketChannel openServerSocketChannel()throws IOException
{
return new ServerSocketChannelImpl(this); }

ServerSocketChannelImpl(this)

ServerSocketChannelImp(SelectorProvider var1)throws IOException
{
super(var1);
this.fd=Net.serverSocket(true);
this.fdval=IOUtil.fdval(fd);  //fd文件描述符,找到句柄,获得一个服务端的channel
this.state=0;
}

fd文件描述符,找到句柄,获得一个服务端的channel

//服务
serverChannel.configureBlocking(false);

第二条语句解读

//服务端channel设置成非阻塞模式
serverChannel.configureBlocking(false);

第三条语句解读

//服务器端的channel监听8888端口
serverChannel.bind(new InetSocketAddress(8888));

二、看Selector组件

第四条语句

Selector selector=Selector.open();
 public static Selector open() throws IOException {
        return SelectorProvider.provider().openSelector();//provider----selector
    }

看到这,有些熟悉,和Channel有点像

    public abstract AbstractSelector openSelector()
        throws IOException;

找实现类

public  AbstractSelector openSelector()throws IOExcetion
{
return new WindowsSelectorImpl(this);}

找到WindowsSelectorImpl(this)

WindowsSelectorImpl(SelectorProvider var1)throws IOException
{
super(var1);
this.wakeupSourceFd=((SelChImpl)this.wakeopPipe.source()).getFDVal();
SinkChannelImpl var2=(SinkChannelImpl)this.wakeopPipe.sink();
var2.sc.socket().setTcpNoDelay(true);
this.wakeupSinkFd=var2.getFDVal();
this.pollWrapper.addWakeupSocket(this.wakeupSinkFd,0);
}

重点看这句
this.pollWrapper.addWakeupSocket(this.wakeupSinkFd,0);
pollWrapper--------包装器 容器 池子 装什么?selector 装socket
点开pollWrapper,你会发现底层对pollWrapper实例化了

//8个大小的数组   容器
//和操作系统申请内存空间 ,8位大小装socket
private PollArraywrapper pollwrapper=new POllArraywrapper(8);

socket会有一个划分:fdval句柄 events
0-3位存句柄 4-7位 events
我们常常提到的epoll模型应用于此

第五条语句

//将socket注册到selector,默认状态
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
 /**
     * Registers this channel with the given selector, returning a selection
     * key.*/
   public final SelectionKey register(Selector sel, int ops)
        throws ClosedChannelException
    {
        return register(sel, ops, null);
    }
  public abstract SelectionKey register(Selector sel, int ops, Object att)
        throws ClosedChannelException;
 public final SelectionKey register(Selector sel, int ops,
                                       Object att)
        throws ClosedChannelException
    {
        synchronized (regLock) {
            if (!isOpen())
                throw new ClosedChannelException();
            if ((ops & ~validOps()) != 0)
                throw new IllegalArgumentException();
            if (blocking)
                throw new IllegalBlockingModeException();
            SelectionKey k = findKey(sel);
            if (k != null) {
                k.interestOps(ops);
                k.attach(att);
            }
            //没有注册,就要注册,下面的代码就是注册的关键
            if (k == null) { 
                // New registration
                synchronized (keyLock) {
                    if (!isOpen())
                        throw new ClosedChannelException();
                    k = ((AbstractSelector)sel).register(this, ops, att);
                    addKey(k);
                }
            }
            return k;
        }
    }
 protected abstract SelectionKey register(AbstractSelectableChannel ch,
                                             int ops, Object att);

找它实现类

 protected final SelectionKey register(AbstractSelectableChannel var1, int var2, Object var3);
if(!(var1 instanceof SelChImpl))
{
throw new IllegalSelectorException();
}else {
SeletionKeyImpl var4=new SeletionKeyImpl((SelChImpl)var1,this);
var4.attach(var3);
Set var5=this.publicKeys;
synchronized(this.publicKeys){
this.implRegister(var4);}    //-------这才是将channel注册到Register上面的代码
var4.interestOps(var2);
return var4;
}
}

再来看
this.implRegister(var4);

protected void implRegister(SelectionKeyImpl var1)
{
Object var2=this.closeLock;
synchronized(this.closeLock){
if(this.pollWrapper==null)
{
throw new CloseSelectorException();
}else{
this.growIfNeeded();
this.channelArray[this.totalChannels]=var1;
var1.setIndex(this.totalChannel);
this.fdMap.put(var1);
this.keys.add(var1);
this.pollWrapper.addEntity(this.totalChannels,var1);
++this.totalChannels;
}
}
}

this.pollWrapper.addEntity(this.totalChannels,var1);
selector 池子
在当前selector中添加socketChannel

void addEntry(int var1,SelectorKeyImpl var2){
this.putDescriptor(var1,var2.channel.getFDVal());
}
void putDescriptor(int var1,int var2)
{
this.pollArray.putInt(SIZE_POLLFD*var1+0,var2);
}
final void putInt(int var1,int var2)
{
unsafe.putInt((long)var1+this.address,var2);
}
public native void putInt(long var1,int var3); //操作系统的native方法,转化为操作系统socket的句柄和状态

第六条语句

   int select=selector.select();   //监听  

select()底层将返回
return this.lockAndDoSelect(var1==0L?-1L:var1);

private int lockAndDoSelect(long var1)throws IOExcetion
{
synchronized(this)
{
if(!this.isOpen())
{throw new CloseSelectorExcetion();}
}else'{
Set var4=this publickeys;
int var10000;
synchronized(this.publickeys){
var10000=this.doSelect(var1);  看到do...就知道真正在执行select的操作了
}
}
return var10000;
}
protected int doSelect(long var1)throws IOException
{
if(this.channelArray==null){
throw new CloseSelectorException();
}else
{
this.timeout=var1;
this.processDeregisterQueue();
if(this.interruptTriggered){
this.resetWakeupSocket();
return 0;
}else
{
this.adjustThreadsCount();
this.finishLock.reset();
this.startLock.startThreads();
try
{
this.begin();
try
{                
 this.subSelector.poll();//selector监听的精华,不断循环监听当前selector中的socket的变化, poll()调用poll0()方法 private native int poll0(...........)操作系统完成监听
}catch(IOExceton var7)
{this.finshLock.setExcetion(var7);}
if(this.threads.size()>0){
this.finishLock.waitForHelperThreads();
}
}finally{this.end();}
this,finishLock.checkForExcetion();
this.processDeregistQueue();
int var3=this.updateSelectedKeys();
this.resetWakeupSocket();
return var3;
}
}
}

每个socket通过什么样的内容表示的呢?
/**
* Registers this channel with the given selector, returning a selectionkey*/
* 有多少selectionkey就有多少socket
Set selectionKeys=selector.selectedKeys();
Iterator iterator=selectionKeys.iterator();`
SelectionKey key=iterator.next();
通过key可判断channel的状态

下面是数据交互

//channel想要进行数据的交互了
    if(key.isReadable())
    { 
     SocketChannel channel=(SocketChannel)key.channel();
     channel.read(buffer);  //需要经过buffer缓冲区读数据
     String request =new String(buffer.array()).trim();
     buffer.clear();
     channel.write(ByteBuffer.wrap(request.getBytes()));  //写数据也要经过buffer缓冲区

三、那么该如何设置缓冲区?

将buffer设计成一个数组
在这里插入图片描述

翻看buffer的一个实现类,以FileBuffer为例,最后会发现一个固定

public final Buffer flip()
{
limit=position;   //目前position所在的位置就是数据已经读好的长度,用limit限制
mark=-1;
position=0;
return this;
}

position 一直读数据,读完了,用limit等于position。position还要读取其他数据,又回到起点
最终实现代码

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.util.Iterator;
import java.util.Set;
public class BioServer {
 public static void main(String[] args)throws Exception {
  ServerSocketChannel serverChannel=ServerSocketChannel.open();
  serverChannel.configureBlocking(false);
  serverChannel.bind(new InetSocketAddress(8888));
  System.out.println("服务器启动"+serverChannel.getLocalAddress());
  Selector selector=Selector.open();
  serverChannel.register(selector, SelectionKey.OP_ACCEPT);
  ByteBuffer buffer=ByteBuffer.allocate(1024);
  while(true)
  {
   int select=selector.select();
   if(select==0)
   {
    continue;
   }
   Set<SelectionKey> selectionKeys=selector.selectedKeys();
   Iterator<SelectionKey> iterator=selectionKeys.iterator();
   while(iterator.hasNext())
   {
    SelectionKey key=iterator.next();
    if(key.isAcceptable())
    {
     ServerSocketChannel channel=(ServerSocketChannel)key.channel();
     SocketChannel clientChannel=channel.accept();
     System.out.println("来自连接"+clientChannel.getRemoteAddress());
     clientChannel.configureBlocking(false);
     //将当前channel的状态改变
     clientChannel.register(selector, SelectionKey.OP_READ); 
    }
    //channel想要进行数据的交互了
    if(key.isReadable())
    { 
     SocketChannel channel=(SocketChannel)key.channel();
     channel.read(buffer);  //需要经过buffer缓冲区读数据
     String request =new String(buffer.array()).trim();
     buffer.clear();
     channel.write(ByteBuffer.wrap(("输出"+request).getBytes()));  //写数据也要经过buffer缓冲区
    }
    iterator.remove();
   }
  }
 }
}

那问题就来了
tomcat是怎么实现nio内容的
netty是怎么封装和优化的
nio的应用场景(netty的应用场景)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值