网络编程之IO多路复用

9 篇文章 3 订阅

首先了解一下什么是流,什么是I/O

  1. 可以进行I/O操作的内核对象。
  2. 比如文件,管道,套接字等都会有流的概念。
  3. 流的入口一般是文件描述符,在Linux中一切皆文件。
  4. 文件的读写需要通过流来进行,而对流的读写操作即为I/O

阻塞

在处理问题时我们一般选择阻塞的方式,节省CPU资源。但不是绝对的。

  1. 阻塞等待:此期间不占用CPU的时间。
  2. 非阻塞轮询:所谓轮询是指不停的询问,占用CPU资源。

当然阻塞的缺点也很明显,比如说我等待的资源刚好这一时刻只来了一个需要我处理的资源,我结束等待处理完成皆大欢喜。但是资源并不是一个接一个的,比如取快递来说,快递没来我在阻塞等待,突然100个快递一起来了,那我就有的忙了。这就是阻塞式等待的缺点。

IO多路复用

因为上述方法不能满足用户需求,所以就有了IO多路复用。
它既有阻塞等待CPU资源节约的优点,还能兼顾同一时刻响应多路请求。

select函数

select函数在并发请求时的作用:继续上述的取快递为例,快递没来之前我CPU处于阻塞休息的状态,突然有100个快递来了,这时候select监听到了有快递来了,但是它监听的数量有限一般就是1024个。select监听到了有快递来告诉我(CPU)要去取快递,但是select不会告诉我具体是哪个快递到了,所以我只能知道有快递到了,这时我再去便利这所有的快递挨个询问是哪个到了,把到了的快递取了(处理请求)。

具体工作如下:

while(1){
	select([]);阻塞
	for(int i = 0; i < maxSize; ++i){
		if(i == 数据){
			处理
		}
	}
}

epoll
虽然说我可以休息避免了不停的问,但是如果只来了3个快递,我还要挨个打电话去问,其余的1021次就是在浪费时间。

所以就有了epoll();

epoll就强大了,它的监听工作和select一样,但是他会同时告诉我有几个快递到了并且具体到是哪个快递。这样我CPU的工作量就大大减轻了。而且epoll所监听的个数往往比1024还要大很多。

epoll的伪代码如下:

while(1){
	需要处理的流[] = epoll_wait(epoll_fd);  // 阻塞
	for(int i = 0; 需要处理流.size; ++i){
		CPU处理 = 需要处理的流[i];
	}
}

epoll虽强但只是Linux独有的。而select是平台无关的。
像其他有用到epoll的都是对原生Linux C中epoll的封装。

什么是epoll

  1. 与select和poll一样,对 I /O多路复用的技术。
  2. 只关心“活跃的连接”,无需遍历不需要CPU处理的描述符。
  3. 能够处理大量的连接请求。(系统能够打开最大的文件个数)(linux下可以用 cat /proc/sys/fs/file-max 查看 一般远远大于1024)

epoll API

  1. 创建epoll:int epoll_create(int size);

  2. 控制epoll:int epoll_ctl(int epfd, int op, struct epoll_event *event);在这里插入图片描述在这里插入图片描述

  3. 等待epoll:int epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout),在这里插入图片描述

  4. C语言epoll编程思路在这里插入图片描述

  5. epoll的两种触发模式:水平触发(LT)和边缘触发(ET)
    水平触发:内核会不断的抛出已经被激活的事件,直到该事件被用户处理完才能返回结果。类似于TCP是比较安全的,不会丢包的处理方式。
    边缘触发:内核只会抛出用户需要处理的事件,只会通知用户一次,后面不管用户处理还是不处理内核都不管。类似于UDP只管发不管你收到没收到,可能丢包的处理方式。

常见服务器的设计模型

单线程Accept

只适合学习的demo练习,实际企业不会使用这种设计模型。

单线程Accept + 多线程读写业务

类比与上一种优化了多个客户端的响应,但是线程之间的切换成本很高,也不实用。

单线程多路 I / O 复用

优点:解决了同时监听多个客户端的读写模型,该模型是阻塞式、非忙轮询的,不浪费CPU资源,对CPU的利用率较高。
缺点:虽然是监听了多个用户,但是同一时刻下正在处理的业务只能是一个,并发为1。当多个客户端同时访问时,由于该模型是串行处理的方式,会造成排队延迟的问题。

在客户端比较少的情况下可以使用。

单线程多路 I / O 复用 + 多线程读写业务(工作池)

较上一种模型降低了排队延迟,因为业务的处理交给了线程处理,主线程只负责分配任务然后响应客户端的读写请求。但是读写的处理并发为1,所以这种模型的并发量不是特别高,依然会出现排队延迟的问题。比单纯使用 IO多路复用的效率要高一些。

单线程多路 I / O 复用 + 多线程多路 I / O 复用(连接线程池)

优点:这种模型将读写的监听以及读写业务的处理都交给一个线程去做,主线程只负责分配资源,这样同一时刻的并发量就是N (线程池中线程的数量)。并发量相比于上述几种要高很多很多。CPU的利用率大大提高。如果线程池数量和CPU核数适配那么可以尝试将CPU核心与线程进行绑定,从而降低线程切换频率,极大的利用了CPU的资源。
缺点:虽然并发量显著提高,但是最高也不过是N,当有多个客户端请求同一个线程时依然会有排队等待的现象。实际上该模型就是 N × (单线程多路 I / O复用)。

目前大部分企业服务器的设计都用的该模型。

单线程多路 I / O 复用 + 多线程多路 I / O 复用(线程池)+ 多线程

模型分析:
①Server在启动监听之前,开辟固定数量(N)的线程,用Thead Pool线程池管理
②主线程main thread创l建listenFd之后,采用多路/O复用机制(如:select、epal)进行IO状态阻塞监控。有一个客户端Connect请求,I/O复用机制检测到ListenFd触发读事件,则进行Accept建立连接,并将新生成的connFd分发给Thread Pool中的某个线程进行监听。
③Thread Pool中的每个thread都启动多路 I/O复用机制(select、epoll,用来监听main thread建立成功并且分发下来的socket套接字。一旦其中某个被监听的客户端套接字触发 I/O读写事件,那么,会立刻开辟一个新线程来处理 I/O读写业务
④当某个读写线程完成当前读写业务,如果当前套接字没有被关闭,那么将当前客户端套接字如:ConnFd重新加回线程池的监控线程中,同时,自身线程自我销毁。

优点:在上一模型基础上,除了能够保证同时响应最高的并发数,又能够解决读写并行通道的局限问题。
同一时刻的读写并行通道,达到了最大化极限,一个客户端可以对应一个单独的执行流程处理读写业务,读写并行通道与客户端的数量1∶1关系。

缺点:过于理想化。因为要求CPU核心数数量足够大。
如果硬件CPU数量可数,那么该模型就造成大量的CPU切换的成本浪费。因为为了保证读写并行通道和客户端是1:1的关系,就要保证server开辟的thread的数量与客户端一致。

综上,
最适合企业服务器的设计模型就是单线程多路 I / O 复用 + 多线程多路 I / O 复用(连接线程池)

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值