示例代码:
public static void main(String[] args) {
//创建一个容量为5的int型buffer
IntBuffer intBuffer = IntBuffer.allocate(5);
//向buffer中存放数据
for (int i = 0; i < intBuffer.capacity(); i++) {
intBuffer.put(i * 2);
}
//转换buffer的读写状态(必须加!)
/**
* public final Buffer flip() {
* limit = position;
* position = 0;
* mark = -1;
* return this;
* }
*/
intBuffer.flip();
//从buffer中获取数据
while (intBuffer.hasRemaining()) {
System.out.print(intBuffer.get() + " ");
}
}
结果:
0 2 4 6 8
缓冲区(Buffer)本质上是一个可以读写数据的内存块,可以理解成一个容器对象(含数组),该对象提供了一组方法,可以更轻松使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer
属性 | 描述 |
---|---|
capacity | 容量,即可以容纳的最大数据量;在缓存区创建时被设定并且不能改变 |
Limit | 表示缓冲区当前的终点,不能对缓冲区中超过Limit的部分进行读写(相当于哨兵)。而且Limit是可以修改的 |
Position | 当前的读/写位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变改值,为下次读写作准备 |
Mark | 标记 |
public abstract class Buffer {
//JDK1.4时,引入的api
public final int capacity()//返回此缓冲区的容量
public final int position()//返回此缓冲区的位置
public final Buffer position (int newPositio)//设置此缓冲区的位置
public final int limit()//返回此缓冲区的限制
public final Buffer limit (int newLimit)//设置此缓冲区的限制
public final Buffer mark()//在此缓冲区的位置设置标记
public final Buffer reset()//将此缓冲区的位置重置为以前标记的位置
public final Buffer clear()//清除此缓冲区, 即将各个标记恢复到初始状态,但是数据并没有真正擦除, 后面操作会覆盖
public final Buffer flip()//反转此缓冲区
public final Buffer rewind()//重绕此缓冲区
public final int remaining()//返回当前位置与限制之间的元素数
public final boolean hasRemaining()//告知在当前位置和限制之间是否有元素
public abstract boolean isReadOnly();//告知此缓冲区是否为只读缓冲区
//JDK1.6时引入的api
public abstract boolean hasArray();//告知此缓冲区是否具有可访问的底层实现数组
public abstract Object array();//返回此缓冲区的底层实现数组
public abstract int arrayOffset();//返回此缓冲区的底层实现数组中第一个缓冲区元素的偏移量
public abstract boolean isDirect();//告知此缓冲区是否为直接缓冲区
}
从前面可以看出对于 Java 中的基本数据类型(boolean除外),都有一个 Buffer 类型与之相对应。最常用的是ByteBuffer 类(二进制数据),该类的主要方法如下:
public abstract class ByteBuffer {
//缓冲区创建相关api
public static ByteBuffer allocateDirect(int capacity)//创建直接缓冲区
public static ByteBuffer allocate(int capacity)//设置缓冲区的初始容量
public static ByteBuffer wrap(byte[] array)//把一个数组放到缓冲区中使用
//构造初始化位置offset和上界length的缓冲区
public static ByteBuffer wrap(byte[] array,int offset, int length)
//缓存区存取相关API
public abstract byte get( );//从当前位置position上get,get之后,position会自动+1
public abstract byte get (int index);//从绝对位置get
public abstract ByteBuffer put (byte b);//从当前位置上添加,put之后,position会自动+1
public abstract ByteBuffer put (int index, byte b);//从绝对位置上put
}
public interface Channel extends Closeable{}
FileChannel 主要用来对本地文件进行 IO 操作,常见的方法有:
public int read(ByteBuffer dst)
,从通道读取数据并放到缓冲区中public int write(ByteBuffer src)
,把缓冲区的数据写到通道中public long transferFrom(ReadableByteChannel src,long position,long count)
,从目标通道中复制数据到当前通道public long transferTo(long position,long count,WritableByteChannel target)
,把数据从当前通道复制给目标通道使用前面学习后的 ByteBuffer(缓冲) 和 FileChannel(通道), 将 “hello,world” 写入到 hello.txt 中
public static void main(String[] args) throws IOException {
//创建字符串
String hello = "Hello World!";
//创建一个文件输出流
FileOutputStream stream = new FileOutputStream("C:\\hello.txt");
//通过FileOutputStream获取到对应的 channel
//channel的实际类型是FileChannelImpl
FileChannel channel = stream.getChannel();
//创建一个缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//将str放入byteBuffer
byteBuffer.put(hello.getBytes());
//引入等下要从头读,所以要进行反转
byteBuffer.flip();
//将byteBuffer中的内容写入到channel
channel.write(byteBuffer);
//关闭流
stream.close();
}
使用前面学习后的 ByteBuffer(缓冲) 和 FileChannel(通道), 将 hello.txt 中的数据读入到程序,并显示在控制
台屏幕
public static void main(String[] args) throws IOException {
//创建文件输入流
File file = new File("C:\\hello.txt");
FileInputStream stream = new FileInputStream(file);
//从输入流获取 FileChannel
FileChannel fileChannel = stream.getChannel();
//创建 ByteBuffer 缓冲区用于存储数据
ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
//将从 fileChannel 读取的数据放入 ByteBuffer
fileChannel.read(byteBuffer);
//将byteBuffer的字节数据转成String输出
System.out.println(new String(byteBuffer.array()));
//关闭输入流
stream.close();
}
使用 FileChannel(通道) 的方法 read和write完成文件的拷贝。其中inputChannel将文件内容写入到ByteBuffer中,而outputChannel将ByteBuffer写入到文件中。
代码示例:
public static void main(String[] args) throws IOException {
//创建输入流
File file = new File("C:\\hello.txt");
FileInputStream inputStream = new FileInputStream(file);
//获得输入channel
FileChannel inputChannel = inputStream.getChannel();
//创建输出流
FileOutputStream outputStream = new FileOutputStream("C:\\hello_copy.txt");
//获得输出channel
FileChannel outputChannel = outputStream.getChannel();
//-----------------------拷贝方法1-----------------------
//创建缓冲区 byteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
while (true) {
//重置position和limit的值,
// 否则下次循环position==limit,
// 导致无法读取内容,且read==0无法退出
byteBuffer.clear();
//将 inputChannel 中的数据读取到 byteBuffer中
int read = inputChannel.read(byteBuffer);
if (read == -1){
//read == -1表示读取完毕
break;
}
//进行flip准备写入
byteBuffer.flip();
// 将byteBuffer中内容写入到outputChannel
outputChannel.write(byteBuffer);
}
//-----------------------拷贝方法2-----------------------
// //创建缓冲区 byteBuffer
// ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
//
// //将 inputChannel 中的数据读取到 byteBuffer中
// inputChannel.read(byteBuffer);
//
// //需要对byteBuffer进行flip操作,准备进行读取
// byteBuffer.flip();
//
// //将byteBuffer中的内容写入到outputChannel
// outputChannel.write(byteBuffer);
//
//关闭输入输出流
inputStream.close();
outputStream.close();
}
使用 FileChannel(通道) 的方法 transferFrom ,完成文件的(零)拷贝
public static void main(String[] args) throws IOException {
//创建输入流
FileInputStream inputStream = new FileInputStream("C:\\hello.txt");
//创建输入channel
FileChannel inputChannel = inputStream.getChannel();
//创建输入流
FileOutputStream outputStream = new FileOutputStream("C:\\hello_copy.txt");
//创建输入channel
FileChannel outputChannel = outputStream.getChannel();
//使用transferTo或者transferFrom进行拷贝
// inputChannel.transferTo(0,inputChannel.size(),outputChannel);
outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
//关闭资源
inputChannel.close();
outputChannel.close();
inputStream.close();
outputStream.close();
}
public static void testPutGetByType() {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.putChar('A');
byteBuffer.putInt(42);
byteBuffer.putLong(10L);
byteBuffer.putDouble(2.33);
byteBuffer.flip();
System.out.println(byteBuffer.getChar());
System.out.println(byteBuffer.getInt());
System.out.println(byteBuffer.getLong());
System.out.println(byteBuffer.getDouble());
}
public static void toReadOnlyBuffer() {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.putInt(42);
//获取该byteBuffer的只读版本
ByteBuffer readOnlyBuffer = byteBuffer.asReadOnlyBuffer();
//注意position和limit的值也和byteBuffer一样
readOnlyBuffer.flip();
System.out.println(readOnlyBuffer.getInt());
readOnlyBuffer.putInt(10);
}
结果:
42
Exception in thread "main" java.nio.ReadOnlyBufferException
at java.nio.HeapByteBufferR.putInt(HeapByteBufferR.java:375)
at Netty.NIO.ByteBufferTest.toReadOnlyBuffer(ByteBufferTest.java:23)
at Netty.NIO.ByteBufferTest.main(ByteBufferTest.java:8)
public static void main(String[] args) throws IOException {
//第一个参数是文件位置,第二个参数是操作类型(r:读,w:写)
RandomAccessFile randomAccessFile =
new RandomAccessFile("C:\\Users\\韩壮\\Desktop\\hello.txt", "rw");
//获得channel
FileChannel channel = randomAccessFile.getChannel();
/**
* 通过channel的map方法获得MappedByteBuffer,实际类型是其子类 DirectByteBuffer
* 参数 mode:操作类型(读、写、读写)
* 参数 position:文件映射到内存的起始地址,可以直接修改的起始位置
* 参数 size:映射到内存的大小(不是索引位置),即将文件的多少个字节映射到内存
*/
MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
//对文件进行修改,第一参数是要修改位置的下标,第二个是内容
mappedByteBuffer.put(0, (byte) 'A');
mappedByteBuffer.put(4, (byte) 'B');
// mappedByteBuffer.put(5, (byte) 'C');//IndexOutOfBoundsException
randomAccessFile.close();
}
public static void main(String[] args) throws IOException {
//使用 ServerSocketChannel 和 SocketChannel 进行网络通信
//创建ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//创建端口对象
InetSocketAddress inetSocketAddress = new InetSocketAddress(6666);
//绑定端口到socket,并启动
serverSocketChannel.socket().bind(inetSocketAddress);
//创建缓冲数组
ByteBuffer[] buffers = new ByteBuffer[2];
buffers[0] = ByteBuffer.allocate(5);
buffers[1] = ByteBuffer.allocate(3);
//等待客户端连接(telnet)
SocketChannel socketChannel = serverSocketChannel.accept();
int messageLength = 8; //假定从客户端接收 8 个字节
while (true) {
int byteRead = 0;
while (byteRead < messageLength) {
long l = socketChannel.read(buffers);
byteRead += l;
System.out.println("byteRead" + byteRead);
for (ByteBuffer buffer : buffers) {
System.out.println("position=" + buffer.position() + " limit=" + buffer.limit());
}
}
//对所有buffer进行flip
for (ByteBuffer buffer : buffers) {
buffer.flip();
}
int byteWrite = 0;
while (byteWrite < messageLength) {
long l = socketChannel.write(buffers);
byteWrite += l;
}
//对所有buffer进行flip
for (ByteBuffer buffer : buffers) {
buffer.clear();
}
System.out.println("byteRead" + byteRead + ", byteWrite" + byteWrite + ", messageLength" + messageLength);
}
}
public abstract class Selector implements Closeable {
//得到一个选择器对象
public static Selector open();
//监控所有注册的通道,当其中有 IO 操作可以进行时,将对应的SelectionKey加入到内部集合中并返回,
public int select();
//带超时时间的select
public int select(long timeout);
//唤醒正在阻塞selector
public void wakeup();
//不阻塞,立马返还
public int selectNow();
//从内部集合中得到所有的 SelectionKey
public Set<SelectionKey> selectedKeys();
}
网络编程相关组件(Selector、SelectionKey、ServerScoketChannel和SocketChannel) 的关系梳理:
服务器端实现代码:
public class NIOServer {
public static void main(String[] args) throws IOException {
//创建ServerSocketChannel对象,类似于BIO中的ServerSocket
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//创建Selector对象
Selector selector = Selector.open();
//绑定端口6666在服务器端监听
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
//将serverSocketChannel设置为非阻塞
serverSocketChannel.configureBlocking(false);
//把serverSocketChannel注册到selector,设置关心时间为OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
if (selector.select(2000) == 0) {
System.out.println("服务器等待2秒,无连接");
continue;
}
//select的返回值>0,说明有事件发生。获取到相关的事件的selectionKeys
//selector.selectedKeys()返回关注事件的集合
//可以通过SelectionKey反向获取channel
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
//根据key,对应通道发生的事件做相应的处理
if (key.isAcceptable()) { //如果是OP_ACCEPT,说明有新的客户端连接
//为该客户端分配一个SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
//设置为非阻塞
socketChannel.configureBlocking(false);
System.out.println("客户端连接成功,对应的socketChannel为:" + socketChannel.hashCode());
//将SocketChannel注册到selector上,关心的事件为OP_READ
//同时给该socketChannel关联一个buffer
socketChannel.register(selector,
SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if (key.isReadable()) { //OP_READ
//根据SelectionKey获取到对应的channel
SocketChannel channel = (SocketChannel) key.channel();
//获取到该 Channel 关联的 buffer
ByteBuffer buffer = (ByteBuffer) key.attachment();
//从channel中读取数据放入到buffer中
channel.read(buffer);
System.out.println("客户端:" + new String(buffer.array()));
}
//手动移除当前的SelectionKey,防止重复操作
iterator.remove();
}
}
}
}
客户端实现代码:
public class NIOClient {
public static void main(String[] args) throws IOException {
//得到一个网络通道
SocketChannel socketChannel = SocketChannel.open();
//设置非阻塞
socketChannel.configureBlocking(false);
//创建一个连接地址
InetSocketAddress inetSocketAddress =
new InetSocketAddress("127.0.0.1", 6666);
//连接服务器
if (!socketChannel.connect(inetSocketAddress)) {
while (!socketChannel.finishConnect()){
System.out.println("连接需要时间,如果连接失败,客户端不会阻塞");
}
}
//如果连接成功,就发送数据
String str = "Hello World";
//将字符串的内容放入Buffer
ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
//将byteBuffer的内容写入到socketChannel
socketChannel.write(byteBuffer);
//暂停
System.in.read();
}
}
运行结果:
服务器等待2秒,无连接
服务器等待2秒,无连接
服务器等待2秒,无连接
客户端连接成功,对应的socketChannel为:94438417
客户端:Hello World 服务器等待2秒,无连接
服务器等待2秒,无连接
SelectionKey,表示 Selector 和网络通道的注册关系, 共四种:
源码:
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
SelectionKey相关方法有:
public abstract class SelectionKey {
public abstract Selector selector();//得到与之关联的 Selector 对象
public abstract SelectableChannel channel();//得到与之关联的通道
public final Object attachment();//得到与之关联的共享数据
public abstract SelectionKey interestOps(int ops);//设置或改变监听事件
public final boolean isAcceptable();//是否可以 accept
public final boolean isReadable();//是否可以读
public final boolean isWritable();//是否可以写
}
ServerSocketChannel 在服务器端监听新的客户端 Socket 连接
相关方法如下:
public abstract class ServerSocketChannel
extends AbstractSelectableChannel
implements NetworkChannel{
public static ServerSocketChannel open();//得到一个 ServerSocketChannel 通道
public final ServerSocketChannel bind(SocketAddress local);//设置服务器端端口号
//设置阻塞或非阻塞模式,取值 false 表示采用非阻塞模式
public final SelectableChannel configureBlocking(boolean block);
public SocketChannel accept();//接受一个连接,返回代表这个连接的通道对象
public final SelectionKey register(Selector sel, int ops);//注册一个选择器并设置监听事件
}
SocketChannel,网络 IO 通道,具体负责进行读写操作。NIO 把缓冲区的数据写入通道,或者把通道里的数据读到缓冲区。
相关方法如下:
public abstract class SocketChannel
extends AbstractSelectableChannel
implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel{
public static SocketChannel open();//得到一个 SocketChannel 通道
public final SelectableChannel configureBlocking(boolean block);//设置阻塞或非阻塞模式,取值 false 表示采用非阻塞模式
public boolean connect(SocketAddress remote);//连接服务器
public boolean finishConnect();//如果上面的方法连接失败,接下来就要通过该方法完成连接操作
public int write(ByteBuffer src);//往通道里写数据
public int read(ByteBuffer dst);//从通道里读数据
public final SelectionKey register(Selector sel, int ops, Object att);//注册一个选择器并设置监听事件,最后一个参数可以设置共享数据
public final void close();//关闭通道
}
实例要求:
服务器端代码:
public class GroupChatServer {
//Selector
private Selector selector;
//定义ServerSocketChannel
private ServerSocketChannel listenChannel;
//定义端口
private static final int PORT = 6666;
//定义构造方法,进行初始化工作
public GroupChatServer(){
try {
//创建Selector
selector = Selector.open();
//创建ServerSocketChannel
listenChannel = ServerSocketChannel.open();
//设置端口
listenChannel.socket().bind(new InetSocketAddress(PORT));
//设置非阻塞
listenChannel.configureBlocking(false);
//将listenChannel注册到selector中
listenChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
GroupChatServer server = new GroupChatServer();
server.listen();
}
//进行监听
public void listen(){
try {
while (true) {
int count = selector.select();
if (count > 0) { //说明有事件发生
//获得事件的SelectionKey
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey key : selectionKeys) { //对每个事件进行处理
if (key.isAcceptable()) { //如果是连接事件
//为客户端分配一个SocketChannel
SocketChannel socketChannel = listenChannel.accept();
//设置非阻塞
socketChannel.configureBlocking(false);
//将SocketChannel注册到selector上,关心的事件为OP_READ
socketChannel.register(selector,SelectionKey.OP_READ);
System.out.println(socketChannel.getRemoteAddress() + " 上线 ");
} else if (key.isReadable()) { //是read事件,即通道是可读事件
//处理读操作
readData(key);
}
selectionKeys.remove(key);
}
} else {
System.out.println("等待中....");
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//从SocketChannel读取客户端发送的数据输出并转发
private void readData(SelectionKey key) {
SocketChannel channel = null;
try {
//从SelectionKey获取SocketChannel
channel = (SocketChannel) key.channel();
//定义缓冲器
ByteBuffer buffer = ByteBuffer.allocate(1024);
//将SocketChannel中的数据读取到buffer
int read = channel.read(buffer);
String msg = new String(buffer.array());
if (read > 0) {
//在服务器端输出
System.out.println(msg);
//转发到其他客户端
redirectMsgToOtherClient(msg, channel);
}
} catch (IOException e) {
try {
//发生异常说明有客户端离线
System.out.println(channel.getRemoteAddress() + " 离线了 ");
//取消注册
key.cancel();
//关闭通道
channel.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
//将一个客户端发送的信息转发到其他客户端(排除自己)
private void redirectMsgToOtherClient(String msg, SocketChannel self) throws IOException {
System.out.println("服务转发消息中...");
//遍历所有注册到selector上的SocketChannel,并排除self
for (SelectionKey key : selector.keys()) {
Channel channel = key.channel();
//不对自己转发
if (channel instanceof SocketChannel && channel != self) {
//转型
SocketChannel sc = (SocketChannel) channel;
//创建缓冲区并赋值
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
//将缓冲区的值写入SocketChannel
sc.write(buffer);
}
}
}
}
客户端代码:
public class GroupCharClient {
//定义属性
private static final String HOST = "127.0.0.1";
private static final int PORT = 6666;
private Selector selector;
private SocketChannel socketChannel;
private String username;
//初始化属性
public GroupCharClient() throws IOException {
selector = Selector.open();
//连接服务器
socketChannel = SocketChannel.open(new InetSocketAddress(HOST, PORT));
//设置非阻塞
socketChannel.configureBlocking(false);
//将socketChannel注册到Selector
socketChannel.register(selector, SelectionKey.OP_READ);
//设置username为本地地址
username = socketChannel.getLocalAddress().toString().substring(1);
System.out.println(username + " 初始化完成");
}
public static void main(String[] args) throws IOException {
GroupCharClient client = new GroupCharClient();
new Thread(()->{
//每隔一秒读取一次从服务器端转发的消息
while (true) {
try {
client.getMsg();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
//用于读取输入数据
Scanner scanner = new Scanner(System.in);
//发送输入的数据
while (scanner.hasNextLine()) {
String s = scanner.nextLine();
client.sendMsg(s);
}
}
//向服务器发送消息
public void sendMsg(String msg) {
msg = username + " : " + msg;
try {
socketChannel.write(ByteBuffer.wrap(msg.getBytes()));
} catch (IOException e) {
e.printStackTrace();
}
}
//读取服务器转发的消息
public void getMsg() {
try {
int count = selector.select();
if (count > 0) {//有可以用的通道
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey key : selectionKeys) {
if (key.isReadable()) {
//根据key获取SocketChannel
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
//将数据从SocketChannel读到buffer
int read = sc.read(buffer);
if (read > 0) {
System.out.println(new String(buffer.array()));
}
}
//删除当前的 selectionKey, 防止重复操作
selectionKeys.remove(key);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
测试结果:
客户端 a 输入输出内容:
零拷贝是网络编程的关键,很多性能优化都离不开。在 Java 程序中,常用的零拷贝有 mmap(内存映射) 和 sendFile。
注意:零拷贝从操作系统角度,是没有cpu 拷贝
下面是Java 传统 IO 和 网络编程的一段代码:
File file = new File("test.txt");
RandomAccessFile raf = new RandomAccessFile(file, "rw");
byte[] arr = new byte[(int) file.length()];
raf.read(arr);
Socket socket = new ServerSocket(8080).accept();
socket.getOutputStream().write(arr);
DMA: direct memory access 直接内存拷贝(不使用CPU)
mmap 通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。这样,在进行网络传输时,就可以减少内核空间到用户控件的拷贝次数。如下图
这里其实有 一次cpu 拷贝kernel buffer -> socket buffer。但是,拷贝的信息很少,比如 lenght , offset , 消耗低,可以忽略
总结:
BIO、NIO、AIO比较:
BIO | NIO | AIO | |
---|---|---|---|
IO 模型 | 同步阻塞 | 同步非阻塞(多路复用) | 异步非阻塞 |
编程难度 | 简单 | 复杂 | 复杂 |
可靠性 | 差 | 好 | 好 |
吞吐量 | 低 | 高 | 高 |
同步阻塞:到理发店理发,就一直等理发师,直到轮到自己理发。
同步非阻塞:到理发店理发,发现前面有其它人理发,给理发师说下,先干其他事情,一会过来看是否轮到自己.
异步非阻塞:给理发师打电话,让理发师上门服务,自己干其它事情,之后理发师会打电话通知你