深入理解NIO

一、什么是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;
        }
    }

}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值