深入剖析linux 网络io多路复用

1.概要:

 网络 IO,会涉及到两个系统对象,一个是用户空间调用 IO 的进程或者线程,另一个是内核空间的内核系统,比如发生 IO 操作 read 时,它会经历两个阶段: 1. 等待数据准备就绪 2. 将数据从内核拷贝到进程或者线程中。 因为在以上两个阶段上各有不同的情况,所以出现了多种网络 IO 模型

 当用户进程调用了 read 这个系统调用,kernel 就开始了 IO 的第一个阶段:准备数据。对于 network io 来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的数据包), 这个时候 kernel 就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当 kernel 一直等到数据准备好了,它就会将数据从 kernel 中拷贝到用户内存,然后 kernel 返回结果, 用户进程才解除 block 的状态,重新运行起来。 所以,blocking IO 的特点就是在 IO 执行的两个阶段(等待数据和拷贝数据两个阶段)都被 block 了,所以服务器端的进程就被阻塞在accept()函数这里,一种解决方法是将accept()后的程序放在另外的线程中处理,让accept()继续监听,但这会涉及到线程的创建与销毁,有性能延迟,而创建一个线程池是对应的方法。但是当有几千个客户端同时连接上来的时候,线程池也无法处理得过来。如果将accept()设置为非阻塞,那么accept()不知道何时会有客户端连接上来。所以,如果内核有一种机制,可以来通知程序何时有客户端连接,那么这个问题就解决了。

2. 什么是网络io多路复用:

在linux系统中,涉及到不同资源之间的信息交换的行为叫做io,比如,内存io,磁盘io,网络io就是用户程序与内核程序之间的网络信息交换。网络io多路复用可以监管用户程序与内核之间的网络报文的交换,通过网络io多路复用,用户程序知道什么时候会有网络报文到达,而不用自己去向内核轮询,可以理解为一个通路,用户程序向这个通路发送和接收网络信息,内核也是;并且有信息的时候,它会提醒用户程序和内核。

linux系统提供了select,poll,epoll三种系统调用去实现网络io多路复用;select,poll的效率低下,因为它们会去轮询已经到达的fd,是否有数据到达,当fd多了的时侯,轮询的开销就变大了;linux系统有了epoll系统调用以后,就变成了真正的服务器os, 因为epoll支持网络并发性能很高,epoll是支持百万并发的。所以,我们也可以看到 io 多路复用只负责检测 io,不负责操作 io。

 下面,我将重点讲epoll的原理与应用:

3. epoll

首先,epoll在内核中的实现是两个数据结构,红黑树储存连接事件块,双向链表储存就绪的事件

红黑树的结构体:

struct epoll_event {
    __uint32_t events;  // 事件类型
    epoll_data_t data;  // 用户数据
};

typedef union epoll_data {
    void *ptr;         // 指针类型的用户数据
    int fd;            // 文件描述符类型的用户数据
    __uint32_t u32;    // 32 位整数类型的用户数据
    __uint64_t u64;    // 64 位整数类型的用户数据
} epoll_data_t;

双向链表的结构体:

struct eventpoll {
    // ...
    struct rb_root rbr; // 管理 epoll 监听的事件
    struct list_head rdllist; // 保存着 epoll_wait
返回满⾜条件的事件
    // ...
};

struct epitem {
    // ...
    struct rb_node rbn; // 红⿊树节点
    struct list_head rdllist; // 双向链表节点
    struct epoll_filefd ffd; // 事件句柄信息
    struct eventpoll *ep; // 指向所属的eventpoll对
象
    struct epoll_event event; // 注册的事件类型
    // ...
};

epoll提供了三个接口,epoll_create(), epoll_trl((),epoll_wait();

#include <sys/epoll.h>
#include <iostream>

#define POLL_SIZE 1024

 int epfd=epoll_create(1);//创建成功,返回epfd,参数非负数 if (epfd<=0) return ;
 if(epfd <=0)  return ;
 struct epoll_event events[POLL_SIZE] = {0};//传入传出参数,将数据从内核态拷贝到用户态
 struct epoll_event ev; //刚开始注册的epoll event

 ev.events = EPOLLIN;
 ev.data.fd = listenfd;

 int ret=epoll_ctl(epfd, EPOLL_CTL_ADD,listenfd,&ev);
 if(ret==-1){
    std::cout<<"epoll_ctl failed"; //也可以使用strerror(errno);
    return;
 }

while(1){
    /*epoll_wait成功时,返回发生的事件描述符fd,失败时返回-1;
    第二个参数events是一个传入传出函数,POLL_SIZE是events的大小
    最后一个参数timeout 为-1时,表面epoll_wait是阻塞的,没有事件到达时,将阻塞程序
    如果 timeout的值 为 0,表示立即返回,无论是否有事件发生。
    如果 timeout的值 大于 0,表示等待指定的毫秒数后返回,无论是否有事件发生。*/
    int nready=epoll_wait(epfd,events,POLL_SIZE,-1);
    if(nready==-1){
        std::cout<<"epoll_ctl failed"; //也可以使用strerror(errno);
         continue;
    }

    for(int i=0; i<nready;i++){
        int clientfd =events[i].data.fd;
        if (clientfd == listenfd) {
            struct sockaddr_in client;
		    socklen_t len = sizeof(client);
		     if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
			     printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
			     return 0;
			    }
            ev.events = EPOLLIN;
			ev.data.fd = connfd;
			epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
        }else if (events[i].events & EPOLLIN) {
             n = recv(clientfd, buff, MAXLNE, 0);			
        }
   }

调用 epoll_create 会创建一个 epoll 对象;调用 epoll_ctl 添加到 epoll 中的事件都会与网卡驱动程序建立回调关系,相应事件触发时会调用回调函数 (ep_poll_callback),将触发的事件拷贝到 rdlist 双向链表中;调用 epoll_wait 将会把 rdlist 中就绪事件拷贝到用户态中;

总结:其实在使用epoll的时候,是要配合事件触发来设计相应的事件结构体,也就是reactor模式的epoll,这个时候epoll就能发挥最大的性能;另外,在使用的时候,都是one epoll per thread,下篇文章将分析reactor模型的epoll封装。

                                                                ----------------------------------------------------------------- -end!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值