NIO通信架构原理学习笔记

1. BIO

1.1 BIO概念

Blocking IO 同步阻塞IO
阻塞:在单线程的环境下,IO操作没有完成的话,当前线程不能进行其他操作(流的一个特性是阻塞的)

1.2 BIO原理

BIO模型中,accept得到一个Socket之后,需要创建线程对每一个Socket进行处理。

1.3 BIO问题

  1. 当客户端多了之后,服务端对应的处理线程也会增加,每一个线程都会占用栈空间和CPU资源
  2. 针对每一个Socket连接创建一个对应的线程,Socket是连接上了,但是它不一定进行IO操作。IO是发生在Socket连接之后,也就是说Socket连接并不一定会发生IO操作,这样会造成资源浪费。
    总结来说
    Socket开启了一个线程,服务端就会进行一个CPU资源的争抢,这种争抢是无意义的
    线程之间的切换运行,会频繁的切换上下文,性能效率会受到影响
    并发很多的时候,CPU的主要任务好像就成了处理这些无意义的Socket线程了

1.4 BIO优化

  1. 使用多线程池,实现BIO操作的伪非阻塞IO
    这样处理的好处有
  • 不需要频繁的开启销毁线程
  • 初始化一定数目的线程,放入线程池,多余的线程会进入到一个队列
  1. 针对线程多了,对服务器CPU、资源的争夺问题
    思路:多线程是必然,问题是线程创建的时机与数量,尽量让线程的数量少一点
    优化方案:Socket连接到来时,不创建线程;需要IO操作的时候,才会创建线程

2. NIO

2.1 NIO概念

Non-Blocking IO 同步非阻塞IO
非阻塞:在单线程的环境下,IO操作没有完成的话,当前线程能进行其他操作

2.2 NIO原理

不要针对每个Socket请求去创建一个线程,而是当进行IO操作的时候,再创建一个对应的线程
NIO通信架构图

与BIO相比,NIO多了以下三种优化处理

  1. Selector选择器
    每当有客户端的连接过来,就会先把它注册到Selector中
  2. Buffer缓冲区
    提高读写性能
  3. Channel多路复用
    预先创建好链路,客户端连接时直接使用Channel中的链路即可。传输过程中的所有数据都是从Channel中走的。

2.4 代码实现

Java代码实现NIO的server端如下:

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class NIOServer {
    private static int port = 8000;
    private static InetSocketAddress address = null;
    private static Selector selector;
    private static ByteBuffer buffer = ByteBuffer.allocate(1024);


    public static void main(String[] args) throws IOException {
        address = new InetSocketAddress(port);

        // Channel多路复用
        ServerSocketChannel server = ServerSocketChannel.open();
        //监听一个端口
        server.bind(address);
        // 设置为非阻塞
        server.configureBlocking(false);

        // 初始化Selector
        selector = Selector.open();

        // 把路和selector关联上
        server.register(selector, SelectionKey.OP_ACCEPT);// 表示客户端可以进行连接请求了

        // 使用selector进行循环判断,看哪一个key被选中,哪些key可以进行操作
        // 轮询操作
        while (true) {
            int count = selector.select();
            if (count == 0) {
                continue;
            }
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                //对key进行后续操作
                process(key);
                iterator.remove();
            }
        }
    }

    // 接下来根据key进行连接的创建,读写操作
    private static void process(SelectionKey key) throws IOException {
        // 判断这个key的状态,可接受、可读写?
        if (key.isAcceptable()) {
            // 创建连接对象,客户端对象  下面代表服务端的channel
            ServerSocketChannel server = (ServerSocketChannel) key.channel();
            SocketChannel client = server.accept();
            client.configureBlocking(false);
            // 将key注册为可读
            client.register(selector, SelectionKey.OP_READ);
        } else if (key.isReadable()) {
            // 创建连接对象,客户端对象  下面代表服务端的channel
            SocketChannel client = (SocketChannel) key.channel();
            // 根据客户端进行接下来的操作 将客户端传来的数据读到buffer中
            int len = client.read(buffer);
            if (len > 0) {
                buffer.flip();// 复位 对缓冲区对象中的数据进行锁定
                String text = new String(buffer.array(), 0, len);
                System.out.println(text);
            }
            // 读完之后,将key注册为可写
            client.register(selector, SelectionKey.OP_WRITE);
            buffer.clear();
        } else if (key.isWritable()) {
            SocketChannel client = (SocketChannel) key.channel();
            client.write(buffer.wrap("获取数据成功".getBytes()));
            client.close();
        }
    }
}

以上信息参考了咕泡学院的公开课相关资料并进行了一些修改。如有错误,欢迎指正。

发布了136 篇原创文章 · 获赞 94 · 访问量 22万+

猜你喜欢

转载自blog.csdn.net/somehow1002/article/details/83095258