Redis网络通信模型

1.1. Java IO读写原理

不管是Socket的读写仍是文件的读写,在Java层面的应用开发或者是linux系统底层开发,都属于输入input和输出output的处理,简称为IO读写。在原理上和处理流程上,都是一致的。区别在于参数的不一样。用户程序进行IO的读写,基本上会用到read&write两大系统调用。可能不一样操做系统,名称不彻底同样,可是功能是同样的。
先强调一个基础知识:read系统调用,并非把数据直接从物理设备,读数据到内存。write系统调用,也不是直接把数据,写入到物理设备。read系统调用,是把数据从内核缓冲区复制到进程缓冲区;而write系统调用,是把数据从进程缓冲区复制到内核缓冲区。这个两个系统调用,都不负责数据在内核缓冲区和磁盘之间的交换。底层的读写交换,是由操做系统kernel内核完成的。

1.1.1. 内核缓冲与进程缓冲区

缓冲区的目的,是为了减小频繁的系统IO调用。你们都知道,系统调用须要保存以前的进程数据和状态等信息,而结束调用以后回来还须要恢复以前的信息,为了减小这种损耗时间、也损耗性能的系统调用,因而出现了缓冲区。
有了缓冲区,操做系统使用read函数把数据从内核缓冲区复制到进程缓冲区,write把数据从进程缓冲区复制到内核缓冲区中。等待缓冲区达到必定数量的时候,再进行IO的调用,提高性能。至于何时读取和存储则由内核来决定,用户程序不须要关心。
在linux系统中,系统内核也有个缓冲区叫作内核缓冲区。每一个进程有本身独立的缓冲区,叫作进程缓冲区。
因此,用户程序的IO读写程序,大多数状况下,并无进行实际的IO操做,而是在读写本身的进程缓冲区。

1.1.2. java IO读写的底层流程

用户程序进行IO的读写,基本上会用到系统调用read&write,read把数据从内核缓冲区复制到进程缓冲区,write把数据从进程缓冲区复制到内核缓冲区,它们不等价于数据在内核缓冲区和磁盘之间的交换。
image.png
首先看看一个典型Java 服务端处理网络请求的典型过程:
(1)客户端请求
Linux经过网卡,读取客户断的请求数据,将数据读取到内核缓冲区。
(2)获取请求数据
服务器从内核缓冲区读取数据到Java进程缓冲区。
(1)服务器端业务处理
Java服务端在本身的用户空间中,处理客户端的请求。
(2)服务器端返回数据
Java服务端已构建好的响应,从用户缓冲区写入系统缓冲区。
(3)发送给客户端
Linux内核经过网络 I/O ,将内核缓冲区中的数据,写入网卡,网卡经过底层的通信协议,会将数据发送给目标客户端。

1.2. 四种主要的IO模型

服务器端编程常常须要构造高性能的IO模型,常见的IO模型有四种:
(1)同步阻塞IO(Blocking IO)
首先,解释一下这里的阻塞与非阻塞:
阻塞IO,指的是须要内核IO操做完全完成后,才返回到用户空间,执行用户的操做。阻塞指的是用户空间程序的执行状态,用户空间程序需等到IO操做完全完成。传统的IO模型都是同步阻塞IO。在java中,默认建立的socket都是阻塞的。
其次,解释一下同步与异步:
同步IO,是一种用户空间与内核空间的调用发起方式。同步IO是指用户空间线程是主动发起IO请求的一方,内核空间是被动接受方。异步IO则反过来,是指内核kernel是主动发起IO请求的一方,用户线程是被动接受方。
(2)同步非阻塞IO(Non-blocking IO)
非阻塞IO,指的是用户程序不须要等待内核IO操做完成后,内核当即返回给用户一个状态值,用户空间无需等到内核的IO操做完全完成,能够当即返回用户空间,执行用户的操做,处于非阻塞的状态。
简单的说:阻塞是指用户空间(调用线程)一直在等待,并且别的事情什么都不作;非阻塞是指用户空间(调用线程)拿到状态就返回,IO操做能够干就干,不能够干,就去干的事情。
非阻塞IO要求socket被设置为NONBLOCK。
强调一下,这里所说的NIO(同步非阻塞IO)模型,并不是Java的NIO(New IO)库。
(3)IO多路复用(IO Multiplexing)
即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。
(4)异步IO(Asynchronous IO)
异步IO,指的是用户空间与内核空间的调用方式反过来。用户空间线程是变成被动接受的,内核空间是主动调用者。
这一点,有点相似于Java中比较典型的模式是回调模式,用户空间线程向内核空间注册各类IO事件的回调函数,由内核去主动调用。

1.3. 同步阻塞IO(Blocking IO)

在linux中的Java进程中,默认状况下全部的socket都是blocking IO。在阻塞式 I/O 模型中,应用程序在从IO系统调用开始,一直到到系统调用返回,这段时间是阻塞的。返回成功后,应用进程开始处理用户空间的缓存数据。
image.png
举个栗子,发起一个blocking socket的read读操做系统调用,流程大概是这样:
(1)当用户线程调用了read系统调用,内核(kernel)就开始了IO的第一个阶段:准备数据。不少时候,数据在一开始尚未到达(好比,尚未收到一个完整的Socket数据包),这个时候kernel就要等待足够的数据到来。
(2)当kernel一直等到数据准备好了,它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存),而后kernel返回结果。
(3)从开始IO读的read系统调用开始,用户线程就进入阻塞状态。一直到kernel返回结果后,用户线程才解除block的状态,从新运行起来。
因此,blocking IO的特色就是在内核进行IO执行的两个阶段,用户线程都被block了。
BIO的优势:
程序简单,在阻塞等待数据期间,用户线程挂起。用户线程基本不会占用 CPU 资源。
BIO的缺点:
通常状况下,会为每一个链接配套一条独立的线程,或者说一条线程维护一个链接成功的IO流的读写。在并发量小的状况下,这个没有什么问题。可是,当在高并发的场景下,须要大量的线程来维护大量的网络链接,内存、线程切换开销会很是巨大。所以,基本上,BIO模型在高并发场景下是不可用的。

public class BIOServerSocket {

    public static void main(String[] args) {
        ServerSocket serverSocket=null;

        try {
            serverSocket=new ServerSocket(8080);
            System.out.println("启动服务:监听端口:8080");
            //表示阻塞等待监听一个客户端连接,返回的socket表示连接的客户端信息
            while(true) {
                Socket socket = serverSocket.accept(); //连接阻塞
                System.out.println("客户端:" + socket.getPort());
                //inputstream是阻塞的(***)
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); //表示获取客户端的请求报文
                String clientStr = bufferedReader.readLine();
                System.out.println("收到客户端发送的消息:" + clientStr);
                BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                bufferedWriter.write("receive a message:" + clientStr + "\n");
                bufferedWriter.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(serverSocket!=null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

线程BIO

public class BIOServerSocketWithThread {

    static ExecutorService executorService= Executors.newFixedThreadPool(10);
    public static void main(String[] args) {
        ServerSocket serverSocket=null;
        try {
            serverSocket=new ServerSocket(8080);
            System.out.println("启动服务:监听端口:8080");
            //表示阻塞等待监听一个客户端连接,返回的socket表示连接的客户端信息
            while(true) {
                Socket socket = serverSocket.accept(); //连接阻塞
                System.out.println("客户端:" + socket.getPort());
                //IO变成了异步执行
                executorService.submit(new SocketThread(socket));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(serverSocket!=null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
public class SocketThread implements Runnable{

    private Socket socket;

    public SocketThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //inputstream是阻塞的(***)
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); //表示获取客户端的请求报文
            String clientStr = bufferedReader.readLine();
            System.out.println("收到客户端发送的消息:" + clientStr);
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            bufferedWriter.write("receive a message:" + clientStr + "\n");
            bufferedWriter.flush();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //TODO 关闭IO流
        }
    }
}

1.4. 同步非阻塞NIO(None Blocking IO)

在linux系统下,能够经过设置socket使其变为non-blocking。NIO 模型中应用程序在一旦开始IO系统调用,会出现如下两种状况:
(1)在内核缓冲区没有数据的状况下,系统调用会当即返回,返回一个调用失败的信息。
(2)在内核缓冲区有数据的状况下,是阻塞的,直到数据从内核缓冲复制到用户进程缓冲。复制完成后,系统调用返回成功,应用进程开始处理用户空间的缓存数据。image.png
举个栗子。发起一个non-blocking socket的read读操做系统调用,流程是这个样子:
(1)在内核数据没有准备好的阶段,用户线程发起IO请求时,当即返回。用户线程须要不断地发起IO系统调用。
(2)内核数据到达后,用户线程发起系统调用,用户线程阻塞。内核开始复制数据。它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存),而后kernel返回结果。
(3)用户线程才解除block的状态,从新运行起来。通过屡次的尝试,用户线程终于真正读取到数据,继续执行。
NIO的特色:
应用程序的线程须要不断的进行 I/O 系统调用,轮询数据是否已经准备好,若是没有准备好,继续轮询,直到完成系统调用为止。
NIO的优势:每次发起的 IO 系统调用,在内核的等待数据过程当中能够当即返回。用户线程不会阻塞,实时性较好。
NIO的缺点:须要不断的重复发起IO系统调用,这种不断的轮询,将会不断地询问内核,这将占用大量的 CPU 时间,系统资源利用率较低。
总之,NIO模型在高并发场景下,也是不可用的。通常 Web 服务器不使用这种 IO 模型。通常不多直接使用这种模型,而是在其余IO模型中使用非阻塞IO这一特性。java的实际开发中,也不会涉及这种IO模型。
再次说明,Java NIO(New IO) 不是IO模型中的NIO模型,而是另外的一种模型,叫作IO多路复用模型( IO multiplexing )。

NIO

public class NIOServerSocket {
    //NIO中的核心
    //channel
    //buffer
    //selector

    public static void main(String[] args) {
        try {
            ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false); //设置连接非阻塞
            serverSocketChannel.socket().bind(new InetSocketAddress(8080));
            while(true){
                //是非阻塞的
                SocketChannel socketChannel=serverSocketChannel.accept(); //获得一个客户端连接
                //                socketChannel.configureBlocking(false);//IO非阻塞
                if(socketChannel!=null){
                    ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
                    int i=socketChannel.read(byteBuffer);
                    Thread.sleep(10000);
                    byteBuffer.flip(); //反转
                    socketChannel.write(byteBuffer);
                }else{
                    Thread.sleep(1000);
                    System.out.println("连接位就绪");
                }
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }

    }
}

1.5. IO多路复用模型(I/O multiplexing)

如何避免同步非阻塞NIO模型中轮询等待的问题呢?这就是IO多路复用模型。(select/poll/epoll)
IO多路复用模型,就是经过一种新的系统调用,一个进程能够监视多个文件描述符,一旦某个描述符就绪(通常是内核缓冲区可读/可写),内核kernel可以通知程序进行相应的IO系统调用。
目前支持IO多路复用的系统调用,有 select,epoll等等。select系统调用,是目前几乎在全部的操做系统上都有支持,具备良好跨平台特性。epoll是在linux 2.6内核中提出的,是select系统调用的linux加强版本。
IO多路复用模型的基本原理就是select/epoll系统调用,单个线程不断的轮询select/epoll系统调用所负责的成百上千的socket链接,当某个或者某些socket网络链接有数据到达了,就返回这些能够读写的链接。所以,好处也就显而易见了——经过一次select/epoll系统调用,就查询到到能够读写的一个甚至是成百上千的网络链接。
在这种模式中,首先不是进行read系统调动,而是进行select/epoll系统调用。固然,这里有一个前提,须要将目标网络链接,提早注册到select/epoll的可查询socket列表中。而后,才能够开启整个的IO多路复用模型的读流程。
(1)进行select/epoll系统调用,查询能够读的链接。kernel会查询全部select的可查询socket列表,当任何一个socket中的数据准备好了,select就会返回。
当用户进程调用了select,那么整个线程会被block(阻塞掉)。
(2)用户线程得到了目标链接后,发起read系统调用,用户线程阻塞。内核开始复制数据。它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存),而后kernel返回结果。
(3)用户线程才解除block的状态,用户线程终于真正读取到数据,继续执行。
多路复用IO的特色:
IO多路复用模型,创建在操做系统kernel内核可以提供的多路分离系统调用select/epoll基础之上的。多路复用IO须要用到两个系统调用(system call), 一个select/epoll查询调用,一个是IO的读取调用。
和NIO模型类似,多路复用IO须要轮询。负责select/epoll查询调用的线程,须要不断的进行select/epoll轮询,查找出能够进行IO操做的链接。
另外,多路复用IO模型与前面的NIO模型,是有关系的。对于每个能够查询的socket,通常都设置成为non-blocking模型。只是这一点,对于用户程序是透明的(不感知)。
多路复用IO的优势:
用select/epoll的优点在于,它能够同时处理成千上万个链接(connection)。与一条线程维护一个链接相比,I/O多路复用技术的最大优点是:系统没必要建立线程,也没必要维护这些线程,从而大大减少了系统的开销。
Java的NIO(new IO)技术,使用的就是IO多路复用模型。在linux系统上,使用的是epoll系统调用。
多路复用IO的缺点:
本质上,select/epoll系统调用,属于同步IO,也是阻塞IO。都须要在读写事件就绪后,本身负责进行读写,也就是说这个读写过程是阻塞的。
如何充分的解除线程的阻塞呢?那就是异步IO模型。
多路复用模型

NIO之Selector

public class NIOSelectorServerSocket implements Runnable{

    Selector selector;
    ServerSocketChannel serverSocketChannel;

    public NIOSelectorServerSocket(int port) throws IOException {
        selector=Selector.open();
        serverSocketChannel=ServerSocketChannel.open();
        //如果采用selector模型,必须要设置非阻塞
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    @Override
    public void run() {
        while(!Thread.interrupted()){
            try {
                selector.select(); //阻塞等待事件就绪
                Set selected=selector.selectedKeys(); //事件列表
                Iterator it=selected.iterator();
                while(it.hasNext()){
                    //说明有连接进来
                    dispatch((SelectionKey) it.next());
                    it.remove();//移除当前就绪的事件
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    private void dispatch(SelectionKey key) throws IOException {
        if(key.isAcceptable()){ //是连接事件?
            register(key);
        }else if(key.isReadable()){ //读事件
            read(key);
        }else if(key.isWritable()){ //写事件
            //TODO
        }
    }
    private void register(SelectionKey key) throws IOException {
        ServerSocketChannel channel= (ServerSocketChannel) key.channel(); //客户端连接
        SocketChannel socketChannel=channel.accept(); //获得客户端连接
        socketChannel.configureBlocking(false);
        socketChannel.register(selector,SelectionKey.OP_READ);
    }
    private void read(SelectionKey key) throws IOException {
        //得到的是socketChannel
        SocketChannel channel= (SocketChannel) key.channel();
        ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
        channel.read(byteBuffer);
        System.out.println("Server Receive Msg:"+new String(byteBuffer.array()));
    }

    public static void main(String[] args) throws IOException {
        NIOSelectorServerSocket selectorServerSocket=new NIOSelectorServerSocket(8080);
        new Thread(selectorServerSocket).start();
    }
}

单线程的Reactor模型(Redis6.0前)

Reactor

public class Reactor implements Runnable{

    private final Selector selector;
    private final ServerSocketChannel serverSocketChannel;

    public Reactor(int port) throws IOException {
        selector=Selector.open();
        serverSocketChannel= ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT,new Acceptor(selector,serverSocketChannel));
    }

    @Override
    public void run() {
        while(!Thread.interrupted()){
            try {
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while(iterator.hasNext()){
                    dispatch(iterator.next());
                    iterator.remove();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    private void dispatch(SelectionKey key){
        //可能拿到的对象有两个
        // Acceptor
        // Handler
        Runnable runnable=(Runnable)key.attachment();
        if(runnable!=null){
            runnable.run(); //
        }
    }
}

Acceptor

public class Acceptor implements Runnable{

    private final Selector selector;
    private final ServerSocketChannel serverSocketChannel;

    public Acceptor(Selector selector, ServerSocketChannel serverSocketChannel) {
        this.selector = selector;
        this.serverSocketChannel = serverSocketChannel;
    }

    @Override
    public void run() {
        SocketChannel channel;

        try {
            channel=serverSocketChannel.accept();//得到一个客户端连接
            System.out.println(channel.getRemoteAddress()+":收到一个客户端连接");
            channel.configureBlocking(false);
            channel.register(selector, SelectionKey.OP_READ,new Handler(channel));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

Handler

public class Handler implements Runnable{
    SocketChannel channe;

    public Handler(SocketChannel channe) {
        this.channe = channe;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"------");
        ByteBuffer buffer=ByteBuffer.allocate(1024);
        /*try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
        int len=0,total=0;
        String msg="";
        try {
            do {
                len = channe.read(buffer);
                if(len>0){
                    total+=len;
                    msg+=new String(buffer.array());
                }
            } while (len > buffer.capacity());
            System.out.println("total:"+total);

            //msg=表示通信传输报文
            //耗时2s
            //登录: username:password
            //ServetRequets: 请求信息
            //数据库的判断
            //返回数据,通过channel写回到客户端

            System.out.println(channe.getRemoteAddress()+": Server receive Msg:"+msg);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(channe!=null){
                try {
                    channe.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

MAIN

public class ReactorMain {
    public static void main(String[] args) throws IOException {
        new Thread(new Reactor(8080),"Main-Thread").start();
    }
}

多线程Reactor模型(6.0+)(线程处理采用隔离机制)

MultiReactor

public class MultiReactor implements Runnable{

    private final Selector selector;
    private final ServerSocketChannel serverSocketChannel;

    public MultiReactor(int port) throws IOException {
        selector=Selector.open();
        serverSocketChannel= ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT,new MultiAcceptor(selector,serverSocketChannel));
    }

    @Override
    public void run() {
        while(!Thread.interrupted()){
            try {
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while(iterator.hasNext()){
                    dispatch(iterator.next());
                    iterator.remove();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    private void dispatch(SelectionKey key){
        //可能拿到的对象有两个
        // Acceptor
        // Handler
        Runnable runnable=(Runnable)key.attachment();
        if(runnable!=null){
            runnable.run(); //
        }
    }
}

MultiAcceptor

public class MultiAcceptor implements Runnable{

    private final Selector selector;
    private final ServerSocketChannel serverSocketChannel;

    public MultiAcceptor(Selector selector, ServerSocketChannel serverSocketChannel) {
        this.selector = selector;
        this.serverSocketChannel = serverSocketChannel;
    }

    @Override
    public void run() {
        SocketChannel channel;

        try {
            channel=serverSocketChannel.accept();//得到一个客户端连接
            System.out.println(channel.getRemoteAddress()+":收到一个客户端连接");
            channel.configureBlocking(false);
            channel.register(selector, SelectionKey.OP_READ,new MutilDispatchHandler(channel));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

MutilDispatchHandler

public class MutilDispatchHandler implements Runnable{

    SocketChannel channel;

    private Executor executor= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public MutilDispatchHandler(SocketChannel channel) {
        this.channel = channel;
    }

    @Override
    public void run() {
        processor();
    }
    private void processor(){
        executor.execute(new ReaderHandler(channel));
    }
    static class ReaderHandler implements Runnable{
        private SocketChannel channel;

        public ReaderHandler(SocketChannel channel) {
            this.channel = channel;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+":-----");
            ByteBuffer buffer=ByteBuffer.allocate(1024);
            try {
                Thread.sleep(100000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int len=0,total=0;
            String msg="";
            try {
                do {
                    len = channel.read(buffer);
                    if(len>0){
                        total+=len;
                        msg+=new String(buffer.array());
                    }
                } while (len > buffer.capacity());
                System.out.println("total:"+total);

                //msg=表示通信传输报文
                //耗时2s
                //登录: username:password
                //ServetRequets: 请求信息
                //数据库的判断
                //返回数据,通过channel写回到客户端
                System.out.println(channel.getRemoteAddress()+": Server receive Msg:"+msg);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                if(channel!=null){
                    try {
                        channel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

MultiReactorMain

public class MultiReactorMain {
    public static void main(String[] args) throws IOException {
        new Thread(new MultiReactor(8080),"Main-Thread").start();
    }
}

1.6. 异步IO模型(asynchronous IO)

如何进一步提高效率,解除最后一点阻塞呢?这就是异步IO模型,全称asynchronous I/O,简称为AIO。
AIO的基本流程是:用户线程经过系统调用,告知kernel内核启动某个IO操做,用户线程返回。kernel内核在整个IO操做(包括数据准备、数据复制)完成后,通知用户程序,用户执行后续的业务操做。
kernel的数据准备是将数据从网络物理设备(网卡)读取到内核缓冲区;kernel的数据复制是将数据从内核缓冲区拷贝到用户程序空间的缓冲区。
image.png
(1)当用户线程调用了read系统调用,马上就能够开始去作其它的事,用户线程不阻塞。
(2)内核(kernel)就开始了IO的第一个阶段:准备数据。当kernel一直等到数据准备好了,它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存)。
(3)kernel会给用户线程发送一个信号(signal),或者回调用户线程注册的回调接口,告诉用户线程read操做完成了。
(4)用户线程读取用户缓冲区的数据,完成后续的业务操做。
异步IO模型的特色:
在内核kernel的等待数据和复制数据的两个阶段,用户线程都不是block(阻塞)的。用户线程须要接受kernel的IO操做完成的事件,或者说注册IO操做完成的回调函数,到操做系统的内核。因此说,异步IO有的时候,也叫作信号驱动 IO 。
异步IO模型缺点:
须要完成事件的注册与传递,这里边须要底层操做系统提供大量的支持,去作大量的工做。
目前来讲, Windows 系统下经过 IOCP 实现了真正的异步 I/O。可是,就目前的业界形式来讲,Windows 系统,不多做为百万级以上或者说高并发应用的服务器操做系统来使用。
而在 Linux 系统下,异步IO模型在2.6版本才引入,目前并不完善。因此,这也是在 Linux 下,实现高并发网络编程时都是以 IO 复用模型模式为主。

小结一下:

四种IO模型,理论上越日后,阻塞越少,效率也是最优。在这四种 I/O 模型中,前三种属于同步 I/O,由于其中真正的 I/O 操做将阻塞线程。只有最后一种,才是真正的异步 I/O 模型。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DougLiang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值