NIO

NIO 是一种同步非阻塞的 IO 模型。同步是指线程不断轮询 IO 事件是否就绪,非阻塞是指线程在等待 IO 的时候,可以同时做其他任务。同步的核心就是 Selector,Selector 代替了线程本身轮询 IO 事件,避免了阻塞同时减少了不必要的线程消耗;非阻塞的核心就是通道和缓冲区,当 IO 事件就绪时,可以通过写道缓冲区,保证 IO 的成功,而无需线程阻塞式地等待。

BIO -blocking IO 同步式阻塞式IO->UDP/TCP
NIO -new IO 同步非阻塞式IO
AIO -AsynchronousIO 异步式非阻塞式IO -->jdk1.8

BIO缺点
1、会产生阻塞行为 receive/accept/connect/read/write
2、一对一的连接:每连接一个客户端,在服务器端就需要开启一个线程去处理请求,在客户端较多的情况下,服务器端就会产生大量的线程-耗费内存
3、连接建立后,如果不发生任何操作,那么会导致服务器中这个线程依然会被占用,耗费服务器资源
4、 无法实现定点操作

NIO优势
1、非阻塞,提高传输效率
2、一对多连接,可以用一个或者少量的服务器中的线程来处理大量的请求,从而节省服务器的内存资源
3、即使已经建立连接,只要没有对应的读写事件,那么依然不能够使用服务器来进行处理
4、利用通道来进行双向传输
5、因为利用缓冲区来存储数据,所以可以对缓冲区中的数据实现定点操作

NIO的三大组件:Buffer缓冲区,Channel通道,Selector多路复用选择器
1、Buffer - 缓冲区
ByteBuffer实现类
容器,存储数据,在底层以数组形式实现
在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,它也是写入到缓冲区中的;任何时候访问 NIO 中的数据,都是将它放到缓冲区中。而在面向流I/O系统中,所有数据都是直接写入或者直接将数据读取到Stream对象中。
capacity 容量位-指定缓冲区的容量,一旦指定,无法更改
limit 限制位-限制操作位所能达到的高度
position 操作位-指定要操作的位置
mark 标记位-标记位置,认为标记位置之前的数据是已经操作过的没有错误的数据
flip()反转缓冲区,先将限制位挪到操作位上,然后将操作位归零清空标记位
clear()清空缓冲区,将操作位归零,将limit挪到capacity,标记位清空
reset()重置缓冲区,将操作位挪到标记位
rewind()重绕缓冲区,将操作位归零,将标记位清空 用于缓冲区多次读取

Code

        // 创建缓冲区对象
        // ByteBuffer底层依靠字节数组来存储数据
        // ByteBuffer buffer = ByteBuffer.allocate(10);

        // 在创建缓冲区的时候传入字节数组,并且先定了字节的数组的大小
        // 虽然这种方式给定了数据,但是position依然从第0位开始计算
        ByteBuffer buffer = ByteBuffer.wrap("hello".getBytes());
        // buffer.put((byte)97);
        // 获取操作位
        // System.out.println(buffer.position());
        // System.out.println(buffer.capacity());
        // System.out.println(buffer.limit());
        // 添加数据
        // buffer.put("abc".getBytes());
        // buffer.put("def".getBytes());
        // System.out.println(buffer.position());

        // 反转缓冲区
        // buffer.limit(buffer.position());
        // 设置操作位
        // buffer.position(0);
        // buffer.flip();

        // 获取数据
        // byte b = buffer.get();
        // System.out.println(b);

        // 实际上判断操作位是否小于限制位
        // while (buffer.hasRemaining()) {
        // byte b = buffer.get();
        // System.out.println(b);
        // }

        // 将缓冲区转化为数组
        byte[] data = buffer.array();
        // buffer.flip();
        // System.out.println(new String(data,0,buffer.limit()));
        System.out.println(new String(data, 0, buffer.position()));

2、 Channel - 通道
传输数据,是面向缓冲区的,在java中Channel默认也是阻塞的,需要手动将其设置为非阻塞模式。
Channel是双向的,读写必须通过Buffer对象处理
BIO : File、UDP-DatagramSocket、TCP-Socket/ServerSocket
NIO : FileChannel、UDP-DatagramChannel、TCP-SocketChannel/ServerSocketChannel
FileChannel-操作文件。可以利用通道实现相同平台之间的零拷贝技术
FileChannel不能切换到非阻塞模式,因为它不是套接字通道,所以
FileChannel不能和Selector绑定事件

Code

package cn.tedu.nio.channel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class ClientDemo {

    public static void main(String[] args) throws IOException, InterruptedException {

        // 创建客户端的通道
        SocketChannel sc = SocketChannel.open();

        // NIO默认是阻塞的,手动设置为非阻塞
        sc.configureBlocking(false);

        // 发起连接 - 即使连接失败,也会继续向下执行
        sc.connect(new InetSocketAddress("localhost", 8090));

        // 如果单独使用channel,需要将它进行手动阻塞
        // 判断连接是否建立
        // 这个方法底层会判断连接是否建立,如果建立则继续往下执行
        // 如果这个连接没有建立,那么在底层会试图再次建立连接
        // 如果试图连接多次失败,那么会抛出异常
        while (!sc.finishConnect())
            ;

        // 写出数据
        sc.write(ByteBuffer.wrap("hello".getBytes()));
        
        Thread.sleep(10);

        // 读取数据
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        sc.read(buffer);
        buffer.flip();
        System.out.println(new String(buffer.array(), 0, buffer.limit()));

        // 关闭
        sc.close();
    }

}
##服务器端
package cn.tedu.nio.channel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class ServerDemo {

    public static void main(String[] args) throws IOException, InterruptedException {

        // 创建服务器端的通道
        ServerSocketChannel ssc = ServerSocketChannel.open();

        // 绑定要监听的端口
        ssc.bind(new InetSocketAddress(8090));

        // 设置为非阻塞
        ssc.configureBlocking(false);

        // 接受连接
        SocketChannel sc = ssc.accept();

        // 手动阻塞
        while (sc == null)
            sc = ssc.accept();

        // 准备一个缓冲区用于存储数据
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        // 读取数据
        sc.read(buffer);
        buffer.flip();
        System.out.println(new String(buffer.array(), 0, buffer.limit()));

        sc.write(ByteBuffer.wrap("接收成功~~~".getBytes()));
        Thread.sleep(10);
        
        ssc.close();
    }

}

3、Selector - 选择器

选择器是NIO的核心,它是channel的管理者

通过selector可以实现利用同一个服务器端来处理多个客户端的数据,实现了用少量的线程处理大量的请求,在底层处理的时候依然是同步的
在NIO中一共有四种事件:
1.SelectionKey.OP_CONNECT:连接事件
2.SelectionKey.OP_ACCEPT:接收事件
3.SelectionKey.OP_READ:读事件
4.SelectionKey.OP_WRITE:写事件

Code

客户端

package cn.tedu.nio.selector;

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.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class ClientDemo {

    public static void main(String[] args) throws IOException {

        // 创建客户端的通道
        SocketChannel sc = SocketChannel.open();

        // 获取选择器
        Selector selc = Selector.open();

        // 选择器管理的连接要求必须是非阻塞的
        sc.configureBlocking(false);

        // 将客户端注册到选择器身上,并且申请了一个可连接事件
        sc.register(selc, SelectionKey.OP_CONNECT);

        // 发起连接
        sc.connect(new InetSocketAddress("localhost", 8090));

        // 从这儿开始的代码针对多客户端来进行操作的
        while (true) {

            // 选择出注册过的通道
            selc.select();

            // 针对通道的不同事件类型进行处理
            Set<SelectionKey> keys = selc.selectedKeys();

            Iterator<SelectionKey> it = keys.iterator();
            while (it.hasNext()) {
                // 根据事件类型进行处理
                SelectionKey key = it.next();

                // 判断是否是可连接事件
                if (key.isConnectable()) {

                    // 从当前事件中获取到对应的通道
                    SocketChannel scx = (SocketChannel) key.channel();

                    // 如果是可连接事件,判断连接是否成功
                    while (!scx.finishConnect())
                        ;

                    // 如果连接成功了,可能会向服务器端发数据或者读数据
                    scx.register(selc, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
                }

                // 判断是否是可写事件
                if (key.isWritable()) {

                    // 从当前事件中获取到对应的通道
                    SocketChannel scx = (SocketChannel) key.channel();

                    // 写出数据
                    scx.write(ByteBuffer.wrap("hello~~~".getBytes()));

                    // 需要去掉可写事件
                    // 获取这个通道身上的所有的事件
                    scx.register(selc, key.interestOps() ^ SelectionKey.OP_WRITE);

                }

                // 判断是否是可读事件
                if (key.isReadable()) {

                    // 从当前事件中来获取到对应的通道
                    SocketChannel scx = (SocketChannel) key.channel();

                    // 读取数据
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    scx.read(buffer);
                    buffer.flip();
                    System.out.println(new String(buffer.array(), 0, buffer.limit()));

                    // 需要去掉这个可读事件
                    scx.register(selc, key.interestOps() ^ SelectionKey.OP_READ);
                }

                // 处理完这一大类事件之后
                it.remove();
            }

        }

    }

}
##服务器端
package cn.tedu.nio.selector;

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.util.Iterator;
import java.util.Set;

public class ServerDemo {
    
    public static void main(String[] args) throws IOException {
        
        // 创建服务器端的通道
        ServerSocketChannel ssc = ServerSocketChannel.open();
        
        // 绑定要监听的端口号
        ssc.bind(new InetSocketAddress(8090));
        
        // 开启选择器
        Selector selc = Selector.open();
        
        ssc.configureBlocking(false);
        
        // 将通道注册到选择器上,需要注册一个可接受事件
        ssc.register(selc, SelectionKey.OP_ACCEPT);
        
        while(true){
            
            // 选择出已经注册的连接
            selc.select();
            
            // 根据事件的不同进行分别的处理
            Set<SelectionKey> keys = selc.selectedKeys();
            
            Iterator<SelectionKey> it = keys.iterator();
            while (it.hasNext()) {
                // 将事件取出来分别进行处理
                SelectionKey key = it.next();
                
                // 判断可接受事件
                if(key.isAcceptable()){
                    
                    // 从事件中获取到对应的通道
                    ServerSocketChannel sscx = (ServerSocketChannel) key.channel();
                    
                    // 接受连接
                    SocketChannel sc = sscx.accept();
                    sc.configureBlocking(false);
                                        
                    // 注册读写事件
                    sc.register(selc, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                    
                }
                
                // 判断可读事件
                if(key.isReadable()){
                    
                    // 从事件中获取到对应的通道
                    SocketChannel sc = (SocketChannel) key.channel();
                    
                    // 读取数据
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    sc.read(buffer);
                    buffer.flip();
                    System.out.println(new String(buffer.array(),0, buffer.limit()));
                    
                    // 需要去掉可读事件
                    sc.register(selc, key.interestOps() ^ SelectionKey.OP_READ);
                    
                }
                
                // 判断可写事件
                if(key.isWritable()){
                    
                    // 从事件中获取到对应的通道
                    SocketChannel sc = (SocketChannel) key.channel();
                    
                    // 写出数据
                    sc.write(ByteBuffer.wrap("收到".getBytes()));
                    
                    // 需要去掉可写事件
                    sc.register(selc, key.interestOps() ^ SelectionKey.OP_WRITE);
                    
                }
                
                // 去掉这一大类的事件
                it.remove();
                
            }
            
        }
    }

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,784评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,745评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,702评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,229评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,245评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,376评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,798评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,471评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,655评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,485评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,535评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,235评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,793评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,863评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,096评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,654评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,233评论 2 341

推荐阅读更多精彩内容