码迷,mamicode.com
首页 > 编程语言 > 详细

Linux多线程编程----IO【select、poll、epoll】

时间:2016-08-11 07:30:22      阅读:420      评论:0      收藏:0      [点我收藏+]

标签:linux

IO操作多   速度就下降

IO数据的 读和写

IO的完成 必须等到 读事件(如磁盘 拷贝  每次要从磁盘查找数据) 和 写事件 (允许写 如写太快 写满就要马上阻塞)的就绪

IO是否高效 :主要看一次IO中 等的时间的比例的多少 (等的时间比例越少  越高效) 

就像钓鱼分两步:1 等 2 钓   (评价钓鱼技术高效 是 等的时间少 钓的次数多)

5中IO模型

1 阻塞式IO: 等的时候自己等   , 数据搬迁也是由自己来操作, IO事件就绪时 自己处理。期间什么也不做

2 非阻塞IO :(轮询方式) 期间做自己的事情

3 信号驱动: (信号事件驱动)期间做自己的事情(IO请求事件 条件满足时 来通知我 , 我再来处理,期间我自己做自己的事情)

4 IO复用:把多个IO事件托管给IO复用, 一次等待 多个 IO操作,有一个满足就来通知我, 我自己就去处理。等的比重小,IO操作的频率变大。

                    Linux中常见有select、poll、epoll

                    Windows中有 select、IOCP

5 异步IO:不用自己来处理, 只是自己将IO的操作分配给别人

1-4是同步IO ,(自己亲自处理,只是等待的方式不同)   5 是异步IO (自己不亲自处理,交给别人去处理)

技术分享

高级IO


文件描述符重定向


以前 将1号文件描述符 关闭    (1号是标准输出)再将 一个新的文件打开  这是printf则将内容 打印到 这个文件中  这样就 实现了文件描述符的 重定向

代码:

#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    char buf[1024];
    read(0, buf, sizeof(buf) - 1);
    close(1);
    int fd = open("./log", O_CREAT|O_RDWR, 0666);
    printf("%d--------%s",fd, buf); //  原本是打印到标准输出 现在 是 文件fd
    return 0;
}

运行:

[bozi@localhost dup]$ ./dup
asdcxz
[bozi@localhost dup]$ cat log
1--------asdcxz


太low

现在用 dup 就可以实现文件描述符的 重定向

 int dup(int oldfd);
       int dup2(int oldfd, int newfd);
       int dup3(int oldfd, int newfd, int flags);
       dup2() makes newfd be the copy of oldfd, closing newfd first if  neces-                  如果有必要 拷贝(这里拷贝 是拷贝文件描述符里面的内容 而不是文件描述符)前 关闭 newfd的文件描述符
       sary, but note the following:                                                                                  例如  将5(oldfd新打开的文件描述符)重定向到1(newfd标准输出) 如果一之前打开着 关闭1
       *  If  oldfd  is  not a valid file descriptor, then the call fails, and
          newfd is not closed.
       *  If oldfd is a valid file descriptor, and newfd has the same value as
          oldfd, then dup2() does nothing, and returns newfd.

重定向  不光是 重定向文件描述符  还可以重定向 网络  这样 发数据 就直接发送到网络

dup练习代码:

#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
    int fd = open("./log", O_CREAT | O_RDWR,0666);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }
    close(1);// 关闭标准输出
    int new_fd = dup(fd);
    if (new_fd == -1)
    {
        perror("dup");
        return 2;
    }
    close(fd);
    char buf[1024];
    while (1)
    {
        memset(buf, ‘\0‘, sizeof(buf));
        fgets(buf, sizeof(buf), stdin);
        if (strncmp("quit", buf, 4) == 0)
        {
            break;
        }
        printf("%s", buf);// 打到文件 中
        fflush(stdout);
    }
    close(new_fd);
    return 0;
}

运行:

[bozi@localhost dup]$ ./dup
hello world
i am here
come on
quit
[bozi@localhost dup]$ cat log
hello world
i am here
come on

dup2 练习代码:

练习代码:
#include<stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    int fd = open("./log", O_CREAT|O_RDWR);
    if (fd < 0)
    {
        perror("open");
        return fd;
    }
    close(1);
    int ret = dup2(fd, 1); // fd->1
    char buf[1024];
    while (1)
    {
        memset(buf, ‘\0‘, sizeof(buf));
        fgets(buf, sizeof(buf) - 1, stdin);
        if (strncmp(buf, "quit", 4) == 0)
        {
            break;
        }
        printf("%s", buf);
        fflush(stdout);
    }
    close(fd);
    return 0;
}

运行:

[bozi@localhost dup]$ ./dup2
hello everybody
nihao sun
quit
[bozi@localhost dup]$ cat log
hello everybody
nihao sun
 on

pipe/socketepair   创建 双向通信管道

练习代码:

#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/socket.h>
int main()
{
int sv[2];// 文件描述符
if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sv) < 0)
{
perror("socketpair");
return 0;
}
pid_t id = fork();
if (id == 0)
{
// child 
close(sv[0]);
const char *msg = "I am child";
char buf[1024];
while (1)
{
write(sv[1], msg, strlen(msg));
memset(buf, 0, sizeof(buf));
ssize_t _s = read(sv[1], buf, sizeof(buf) - 1);
if (_s > 0)
{
// buf[_s] = ‘\0‘;
printf("%d father->child: %s\n",_s, buf);
}
else if (_s == 0)
{
printf("child wait father...\n");
}
else
{
exit(1);
}
sleep(2);
}
}
else
{
//father
close(sv[1]);// 只要 和 child 不一样就行
char buf[1024];
while (1)
{
  memset(buf, 0, sizeof(buf));
          read(sv[0], buf, sizeof(buf) - 1);
          printf("child -> father: %s\n", buf);
          strcpy(buf, "I am father");
         int sz = write(sv[0], buf, strlen(buf));
         //int sz = write(sv[0], buf, sizeof(buf) - 1); // error sizeof(buf)
printf("%d\n", sz);
}
}
}

运行:

[bozi@localhost test_20160806]$ ./my_socketpair
child -> father: I am child
11
11 father->child: I am father
child -> father: I am child
11
11 father->child: I am father
child -> father: I am child
11
11 father->child: I am father
child -> father: I am child
11
11 father->child: I am father




select     

  int select(int nfds, fd_set *readfds, fd_set *writefds,

                  fd_set *exceptfds, struct timeval *timeout);

     第一个参数是 输入型参数  其他 既是输入型又是输出型参数               

nfds表示 当前最大的文件描述符+1

               fd_set *readfds   调用时 表示 只关心文件描述符集中的读事件   返回时 表示 该文件描述符集 中 有哪些读事件 就绪

               fd_set *writefds  调用时 表示 只关心写事件

                fd_set *exceptfds    只关心 异常事件

struct timeval *timeout); 设置 超时时间 因为是 输入输出型的  如果没超时 返回的是 剩余的时间   如果超时  返回的是0   

       void FD_CLR(int fd, fd_set *set);

       int  FD_ISSET(int fd, fd_set *set);

       void FD_SET(int fd, fd_set *set);

       void FD_ZERO(fd_set *set);

练习代码1:

/* select 检测标准输入输出*/

#include<stdio.h>
#include <string.h>
#include <sys/select.h>
int main()
{
    fd_set read_set;
    fd_set write_set;
    FD_ZERO(&read_set);
    FD_ZERO(&write_set);
    int read_fd = 0;
    int write_fd = 1;
    int max_fd = 2;
    char buf[1024];
    while (1)
    {
        struct timeval timeout= {3,0};
        FD_SET(read_fd, &read_set);
        FD_SET(write_fd, &write_set);
        int ret = select(max_fd + 1, &read_set, &write_set, NULL, &timeout);// 除了第一个参数  后面4个 都是 输入-输出型参数
        switch(ret)
        {
            case -1:
                perror("select");
                return 1;
            case 0:
                printf("select timeout");
                break;
            default:
                {
                    if (FD_ISSET(read_fd,& read_set ))
                    {
                        memset(buf, ‘\0‘, sizeof(buf));
                        gets(buf);
                        if (!strncmp(buf, "quit", 4))
                        {
                            return 0;
                        }
                        printf("read_fd: %s\n", buf);
                    }
                    if (FD_ISSET(write_fd, &write_set))
                    {
                        strcpy(buf, "output buffer is ok");
                        puts(buf);
                        sleep(3);
                    }
                }
        }
    }
    return 0;
}

运行:

[bozi@localhost test_20160807]$ ./select_1
output buffer is ok
output buffer is ok
output buffer is ok
zai
read_fd: zai
output buffer is ok

练习代码2:

/* 利用select 编写网络服务器 */

#include<stdio.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
      #include <arpa/inet.h>
#define _PORT_ 9999
#define _MAX_SIZE_ 10
#define _BACK_LOG_ 5
#define _BUF_SIZE_ 1024
int fd_arr[_MAX_SIZE_];
int max_fd = 0;
static void init_fd_arr()
{
    int i =0;
    for (; i < _MAX_SIZE_; i++)
    {
        fd_arr[i] = -1;
    }
}
static int add_fd_arr(int fd)
{
    int i = 0;
    for (; i < _MAX_SIZE_; i++)
    {
        if (fd_arr[i] == -1)
        {
            fd_arr[i] = fd;
            return 0;
        }
    }
    return -1;
}
static int remove_fd_arr(int fd)
{
    int i = 0;
    for (; i < _MAX_SIZE_; i++)
    {
        if (fd_arr[i] == fd)
        {
            fd_arr[i] = -1;
            return 0;
        }
    }
    return -1;
}
static void reload_fd_set(fd_set *fd_setp)
{
    int i = 0;
    for (; i < _MAX_SIZE_; i++)
    {
        if (fd_arr[i] != -1)
        {
            FD_SET(fd_arr[i], fd_setp);
            if (fd_arr[i] > max_fd)
            {
                max_fd = fd_arr[i];
            }
        }
    }
}
static void print_msg(int i , char buf[])
{
    printf("fd : %d, msg: %s\n", i , buf);
}
int select_server()
{
    struct sockaddr_in server;
    struct sockaddr_in client;
    fd_set fds;
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
    {
        perror("socket");
        return 1;
    }
    printf("create socket success\n");
    int yes = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes ,sizeof(int));// 允许IP地址复用
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(_PORT_);
    server.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(fd, (struct sockaddr*)&server, sizeof(server)) < 0)
    {
        perror("bind");
        return 2;
    }
    printf("bind socket success\0");
    init_fd_arr();
    add_fd_arr(fd);
    FD_ZERO(&fds);
    if (listen(fd, _BACK_LOG_) < 0)
    {
        perror("listen error");
        return 3;
    }
    printf("listen socket success\n");
    while (1)
    {
        // 由于 select fds是 输入-输出型参数 完了值会被改变 所以每次从arr中重新加载
        reload_fd_set(&fds);
        struct timeval timeout = {30, 0};
        switch(select(max_fd + 1, &fds/*read*/,NULL, NULL, &timeout ))
        {
            case -1:
                printf("select error, quit\n");
                return 4;
            case 0:
                printf("select timeout, continue wait...\n");
                break;
            default:
                {
                    int index = 0;
                    for (; index < _MAX_SIZE_; index++)
                    {
                        if (index == 0 && fd_arr[index] != -1 && FD_ISSET(fd_arr[index], &fds))
                        {
                            socklen_t len = sizeof(client);
                            bzero(&client, len);
                            int new_fd = accept(fd_arr[index], (struct sockaddr*)&client,&len );
                            // 有新的连接
                            if (new_fd != -1)
                            {
                                printf("get a new request!\n");
                                // 添加到arr中
                                if (-1 == add_fd_arr(new_fd))
                                {
                                    perror("fd arr is full, close new fd\n");
                                    close(new_fd);
                                }
                            }
                            continue;
                        }
                        if (fd_arr[index] != -1&& FD_ISSET(fd_arr[index], &fds))
                        {
                            // 有客户端发送消息
                            char buf[_BUF_SIZE_];
                            memset(buf, ‘\0‘, sizeof(buf));
                            ssize_t size = read(fd_arr[index], buf,sizeof(buf) );
                            if (size == 0)
                            {
                                printf("remote client close\n" );
                                close(fd_arr[index]);
                                //printf("%d is close\n", fd_arr[index]);
                                // 清除 fds中相应的文件描述符
                                FD_CLR(fd_arr[index], &fds);
                                remove_fd_arr(fd_arr[index]);
                            }
                            else
                            {
                                print_msg(fd_arr[index],buf );
                            }
                        }
                    }
                }
        }
    }
}
int main()
{
    select_server();
    return 0;
}

代码:【客户端】

/* select_2 的客户端*/

#include<stdio.h>
#include <string.h>
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
       #include <netinet/in.h>
       #include <arpa/inet.h>
#include <stdlib.h>
void usage(const char* arg)
{
    printf("please enter : %s [IP] [PORT]\n",arg );
}
int select_client(char *ip, int port)
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
    {
        perror("create socket error");
        return 1;
    }
    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(ip);
    server.sin_port = htons(port);
    if (connect(fd, (struct sockaddr*)&server, sizeof(server)) < 0)
    {
        perror("connect error");
        return 2;
    }
    char buf[1024];
    while (1)
    {
        memset(buf, ‘\0‘, sizeof(buf));
        printf("please enter: ");
        fflush(stdout);
        fgets(buf, sizeof(buf) - 1, stdin);
        printf("buf is: %s\n", buf);
        int ret = send(fd, buf, strlen(buf) + 1, 0);
        if (ret < 0)
        {
            perror("send msg error\n");
        }
    }
    close(fd);
}
int main(int argc ,char* argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        return 44;
    }
    int port = atoi(argv[2]);
    select_client(argv[1],port);
    return 0;
}

servers iptables stop     关闭防火墙





poll  

输入的文件监听描述符 和 返回的 监听 到的 事件   与select 不同 select 是 输入-输出型 

poll 

不依赖于 文件系统, 所以他所关心的文件描述符的数量是没有限制的

poll代码:

 

   struct pollfd poll_set[2];
    //struct pollfd poll_set[1];
    //read
    poll_set[0].fd = 0;
    poll_set[0].events = POLLIN;
    poll_set[0].revents = 0;
    // write
     poll_set[1].fd = 1;
     poll_set[1].events = POLLOUT;
     poll_set[1].revents = 0;
    int timeout = 3000;
    char buf[1024];
    while (1)
    {
         switch(poll(poll_set, 2, timeout))
    //    switch(poll(poll_set, 1, timeout))
        {
            case -1:
                printf("poll error\n");
                return 1;
                break;
            case 0:
                printf("poll timeout\n");
                break;
            default:
                if (poll_set[0].revents & POLLIN)
                {
                    memset(buf, ‘\0‘, sizeof(buf));
                    printf("poll get \n");
                    fgets(buf, sizeof(buf) - 1, stdin);
                    printf("msg is: %s\n", buf);
                }
                if(poll_set[1].revents & POLLOUT)
                {
                    printf("pollout is ok\n");
                    sleep(1);
                }
                break;
        }
    }
    return 0;
}
int main()
{
    mypoll();
    return 0;
}

运行:

[bozi@localhost poll]$ ./poll
pollout is ok
pollout is ok
pollout is ok
pollout is ok
asd
pollout is ok
poll get
msg is: asd
pollout is ok
adasd

epoll    

区分: select 和 poll  是 在文件描述符之上的 不依赖于文件系统的

               epoll 是基于文件系统的  是占用文件描述符的

       int epoll_wait(int epfd, struct epoll_event *events,  int maxevents, int timeout);

struct epoll_event *events 输出型参数  返回  所有监听成功的事件

       The struct epoll_event is defined as :

           typedef union epoll_data {

               void    *ptr;

               int      fd;

               uint32_t u32;

               uint64_t u64;

           } epoll_data_t;

           struct epoll_event {

               uint32_t     events;    /* Epoll events */

               epoll_data_t data;      /* User data variable */

           };

代码:

#include<stdio.h>
#include <string.h>
       #include <sys/types.h>
       #include <sys/socket.h>
       #include <netinet/in.h>
       #include <arpa/inet.h>
        #include <sys/epoll.h>
#include <unistd.h>
#include <stdlib.h>
#define LISTEN_BACK_LOG 10
// 创建 监听套接字
int startup(char* ip, int port)
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        perror("socket");
        exit(1);
    }
    int op = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(int));
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr= inet_addr(ip);
    if (bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
    {
        perror("bind");
        exit(2);
    }
    if (listen(sock,LISTEN_BACK_LOG) < 0)
    {
        perror("listen");
        exit(3);
    }
    return sock;
}
int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        printf("please enter: %s [ip] [port]\n", argv[0]);
        exit(4);
    }
    int listen_sock = startup(argv[1], atoi(argv[2]));
    // 创建 epoll 句柄 事件表
    int epfd = epoll_create(101);// 这里数字不固定,100 只是告诉内核 预计用100个文件描述符 但实际这个参数不起作用 只是建议
    if (epfd < 0)
    {
        perror("epoll_create");
        exit(5);
    }
    struct epoll_event event;
    event.events = EPOLLIN; // 读 事件 列表
    event.data.fd = listen_sock;
    // 向 事件表中增加事件
    epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &event);
    struct epoll_event fd_events[100];
    int size = sizeof(fd_events)/sizeof(fd_events[0]);
    int i = 0;
    for (i = 0; i < size; i++)
    {
        fd_events[i].events = 0;
        fd_events[i].data.fd = -1;
    }
    int nums = 0;
    int timeout = 10000;
    int done = 0;
    while (!done)
    {
        // 返回就绪的文件个数
        nums = epoll_wait(epfd, fd_events, size, timeout);
        switch (nums)
        {
            case 0:
                printf("timeout...\n");
                break;
            case -1:
                perror("epoll_wait\n");
                exit(6) ;
            default:
                {
                    for (i = 0; i < nums; i++)
                    {
                        int fd = fd_events[i].data.fd;
                        if ((fd == listen_sock) && (fd_events[i].events & EPOLLIN))
                        {
                            // listen socket 有新的连接请求
                            struct sockaddr_in peer;
                            socklen_t len = sizeof(peer);
                            int new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
                            if (new_sock < 0)
                            {
                                perror("accept");
                                continue;
                            }
                            printf("get a new client, socket->%s:%d\n", inet_ntoa(peer.sin_addr),ntohs(peer.sin_port));
                            event.events = EPOLLIN;
                            event.data.fd = new_sock;
                            // 将new_sock 添加进内核事件表
                            epoll_ctl(epfd, EPOLL_CTL_ADD,new_sock, &event);
                        }
                        else
                        {
                            // other socket
                            // // 读事件满足 处理客户端发送的 数据
                            if (fd_events[i].events & EPOLLIN)
                            {
                                char buf[1024];
                                memset(buf, ‘\0‘, sizeof(buf));
                                ssize_t _s = recv(fd, buf, sizeof(buf) - 1, 0);
                                if (_s > 0)
                                {
                                    printf("client: %s\n", buf);
                                    event.events =    EPOLLOUT;//将fd事件改为写 方便服务器 给请求的客户端发数据
                                    epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event);
                                }
                                else if (_s == 0)
                                {
                                    printf("client close...\n");
                                    epoll_ctl(epfd, EPOLL_CTL_DEL,fd,NULL );// 空表示不关心返回值
                                    close(fd);
                                }
                                else
                                {
                                    perror("recv");
                                    continue;
                                }
                            }
                            else if(fd_events[i].events & EPOLLOUT)
                            {   
                                // WRITE
                                char *msg = "HTTP/1.1 200 OK\r\n\r\n<html><h1>hello ^_^</h1></html>\r\n";
                                send(fd, msg, strlen(msg), 0);
                                //event.events = EPOLLIN;
                                //epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event);
                                epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
                                close(fd);
                            }
                            else
                            {
                            }
                        }
                    }
                }
            break;
        }
    }
    exit(0);
}

运行:

[bozi@localhost epoll]$ ./epoll_IO 127.0.0.1 8888
get a new client, socket->127.0.0.1:59387
client: GET / HTTP/1.1
Host: 127.0.0.1:8888
User-Agent: Mozilla/5.0 (X11; Linux i686; rv:17.0) Gecko/20131029 Firefox/17.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive




比较 select、  poll 、epoll的优缺点

     (1)为啥多路复用效率高(IO等的时间少,有效工作时间比例大)

     (2)多路复用 服务器  与 之前的 线程 进程的服务器 比较 至少 高 1个数量级 

                         以前 多进程,大部分进程都是阻塞状态,等待状态。浪费比较大。

                         同时多进程占用的 空间 也比较大。

     (3)select

             1、select主要是通过3个文件描述符集(类似于位图,用上面的位来记录,有专门的对这些位操作的宏)来记录事件

             rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合。

               下面的宏提供了处理这三种描述词组的方式:

                    FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位

                    FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真

                    FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位

                    FD_ZERO(fd_set *set);用来清除描述词组set的全部位

               2、可监控文件描述符的个数取决于sizeof(fd_set)的大小,这个是有内核限制的,所以select所能监控的事件个数比较少

               3、select的3个文件描述符集是 输入-输出型参数,输入时表示要监听的事件,select运行后,3个文件描述符集表示发生的事件,(可以理解为将监听事件相应位为1中 发生的 发生的事件的位置1,没发生的位置0),这样下次还要监听这些事件时,就找不到之前的文件描述符集的值了,所以要额外用数组来记录要监听的文件描述符,以便于每次select之前初始化文件描述符集

               优点:多路复用的优点(IO等的时间少,有效工作时间比例大)

               缺点:(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

                         (2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

                         (3)select支持的文件描述符数量太小了,默认是1024( 使用32个整数的32位,即32*32=1024来标识fd

     (4)poll

               1、不同与select使用三个位图来表示三个fdset的方式,poll使用一个 pollfd的指针实现。可以创建struct pollfd的结构体,不再是按照三个位图读、写、异常来存放事件,而是分别设置每个pollfd结构体的事件属性。

                         struct pollfd

  {

    int fd;            /* File descriptor to poll.  */

    short int events;        /* Types of events poller cares about.  */ 监听事件类型 常见EPOLLIN 读事件  EPOLLOUT写事件

    short int revents;        /* Types of events that actually occurred.  */ 就绪事件

  };

                    2、 pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式

                   3、pollfd并没有最大数量限制(但是数量过大后性能也是会下降)。

                     优点:1、多路复用的优点

                              2、与select相比没有事件个数的限制,传的是pollfd数组指针,数组大小可以很大

                     缺点:和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符从上面看,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。

     (5)epoll(最高效的)

                         poll的改进版

               优点:

                          1、多路复用的优点

                         2、支持一个进程打开大数目的socket描述符

                              与select不同, epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,和机器内存大小有关。

                          3、IO效率不随FD文件描述符数目增加而线性下降

                              传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socke是"活跃"的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback(就绪事件链表)函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。

                         4、使用mmap加速内核与用户空间的消息传递,避免不必要的内存拷贝

                         这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工 mmap这一步的。

                         5、内核微调

                         这一点其实不算epoll的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。比如,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小--- 通过echoXXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。

                    缺点:

                         1、如果在并发量低,socket都比较活跃的情况下,select就不见得比epoll慢了

                         2、epoll跨平台不好 select  linux、windows都支持





epoll 为什么 快 高效   epoll 红黑树  就绪队列

技术分享

epoll高效的原理,就绪队列 和 红黑树

将注册的事件 插入到红黑树  中 这样每次操作系统找就绪的事件就比较快,找到后就链到就绪队列的链表中

                         这样每次epoll_wait 就只处理就绪队列的事件, 比起poll 和 select  每次全部遍历一遍 高效的多

                         LT每一次没处理完 就又放到 就绪队列中 下次epoll_wait还会返回当前未处理完事件

                          相反   ET 从就绪队列摘下来要处理的事件后 就不放回了  所以 ET要每次一次性处理完、同时ET模式下文件描述符必须设置为非阻塞的


epoll 默认是LT模式

LT模式 水平触发  缓冲区没处理完 epoll轮询着 下次事件还可以处理上次事件 缓冲区 剩余的数据

ET模式 边缘触发 缓冲区的数据每次必须一次性取完  否则下次事件触发时 上一次事件中 没有读取完的数据将丢失

     

处于 epoll的服务器     ET模式 必须一次全部将数据取走  所以文件描述符 必须设置为 非阻塞    因为:如果是阻塞的 ,while循环read时, 最后一次 读到文件结尾 read阻塞 服务器 不能epoll处理别的 请求事件


_ev.events = EPOLLIN| EPOLLET 设置事件为 ET模式


代码:设置文件描述符为非阻塞的

#include<stdio.h>
       #include <unistd.h>
       #include <fcntl.h>
#include <string.h>
// 设置文件描述符号 不阻塞
int set_noblock(int fd)
{
    int fl = fcntl(fd, F_GETFL);// 获取 原来文件描述符的 状态
    if (fl < 0)
    {
        perror("fcntl F_GETFL ");
        return 1;
    }
    if (fcntl(fd, F_SETFL, fl | O_NONBLOCK) < 0) // 设置为 不阻塞状态
    {
        perror("fcntl F_SETFL");
        return 2;
    }
}
int myfcntl()
{
    set_noblock(0);
    char buf[10];
    while (1)
    {
        memset(buf, ‘\0‘, sizeof(buf));
        int ret = read(0, buf, sizeof(buf) - 1);
        sleep(1);
        printf("%s, ret: %d\n", buf, ret);
    }
}
int main()
{
    myfcntl();
    return 0;
}

运行:

[bozi@localhost fcntl]$ ./fcntl
, ret: -1
, ret: -1
, ret: -1
, ret: -1
, ret: -1
, ret: -1
, ret: -1
, ret: -1

练习代码:【epoll ET模式服务器】

#include<stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#define LISTEN_BACK_LOG 10
// 创建 监听套接字
int startup(char* ip, int port)
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        perror("socket");
        exit(1);
    }
    int op = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(int));
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr= inet_addr(ip);
    if (bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
    {
        perror("bind");
        exit(2);
    }
    if (listen(sock,LISTEN_BACK_LOG) < 0)
    {
        perror("listen");
        exit(3);
    }
    return sock;
}
// 设置 文件描述符 为非阻塞
static void set_noblock(int fd)
{
    int fl = fcntl(fd, F_GETFL);
    if (fl < 0)
    {
        perror("fcntl F_GETFL");
        return;
    }
    if (fcntl(fd, F_SETFL, fl|O_NONBLOCK) < 0)
    {
        perror("fcntl F_SETFL");
        return;
    }
}
//    处理非阻塞方式的 读
static int read_noblock(int fd,  char* buf,const  int size)
{
    int count = 0;
    int ret;
    while ((ret = read(fd, buf, size - count)) > 0 )//&& errno != EAGAIN)
    {
        count += ret;
        buf = buf + ret;
    }
    buf[count] = ‘\0‘;
    return count;
}
int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        printf("please enter: %s [ip] [port]\n", argv[0]);
        exit(4);
    }
    int listen_sock = startup(argv[1], atoi(argv[2]));
    // 创建 epoll 句柄 事件表
    int epfd = epoll_create(101);// 这里数字不固定,100 只是告诉内核 预计用100个文件描述符 但实际这个参数不起作用 只是建议
    if (epfd < 0)
    {
        perror("epoll_create");
        exit(5);
    }
    struct epoll_event event;
    event.events = EPOLLIN|EPOLLET; // 读 事件 列表
    set_noblock(listen_sock);
    event.data.fd = listen_sock;
    // 向 事件表中增加事件
    epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &event);
    struct epoll_event fd_events[50];
    int size = sizeof(fd_events)/sizeof(fd_events[0]);
    int i = 0;
    for (i = 0; i < size; i++)
    {
        fd_events[i].events = 0;
        fd_events[i].data.fd = -1;
    }
    int nums = 0;
    int timeout = 10000;
    int done = 0;
    while (!done)
    {
        // 返回就绪的文件个数
        nums = epoll_wait(epfd, fd_events, size, timeout);
        switch (nums)
        {
            case 0:
                printf("timeout...\n");
                break;
            case -1:
                perror("epoll_wait\n");
                exit(6) ;
            default:
                {
                    for (i = 0; i < nums; i++)
                    {
                        int fd = fd_events[i].data.fd;
                        if ((fd == listen_sock) && (fd_events[i].events & EPOLLIN))
                        {
                            // listen socket 有新的连接请求
                            struct sockaddr_in peer;
                            socklen_t len = sizeof(peer);
                            int new_sock;
                            while ((new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len)) > 0)
                            {
                                event.events = EPOLLIN|EPOLLET; // ET模式///////////////////
                                set_noblock(new_sock);
                                event.data.fd = new_sock;
                                // 将new_sock 添加进内核事件表
                            epoll_ctl(epfd, EPOLL_CTL_ADD,new_sock, &event);
                            printf("get a new client, socket->%s:%d\n", inet_ntoa(peer.sin_addr),ntohs(peer.sin_port));
                            }
                            continue;
                        }
                        else
                        {
                            // other socket
                            // // 读事件满足 处理客户端发送的 数据
                            if (fd_events[i].events & EPOLLIN)
                            {
                                char buf[1024];
                                memset(buf, ‘\0‘, sizeof(buf));
                        //        ssize_t _s = recv(fd, buf, sizeof(buf) - 1, 0);
                        int _s = read_noblock(fd, buf, sizeof(buf) - 1);
                        printf("\n%d\n",_s);
                                if (_s > 0)
                                {
                                    printf("client: %s\n", buf);
                                    event.events =    EPOLLOUT|EPOLLET;//将fd事件改为写 方便服务器 给请求的客户端发数据
                                    epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event);
                                }
                                else if (_s == 0)
                                {
                                    printf("client close...\n");
                                    epoll_ctl(epfd, EPOLL_CTL_DEL,fd,NULL );// 空表示不关心返回值
                //                    event.events =    EPOLLOUT|EPOLLET;//将fd事件改为写 方便服务器 给请求的客户端发数据
                        //            epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event);
                                    close(fd);
                                break;
                                }
                                else
                                {
                                    perror("recv");
                                    continue;
                                }
                            }
                            else if(fd_events[i].events & EPOLLOUT)
                            {   
                                // WRITE
                                char *msg = "HTTP/1.1 200 OK\r\n\r\n<html><h1>hello ^_^</h1></html>\r\n";
                                send(fd, msg, strlen(msg), 0);
                                //event.events = EPOLLIN;
                                //epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event);
                                epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
                                close(fd);
                            }
                            else
                            {
                            }
                        }
                    }
                }
            break;
        }
    }
    exit(0);
}

运行:

技术分享

本文出自 “城市猎人” 博客,请务必保留此出处http://alick.blog.51cto.com/10786574/1836718

Linux多线程编程----IO【select、poll、epoll】

标签:linux

原文地址:http://alick.blog.51cto.com/10786574/1836718

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!