网络编程 - 2(BIO、NIO、AIO)

180 阅读15分钟

image.png

1.1 BIO

BIO有的称之为basic(基础)IO, 有的称之为block(阻塞)IO,主要应用于文件IO和网络IO。处理单位是字节。

在JDK1.4之前,我们建立网络连接只能使用BIO,需要在服务端先启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一个线程等待请求,而客户端发送请求后,先咨询服务端是否有线程响应,如果没有则会一直等待或者遭到拒绝,如果有的话,客户端线程会等待请求结束之后才继续执行,这就是阻塞式IO。

image.png

BIO的基础用法(基于TCP)

服务端代码

package com.example.netty1.tcp;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {

    public static void main(String[] args) throws IOException {
        // 1. 创建ServerSocket对象
        System.out.println("服务端 启动~~~");
        System.out.println("初始化端口 9999~~~");
        ServerSocket ss = new ServerSocket(9999);

        while (true) {
            // 2. 监听客户端
            Socket s = ss.accept();// 阻塞
            // 3. 从连接中取输入流来接收消息
            InputStream is = s.getInputStream();
            byte[] b = new byte[100];
            is.read(b);
            String hostAddress = s.getInetAddress().getHostAddress();
            System.out.println(hostAddress + "说" + new String(b).trim());
            // 4. 从连接中取出输出流并回话
            OutputStream os = s.getOutputStream();
            os.write("没钱".getBytes());
            // 5. 关闭
            s.close();
        }
    }
}

客户端代码

package com.example.netty1.tcp;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class TCPClient {

    public static void main(String[] args) throws Exception {
        while (true) {
            //1. 创建Socket对象
            Socket socket = new Socket("127.0.0.1", 9999);
            //2. 从连接中取出输出流并发送信息
            OutputStream os = socket.getOutputStream();
            System.out.println("请输入:");
            Scanner sc = new Scanner(System.in);
            String msg = sc.nextLine();
            os.write(msg.getBytes());
            //3. 从连接中取出输入流并接收回话
            InputStream is = socket.getInputStream();
            byte[] bytes = new byte[100];
            is.read(bytes);
            System.out.println("老板说:" + new String(bytes).trim());
            // 5.关闭
            socket.close();
        }
    }
}

编写了一个客户端程序,通过9999端口连接服务器端,getInputStream方法用来等待服务端返回数据,如果一直没有返回,就一直等待,程序就会阻塞到这里。

1.2 NIO

Java.nio 全程 Java Non-Blocking IO,是指JDK提供的新API。 从JDK1.4开始,Java提供了一系列改进的输入/输出的新特性,被统称为NIO。新增了许多用于处理输入输出的类,这些类被放在Java.nio的包及子包下,并对原Java.io包中的许多类进行了改写,新增满足NIO的功能。

NIO和BIO有着相同的目的和作用,但是他们的视线方式完全不同。

  • BIO是以流的方式处理数据,而NIO是以块的方式处理数据,块IO的效率比流IO高很多。
  • NIO是非阻塞式的,BIO是阻塞式的,使用NIO可以提供非阻塞式的高延伸网络。
  • BIO是单向的,NIO是双向的

NIO三大核心部分:

  • Channel通道
  • Buffer缓存区
  • Selector选择器

image.png 传统的BIO基于字节流和字符流进行操作,而NIO基于Channel和Buffer进行操作,数据总是从通道读到缓存区中,或者从缓存区写入到通道里。Selector用于监听多个通道的是事件(比如:连接请求,数据到达等),因此,使用单线程就可以监听多个客户端请求(Channel)。

1.3 NIO-文件IO

image.png

Buffer(缓冲区):是一个缓冲容器(底层是数组),内置了一些机制能够跟踪和记录缓冲区的状态变化。

Channel(通道):提供从文件和网络读取数据的通道,读取和写入数据都需要经Buffer。

1.3.1 NIO-文件IO-Buffer

image.png

常用方法:

ByteBuffer put(byte[]); 存储字节数据到Buffer
byte[] get(); 从Buffer中获取数据
byte[] array(); 把Buffer数据转为字节数组
ByteBuffer allocate(int capacity); 设置缓冲区的初始容量
ByteBuffer wrap(byte[] array); 将数据放到缓冲区
Buffer flip(); 翻转缓冲区,重置位置到初始位置
1.3.2 NIO-文件IO-Channel

image.png

在NIO中,Channel是一个接口,标识通道,通道是双向的,可以用来读,也可以用来写数据。

常用的Channel实现类:FileChannel、DatagramChannel、ServerSocketChannel、SocketChannel

FileChannel用于文件数据的读写;

DatagramChannel用于UDP数据的读写;

ServerSocketChannel和SocketChannel用于TCP数据的读写;

FileChannel类常用的方法:

int read(ByteBuffer dst); 从Channel中读取数据并放到Buffer中
int write(ByteBuffer dst); 把Buffer的数据写到Channel中
long transferFrom(ReadableByteChannel src, long position, long count); 从目标channel复制数据到当前channel
long transferTo(long position, long count, WriteByteChannel target); 把数据从当前channel复制到目标channel

NIO写数据

package com.example.netty1.tcp;

import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * 通过NIO实现文件IO
 */
public class NIOTest {
    public static void main(String[] args) throws Exception {
        //往本地文件写数据
        // 1. 创建流
        FileOutputStream fos = new FileOutputStream("basic.txt");
        // 2. 往流中获取一个通道
        FileChannel channel = fos.getChannel();
        // 3. 提供一个缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        // 4. 往缓冲区写数据
        buffer.put("HelloWorld".getBytes());
        // 5. 翻转缓冲区
        buffer.flip();
        // 6. 把缓冲区写到通道中
        channel.write(buffer);
        // 7. 关闭
        fos.close();
    }
}

NIO中的通道,是从输出流对象里通过getChannel()获取的,这个通道是双向的,既可以读,也可以写。在往通道写入数据之前,必须通过put()方法将数据放到Buffer中,然后通过channel的write()方法写入数据。在write之前,需要调用flip()方法翻转缓冲区,把内部重置到初始位置,这样才能把所有数据写到通道里。

image.png flip()方法的作用:翻转缓冲区,在缓冲区有有一个指针从头(pos)写到尾(lim)。默认的pos是缓冲区内元素的size,lim是缓冲区大小。当从缓冲区向通道去写时,是从pos位置去写,写到lim,这样就得不到元素,需要将lim = pos, pos = 0再写。

NIO读数据

@Test
public void test1() throws Exception {
    File file = new File("basic.txt");
    // 1. 创建输入流
    FileInputStream fis = new FileInputStream(file);
    // 2. 得到一个通道
    FileChannel channel = fis.getChannel();
    // 3. 准备一个缓冲区
    ByteBuffer buffer = ByteBuffer.allocate((int) file.length());
    // 4. 从通道中读取数据到缓冲区
    channel.read(buffer);
    System.out.println(new String(buffer.array()));
    // 5. 关闭
    fis.close();
}

BIO复制文件

@Test
public void test2() throws Exception {
    // BIO复制文件 - 通过输入流和输出流来实现
    FileInputStream fis = new FileInputStream("basic.txt");
    FileOutputStream fos = new FileOutputStream("basic1.txt");
    byte[] bytes = new byte[1024];
    while (true) {
        int res = fis.read(bytes);
        if (res == -1) {
            break;
        }
        fos.write(bytes, 0, res);
    }
    fos.close();
    fis.close();
}

NIO复制文件

@Test
    public void test3() throws Exception {
        // NIO复制文件 - 通过通道实现
        FileInputStream fis = new FileInputStream("basic.txt");
        FileOutputStream fos = new FileOutputStream("basic3.txt");
        FileChannel fisChannel = fis.getChannel();
        FileChannel fosChannel = fos.getChannel();
        fosChannel.transferFrom(fisChannel, 0, fisChannel.size());
//        fisChannel.transferTo(0 , fisChannel.size(), fosChannel);
        fos.close();
        fis.close();
    }

1.4 NIO-网络IO

JavaNIO中,网络通道是非阻塞IO,基于事件驱动,很适合需要维持大量连接,但数据交换量不大的场景, 例如:RPC、即时通讯、Web服务器等。

Java编写网络应用,通常有以下几种模式:

1、 为每个请求创建线程:一个客户端连接用一个线程,阻塞式IO

 优点:程序编码简单
 缺点:如果连接非常多,分配的线程也会非常多,服务器可能会因为资源耗尽而崩溃

2、 线程池:创建固定线程数量线程的线程池,来接受客户端的请求,阻塞式IO

 优点:程序编码简单,可以处理大量连接
 缺点:线程的开销非常大,连接如果非常多,排队现象比较严重

3、 JavaNIO:用非阻塞IO

 优点:这种模式可以用一个线程,来处理大量的客户端请求
 缺点:代码复杂度高
1.4.1 NIO-网络IO-Selector

Selector选择器也叫多路复用器

  • NIO的三大核心组件之一;
  • 用于检测多个注册Channel上是否有事件发生(读、写、连接),如果有,就获取事件,并对每个事件进行处理;
  • 只需要一个线程就能管理多个Channel,也就是多个连接;
  • 只有连接真正有读写事件时,才会调用方法进行处理,大大降低了系统分配线程与线程上下文切换的开销;

image.png

Selector常用方法:

Selector open(); 开启一个Selector
int select(long timeout); 监控所注册的通道
Set<SelectionKey> selectedKeys(); 从selector获取所有的selectionKey
1.4.2 NIO-网络IO-SelectionKey

SelectionKey : 代表了Selector和网络SocketChannel的注册关系

  • OP_ACCEPT: 有新的网络连接可以accept,值为16
  • OP_CONNECT: 代表连接已经建立,值为8
  • OP_READ: 读,值为1
  • OP_WRITE: 写,值为4

常用方法:

Selector selector(); 得到与之关联的Selector对象
SelectableChannel channel(); 得到与之关联的通道
Object attachment(); 得到与之关联的共享数据
boolean isAcceptable(); 是否可接入
boolean isReadable(); 是否可读
boolean isWritable(); 是否可写
1.4.3 NIO-网络IO-ServerSocketChannel

ServerSocketChannel : 用来在服务器端监听新的客户端socket连接

常用方法:

ServerSocketChannel open(); 得到一个ServerSocketChannel通道
ServerSocketChannel bind(SocketAddress local); 设置服务端端口号
SelectableChannel configureBlocking(boolean block); 设置阻塞或者非阻塞模式,false代表非阻塞模式
SocketChannel accept(); 接受一个连接,返回代表这个连接的通道对象
SelectionKey register(Selector sel, int ops); 注册一个选择器并设置监听事件 
1.4.4 NIO-网络IO-SocketChannel

SocketChannel: 网络IO通道,具体负责进行读和写操作。

NIO把数据写入通道,或者从通道读取数据。

常用方法:

SocketChannel open(); 得到一个SocketChannel通道
SelectableChannel configureBlocking(boolean block); 设置阻塞或者非阻塞模式,false代表非阻塞模式
boolean connect(SocketAddress remote); 连接服务器
boolean finishConnect(); 如果上面方法连接失败,接下来通过这个方法完成连接操作
int write(ByteBuffer src); 往通道里写数据
int read(ByteBuffer src); 从通道里读数据
SelectionKey register(Selector sel, int ops, Object att); 注册一个选择器并选择监听事件,最后一个参数可以设置共享数据
void close(); 关闭通道
1.4.5 NIO-网络IO-Selector、ServerSocketChannel、SocketChannel关系图

image.png

服务器端有一个选择器对象,服务端的ServerSocketChannel对象也要注册给selector,他的accept方法负责接受客户端的连接请求。有一个客户端连接过来,服务端就会建立一个通道。Selector会监控所有注册的通道,检查这些通道中是否有事件发生(连接、断开、读、写等事件),如果某个通道有事件发生,则做响应的处理。

1.4.6 NIO-网络IO-NIO实现客户端和服务器端的通信

image.png

客户端代码

package com.example.netty1.tcp;

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

/**
 * 网络客户端程序
 */
public class NIOClient {
    public static void main(String[] args) throws IOException {
        // 1. 得到一个网络通道
        SocketChannel socketChannel = SocketChannel.open();
        // 2. 设置非阻塞方式
        socketChannel.configureBlocking(false);
        // 3. 提供服务器端的IP地址的端口号
        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 9999);
        // 4. 连接服务器端,如果用connect方法连接不成功,就用finishConnect方法连接
        if (!socketChannel.connect(address)) {
            // 因为连接需要花费事件,所以用while一直循环尝试连接。在连接服务器时,还可以做一些别的事情,体现非阻塞。
            while (!socketChannel.finishConnect()) {
                // nio作为非阻塞的优势,如果服务器没有响应(不启动服务器),客户端不会阻塞,最后会报错,客户端尝试连接服务器,连接不上
                System.out.println("Client: 连接不上,做一些别的事情");
            }
        }
        // 5. 得到一个缓冲区并存入数据
        String msg = "你好,这里是客户端,打个招呼";
        ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
        // 6. 发送数据
        socketChannel.write(buffer);
        // 阻止客户端停止,否则服务端也会停止。
        System.in.read();
    }
}

服务端代码

package com.example.netty1.tcp;

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;

public class NIOServer {
    public static void main(String[] args) throws Exception {
        // 1. 开启一个ServerSocketChannel通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 2. 开启一个Selector选择器
        Selector selector = Selector.open();
        // 3. 绑定端口9999
        System.out.println("服务端 启动。。。");
        System.out.println("初始化端口 9999");
        serverSocketChannel.bind(new InetSocketAddress(9999));
        // 4. 配置非阻塞方式
        serverSocketChannel.configureBlocking(false);
        // 5. Selector选择器注册ServerSocketChannel通道,绑定连接操作
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        // 6. 循环执行:监听连接事件和读取数据操作
        while (true) {
            // 6.1 监控客户端连接:selector.select()方法返回的是客户端的通道数,如果为0,则说明没有客户端连接。
            // nio非阻塞式的优势
            if (selector.select(2000) == 0) {
                System.out.println("Server: 没有客户端连接,去码头搞点薯条");
                continue;
            }
            // 6.2 得到selectionKeys, 判断通道里的事件
            Iterator<SelectionKey> selectionKeyIterator = selector.selectedKeys().iterator();
            // 遍历所有selectionKey
            while (selectionKeyIterator.hasNext()) {
                SelectionKey selectionKey = selectionKeyIterator.next();
                // 客户端先连接上,处理连接事件,然后客户端会向服务端发信息,再处理读取服务端数据事件。
                if (selectionKey.isAcceptable()) {
                    // 客户端连接请求事件
                    System.out.println("OP_ACCOUNT");
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    // 注册通道,将通道交给Selector进行监控
                    // 参数1: 选择器
                    // 参数2:服务器要监控的事件,客户端发send数据,服务端读read数据
                    // 参数3: 客户端传过来的数据要放到缓存区
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if (selectionKey.isReadable()) {
                    // 读取客户端数据事件
                    // 数据在通道中,先获取通道
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    // 取到一个缓冲区,nio读写都是基于缓冲区
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    // 从通道中将客户端发过来的数据读到缓冲区
                    channel.read(buffer);
                    System.out.println("客户端发来数据:" + new String(buffer.array()));
                }
                // 手动移除当前key, 防止重复处理
                selectionKeyIterator.remove();
            }

        }
    }
}
1.4.7 NIO-网络IO-网络聊天室1.0

客户端代码:使用 NIO 编写了一个聊天程序的服务器端,可以接受客户端发来的数据,并能把数据广播给所有客户端。

package com.example.netty1.tcp;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;

/**
 * 使用 NIO 编写了一个聊天程序的服务器端,可以接受客户端发来的数据,并能把数据广播给所有客户端。
 */
public class ChatServer {

    //监听通道
    private ServerSocketChannel listenerChannel;
    // 选择器
    private Selector selector;
    // 服务器端口
    private static final int PORT = 9999;

    public ChatServer() {
        try {
            // 开启socket监听通道
            listenerChannel = ServerSocketChannel.open();
            // 开启选择器
            selector = Selector.open();
            // 绑定端口
            listenerChannel.bind(new InetSocketAddress("127.0.0.1", PORT));
            // 设置为非阻塞模式
            listenerChannel.configureBlocking(false);
            // 将选择器绑定到监听通道并监听accept事件
            listenerChannel.register(selector, SelectionKey.OP_ACCEPT);
            printInfo("真人网络聊天室 启动.......");
            printInfo("真人网络聊天室 初始化端口 9999.......");
            printInfo("真人网络聊天室 初始化网络ip地址 127.0.0.1.......");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void start() throws Exception {
        try {
            // 不停循环
            while (true) {
                //
                if (selector.select(2000) == 0) {
                    System.out.println("Server: 没有客户端连接,搞点兼职");
                    continue;
                }
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    // 如果是连接请求事件
                    if (selectionKey.isAcceptable()) {
                        SocketChannel accept = listenerChannel.accept();
                        accept.configureBlocking(false);
                        accept.register(selector, SelectionKey.OP_READ);
                        printInfo(accept.getRemoteAddress().toString().substring(1) + "上线了。。。");
                    }
                    if (selectionKey.isReadable()) {
                        // 读取数据事件
                        this.readMsg(selectionKey);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 读取客户端的信息,并广播出去
     * @param selectionKey selectionKey
     */
    private void readMsg(SelectionKey selectionKey) throws Exception {
        SocketChannel channel = (SocketChannel) selectionKey.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int count = channel.read(buffer);
        if (count > 0) {
            String msg = new String(buffer.array());
            // 打印消息
            printInfo(msg);
            // 广播消息
            broadCast(channel, msg);
        }
    }

    private void broadCast(SocketChannel channel, String msg) throws Exception {
        System.out.println("服务器广播了消息。。。");
        // 获取选择器上所有的selectionKey
        for (SelectionKey key : selector.keys()) {
            // 获取通道
            Channel targetChannel = key.channel();
            // 如果不是自身通道,发送信息
            if (targetChannel instanceof SocketChannel && targetChannel != channel) {
                SocketChannel destChannel = (SocketChannel) targetChannel;
                ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
                destChannel.write(buffer);
            }
        }
    }

    private void printInfo(String str) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("[" + simpleDateFormat.format(new Date()) + "] -> " + str);
    }

    public static void main(String[] args) throws Exception{
        new ChatServer().start();
    }
}

客户端代码:通过 NIO 编写了一个聊天程序的客户端,可以向服务器端发送数据,并能接收服务器广播的数据。

package com.example.netty1.tcp;

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

/**
 * 聊天程序客户端
 */
public class ChatClient {

    // 服务器地址
    private static final String HOST = "127.0.0.1";
    // 服务器端口
    private static final int PORT = 9999;
    // 网络通道
    private final SocketChannel socketChannel;
    // 聊天用户名
    private final String username;

    public ChatClient() throws IOException {
        // 1. 得到网络通道
        socketChannel = SocketChannel.open();
        // 2. 设置非阻塞模式
        socketChannel.configureBlocking(false);
        // 3. 设置服务端地址和端口号
        InetSocketAddress address = new InetSocketAddress(HOST, PORT);
        // 4. 连接服务端
        if (!socketChannel.connect(address)) {
            // NIO非阻塞的优势
            while (!socketChannel.finishConnect()) {
                System.out.println("Clint:连接服务端的同时,别闲着,去码头搞点薯条吃吃");
            }
        }
        // 5. 得到服务端IP和端口信息,作为聊天用户名使用
        username = socketChannel.getLocalAddress().toString().substring(1);
        System.out.println("-------Clint(" + username +") is ready---------");
    }

    /**
     * 向服务端发送信息
     * @param msg 消息
     * @throws Exception
     */
    public void sendMsg(String msg) throws Exception {
        if (msg.equalsIgnoreCase("bye")) {
            socketChannel.close();
            return;
        }
        msg = username + "说" + msg;
        ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
        socketChannel.write(buffer);
    }

    /**
     * 从服务端接收数据
     * @throws Exception
     */
    public void receiveMas() throws Exception {
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int size = socketChannel.read(buffer);
        if (size > 0) {
            String msg = new String(buffer.array());
            System.out.println(msg.trim());
        }
    }



}

测试代码 : 运行了聊天程序的客户端,该代码运行一次就是一个聊天客户端,可以同时运行多个聊天客户端。在一个聊天客户端中发送消息,会广播给所有其他聊天客户端。客户端互相发送消息,需要提前将服务端启动。

package com.example.netty1.tcp;

import java.util.Scanner;

public class TestChat {

    public static void main(String[] args) throws Exception {
        ChatClient chatClient = new ChatClient();

        new Thread(() -> {
           // 监听服务器
            while (true) {
                try {
                    chatClient.receiveMas();
                    Thread.sleep(2000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            String msg = scanner.nextLine();
            chatClient.sendMsg(msg);
        }
    }
}

1.5 AIO

JDK 7 引入了Asynchronous IO,即AIO,也叫异步不阻塞的IO,也可以叫做NIO2。在进行IO编程中,常用到两种模式: Reactor模式和Proactor模式。

  • NIO采用Reactor模式,当有事件触发时,服务器端得到通知,进行相应的处理
  • AIO采用Proactor模式,采用异步通道的概念,简化了程序编写,一个有效的请求才启动一个线程,他的特点是先由操作系统完成后,才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。

1.6 对比总结

IO的方式通常分为几种:同步阻塞的BIO,同步非阻塞的NIO,异步非阻塞的AIO。

BIO方式:适合连接数据较小且固定的架构

  • 这种方式对服务器资源要求比较高,并发局限于应用中
  • JDK1.4 以前的唯一选择,但程序直观简单易理解
  • 同步阻塞:食堂排队取餐:中午去食堂吃饭,排队等着,啥都干不了,到你了选餐,付款,然后找位子吃饭

NIO方式:适合连接数目多并且连接比较短(轻操作)的架构

  • 比如:聊天服务器,并发局限在应用中,编程比较复杂
  • JDK1.4开始支持
  • 同步非阻塞:例如下馆子:点完餐,就去商场玩。玩一会回来问一下,饭好了没

AIO方式:适用于连接数目多且连接比较长(重操作)的架构

  • 比如:相册服务器,充分利用OS参与并发操作,编程比较复杂
  • JDK1.7开始支持
  • 异步非阻塞

image.png