I/O复用的 select poll和epoll的简单实现

一个tcp的客户端服务器程序

服务器端不变,客户端通过I/O复用轮询键盘输入与socket输入(接收客户端的信息)

服务器端:

 1 /*selcet服务器客户端模型:
 2 1.客户端关闭后,服务器再向客户端发送信息,第一次会收到一个RST复位报文,第二次会收到SIGPIPE信号,导致服务器关闭,必须对这个信号进行处理:
 3     1.在服务器对read返回值为0的情况进行处理,不向客户端发送信息
 4     2.signal函数: signal(SIGPIPE, handle) 或者直接忽略signal(SIGPIPE, SIG_IGN)
 5 */
 6 #include <stdio.h>
 7 #include <stdlib.h>
 8 #include <string.h>
 9 #include <unistd.h>
10 #include <errno.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <sys/socket.h>
14 #include <arpa/inet.h>
15 #include <signal.h>
16
17 #define ERR_EXIT(m) 18     do { 19         perror(m);20         exit(EXIT_FAILURE);21     }while(0)//宏定义的错误处理
22
23 static void do_service(int fd)
24 {
25     char recvbuf[1024] = {0};
26     int ret;
27     while(1)
28     {
29         memset(recvbuf, 0, sizeof recvbuf);
30         ret = read(&rt, recvbuf, 1024);
31         sleep(3);
32         if(ret == 0)//关闭客户端时会导致ret == 0
33             printf("no message\n");
34         else if(ret == -1)
35         {
36             if(errno == EINTR)
37                 continue;
38             return ;
39         }
40         rio_writen(fd, recvbuf, 5);
41     }
42 }
43
44 void handle(int signum)//SIGPIPE信号处理函数
45 {
46 printf("hello\n");
47 }
48
49 int main(int argc, const char *argv[])
50 {
51     if(signal(SIGPIPE, handle) == SIG_ERR)
52         ERR_EXIT("signal");
53
54     int listenfd = socket(AF_INET, SOCK_STREAM, 0);//准备一个socketfd
55     if(listenfd == -1 )
56         ERR_EXIT("listen");
57
58     int on = 1;
59     if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)//setsockopt设置端口复用
60     {
61         close(listenfd);
62         ERR_EXIT("setsockopt");
63     }
64
65     struct sockaddr_in seraddr;
66     seraddr.sin_family = AF_INET;
67     seraddr.sin_port = htons(8888);
68     seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
69     socklen_t len = sizeof(seraddr);
70     if(bind(listenfd, (struct sockaddr*)&seraddr, len) == -1)//监听socket端口,
71     {
72         close(listenfd);
73         ERR_EXIT("bind");
74     }
75 //上述过程可以封装到起来,不用全部写入main
76     if(listen(listenfd, 6) == -1)
77     {
78         close(listenfd);
79         ERR_EXIT("listen");
80     }
81
82     struct sockaddr_in cliaddr;
83     bzero(&cliaddr, sizeof(cliaddr));
84     socklen_t cli_len = sizeof cliaddr;
85
86     int clientfd;
87     clientfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cli_len);
88
89     if(clientfd == -1)
90     {
91         close(listenfd);
92         ERR_EXIT("accept");
93     }
94     do_service(clientfd);
95
96     close(clientfd);
97     close(listenfd);
98     return 0;
99 }

1.select客户端: 

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <errno.h>
 5 #include <sys/types.h>
 6 #include <sys/socket.h>
 7 #include <netinet/in.h>
 8 #include <arpa/inet.h>
 9 #include <sys/select.h>
10
11 #define ERR_EXIT(m) 12     do { 13         perror(m);14         exit(EXIT_FAILURE);15     }while(0)
16
17 static void do_client(int fd)
18 {
19     char recvbuf[MAXLINE + 1] = {0};
20     char sendbuf[MAXLINE + 1] = {0};
21
22     fd_set reade, ready;//将ready集合放入select轮询,每次轮询前将reade赋值给ready
23     FD_ZERO(&reade);//清空reade集合
24     int fd_stdin = fileno(stdin);
25     FD_SET(fd_stdin, &reade);
26     FD_SET(fd, &reade);//将需要监听的文件描述符加入集合
27     int fd_max = (fd_stdin > fd) ? fd_stdin : fd;//select轮询的最大文件描述符
28
29     int ret;
30     while(1)
31     {
32         ready = reade;
33         ret = select( fd_max+1, &ready, NULL, NULL, NULL);//轮询,最后一个参数struct *timeval设置为NULL表示即时轮询(每次轮询间没时间间隔)
34         if(ret < 0)
35         {
36             if(errno == EINTR)
37                 continue;
38             ERR_EXIT("select");
39         }else if(ret ==  0)//监听的文件描述符状态未发生变化
40         {
41             continue;
42         }
43
44         if(FD_ISSET(fd_stdin, &ready))
45         {
46             if(fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)
47             {
48                 shutdown(fd, SHUT_WR);//关闭fd的写端,close关闭时关闭整个描述符,若再写的话会导致write返回-1,系统会向客户端进程发送一个SIGPIPE信号,               导致关闭,可以对信号进行signal(SIGPIPE, SIG_IGN)处理,也可将fd_stdin从监听队列删除
49                // close(fd);
50                 exit(EXIT_SUCCESS);
51                 //break;52                   }else
53                 write(fd, sendbuf, strlen(sendbuf));
54         }
55
56         if(FD_ISSET(fd, &ready))
57         {
58             int nread = read(fd, recvbuf, MAXLINE);
59             if(nread < 0)
60                 ERR_EXIT("read");
61             if(nread == 0)//如果没接收到消息,打印关闭描述符,退出循环
62             {
63                 fprintf(stdout, "fd close\n");
64                 break;
65             }
66             fprintf(stdout, "receive:%s\n", recvbuf);//注意要刷新
67         }
68         memset(recvbuf, 0, sizeof recvbuf);
69         memset(sendbuf, 0, sizeof sendbuf);
70     }
71 }
72
73 int main(int argc, const char *argv[])
74 {
75     int fd = socket(AF_INET, SOCK_STREAM, 0);
76     if(fd < 0)
77         ERR_EXIT("socket");
78
79     struct sockaddr_in cliaddr;
80     cliaddr.sin_family = AF_INET;
81     cliaddr.sin_port = htons(8888);
82     cliaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
83     socklen_t len = sizeof cliaddr;
84
85     int ret ;
86     if((ret = connect(fd, (struct sockaddr*)&cliaddr, len)) == -1)
87     {
88         close(fd);
89         ERR_EXIT("connect");
90     }
91     do_client(fd);
92     close(fd);
93     printf("123\n");
94     return 0;
95 }

2.poll客户端:

static void do_client(int fd)
{
    char recvbuf[MAXLINE + 1] = {0};
    char sendbuf[MAXLINE + 1] = {0};

     struct pollfd pfd[2];//struct pollfd数组,里面存放监听描述符相关信息,供poll轮询使用
     pfd[0].fd = fileno(stdin);
     pfd[0].events = POLLIN;
     pfd[1].fd = fd;
     pfd[1].events = POLLIN;

     int ret;
    while(1)
    {
        ret = poll( pfd, 2, -1);//轮询,-1和select中的时间结构体指针为NULL的效果一样
        if(ret < 0)
        {
            if(errno == EINTR)
                continue;
            ERR_EXIT("select");
        }else if(ret ==  0)
        {
            continue;
        }

        if(pfd[0].revents & POLLIN)
        {
            if(fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)
            {
                shutdown(fd, SHUT_WR);//关闭fd的写端
                pfd[0].fd = -1;
            }else
                write(fd, sendbuf, strlen(sendbuf));
        }

        if(pfd[1].revents & POLLIN)
        {
            int nread = read(fd, recvbuf, MAXLINE);
            if(nread < 0)
                ERR_EXIT("read");
            if(nread == 0)//如果没接收到消息,打印关闭描述符,退出循环
            {
                fprintf(stdout, "fd close\n");
                break;
            }
            fprintf(stdout, "receive:%s\n", recvbuf);//注意要刷新
        }
        memset(recvbuf, 0, sizeof recvbuf);
        memset(sendbuf, 0, sizeof sendbuf);
    }
}

//其余部分和select中一样

3.epoll客户端

static void do_client(int fd)
{
    char recvbuf[MAXLINE + 1] = {0};
    char sendbuf[MAXLINE + 1] = {0};

    int epollfd = epoll_create(2);//返回值也是一个文件描述符,记得要关闭,不然会导致fd耗尽
    if(epollfd == -1)
        ERR_EXIT("epoll_create");

    struct epoll_event events[2];
    struct epoll_event ev;

    int ret;
    ev.data.fd = fd;
    ev.events = EPOLLIN;
    ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);//注册fd
    if(ret == -1)
        ERR_EXIT("epoll_ctl");

    ev.data.fd = fileno(stdin);
    ev.events = EPOLLIN;
    ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev);//注册fd
    if(ret == -1)
        ERR_EXIT("epoll_ctl");

    int nready;

    while(1)
    {
        nready = epoll_wait(epollfd, events, 2, -1);
        if(nready < 0)
        {
            if(errno == EINTR)
                continue;
            ERR_EXIT("select");
        }else if(nready  ==  0)
        {
            continue;
        }

        int i;
        for(i = 0; i < nready; i++)
        {
            int nfd = events[i].data.fd;
            if(nfd == STDIN_FILENO)
            {
                if(fgets(sendbuf, 1024, stdin) == NULL)
                {
                    shutdown(fd, SHUT_WR);
                    struct epoll_event ee;
                    ee.data.fd = STDIN_FILENO;
                    if(epoll_ctl(epollfd, EPOLL_CTL_DEL, STDIN_FILENO, &ee) == -1)
                        ERR_EXIT("epoll_ctl");
                }else
                    write(fd, sendbuf, strlen(sendbuf));
            }
            if(nfd == fd)
            {
                int ret = read(fd, recvbuf, 1024);
                if(ret == -1)
                    ERR_EXIT("readline");
                else if(ret == -1)
                {
                    close(fd);
                    printf("fd close\n");
                    exit(EXIT_SUCCESS);
                }
                printf("recv data :%s\n", recvbuf);
            }
        }
    }
    memset(recvbuf, 0, sizeof recvbuf);
    memset(sendbuf, 0, sizeof sendbuf);
}

 

    

I/O复用的 select poll和epoll的简单实现,布布扣,bubuko.com

时间: 2024-12-24 16:24:04

I/O复用的 select poll和epoll的简单实现的相关文章

IO复用之select poll epoll的总结

I/O复用使得程序能够同时监听多个文件描述符,对于提高程序性能至关重要.I/O复用不仅仅在网络程序中使用,但是我接触到的例子中,TCP网络编程那块使用I/O复用比较多,例如,TCP服务器同时处理监听socket和连接socket. 在了解I/O复用之前,我们需要先了解几个概念. 1,同步I/O与异步I/O 2,LT(水平触发)和ET(边缘触发) POSIX把两个术语定义如下: 同步I/O:导致请求进程阻塞,直到I/O操作完成 异步I/O:  不导致请求进程阻塞 阻塞是进程在等待某种资源,但是不能

I/O多路复用之select,poll,epoll的区别

一.关于select,poll,epoll 三种IO模型,都属于多路IO就绪通知,提供了对大量文件描述符就绪检查的高性能方案,只不过实现方式有所不同: select原理概述: 调用select时,会发生以下事情: (1)从用户空间拷贝fd_set到内核空间: (2)注册回调函数__pollwait: (3)遍历所有fd,对全部指定设备做一次poll(这里的poll是一个文件操作,它有两个参数,一个是文件fd本身,一个是当设备尚未就绪时调用的回调函数__pollwait,这个函数把设备自己特有的等

Linux下select, poll和epoll IO模型的详解(转)

http://blog.csdn.net/tianmohust/article/details/6677985 一).Epoll 介绍 Epoll 可是当前在 Linux 下开发大规模并发网络程序的热门人选, Epoll 在 Linux2.6 内核中正式引入,和 select 相似,其实都 I/O 多路复用技术而已 ,并没有什么神秘的.其实在 Linux 下设计并发网络程序,向来不缺少方法,比如典型的 Apache 模型( Process Per Connection ,简称 PPC ), TP

阻塞、非阻塞、异步、同步以及select/poll和epoll

针对IO,总是涉及到阻塞.非阻塞.异步.同步以及select/poll和epoll的一些描述,那么这些东西到底是什么,有什么差异? 一般来讲一个IO分为两个阶段: 等待数据到达 把数据从内核空间拷贝到用户空间 现在假设一个进程/线程A,试图进行一次IO操作. A发出IO请求,两种情况: 1)立即返回 2)由于数据未准备好,需要等待,让出CPU给别的线程,自己sleep 第一种情况就是非阻塞,A为了知道数据是否准备好,需要不停的询问,而在轮询的空歇期,理论上是可以干点别的活,例如喝喝茶.泡个妞.

Linux I/O复用中select poll epoll模型的介绍及其优缺点的比较

关于I/O多路复用: I/O多路复用(又被称为"事件驱动"),首先要理解的是,操作系统为你提供了一个功能,当你的某个socket可读或者可写的时候,它可以给你一个通知.这样当配合非阻塞的socket使用时,只有当系统通知我哪个描述符可读了,我才去执行read操作,可以保证每次read都能读到有效数据而不做纯返回-1和EAGAIN的无用功.写操作类似.操作系统的这个功能通过select/poll/epoll之类的系统调用来实现,这些函数都可以同时监视多个描述符的读写就绪状况,这样,**多

Linux I/O复用中select poll epoll模型的介绍及其优缺点的比較

关于I/O多路复用: I/O多路复用(又被称为"事件驱动"),首先要理解的是.操作系统为你提供了一个功能.当你的某个socket可读或者可写的时候.它能够给你一个通知.这样当配合非堵塞的socket使用时,仅仅有当系统通知我哪个描写叙述符可读了,我才去运行read操作.能够保证每次read都能读到有效数据而不做纯返回-1和EAGAIN的无用功.写操作相似.操作系统的这个功能通过select/poll/epoll之类的系统调用来实现.这些函数都能够同一时候监视多个描写叙述符的读写就绪状况

IO复用一select, poll, epoll用法说明

三种IO复用类型 Select系统调用 #include<sys/select.h> int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* execptfds,struct timeval* timeout); #nfds表示监听的文件描述符总数: #readfds,writefds,execptfds分别表示对应的fd_set类型的集合 可以如下定义:fd_set readfds,writefds,execptfds

Linux中select poll和epoll的区别

在Linux Socket服务器短编程时,为了处理大量客户的连接请求,需要使用非阻塞I/O和复用,select.poll和epoll是Linux API提供的I/O复用方式,自从Linux 2.6中加入了epoll之后,在高性能服务器领域得到广泛的应用,现在比较出名的nginx就是使用epoll来实现I/O复用支持高并发,目前在高并 发的场景下,nginx越来越收到欢迎.这里有个文章参考.Nginx成为全球Top1000网站最受欢迎的Web服务器. 据 w3techs 7月 3 日的统计数据表明

select poll和 epoll

select .poll.epoll 都是多路io复用的机制,i/o多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知乡音的程序进行相应的读写操作.但select poll epoll 本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的(即就绪后切换到内核态进行吧数据从内核拷贝到空间),而异步I/O则无需自己负责读写. 与多进程多线程技术相比.I/O多路复用技术的最大优势是减小系统开销,系统不必创建.