网络IO之由浅入深(1)

1、啥是IO复用

网络交互比喻

服务员 和 客户的关系,每天都会有客户到餐厅用餐,而每次用餐都需要服务员的招待,如果没有服务员的招待则会出现餐厅异常的情况,所以当客户的出现则必须要有服务员的接待,

1.1、但是接待会出现两种情况:

(1)一种是每出现一个客户,则专门指派一个服务员进行服务,此服务员只处理这个客户的请求

(2)另一种则是,安排一个服务员同时对接多个食客,也能保证食客的客户请求

在这里插入图片描述
(食客 与 服务员的两种图)

1.2、这种接待形式对应的也是我们cs架构中两种形式:

(1)在早期的cs架构中内核没有提供IO复用的接口时,

就是一个client 连接请求过来,server端直接使用一个进程或者线程去对接,这样一个检测fd的接收缓存中是否有数据,如若有数据则进行读取接收,众所周知基于Linux的线程开辟的进程资源都是受限于内核空间,则注定了不能多开,所以一个进程/线程对应一个fd在之后的历史发展中就慢慢淘汰了

fork(callback());
void callback(){
    while(1){
        int ret = recv();
        if(ret > 0){
            send();
        }
    }
}

在这里插入图片描述

(2)而第二种也就是今天的主角,一个服务员接待多个食客乃至N多个这种行为叫做IO 复用。

while(1){
    int ret = ( io 复用接口(select / poll / epoll));
    for(int i = 0 ; i < ret ; i ++){
        // 处理每一个IO的事物
    }
}

在这里插入图片描述

2、server端如何接受数据

2.1、相关知识

2.1.1、阻塞、非阻塞、同步、异步

以出门去商店打酱油的方式讲一下这四种形式

同步:(占用客户资源,己方线程)

(1)阻塞:客户出门之后去打酱油,商店没有酱油了,客户此时一直到商店有酱油了再回来

(2)非阻塞:出门去打酱油,商店没有酱油了,客户就回家,不就之后又来商店看是否有酱油,如果没有酱油,客户就一直执行回家、来店咨询

异步:(占用商家资源,就是对方线程)

客户出门之后去打酱油,商家没有了,客户说有了给我送过来

2.1.2、socket 的组成

socket的是由接收缓存、发送缓存、等待队列和异步通知队列一些成员组成(这些存在都是由内核进行管理)

所以如果应用层要收到数据最先收到的是socket的接收缓存

2.1.3、进程调度:

Linux经常是以多进程存在,但是又时常都是单核的CPU,

所以在同一个时间是没法运行多个进程,就会存在有的进程在运行,而有的进程则在等待,就会有以下两种队列

(1)工作队列
多个业务需要执行时就将数据排列在工作队列,等待时间轮片算法的调度

(2)等待队列
在我们所说的socket阻塞模式下,这些负责管理的阻塞socket的进程就在等待队列休眠,直到socket的接收缓存中有了数据,等待操作系统的唤醒

在这里插入图片描述

2.2、网口接收数据

接收流程

(1)我们的数据最开始是先到达至网口,

(2)网口将数据映射至Linux内存中,

(3)通过硬件中断通知到cpu(众所周知硬件中断是级别最高的通知),CPU将网络数据写至socket的接收缓存

在这里插入图片描述

(4)数据在接收缓存可读写之后,应用层根据之前所说

  • 要么自身的进程一直读取此IO,直到读取到数据(同步操作)

  • 要么通过各种IO复用的形式管理这些IO,保证数据已经在缓存中的时候应用层能知道,并将句柄发送给专门的线程池进行读取数据(同步、异步都有)

在这里插入图片描述

3、IO复用之select

3.1、Select 使用方式

fd_set set;

FD_ZERO(&set); /将set清零使集合中不含任何fd/

FD_SET(fd, &set); /将fd加入set集合/

FD_CLR(fd, &set); /将fd从set集合中清除/

FD_ISSET(fd, &set); /在调用select()函数后,用FD_ISSET来检测fd是否在set集合中,
当检测到fd在set中则返回真,否则,返回假(0)
/


FD_ZERO(&readfds);              // 初始化集合
FD_SET(server_sockfd, &readfds);// 将服务器端socket加入到集合中

testfds = readfds;
while(1) 
{
	select(&testfds); // 内核中针对readfds 集合句柄进行检测 ,将有数据的句柄返回回来
	
	/*扫描所有的文件描述符*/
    for(fd = 0; fd < FD_SETSIZE; fd++) 
    {
        /*找到相关文件描述符*/
        if(FD_ISSET(fd,&testfds)) 
    	{
    		if(fd == can_connect)        // 如果与服务端FD 一致,则接受客户请求建立链接,并将建立链接对应的客户FD加入集合
    		{
    		    aeecpt(socket);
    		    FD_SET(client_sockfd, &readfds);
    		}
    		else if(fd == can_read)    // 如果是集合中的客户FD,则读取
    		{
    			read(socket, buffer);
    			process(buffer);
    		}
    		 
    	}
}

3.2、Select 底层实现原理

回顾知识点:

(1)socket 拥有 读写缓存区、等待队列、异步通知队列

  • 等待队列:

当有一个socket事件时,会将其相关的进程放至其等待队列上等待唤醒

在这里插入图片描述

(1)select 将其管理所有socket的等待队列都填写的当前进程A

(2)当有一个或者多个socket收到数据的时候,就会将进程A从所有的等待队列中A进程移除,加入到工作队列中

(3)进程将他管理的socket缓存便利一遍即可知道哪些socket有数据了

(4)将对应的集合以比特位的形式返回

其一,每次调用select都需要将进程加入到所有监视socket的等待队列,每次唤醒都需要从每个队列中移除(此时已经成为工作队列中节点,不再适合等待队列)。这里涉及了两次遍历,而且每次都要将整个fds列表传递给内核,有一定的开销。正是因为遍历操作开销大,出于效率的考量,才会规定select的最大监视数量,默认只能监视1024个socket。

其二,进程被唤醒后,应用程序并不知道哪些socket收到数据,还需要遍历一次。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值