设为首页 加入收藏

TOP

20分钟了解Epoll + 聊天室实战(一)
2019-04-02 20:08:09 】 浏览:442
Tags:20分钟 了解 Epoll 聊天室 实战

我们知道,计算机的硬件资源由操作系统管理、调度,我们的应用程序运行在操作系统之上,我们的程序运行需要访问计算机上的资源(如读取文件,接收网络请求),操作系统有内核空间和用户空间之分,所以数据读取,先由内核读取数据到内核缓冲区,然后才会从操作系统的内核空间拷贝到用户空间,这个就是缓存I/O,又被称作标准I/O。

几种常见的IO模式:阻塞I/O、非阻塞I/O、I/O多路复用

1、阻塞I/O

用户进程向内核发起I/O系统调用,内核去准备所需的数据,直到数据都准备好了(需要一段时间)返回给用户进程,在这期间,用户进程一直处于阻塞状态,拿到所需数据,才会继续向下执行。

2、非阻塞I/O

用户进程向内核发起I/O系统调用,内核发现数据还没准备好,立即返回error,用户进程拿到error,可以再次向内核发起请求,直到获取所需数据

3、I/O多路复用

下面详细介绍

一、I/O多路复用

定义:I/O multiplexing allows us to simultaneously monitor multiple file descriptors to see if I/O is possible on any of them.

1、为什么要同时监视多个文件描述符?

传统的阻塞I/O模型可以满足大部分的应用程序使用场景,但有的时候,一些应用程序会同时需要如下特性:

  • 检查文件I/O是否已经ready,如果没有,不阻塞,直接返回
  • 同时监视多个文件描述符,判断他们中的任意一个的I/O是否已经ready

比如一个web服务器,可能同时打开了几千个连接,每accept一个连接,操作系统产生一个fd(文件描述符),我们的服务器需要监听这些文件描述符,当客户端发来新的数据的时候,我们需要处理请求数据,并给出响应,正常的实现方式可能如下:

connections = [fd1, fd2, fdn];
for (c in connections) {
    if (hasNewInput(x)) {
        processInput(x);
    }
}

这样实现的问题是,我们自己维护着所有的连接,需要主动的去轮寻判断I/O是否已经ready以便进行读写,但事实上并不是所有连接都是活跃状态(建立的连接并没有数据交互),如果我们轮寻的频率很低,那用户获取响应的时间可能长的无法忍受,如果轮寻的频率非常的高,则会浪费CPU的时间。

所以如果可以将主动遍历所有连接,判断每一个的IO状态,改为将这些文件描述符(fd)交给内核去监视,然后当一个或多个文件描述符IO ready的时候,内核来告诉我们,这样效率就大大提高了,因此我们需要IO多路复用。

2、I/O多路复用的几种实现

IO多路复用的实现比较普遍的有两个:select和poll,相比较poll,select更为普遍,最早与BSD socket api一起出现,已被纳入SUSV3标准规范,epoll是只属于Linux的特性,最早的API出现在Linux2.6。

名字叫I/O多路复用,所谓的复用,复用的是同一个进程(线程),也就是在同一个进程中“并发“的完成多个文件描述符的I/O。

2.1、select

select系统调用会阻塞,直到一个或多个文件描述符I/O ready

1>定义:

#include <sys/time.h>         /* For portability */ 
#include <sys/select.h> 

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeva l *timeout);

2>参数:

  • nfds:应设置为大于等于下面三个fds监听的文件描述符的总和
  • readfds:需要监听输入事件的文件描述符集合
  • writefds:需要监听输出事件的文件描述符集合
  • exceptfds:需要被监听是否有异常情况发生的文件描述符集合
  • timeout:,阻塞;设置为0,检查一遍文件描述符状态立即返回;设置为NULL或者非0值,会一直阻塞直到:
    • 三种fds中有至少一个文件描述符进入ready状态
    • 被信号处理中断
    • 到达指定的超时时间

3>返回值:

  • 返回-1:产生错误
  • 返回0:超时返回了,这期间没有文件描述符ready
  • 返回正数:返回的数值就是已经ready的文件描述符的数量

4>小示例

#include <stdio.h>
#include <string.h>
#include <sys/time.h> 
#include <sys/select.h>

int main(int argc, char *argv[])
{
    fd_set readfds, writefds;
    int ready, nfds, fd, numRead, j;
    struct timeva l timeout;
    struct timeva l *pto;
    char buf[10];
    
    //判断参数是否包含短斜线,包含timeout设置NULL
    if (strcmp(argv[1], "-") == 0) {
        pto = NULL;
    } else {
        pto = &timeout;
        pto.tv_sec = atoi(argv[1]);
        pto.tv_usec = 0;
    }
    //准备监听文件描述符集合
    nfds = 0;
    //用select 提供的宏初始化fd_set
    FD_ZERO(&readfds);
    FD_ZERO(&writefds);

    for (j = 2; j < argc; j++) {
        numRead = sscanf(argv[j], "%d%2[rw]", &fd, buf);
        if (numRead != 2) {
            printf("error");
            return 0;
        }
        if (fd >= nfds) {
            nfds += 1;
        }
        //判断是监听读还是写
        if (strchr(buf, 'r') != NULL) {
            FD_SET(fd, &readfds);
        }
        if (strchr(buf, 'w') != NULL) {
            FD_SET(fd, &writefds);
        }
    }
    //调用select函数
    ready = select(nfds, &readfds, &writefds, NULL, pto);
    if (ready == -1) {
        printf("select error\n");
        return 0;
    }
    for (fd = 0; fd < nfds; fd++) {
        //打印ready 状态
        printf("%d: %s%s\n", fd, FD_ISSET(fd, &readfds) ? "r" : "", FD_ISSET(fd, &writefds) ? "w" : "");
    }
    return 0;
}

使用文件描述符0 也就是标准输入测试,首先编译一下这段代码gcc -o select select.c

运行./select 10 0r

首页 上一页 1 2 3 4 下一页 尾页 1/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇[C]最大公约数和最小公倍数 下一篇C++编程丨C++11的指针

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目