网络通信与IO多路复用
- 一、网络通信
- 1.缓存
- 1.1 网卡缓存
- 1.2 内核缓存
- 1.3 用户缓存
- 2.发送过程
- 3.接收过程
- 二、IO通信模型
- 1.阻塞IO
- 2.非阻塞IO
- 3.IO多路复用
- 4.信号驱动IO
- 5.异步IO
- 三、IO多路复用
- 1.select机制
- 1.1 底层原理
- 1.2 底层机制
- 1.3 优点
- 1.4 缺点
- 2.poll机制
- 2.1 底层原理
- 2.2 底层机制
- 2.3 优点
- 2.4 缺点
- 3.epoll机制
- 3.1 底层原理
- 3.2 底层机制
- 3.3 优点
- 3.4 触发方式
- 总结
- 参考链接
一、网络通信
不同的机器想要进行信息互通需要将信息序列化后通过网络进行交换。其中每个机器分为操作系统和网卡,其中操作系统又分为内核态和用户态。针对TCP通信来说,这个过程涉及到应用程序缓冲区、系统内核缓冲区和网卡等。
1.缓存
1.1 网卡缓存
网卡缓存不属于内核空间也不属于用户空间。网卡缓存属于硬件缓存,由于硬件对数据的读写与操作系统对数据的操作速度上有差异,所以需要在网卡与操作系统之间加一个缓冲。
1.2 内核缓存
内核缓冲区是在内核空间,作为从硬件读取数据或者写入硬件的数据缓冲区。
1.3 用户缓存
用户缓冲区是在用户空间,作为用户空间与内核空间数据交换的缓冲区。
2.发送过程
- 用户进程准备数据,将数据存储在用户缓冲区
- 用户进程进行发送系统调用,进程转入到内核态,CPU将数据从用户缓冲区写入到内核发送缓冲区
- 内核发送缓冲区数据写入完毕,切换回用户态
- 内核异步调用DMA,DMA将数据从内核发送缓冲区写入到网卡缓存
- 网卡将网卡缓存数据发送到网络
3.接收过程
- 网卡从网络读取数据流,将数据存储到网卡缓存
- 内核异步调用DMA,DMA将数据从网卡缓存写入到内核接受缓冲区
- DMA通知CPU内核接受缓冲区写入完毕,内核等待用户进程读取数据
- 用户进程进行读取系统调用,进程转入到内核态。如果数据已经在内核中,则CPU将数据从内核接受缓冲区写入到用户缓冲区;如果数据还没准备好,则需要等待数据到达。
- 用户进程对用户缓冲区进行处理
二、IO通信模型
由以上网络通信流程可知,网卡缓存与内核缓冲区主要是由DMA进行复制,内核负责内核缓冲区与用户应用程序之间数据的复制。如果应用程序需要网络通信,应用程序需要通过内核才能感知是否有信息到达,然后内核IO将数据从内核缓冲区复制到应用程序缓存区,应用程序进行后续处理。IO模型主要是指应用程序与内核针对接受IO数据的交换方式,IO模型总共有五种。
1.阻塞IO
同步阻塞IO指的用户程序主动发起,需要等待内核IO操作彻底完成后返回到用户空间的IO操作。整个IO操作过程中,用户进程处于阻塞状态。其中需要等待对方发送消息,数据从网卡缓存复制到内核缓存区,内核IO将数据从内核缓冲区复制到用户缓存区。由于需要等待发送方数据,所以会导致用户程序长时间阻塞,占用线程资源时间比较长,会消耗大量线程资源。
2.非阻塞IO
同步非阻塞IO指的用户程序主动发起。如果内核缓冲区没有数据会立即返回,如果内核缓冲区有数据会等待内核将数据复制到应用缓存区后返回。不会长时间等待发送方数据到达,但是需要频繁轮询内核,会占用大量CPU资源。
3.IO多路复用
非阻塞IO中需要为每个Socket创建对应的监控线程,这样会消耗大量的线程资源和浪费CPU资源。
IO多路复用就是单个线程同时监控多个IO的文件描述符,从而减少线程资源和CPU资源消耗。这样一旦某个IO数据就绪,内核会将文件描述符的就绪状态返回给用户进程,用户进程就可以对就绪的IO进行相应操作。
4.信号驱动IO
当进程发起一个 IO 操作,会向内核注册一个信号处理函数,然后进程返回不阻塞;当内核数据就绪时会发送一个信号给进程,进程便在信号处理函数中调用 IO 读取数据。
5.异步IO
异步IO是指用户程序发起一个IO操作立即返回,当内核IO操作将到达内核缓冲区的数据复制到用户缓冲区后,会通知用户线程。用户线程直接使用数据即可。
三、IO多路复用
IO多路复用就是一个线程同时监控多个文件句柄。一旦某个句柄就绪就返回给应用程序,应用程序就行对应的IO读写操作;如果没有句柄就绪就需要阻塞监控的线程,然后让出CPU资源,等待IO唤醒监控线程。
用一个或者少量线程监控大量TCP连接,可以减少系统开销,不必创建过多线程。IO多路复用主要有三种实现方式:select、poll、epoll。
1.select机制
相关函数:
// nfds 所有监听的fd,数值最大的一个+1
// readfds 读事件集合
// writefds 写事件集合
// exceptfds 异常事件集合
// timeout 最长阻塞时间,NULL表示永久阻塞
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
1.1 底层原理
select机制是用bitmap来标识fd,将需要监控的fd和就绪的fd以bitmap的形式,在用户态和内核态之间传递。由数据结构可知,fd_set是一个16个元素的long数组,此结果可以存放1024个文件描述符。
// fd_set的底层,实际就是用的 位图 这个数据结构
typedef __kernel_fd_set fd_set;
#undef __FD_SETSIZE
#define __FD_SETSIZE 1024
typedef struct {
unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(long))];
} __kernel_fd_set;
1.2 底层机制
- 调用select函数后,会将fd_set从用户态拷贝到内核态
- 内核态对fd_set进行遍历,将有响应的文件描述符进行标记
- 返回select函数,将fd_set从内核态拷贝到用户态
- 用户程序遍历从内核返回的fd_set,对就绪的文件描述符进行下一步操作
1.3 优点
- 支持跨平台 采用bitmap、节省空间,可以存储更多映射关系
1.4 缺点
- 单个进程可监控fd数量受限,最多1024个
- 每次调用会经历两次fd_set拷贝,在用户态和内核态传递复制开销大
- 每次调用都会经历两次遍历所有文件描述符,系统效率低
2.poll机制
相关函数:
// fds:文件描述符数组
// nfds:文件描述符数组中描述符数量。
// timeout
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd
{
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
2.1 底层原理
poll机制是用链表来标识fd,将需要监控的fd和就绪的fd以链表的形式,在用户态和内核态之间传递。
2.2 底层机制
poll底层机制与select基本一致
2.3 优点
- 单个进程监控fd数据不受限制,场景灵活
2.4 缺点
如果需要监控的fd数量较大情况下
- 每次调用会经历两次链表拷贝,在用户态和内核态传递复制开销会很大
- 每次调用都会经历两次遍历链表的文件描述符,系统效率低
3.epoll机制
相关函数:
// 创建epoll数据结构(红黑树和就绪队列)
int epoll_create(int size);
// 将需要监控的文件描述符和事件 加入到epoll的红黑树中并注册对应回调函数
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 等待所监控文件描述符有事件产生
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
3.1 底层原理
epoll机制是用红黑树来标识待监控fd,用就绪列表来标识有响应的文件描述符。这两个结构是用户态和内核态共享,避免复制的开销。
3.2 底层机制
- 调用epoll_create,建立红黑树和就绪队列
- 调用epoll_ctl,将待监控fd加入到红黑树,并设置回调函数
- 待监控fd数据就绪,内核会根据回调函数将fd加入到就绪列表
- 调用epoll_wait,内核返回后,应用程序只需要将绪列表中fd进行下一步处理即可
3.3 优点
- 单个进程监控fd数据不受限制,场景灵活
- 用户态和内核态共享epoll的数据结构,避免复制的开销
- 不用遍历文件描述符,系统效率高
3.4 触发方式
epoll支持边缘触发(edge trigger,ET)或水平触发(level trigger,LT)。select和poll只支持LT工作模式,epoll的默认工作模式是LT模式。
- 水平触发:针对读操作,只要缓冲区不为空、就会返回读就绪;针对写操作,只要缓冲区不满就会返回可写状态;
- 边缘触发:针对读操作,只有数据到达、才会返回读就绪;针对写操作,只要缓冲区有数据被发送走时会返回可写状态;
在边缘触发模式下,如果可读写事件发生,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完,那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你。这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符。
总结
网络IO通信涉及到硬件网卡以及操作系统内核态与用户态。其中网卡与内核态数据交互主要是通过DMA来进行传输并且由内核进行控制。用户态不需要感知网卡数据的情况,用户态主要是需要知晓内核态socket缓冲区数据是否就绪、并且将数据复制到用户缓冲区。
IO模型是指用户态和内核态对IO数据交互处理方式。模型共有5种方式,可以根据连接数量和系统资源进行IO模型的选择,选择合适的IO模型可以加快系统效率节约资源。
参考链接
1.网络IO的通信原理及Reactor模型
2.详解IO多路复用
3.IO多路转接 —— poll和epoll