从零开始学习Java网络编程

67 阅读7分钟

liunx中网络I/O模型

Linux的内核将所有外部设备都看做一个文件来操作,对一个文件的读写操作会调用内核提供的系统命令,返回一个file descriptor(fd,文件描述符)。而对一个socket的读写也会有相应的描述符,称为socket fd(socket描述符),描述符就是一个数字,它指向内核中的一个结构体(文件路径,数据区等一些属性)。

根据UNIX网络编程对I/O模型的分类,UNIX提供了5种I/O模型

  1. 阻塞I/O模型:最简单、最常见的一种,在这种模型中,应用程序发起 I/O 操作的系统调用(如 read、recv)会直接进入阻塞状态,直到数据准备好或者 I/O 操作完成。这意味着在等待数据的过程中,应用程序无法进行其他操作,必须等待 I/O 操作完成才能继续执行下一步指令
  2. 非阻塞I/O模型:是一种在 I/O 操作中可以立即返回而不会阻塞应用程序执行的模型。非阻塞 I/O 调用不会等待数据就绪或操作完成,而是立即返回控制权给应用程序。应用程序可以反复检查 I/O 操作的状态,等待数据准备好后再进行处理。
  3. I/O复用模型:liunx中提供了三种多路复用模型,分别是select、poll、epoll。其中的select/poll,是进程通过将一个或多个fd传递给select或poll系统调用,阻塞在select操作上,这样select/poll可以帮我们侦测多个fd是否处于就绪状态。select/poll是顺序扫描fd是否就绪,而且支持的fd数量有限,因此它的使用受到了一些制约。但是epoll系统调用是没有这个限制的,epoll使用基于事件驱动方式代替顺序扫描,因此性能更高。当有fd就绪时,立即回调函数rollIback
  4. 信号驱动I/O模型:是一种通过信号机制来处理 I/O 操作的方式。在该模型中,应用程序会向操作系统注册一个信号处理程序,并通知操作系统在文件描述符变为可读或可写时发送一个信号。收到信号后,信号处理程序会被触发,应用程序再在处理程序中执行对应的 I/O 操作。相比传统的阻塞和非阻塞 I/O 模型,信号驱动 I/O 模型更适合处理间歇性的 I/O 请求。
  5. 异步I/O模型:是一种处理 I/O 操作的方式,它允许应用程序发起 I/O 请求后立即返回并继续执行其他任务,而无需等待 I/O 操作完成。操作系统会在 I/O 完成后通知应用程序并处理结果,从而实现完全的非阻塞操作。该模型通常用于高并发和低延迟的场景,可以大幅提升应用的资源利用率和响应性能。

I/O多路复用

I/O 多路复用(I/O Multiplexing)是一种处理多个 I/O 操作的技术,使得单个进程或线程能够同时监视多个 I/O 设备(如文件描述符或网络连接)的状态。在传统的阻塞 I/O 模型中,当进程发起 I/O 请求(如读取数据)时,该进程会被阻塞,直到操作完成,这样在此期间它无法执行其他任务。I/O 多路复用通过允许进程在等待 I/O 完成时继续处理其他任务。

I/O 多路复用通常使用一个或多个系统调用来监视多个文件描述符的状态。主流的多路复用机制包括 select、poll 和 epoll。以下是它们的基本工作原理:

​ 1. 注册 I/O 事件:应用程序将多个文件描述符(如网络连接、文件等)注册到 I/O 多路复用机制中,并指定感兴趣的事件类型(如可读、可写或异常)。

​ 2. 等待事件发生:应用程序调用多路复用函数,进入阻塞状态,直到其中一个或多个文件描述符的事件发生。

3.	**处理事件**:一旦有事件发生,应用程序可以通过返回的文件描述符信息来确定哪些描述符可以被处理,并进行相应的 I/O 操作。
常见的I/O多路复用机制

​ 1. select

​ • select 是最早的 I/O 多路复用机制,允许进程监视多个文件描述符。

​ • 有最大文件描述符数量限制(通常为 1024),适用于小规模的 I/O 处理。

​ • 其实现需要复制描述符集合,性能较低。

​ 2. poll

​ • poll 解决了 select 的最大文件描述符数量限制,支持任意数量的文件描述符。

​ • 使用 pollfd 结构来监视事件,但仍需遍历整个描述符列表,性能在高负载时可能下降。

​ 3. epoll(Linux 特有):

​ • epoll 是针对大规模 I/O 处理优化的机制,支持边缘触发和水平触发。

​ • 能够高效地处理大量文件描述符,避免了 select 和 poll 的复制和遍历操作。

​ • 提供了更好的性能和可扩展性,适合高并发网络服务器。

Java的I/O演进

站在现在的角度看Java中I/O演进还是很清晰的,在JDK 1.4之前只有BIO(阻塞IO),在JDK1.4之后才出现了NIO(非阻塞IO)。在JDK1.7时NIO才逐渐完善成熟。同时在JDK 1.8时引入了异步IO(AIO)。

1. 传统 I/O(Java 1.0)

在 Java 的早期版本中,I/O 是通过传统的流(Stream)类来实现的。主要特点包括:

​ • 字节流和字符流:Java 提供了两种流的抽象:

​ • 字节流(如 InputStream 和 OutputStream)用于处理原始字节数据,适用于所有类型的文件(如图像、音频等)。

​ • 字符流(如 Reader 和 Writer)用于处理字符数据,支持字符编码和解码。

​ • 同步阻塞:传统的 I/O 操作是同步和阻塞的,意味着一个线程在执行 I/O 操作时会被阻塞,直到操作完成。这在某些情况下可能导致性能瓶颈,尤其是在处理大量并发请求时。

2. Java NIO(Java 1.4)

为了克服传统 I/O 的限制,Java 1.4 引入了 NIO(New I/O)。NIO 提供了以下新特性:

​ • 缓冲区(Buffer):NIO 引入了缓冲区的概念,所有的数据读取和写入都通过缓冲区进行,提供更高效的数据处理。

​ • 通道(Channel):NIO 使用通道(如 FileChannel、SocketChannel 和 ServerSocketChannel)来实现与 I/O 的连接,允许双向数据传输。

​ • 选择器(Selector):NIO 引入了选择器的概念,允许一个线程监视多个通道的 I/O 状态,实现 I/O 多路复用。这样可以在高并发情况下显著提高性能。

​ • 非阻塞 I/O:NIO 支持非阻塞 I/O 操作,允许线程在等待 I/O 时执行其他任务,提升系统的整体性能。

3. Java NIO.2(Java 7)

在 Java 7 中,NIO 进行了进一步扩展,引入了 NIO.2,主要包括:

​ • 文件 I/O(java.nio.file):引入了全新的文件 I/O API,支持文件系统的操作,包括文件的创建、删除、移动和读取等,提供了更直观的文件操作方法。

​ • 异步文件 I/O(Asynchronous I/O):引入了异步文件通道(AsynchronousFileChannel),允许进行非阻塞文件操作,提升文件 I/O 的性能。

​ • 更好的路径和文件系统支持:通过 Path 和 Files 类,Java NIO.2 提供了更强大的文件系统操作能力,支持多种文件系统(如 FAT、NTFS、ext4 等)。

4. Java 的异步 I/O(Java 8 及以后)

在 Java 8 及以后的版本中,异步 I/O 进一步发展,主要特性包括:

​ • CompletableFuture:Java 8 引入了 CompletableFuture 类,使得在进行异步编程时能够更方便地组合和处理异步结果。这与 I/O 操作结合使用时,可以提高 I/O 处理的灵活性和性能。

​ • Reactive Programming:随着响应式编程的流行,Java 也开始支持响应式编程模型,例如使用 Reactive Streams API,使得 I/O 操作更加灵活、高效。

Java I/O 的演进从早期的同步阻塞流到 NIO 的引入,再到异步 I/O 和响应式编程的支持,体现了对高效性和可扩展性的追求。NIO 和其后续版本的引入,使得 Java 能够更好地处理高并发 I/O 操作