Java IO
IO是什么
Java I/O是指Java中用于读写数据的类库,包括输入流和输出流两个方面。其中,输入流用于从外部读取数据到程序中,输出流则用于将程序中的数据写入到外部。Java I/O类库提供了丰富的API,可以支持多种不同类型的数据操作,例如:文件操作、网络操作等。
Java I/O类库主要分为字节流和字符流两种。字节流以字节为单位进行读写数据,适用于处理二进制数据或者非文本数据;而字符流以字符为单位进行读写数据,适用于处理文本数据。
-
前提概念理解:
-
同步和异步:同步指的是用户进程触发 IO 操作并等待或者轮询的去查看 IO 操作是否就绪。异步是指用户进程触发IO操作以后便开始做自己的事情,而当 IO 操作已经完成的时候会得到 IO 完成的通知(异步的特点就是通知)。
IO 操作主要分为两个步骤,即发起 IO 请求和实际 IO 操作,同步与异步的区别就在于第二个步骤是否阻塞。
若实际 IO 操作阻塞请求进程,即请求进程需要等待或者轮询查看 IO 操作是否就绪,则为同步 IO;若实际 IO 操作并不阻塞请求进程,而是由操作系统来进行实际 IO 操作并将结果返回,则为异步 IO。
计算把内存分为用户内存和系统内存两部分,同步和异步是针对应用程序(用户内存)和内核(系统内存)的交互而言的。
-
阻塞和非阻塞:阻塞操作通常用于需要等待某种条件发生的场景,例如等待数据读取完成、等待网络连接建立成功等。非阻塞是指的不需要去等待实际的IO操作过程。
若发起 IO 请求后请求线程一直等待实际 IO 操作完成,则为阻塞 IO;若发起 IO 请求后请求线程返回而不会一直等待,即为非阻塞 IO。
-
什么时候使用IO
1、文件操作:需要使用到IO进行操作,例如读取文件内容、上传文件等操作。
2、网络通信:Java的网络编程库提供了Socket、ServerSocket等类,可以实现TCP/IP协议的网络通信,包括客户端与服务端之间的消息传输、文件传输等操作。
3、序列化和反序列化:Java的对象序列化机制可以将Java对象转换为字节流,以便进行存储、传输和持久化。而反序列化则是将字节流还原为Java对象的过程,也需要进行IO操作。
4、用户输入输出:Java的标准输入输出流(System.in、System.out、System.err)可以实现控制台输入输出,而Scanner和PrintStream等类则可以实现更加灵活的用户输入输出操作。
总之,当需要进行输入输出操作时,就需要使用到IO。
如何使用IO
BIO:同步阻塞式IO
也就是我们使用的传统式IO。服务器实现模式为一个线程一个连接。同步并阻塞 IO,服务器实现模式一个连接一个线程,即客户端有连接请求时服务端就需要启动一个线程进行处理,如果这个连接不做任何事情,就会造成不必要的线程开销,当然可以通过线迟(Thread-Pool)程机制改善。它的特点是模式简单使用方便,并发处理能力低。
NIO:同步非阻塞式IO
是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,服务器实现模式为一个线程可以处理多个连接。即有新的连接请求时,需要将其通道channel注册到选择器selector上。对注册的通道进行监听,有就绪的就在该线程进行处理,每个通道之间互不干扰,提高了资源利用率,实现了多路复用。
NIO 三大核心件:Channel(管道)、Buffer(缓冲区)、Selector(选择器)
-
Channel:
-
通道可以同时进行读写操作,而流只能读或只能写
-
通道可以实现异步读写数据
-
通道可以从缓冲区(Buffer)读数据,也可以把数据写入缓冲区(Buffer)
-
常用的Channel类:
-
FileChannel
FileChannel主要用于对本地文件进行IO操作,常用如下
public int read(ByteBuffer var1) //从通道(Channel)读取数据到缓冲区(Buffer) public int write(ByteBuffer var1) //从缓冲区(Buffer)读取数据到通道(Channel) pubcic void public long transferFrom(ReadableByteChannel var1, long var2, long var4) //从目标通道复制数据到当前通道 public long transferTo(long var1, long var3, WritableByteChannel var5) //把数据从当前通道复制到目标通道
-
ServerSocketChannel
ServerSocketChannel在服务端监听新的客户端Socket连接
public static ServerSocketChannel open() //获取一个ServerSocketChannel通道 public final ServerSocketChannel bind(SocketAddress local) //设置服务器端口号 public final SelectableChannel configureBlocking(boolean block) //设置阻塞或非阻塞,false表示使用非阻塞模式 public abstract SocketChannel accept() //接受一个连接,返回生成这个连接对应的通道对象 public final SelectionKey register(Selector sel, int ops) //把当前通道注册到指定选择器中,并设置监听事件
-
SocketChannel
SocketChannel,由ServerSocketChannel为每一个连接的客户端创建的网络IO通信,具体负责进行读写操作,NIO把缓冲区的数据写入通道,或者把通道的数据读到缓冲区
public static SocketChannel open() //获取一个SocketChannel通道 public final SelectableChannel configureBlocking(boolean block) //设置阻塞或非阻塞,false表示使用非阻塞模式 public abstract boolean connect(SocketAddress remote) //尝试连接服务器,false表示连接失败 public abstract boolean finishConnect() //如connect连接失败,需使用该方法再次尝试连接 public abstract int write(ByteBuffer src) //ByteBuffer写数据到通道里面 public abstract int read(ByteBuffer dst) //ByteBuffer从通道读数据 public final SelectionKey register(Selector sel, int ops) //把当前通道注册到指定选择器中,并设置监听事件 public final void close() //关闭通道
-
-
-
Buffer
-
就是一个可以读写数据的内存块
-
缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况,Channel提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由Buffer
-
其子类有:ByteBuffer 存储字节数据到缓冲区,ShortBuffer 存储字符串数据到缓冲区,CharBuffer 存储字符数据到缓冲区,IntBuffer 存储整数数据到缓冲区,LongBuffer 存储长整型数据到缓冲区,DoubleBuffer 存储双精度小数到缓冲区
FloatBuffer 存储单精度小数到缓冲区。常用的主要是ByteBuffer:-
ByteBuffer主要方法:
allocate(int capacity):创建一个新的ByteBuffer,分配指定容量的空间。 put(byte b) / putInt(int value) / putDouble(double value)等:将指定的数据写入到ByteBuffer中。 get() / getInt() / getDouble()等:从ByteBuffer中读取数据。 flip():将ByteBuffer从写模式切换为读模式,设置limit为当前位置,然后将位置重置为0,准备读取数据。 rewind():将位置重置为0,可以重新读取之前已经读取过的数据。 clear():清空ByteBuffer,将位置、限制和标记重置为初始状态,以便重新写入数据。 compact():在读模式下,将未读取的数据拷贝到缓冲区的开始位置,然后将位置移动到未读取数据的末尾,为继续写入数据做准备。 mark() / reset():在某个位置设置标记,并能够在之后通过reset()方法将位置重置到标记的位置。 remaining():返回当前位置与限制之间的元素数量。 hasRemaining():检查是否还有剩余的元素可以读取。 slice():创建一个与原ByteBuffer共享底层数据的新ByteBuffer,共享的数据从当前位置到限制。
-
-
Selector
用于实现非阻塞I/O操作。选择器提供了一种方式,可以通过单个线程来管理多个通道(Channel),从而实现高效的I/O处理。
open():静态方法,用于创建一个新的选择器对象。 close():关闭选择器,并释放相关资源。 select():阻塞方法,等待至少一个通道上的就绪事件发生。返回值为已经就绪的通道数量。如果没有就绪的通道,该方法将一直阻塞。 select(timeout):阻塞方法,等待指定时间内的就绪事件发生。返回值为已经就绪的通道数量。如果在超时时间内没有就绪的通道,该方法将返回0。 selectNow():非阻塞方法,立即返回已经就绪的通道数量,不会进行阻塞等待。 wakeup():唤醒阻塞在select()或select(timeout)方法中的线程,使其立即返回。 selectedKeys():获取当前已经就绪的SelectionKey集合,可以通过遍历该集合来处理每个就绪的通道。 keys():获取当前注册到选择器上的所有SelectionKey集合。 selector():获取与选择器关联的底层Java NIO Selector 对象。
代码案例(单线程):
服务端:
package com.example.nio;
import java.io.IOException;
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.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;
public class ChannelSocketServer {
public static void main(String[] args) {
try (ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
serverSocketChannel.bind(new InetSocketAddress(8888));
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务端启动成功");
while (true) {
// 阻塞,等待至少一个通道就绪
selector.select();
// 获取到所有已经就绪的通道进行遍历
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
//获取到元素
SelectionKey selectionKey = iterator.next();
// 如果是连接就执行handleAccept
if (selectionKey.isAcceptable()) {
handleAccept(serverSocketChannel, selector);
} else if (selectionKey.isReadable()) {
// 如果是客户端写到服务器,服务器进行读则执行这个方法
handleRead(selectionKey);
}
// 最后移除该遍历的元素
iterator.remove();
}
}
} catch (IOException e) {
// throw new RuntimeException(e);
}
}
/**
* 建立连接,注册通道到selector选择器
* @param serverSocketChannel
* @param selector
*/
private static void handleAccept(ServerSocketChannel serverSocketChannel, Selector selector) {
try {
//接受与此通道的套接字建立的连接。返回连接的socketChannel注册到selector上
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("接收到连接,处理线程为:"+Thread.currentThread().getId());
} catch (IOException e) {
// throw new RuntimeException(e);
}
}
/**
* 处理到客户端的请求
* @param selectionKey
*/
private static void handleRead(SelectionKey selectionKey) {
try {
// 得到本次的socketChannel对象
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
// 创建bytebuffer缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 读取socketChannel发送的内容到缓冲区
int bytesRead = socketChannel.read(byteBuffer);
if (bytesRead == -1) {
System.out.println("客户端关闭连接:" + socketChannel.getRemoteAddress());
socketChannel.close();
return;
}
// 切换为只读模式,位置到了起始位置
byteBuffer.flip();
// 返回当前位置与限制之间的元素数量。byteBuffer.flip();之后相当于字节长度
byte[] data = new byte[byteBuffer.remaining()];
// 读取缓冲区的内容到字节数组
byteBuffer.get(data);
System.out.println("收到客户端消息:" + new String(data, StandardCharsets.UTF_8));
// 写入响应数据
String response = "Hello, Client!";
// 将字节数组包装到缓冲区中。
ByteBuffer responseBuffer = ByteBuffer.wrap(response.getBytes(StandardCharsets.UTF_8));
socketChannel.write(responseBuffer);
} catch (IOException e) {
// throw new RuntimeException(e);
}
}
}
客户端:
package com.example.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class ChannelSocketClient {
public static void main(String[] args) {
new ChannelSocketClient().start();
}
public void start(){
try {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 8888));
System.out.println("成功连接到服务器:" + socketChannel.getRemoteAddress());
Scanner scanner = new Scanner(System.in);
StringBuffer stringBuffer = new StringBuffer();
while (true) {
stringBuffer.append(scanner.nextLine());
if (stringBuffer.toString().equals("end")) {
break;
}
// 发送
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(stringBuffer.toString().getBytes(StandardCharsets.UTF_8));
byteBuffer.flip();
socketChannel.write(byteBuffer);
byteBuffer.clear();
// 接收
int read = socketChannel.read(byteBuffer);
if (read == -1) {
System.out.println("服务器已关闭");
continue;
}
byteBuffer.flip();
System.out.println("接收到的数据:" + new String(byteBuffer.array(), 0, byteBuffer.remaining(),StandardCharsets.UTF_8));
stringBuffer.delete(0, stringBuffer.length());
}
socketChannel.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void connect(){
try {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 8888));
System.out.println("成功连接到服务器:" + socketChannel.getRemoteAddress());
} catch (Exception e){
e.printStackTrace();
}
}
}
AIO:异步非阻塞式IO
是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。当用户进程发起IO请求后,不需要等待IO操作完成,而是可以继续执行后续的代码。当IO操作完成后,操作系统会向用户进程发送通知,用户进程可以通过回调函数来处理IO操作结果。
AIO 和 NIO 的区别不仅仅在于实现方式的不同,还在于适用场景的不同。相比于 NIO,AIO 更适合处理高并发、低延迟的场景,例如实时通信、网络游戏等。而在一些对响应速度要求不高、连接数较少的场景,NIO 可以更好地发挥作用。
AIO 相比于 NIO,尚且存在一些性能问题和兼容性问题,例如在 Linux 平台上的性能并不尽如人意,且一些旧版本的 JDK 并不支持 AIO
AIO的核心概念包括:
- 异步通道(AsynchronousChannel): 是Java AIO的基础,用于支持异步I/O操作。异步通道包括了AsynchronousServerSocketChannel和AsynchronousSocketChannel两种类型,分别用于异步处理TCP服务端和客户端的I/O操作。
- CompletionHandler:是Java AIO的核心回调函数,用于处理异步I/O操作完成后的回调事件。当异步I/O操作完成后,操作系统会通知应用程序,应用程序需要通过CompletionHandler来处理I/O事件。
- ByteBuffer:是Java NIO中的核心概念,也是Java AIO中的关键概念,用于存放I/O操作读取或写入的数据。在Java AIO中,每个异步I/O操作都需要指定一个ByteBuffer对象来存放读取或写入的数据。
- Future:是Java AIO中的一个重要概念,用于表示异步I/O操作的结果。在发起异步I/O操作时,可以通过Future对象来获取异步I/O操作的结果。
- AsynchronousChannelGroup:用于管理异步通道并提供线程池支持。异步通道必须注册到 AsynchronousChannelGroup 中,用于管理和处理异步通道。一个 AsynchronousChannelGroup 可以管理多个异步通道,但是一个异步通道只能注册到一个 AsynchronousChannelGroup 中。
案例代码:
服务端:
package com.example.aio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Executors;
/**
* @Classname AsyncServerSockerChannel
* @Description TODO
* @Version 1.0.0
* @Date 2023/6/19 11:21
* @Created by wlh12
*/
public class AsyncServerSocketChannel {
public static void main(String[] args) {
try {
// 创建处理线程池
AsynchronousChannelGroup asynchronousChannelGroup = AsynchronousChannelGroup.withThreadPool(Executors.newFixedThreadPool(10));
// 1.创建异步连接通道
AsynchronousServerSocketChannel asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open(asynchronousChannelGroup);
asynchronousServerSocketChannel.bind(new InetSocketAddress("localhost",9999));
System.out.println("服务器已启动,等待客户端连接...");
// 2.等待连接
asynchronousServerSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel client, Object o) {
// 1.继续接收请求
System.out.println("接收到客户端的连接");
asynchronousServerSocketChannel.accept(null,this);
try {
// 2.处理读取
ByteBuffer byteBuffer = ByteBuffer.allocate(2048);
client.read(byteBuffer,byteBuffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
// 读取完成并且客户端关闭了连接,关闭当前连接
if (result == -1){
try {
client.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
return;
}
// 切换读模式,从头开始读取
attachment.flip();
byte[] bytes = new byte[attachment.limit()];
// 输出到字节数组
attachment.get(bytes);
String req = new String(bytes, StandardCharsets.UTF_8);
System.out.println("收到的客户端的请求:"+req);
// 响应客户端
String resp = "hello,client!你的请求为:"+req;
ByteBuffer wrap = ByteBuffer.wrap(resp.getBytes(StandardCharsets.UTF_8));
CompletionHandler completionHandler = this;
client.write(wrap, null, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("发送客户端: " + resp);
// 清空缓存区
byteBuffer.clear();
// 继续执行读取
client.read(byteBuffer,byteBuffer,completionHandler);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
}
});
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void failed(Throwable exc, Object o) {
exc.printStackTrace();
}
});
// 主线程休眠,等待异步线程执行
Thread.currentThread().join();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
客户端
package com.example.aio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class AsyncSocketChannel {
public static void main(String[] args) {
try {
AsynchronousSocketChannel asynchronousSocketChannel = AsynchronousSocketChannel.open();
asynchronousSocketChannel.connect(new InetSocketAddress("localhost", 9999));
System.out.println("已连接到服务器");
Scanner scanner = new Scanner(System.in);
// 发送到客户端
String str = scanner.nextLine();
ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes(StandardCharsets.UTF_8));
System.out.println("发送请求数据:"+str);
asynchronousSocketChannel.write(byteBuffer, byteBuffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
// 接收响应
ByteBuffer buffer = ByteBuffer.allocate(2048);
CompletionHandler completionHandler = this;
asynchronousSocketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
if (result==-1){
System.out.println("断开连接");
try {
asynchronousSocketChannel.close();
return;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
attachment.flip();
byte[] bytes = new byte[attachment.limit()];
attachment.get(bytes);
String resp = new String(bytes, StandardCharsets.UTF_8);
System.out.println("服务端响应数据:"+resp);
// 继续发送请求
String str = scanner.nextLine();
ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes(StandardCharsets.UTF_8));
System.out.println("发送请求数据:"+str);
asynchronousSocketChannel.write(byteBuffer,byteBuffer,completionHandler);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
// 主线程休眠,等待异步线程执行
Thread.currentThread().join();
} catch (IOException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
常见问题
1、tomcat IO模型 变化:
-
Tomcat 的 IO 模型主要有四种:
1.BIO(Blocking I/O)模型
BIO 是一种同步阻塞的 IO 模型,每个请求都需要创建一个线程来处理,对性能开销较大,不适合高并发场景。在 BIO 模型中,当一个线程在读写操作时,如果没有数据可读,线程会一直阻塞,直到有数据可读。当有多个请求同时到达时,就需要创建多个线程来处理,这会导致线程数量的急剧增加,带来的性能和资源消耗问题。
Tomcat7(包含)以下默认使用这种方式,但是从6开始就支持了NIO,需要手动的去修改server.xml文件
2.NIO(Non-blocking I/O)模型
NIO 是一种同步非阻塞的 IO 模型,基于多路复用 Selector 监测连接状态,当有可读数据时通知线程进行处理,达到非阻塞的目的。在 NIO 模型中,一个线程可以处理多个请求,大大减少了线程数量,提高了并发处理能力。
Tomcat8及以上版本默认使用这种方式
3.AIO(Asynchronous I/O)模型
AIO 是一种异步非阻塞的 IO 模型,基于事件驱动和回调机制实现 I/O 操作。在 AIO 模型中,应用程序不需要等待 I/O 操作完成,可以继续处理其他操作,当 I/O 操作完成时,操作系统会通知应用程序进行相应的处理。
Tomcat 7 开始支持使用 AIO(Asynchronous I/O)模型,需要手动配置
4.APR(Apache Portable Runtime)模型
APR 是 Apache Http 服务器的支持库,通过 JNI 技术调用本地库来实现非阻塞 IO 操作。APR 提供了更高效、更可靠的 I/O 操作,可以显著提高 Tomcat 的性能和可靠性。在 Tomcat 中,可以通过使用 APR 来实现更高效的 IO 模型。
APR 是从 Tomcat 7 开始支持的需要手动修改配置
附加:Tomcat本身如何调优,涉及哪些参数:
1. 架构优化:动静分离,所谓动静分离就是将所有的静态资源的请求响应处理放在一个独立的服务器上,比如nginx(目前使用最 多的),tomcat只负责jsp和servlet的加载和处理。这样就能在一定程度上降低tomcat的压力。
- 线程模型选择(IO优化) ,根据业务选择合适的io模型,BIO方式适用于连接数目比较小且固定的架构。NIO适用于连接数目多且连接比较短(轻操作)的架构, AIO(NIO2)方式使用于连接数目多且连接比较长(重操作)的架构,充分调用OS参与并发操作
- 并发优化(线程优化):线程池优化。
2、底层IO相关知识,系统IO知识的演变需要了解
-
底层IO相关知识:
-
文件描述符
在 Unix/Linux 系统中,每个进程都有一张打开文件的表格,其中包含了每个打开文件的信息,包括文件描述符、文件类型、文件打开模式等。文件描述符是操作系统中用来标识一个文件或者一种文件类型的标识符,它是一个非负整数。在 C 语言中,通过调用 open() 函数创建一个文件时,会返回一个文件描述符,该文件描述符可以用来进行读写操作。
-
文件 IO
文件 IO 是指对文件的读写操作,包括以字节流方式读写文件,以及以块状方式读写文件。在 Unix/Linux 系统中,文件 IO 是通过系统调用实现的,主要包括 open()、read()、write()、close() 等函数。文件 IO 操作和文件描述符密切相关,每个文件描述符都对应一个打开的文件。在进行文件 IO 操作时,需要使用文件描述符来指定要操作的文件。
-
套接字(Socket)
套接字是实现网络通信的一种机制,它是一种文件描述符,用来标识正在通信的两个进程之间的连接。套接字可以分为流式套接字和数据报套接字两种类型。流式套接字是基于 TCP 协议实现的,可以保证数据的可靠传输;数据报套接字是基于 UDP 协议实现的,可以实现无连接的通信,但不保证数据的可靠性。
-
缓冲区
缓冲区是指内存中的一块区域,用来暂存从文件或套接字中读取的数据,或者将数据写入到文件或套接字中。缓冲区的使用可以减少 IO 操作的次数,从而提高 IO 的效率。在进行 IO 操作时,首先将数据从文件或套接字中读取到缓冲区中,然后再从缓冲区中将数据写入到文件或套接字中。
-
IO 多路复用
IO 多路复用是指通过一种机制可以同时监听多个文件描述符,从而避免了使用多个线程或进程来处理多个连接的开销。常用的 IO 多路复用机制包括 select、poll 和 epoll 等。在进行 IO 多路复用时,需要将要监听的文件描述符添加到一个文件描述符集合中,然后通过调用 select()、poll() 或 epoll() 函数来监听文件描述符,当有数据到来时,程序会得到相应的通知。
-
异步 IO
异步 IO 是一种高级的 IO 模型,它可以让应用程序在 IO 操作完成之前不需要一直等待,而是可以继续处理其他任务,当 IO 操作完成后再通过回调函数来处理结果。异步 IO 可以大大提高系统的并发能力和效率。在进行异步 IO 时,需要将 IO 操作的请求和处理分离开来,即应用程序发送 IO 请求后,不需要等待 IO 操作完成,而是可以继续处理其他任务,当 IO 操作完成后再通过回调函数来处理结果。
-
-
大体可以分为以下演变阶段:
-
阻塞式 IO(Blocking I/O):阻塞式 IO 是最早的 IO 模型,它的特点是在进行 IO 操作时会阻塞当前线程,直到操作完成才会返回结果。阻塞式 IO 的效率较低,因为线程在等待 IO 操作完成时会一直被阻塞,无法进行其他操作。
-
非阻塞式 IO(Non-blocking I/O):为了提高 IO 的效率,出现了非阻塞式 IO 模型。非阻塞式 IO 在进行 IO 操作时不会阻塞当前线程,而是立即返回一个状态码,告诉应用程序 IO 操作是否完成。如果 IO 操作没有完成,则应用程序可以选择等待或者进行其他操作。非阻塞式 IO 的效率相比阻塞式 IO 有所提高,但仍然有一些局限性,比如处理高并发时仍然存在线程切换的开销。
-
多路复用 IO(Multiplexed I/O):多路复用 IO 是一种更高效的 IO 模型,它可以通过一个线程同时监听多个连接的 IO 事件,从而避免了线程切换的开销。多路复用 IO 的实现方式主要有两种,一种是基于 select 函数,另一种是基于 epoll 函数。
-
异步 IO(Asynchronous I/O):异步 IO 是一种更高级的 IO 模型,它将 IO 操作的请求和处理分离开来,即应用程序发送 IO 请求后,不需要等待 IO 操作完成,而是通过回调函数来处理 IO 操作完成后的结果。
-