网络编程常见名词介绍
这篇文章总结了网络编程中的常见名词,是博主自己的理解。如果和你的有出入,一切以你为准,欢迎交流。
BIO、NIO、AIO
这三个名词对应网络编程中的三种编程方式,代表不同的网络编程思想。
BIO 是阻塞 IO,也就是内核读取数据时会阻塞用户线程,用户线程从内核态拷贝数据时也会阻塞线程。对于这种编程方式,相当于所有读写操作都做了串行化。
NIO 是非阻塞 IO,当执行 IO 函数时,用户线程不必等待内核态加载数据。
AIO 是异步 IO,用户态程序执行 IO 函数,当用户接收到读写完成的通知,就可以拿数据来用;注意此时没有内核态到用户态的数据拷贝。
内核态/用户态
为了保证操作系统执行程序时的安全,操作系统执行环境分为用户态和内核态。
所有系统级别的操作都是在内核态执行的。常见的系统级别的操作有:线程上下文切换、IO、虚拟内存管理等。
Unix IO
大名鼎鼎的 Unix 或类 Unix 操作系统内核实现的 IO 函数,分为五类:
- 阻塞 IO
- 非阻塞 IO
- 多路 IO 复用
- 信号驱动 IO
- 异步 IO
要理解 Unix IO 就要先知道 IO 的加载拷贝过程。大致可分为加载阶段、拷贝阶段。
以读取磁盘上的一个文件为例,假如读取 A 文件:第一个阶段,操作系统接收到读取信号,然后加载磁盘上的 A 文件;加载完毕后进入第二个阶段,用户线程从内核态拷贝 A 文件,读取完毕算完成了一次 IO。
说 Unix IO 时也经常听到同步阻塞、同步非阻塞……,那么其中的“同步”、“阻塞”、“非阻塞”是什么意思?
阻塞、非阻塞指的是在调用读取函数时,用户线程是否等待内核加载完毕数据,这个时间节点在加载完毕:从加载到这个时间点,如果用户线程等待就是“阻塞”,反之就是“非阻塞”。
同步、非同步指的是内核加载完毕数据后,用户线程从内核拷贝数据这个期间是否等待:等待就是“同步”,不等待就是“非同步”。不过,从内核态拷贝数据是内存间的数据拷贝,非常快,能达到磁盘读取的几百倍,时间几乎可以忽略。
到现在,当提到“同步非阻塞 IO”时,应该理解它节省了当前线程等待内核加载文件的时间,然而从内核拷贝文件仍然需要当前线程进行。
多路复用
多路复用指的是三个网络 IO 函数:select、poll、epoll。
多路复用可以理解为节省了用户线程等待内核准备数据的阶段,在网络 IO 中是等待读信号、写信号。
三者的区别:
select、poll 比较相似,都是通过一个 O(n) 的遍历操作获取关心的文件描述符(fd,整型值,用于标识文件)。不同的是,select 有文件描述符数量的限制,为 1024,超出就会报错,poll 则取消了这个限制。
epoll 于 Linux 2.5.44 首度登场,其设计目的旨在取代既有POSIX select与poll系统函数,让需要大量操作文件描述符的程序得以发挥更优异的性能(举例来说:旧有的系统函数所花费的时间复杂度为 O(n),epoll的时间复杂度 O(log n))。epoll 实现的功能与 poll 类似,都是监听多个文件描述符上的事件。
epoll 有水平触发和边缘触发。当检测 epoll 文件描述符时,有读或写事件就绪通知:水平触发时会通知多次,直到用户处理了事件;边缘触发则仅通知一次。
参考:Epoll
零拷贝
网络 IO 中,除了内核态加载数据的时间开销,还有内核态到用户态间的数据拷贝开销,另外用户线程读取内核态数据还伴有上下文切换的开销。为了优化这块的性能,人们想出了在内核态直接发送、接收数据,这样用户态就不必拷贝数据了,节省了拷贝开销。Linux 中 sendfile 函数就是零拷贝操作。
内存映射
内存映射(Memory Map),是另一种解决从内核态向用户态拷贝数据开销的方法,从名字可以看出它是一种映射技术,是利用一块用户态的非堆内存与内核态加载好的数据映射,实现直接读取数据,避免了拷贝开销。
直接内存
这是独属于 Java 的名词。在 JVM 中,直接内存属于非堆内存,非堆内存是 Java 程序不可见的内存,但很巧妙地通过 JVM 进行了管理,Java 中是通过“虚引用”类进行控制的。
直接内存相较于堆内存,效率提升在了哪里呢?我们都知道,JVM 像一个沙盒,隔离了 Java 程序与操作系统,也就是说当 JVM 加载完数据时,此时对 Java 程序还不可见,只有再次拷贝到 Java 堆中才可见。这样一来,加载文件的数据就成了:操作系统内核加载磁盘文件->拷贝到用户态(JVM 可见)->拷贝到 Java 堆中(Java 可见)。此时我们发现,已经到用户态的数据被多拷贝了一次;为了避免这一次拷贝,Java 引入了直接内存来使用 JVM 可见的这块内存。