IO多路复用(select poll epoll)

IO多路复用是高性能网络编程的关键技术,通过单线程管理多个I/O流,提高了服务器的吞吐能力。本文介绍了select、poll和epoll三种实现方式,强调了epoll在效率和资源管理上的优势。select存在文件描述符数量限制、轮询效率低等问题;poll虽然扩大了文件描述符数量,但仍存在轮询效率问题;epoll采用回调机制,避免了轮询并提供线程安全性。
摘要由CSDN通过智能技术生成

IO多路复用是高性能网络编程一个重要的手段。

一,IO多路复用的概念
以前我们用多线程来处理并发的请求,现在可以只用单线程来实现。单线程,通过记录跟踪每个每个I/O流(sock)的状态,来达到同时管理多个I/O流的目的,提高了服务器的吞吐能力。

这里写图片描述
如图所示,IO多路复用,就如同中间的开关,哪个sock就绪就连上开关,达到了单开关处理了多个I/O流的目的。这就是单线程却能处理多个Sock传输数据的IO多路复用模型。(当Sock请求很多时,可以进行分组,一个线程控制一个组,把多线程与IO复用结合使用)

之前的博客介绍了多线程的方式,两者的区别在于
1. 多线程模型适合于处理短连接,且连接的打开关闭非常频繁的情况,但不适合处理长连接。
2. 多线程毕竟是要耗费资源的,IO多路复用基本不耗费资源,也不必创建,维护线程,使得系统的开销大大减小,效率变得更快。(效率与单线程比较,不一定比多线程快)

I/O复用典型使用在下列网络应用场合:
1. 当客户处理多个描述符(通常是交互式输入和网络套接字)时,必须使用I/O复用。
2. 如果一个TCP服务器既要处理TCP,又要处理UDP,一般就要用I/O复用。
3. 如果一个服务器要处理多个服务或者多个协议,一般就要使用I/O复用。

二,I/O多路复用的实现
在I/O多路复用这个概念被提出以后,select()是第一个实现的(1983年)。

select()原理:

1、使用copy_from_user从用户空间拷贝fd_set到内核空间
2、注册回调函数__pollwait
3、遍历所有fd,调用其对应的poll方法(对于socket,这个poll方法是sock_poll,sock_poll根据情况会调用到tcp_poll,udp_poll或者datagram_poll)
4、以tcp_poll为例,其核心实现就是__pollwait,也就是上面注册的回调函数。
5、__pollwait的主要工作就是把current(当前进程)挂到设备的等待队列中,不同的设备有不同的等待队列,对于tcp_poll来说,其等待队列是sk->sk_sleep(注意把进程挂到等待队列中并不代表进程已经睡眠了)。在设备收到一条消息(网络设备)或填写完文件数据(磁盘设备)后,会唤醒设备等待队列上睡眠的进程,这时current便被唤醒了。
6、poll方法返回时会返回一个描述读写操作是否就绪的mask掩码,根据这个mask掩码给fd_set赋值。
7、如果遍历完所有的fd,还没有返回一个可读写的mask掩码,则会调用schedule_timeout是调用select的进程(也就是current)进入睡眠。当设备驱动发生自身资源可读写后,会唤醒其等待队列上睡眠的进程。如果超过一定的超时时间(schedule_timeout指定),还是没人唤醒,则调用select的进程会重新被唤醒获得CPU,进而重新遍历fd,判断有没有就绪的fd。
8、把fd_set从内核空间拷贝到用户空间

如下图流程所示:
这里写图片描述
通俗点说,select将所有fd放入一个集合中,不停轮询遍历集合,并将未就绪的fd踢出集合,系统可以通过集合来调动就绪fd,只要fd就绪就会被放入集合处理,可以等价于select管理了所有的fd!

当然select细节还有很多,后面提供代码分析。先来总结select的不足之处:
1. 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
2. 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。
3. select 如果任何一个sock(I/O stream)出现了数据,select 仅仅会返回,但是并不会告诉你是那个sock上有数据,只能自己一个一个的找,数据大时不方便。
4. select不是线程安全的,不同线程不可对同一sock操作。
5. select支持的文件描述符数量太小了,默认是1024。

select代码如下:

#include"../unp.h"
#include<malloc.h>

typedef struct server_context_st
{
    int cli_cnt; 
    int clifds[SIZE];
    fd_set allfds;
    int maxfd;
}server_context_st;

static server_context_st *s_srv_ctx = NULL;

int server_init()
{
    s_srv_ctx = (server_context_st*)malloc(sizeof(server_context_st));
    if(s_srv_ctx == NULL)
        return -1;
    memset(s_srv_ctx, 0, sizeof(server_context_st));
    for(int i=0; i<SIZE; ++i)
    {
        s_srv_ctx->clifds[i] = -1;
    }
    return 0;
}
void server_uninit()
{
    if(s_srv_ctx)
    {
        free(s_srv_ctx);
        s_srv_ctx = NULL;
    }
}

int create_server_proc(const char *ip, short port)
{
    int fd;
    fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd == -1)
    {
        perror("socket");
        return -1;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值