五种网络IO模型分析

目录

网络IO 流程

名词解释

实际流程

五种网络IO模型

简介

同步、异步 & 阻塞、非阻塞

阻塞IO(bloking IO)

简介

在 linux 中,默认情况下所有的 socket 都是 blocking

多线程优化

缺点

非阻塞IO(non-blocking IO)

简介

优点

缺点

多路复用IO (IO multiplexing)

简介

select &poll &epoll

优点

缺点

异步IO(Asynchronous I/O)

简介

信号驱动IO(signal driven I/O, SIGIO)

简介


网络IO 流程

名词解释

  • CPU 拷贝:由 CPU 直接处理数据的传送,数据拷贝时会一直占用 CPU 的资源。

  • DMA 拷贝:由 CPU 向DMA磁盘控制器下达指令,让 DMA 控制器来处理数据的传送,数据传送完毕再把信息反馈给 CPU,从而减轻了 CPU 资源的占有率。

  • 上下文切换:当用户程序向内核发起系统调用时,CPU 将用户进程从用户态切换到内核态;当系统调用返回时,CPU 将用户进程从内核态切换回用户态。

实际流程

用户程序接收网络数据的流程如下:

  1. 用户进程通过 read() 函数向 Kernel 发起 System Call,上下文从 user space 切换为 kernel space。

  2. CPU 利用 DMA 控制器将数据从网卡拷贝到 kernel space 的读缓冲区(Read Buffer)。

  3. CPU 将socket缓冲区(socket Buffer)中的数据拷贝到 user space 的用户缓冲区(User Buffer)。

  4. 上下文从 kernel space 切换回用户态(User Space),read 调用执行返回。

基于传统的 I/O 读取方式,read 系统调用会触发 2 次上下文切换,1 次 DMA 拷贝和 1 次 CPU 拷贝。

用户程序发送网络数据的流程如下:

用户进程通过 send() 函数向 kernel 发起 System Call,上下文从 user space 切换为 kernel space。

  1. CPU 将用户缓冲区(User Buffer)中的数据拷贝到 kernel space 的网络缓冲区(Socket Buffer)。

  2. CPU 利用 DMA 控制器将数据从网络缓冲区(Socket Buffer)拷贝到 NIC 进行数据传输。

  3. 上下文从 kernel space 切换回 user space,write 系统调用执行返回。

基于传统的 I/O 写入方式,write() 系统调用会触发 2 次上下文切换,1 次 CPU 拷贝和 1 次 DMA 拷贝。

五种网络IO模型

简介

· 阻塞IO(bloking IO)

· 非阻塞IO(non-blocking IO)

· 多路复用IO(multiplexing IO)

· 信号驱动IO(signal-driven IO)

· 异步IO(asynchronous IO)

同步、异步 & 阻塞、非阻塞

Synchronization is a reliable sequence of tasks. When the completion of a task depends on another task, the dependent task can be counted as complete only after the dependent task is completed. Either both succeed or both fail, and the state of the two tasks can remain the same.

Asynchrony does not need to wait for the dependent task to complete, but only notifies the dependent task of what to complete, and the dependent task is executed immediately. As long as it completes the whole task, it is completed. As for whether the dependent task is actually completed, the dependent task cannot be determined, so it is an unreliable task sequence.

A blocking call means that the current thread is suspended until the result of the call is returned, waiting for a message notification and unable to execute other services. The function returns only after it gets the result.

A nonblocking call is a function that does not block the current thread until the result is not immediately available and returns immediately.

同步是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么都成功,要么都失败,两个任务的状态可以保持一致。

异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列

阻塞调用指调用结果返回之前,当前线程会被挂起,一直处于等待消息通知,不能够执行其他业务。函数只有在得到结果之后才会返回。

非阻塞调用指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

同步/异步关注的是消息通知的机制,而阻塞/非阻塞关注的是程序(线程)等待消息通知时的状态:等待消息的同时是否处理其他消息。

如果用户发起了读写请求,但内核态数据还未准备就绪,该阶段不会阻塞用户操作,内核立马返回,则称为非阻塞 IO。如果该阶段一直阻塞用户操作。直到内核态数据准备就绪,才返回。这种方式称为阻塞 IO。

当进程进入阻塞状态,是不占用CPU资源的, 从非阻塞到阻塞再到非阻塞这个过程很消耗资源。


阻塞IO(bloking IO)

简介


在 linux 中,默认情况下所有的 socket 都是 blocking

调用read 就会使用系统调用,产生中断,用户进程阻塞,去kernel 缓存区中取数据, 若数据还未到达(一般情况下,都会有延迟),中断程序无法完成任务,用户进程持续阻塞,直到数据到达,中断程序完成任务后返回(数据从kernel copy到user ),用户进程解除阻塞。

blocking IO 的特点就是在 IO 执行的两个阶段(等待数据和拷贝数据两个阶段)都被 block 了。

多线程优化

缺点

  1. 进程状态切换耗时

  1. 使用中一般使用多进程&多线程, 而进程线程开辟数量有限,会导致降低系统对外界响应效率,而线程与进程本身也更容易进入假死状态

  1. “线程池”或“连接池”或许可以缓解部分压力,但是不能解决所有问题

非阻塞IO(non-blocking IO

简介

在第一阶段(网卡-内核态)数据未到达时不等待,然后直接返回。因此非阻塞 IO 需要不断的用户发起请求,询问内核数据好了没。

非阻塞 IO 是需要系统内核支持的,在创建了连接后,可以调用 setsockop 设置noblocking,或者使用fcntl( fd, F_SETFL, O_NONBLOCK )

优点

解决了阻塞IO 必须要使用多线程才可以同时处理多个客户端的问题

缺点

频繁的系统调用非常消耗系统资源

多路复用IO (IO multiplexing)

简介

非阻塞IO 中已经可以使用单线程管理多个连接,但是多个连接需要分别使用recv去判断kernel中是否有数据到达,这样频繁的系统调用会严重占用系统资源。

所以多路复用IO,就是希望复用系统调用,在有限次系统调用中,判断数据是否准备好。

下面是以select 为例子,与此类似的还有poll 和epoll:

可以看出select后用户进程阻塞,当select 管理fd 中进行轮询判断数据到达,之后再调用read。

select &poll &epoll

select

// readfds:关心读的fd集合;writefds:关心写的fd集合;excepttfds:异常的fd集合
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select 函数监视的文件描述符分 3 类,分别是 writefds、readfds、和 exceptfds。调用后 select 函数会阻塞,直到有描述副就绪(有数据 可读、可写、或者有 except),或者超时(timeout 指定等待时间,如果立即返回设为 null 即可),函数返回。当 select 函数返回后,可以 通过遍历 fdset,来找到就绪的描述符。

poll

int poll (struct pollfd *fds, unsigned int nfds, int timeout);
 
struct pollfd {
    int fd; /* file descriptor */
    short events; /* requested events to watch */
    short revents; /* returned events witnessed */
};
 

pollfd 结构包含了要监视的 event 和发生的 event,不再使用 select“参数-值”传递的方式。同时,pollfd 并没有最大数量限制(但是数量过大后性能也是会下降)。和 select 函数一样,poll 返回后,需要轮询 pollfd 来获取就绪的描述符。

select 和 poll 的区别

  • select 能处理的最大连接,默认是 1024 个,可以通过修改配置来改变,但终究是有限个;而 poll 理论上可以支持无限个

  • select 和 poll 在管理海量的连接时,会频繁的从用户态拷贝到内核态,比较消耗资源。

select 和 poll 的缺点

select 和 poll 都需要在返回后,通过遍历文件描述符来获取已经就绪的 socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。

epoll

为了解决【现频繁的将海量 fd 集合从用户态传递到内核态,再从内核态拷贝到用户态】这个问题,而产生。

epoll 是内核 2.6 以后开始支持的。

不同操作系统平台的epoll 实现不同,效率不同。

//创建epollFd,底层是在内核态分配一段区域,底层数据结构红黑树+双向链表
int epoll_create(int size);//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大
 
//往红黑树中增加、删除、更新管理的socket fd
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
 
//这个api是用来在第一阶段阻塞,等待就绪的fd。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

1. int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大,这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值,参数size并不是限制了epoll所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议。
当创建好epoll句柄后,它就会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
 
2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
函数是对指定描述符fd执行op操作。
- epfd:是epoll_create()的返回值。
- op:表示op操作,用三个宏来表示:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分别添加、删除和修改对fd的监听事件。
- fd:是需要监听的fd(文件描述符)
- epoll_event:是告诉内核需要监听什么事,struct epoll_event结构如下:
struct epoll_event {
  __uint32_t events;  /* Epoll events */
  epoll_data_t data;  /* User data variable */
};
 
//events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
 
3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待epfd上的io事件,最多返回maxevents个事件。
参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

epoll 的工作模式

epoll 对文件描述符的操作有两种模式:LT(level trigger)和 ET(edge trigger)。

LT 模式 LT(level triggered)是缺省的工作方式,并且同时支持 block 和 no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行 IO 操作。如果你不作任何操作,内核还是会继续通知你的。

ET 模式

ET(edge-triggered)是高速工作方式,只支持 no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过 epoll 告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个 EWOULDBLOCK 错误)。但是请注意,如果一直不对这个 fd 作 IO 操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)

ET 模式在很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。epoll 工作在 ET 模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

epoll 与select、poll 的区别

一开始就在内核态分配了一段空间,来存放管理的 fd,所以在每次连接建立后,交给 epoll 管理时,需要将其添加到原先分配的空间中,后面再管理时就不需要频繁的从用户态拷贝管理的 fd 集合。通通过这种方式大大的提升了性能。

优点

复用了系统调用,单线程有限次系统调用就可以管理全部FD

缺点

  1. 在read前阻塞了用户进程。

  1. 将事件探测和事件响应夹杂在一起,没有进行解耦合。

异步IO(Asynchronous I/O)

简介

异步 IO 指:内核态拷贝数据到用户态这种方式也是交给系统线程来实现,不由用户线程完成,即全部完成后,才会用其他的方式通知用户。

Linux 下的 asynchronous IO 用在磁盘 IO 读写操作,不用于网络 IO,从内核 2.6 版本才开始引 入。

用户进程发起 read 操作之后,立刻就可以开始去做其它的事。而另一方面,从 kernel 的角度,当它受到一个 asynchronous read 之后,首先它会立刻返回,所以不会对用户进 程产生任何 block。然后,kernel 会等待数据准备完成,然后将数据拷贝到用户内存,当 这一切都完成之后,kernel 会给用户进程发送一个 signal,告诉它 read 操作完成了。

windows 系统的 IOCP 是属于异步 IO。

信号驱动IO(signal driven I/O, SIGIO)

简介

允许套接口进行信号驱动 I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函 数处理数据。当数据报准备好读取时,内核就为该进程产生一个 SIGIO 信号。我们随后既可 以在信号处理函数中调用 read 读取数据报,并通知主循环数据已准备好待处理,也可以立 即通知主循环,让它来读取数据报。无论如何处理 SIGIO 信号,这种模型的优势在于等待数 据报到达(第一阶段)期间,进程可以继续执行,不被阻塞。免去了 select 的阻塞与轮询,当 有活跃套接字时,由注册的 handler 处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ym影子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值