NIO和IO多路复用

1.Buffer

常用ByteBuffer

  1. 其中的三个属性position,limit,capacity
  • capacity 指的是容量,如capacity为1024的IntBuffer可以存放1024个int类型的值
  • position默认在位置0,代表下一个 要读或写的位置,每次读写一个数据,后移一位
  • limit 写模式下,代表最大写入多少个数据,此时和capacity相同,读模式下,代表最多读多少个数据(数据个数)
  1. 一些常用方法
ByteBuffer byteBuf = ByteBuffer.allocate(1024);
public static ByteBuffer wrap(byte[] array) {
    ...
}
  1. 一些api
    写模式切换到读模式的flip
public final Buffer flip() {
    limit = position; // 将 limit 设置为实际写入的数据数量
    position = 0; // 重置 position 为 0
    mark = -1; // mark 之后再说
    return this;
}

读完之后可能重新用buffer来写,使用clear重新设置

public final Buffer clear() {
    position = 0;
    limit = capacity;
    mark = -1;
    return this;
}

当读到第5个数据时先mark一下,读到10时reset一下就可以实现重新从第5个数据读mark reset.就是将position的位置记录一下,等会再修改回来.

public final Buffer mark() {
    mark = position;
    return this;
}
public final Buffer reset() {
    int m = mark;
    if (m < 0)
        throw new InvalidMarkException();
    position = m;
    return this;
}

2.channel

  1. 和buffer的读写
    将数据从buffer写到channel里面channel.write(buffer)
  2. channel的实现类有FileChannel SocketChannel ServerSocketChannel
  3. 使用channel实现文件拷贝
static void channel() throws IOException {
        File file = new File("F:\\in.txt");
        File file1 = new File("F:\\out.txt");
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        FileInputStream fileInputStream = new FileInputStream(file);
        FileOutputStream outputStream = new FileOutputStream(file1);
        FileChannel inChannel = fileInputStream.getChannel();
        FileChannel outChannel = outputStream.getChannel();
        int len = -1;
        while ((len = inChannel.read(byteBuffer)) != -1) {
            byteBuffer.flip();
            outChannel.write(byteBuffer);
            byteBuffer.clear();

        }
        fileInputStream.close();
        outputStream.close();
    }

SocketChannel和ServerSocketChannel

// 打开一个通道
SocketChannel socketChannel = SocketChannel.open();
// 发起连接
socketChannel.connect(new InetSocketAddress("https://www.javadoop.com", 80));

// 实例化
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 监听 8080 端口
serverSocketChannel.socket().bind(new InetSocketAddress(8080));

while (true) {
    // 一旦有一个 TCP 连接进来,就对应创建一个 SocketChannel 进行处理
    SocketChannel socketChannel = serverSocketChannel.accept();
}

3.Selector

可以监听多个Channel
可以监听channel的读事件,写事件,建立连接的事件
selectionKey = channel.register(selector,read)将通道注册到selector上,并监听可读的请求.
之后可以通过Set<SelectionKey> selectedKeys = selector.selectedKeys();获取SelectionKey的集合,通过遍历处理事件(如果是连接请求,创建一个新的SocketChannel并注册到selector上,如果是可读的事件,调用channel.read(buffer)接收数据)
举例

public class SelectorServer {

    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();

        ServerSocketChannel server = ServerSocketChannel.open();
        server.socket().bind(new InetSocketAddress(8080));

        // 将其注册到 Selector 中,监听 OP_ACCEPT 事件
        server.configureBlocking(false);
        server.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            int readyChannels = selector.select();
            if (readyChannels == 0) {
                continue;
            }
            Set<SelectionKey> readyKeys = selector.selectedKeys();
            // 遍历
            Iterator<SelectionKey> iterator = readyKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();

                if (key.isAcceptable()) {
                    // 有已经接受的新的到服务端的连接
                    SocketChannel socketChannel = server.accept();

                    // 有新的连接并不代表这个通道就有数据,
                    // 这里将这个新的 SocketChannel 注册到 Selector,监听 OP_READ 事件,等待数据
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    // 有数据可读
                    // 上面一个 if 分支中注册了监听 OP_READ 事件的 SocketChannel
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                    int num = socketChannel.read(readBuffer);
                    if (num > 0) {
                        // 处理进来的数据...
                        System.out.println("收到数据:" + new String(readBuffer.array()).trim());
                        ByteBuffer buffer = ByteBuffer.wrap("返回给客户端的数据...".getBytes());
                        socketChannel.write(buffer);
                    } else if (num == -1) {
                        // -1 代表连接已经关闭
                        socketChannel.close();
                    }
                }
            }
        }
    }
}

4.NIO的Selector和linux的select poll epoll

Selector封装了后面三种方式,对于linux来讲,可能用的其中一个

5.select

一个进程管理多个socket的连接

  • 进程A调用select进入阻塞,即进入每个socket的等待队列.由于CPU并行执行其他进程,因此进入阻塞的进程不会占用CPU
  • select调用时传入需要管理的socket集合,将对应的文件描述符由用户进程传入内核,并进行检查有没有对应的事件到达,如果有则立即返回.
  • 当网络数据到达时,引发中断,cpu调用中断处理程序,将数据通过DMA 硬件等方式由网卡传递到socket的读缓冲区
  • 进程A由于等待的事件到达被唤醒,从socket的等待队列移动到进程运行的队列
  • 即select函数返回值大于0,表示有多少个等待的事件到达,这时需要遍历所有socket,看看是哪个socket的事件发生了
  • 处理事件
    epoll
    epoll_create创建对象
    epoll_ctl将管理的socket加入到epoll对象当中
    epoll_wait等待数据到达
  • 首先调用epoll_create创建eventpoll对象
  • 调用epoll_ctl会将eventpoll对象加入到需要管理的socket的等待队列当中
  • 当数据到达网卡,触发中断信号,中断程序将数据由网卡通过DMA 硬件等方式拷贝到内存,还会操作socket的等待队列中的eventpoll对象,其维护了一个就绪列表,存放有事件发送的socket
  • 调用epol_wait时,如果eventpoll对象里面的就绪列表中已经有引用的socket,就会返回,如果没有会阻塞进程.
  • eventpoll对象维护监视的socket集合是通过红黑树来维护,而就绪列表是通过双向链表来维护.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • java NIO 开始于JDK1.4,其核心元件有:Channel、Buffer、Selector。 Channe...
    join_a922阅读 632评论 0 0
  • # Java NIO # Java NIO属于非阻塞IO,这是与传统IO最本质的区别。传统IO包括socket和文...
    Teddy_b阅读 581评论 0 0
  • 前言 现在使用NIO的场景越来越多,很多网上的技术框架或多或少的使用NIO技术,譬如Tomcat,Jetty。学习...
    路远处幽阅读 664评论 1 0
  • ------NIO简介(1)-------- NIO组件 channel,buffer,selector,pip,...
    任嘉平生愿阅读 532评论 1 0
  • Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java I...
    zhisheng_blog阅读 1,111评论 0 7