linux网络编程中select/poll/epoll的比较分析

2 篇文章 0 订阅

一.select

1.概述

select本质是通过设置或检查存放fd标志位的数据结构来进行下一步的处理。会阻塞,直到有一个或多个I/O就绪。

监视的文件描述符分为三类set,每一种对应不同的事件。readfds、writefds和exceptfds是指向描述符集的指针。

readfds列出的文件描述符被监视是否有数据可供读取。(可读)

writefds列出的文件描述符被监视是否有写入操作完成。(可写)

exceptfds列出的文件描述符被监视是否发生异常,或无法控制的数据是否可用。(仅仅用于socket)

这三类set为NULL时,select()不监视其对应的该类事件。

select()成功返回时,每组set都被修改以使它只包含准备好的I/O描述符。

其缺点:(a)单个进程可监视的fd数量被限制;

(b)需要维护一个用来存放大量fd的数据结构,这样会使用户空间和内核空间在传递该结构时复制开销大;

(c)对fd进行扫描是线性的,fd剧增后,IO效率较低,因为每次调用都对fd进行线性扫描遍历,所以随着fd的增加会造成遍历速度慢的性能问题;

(d)内核需要将消息传递用户空间,需要内核拷贝动作;

(e)最大支持1024个fd。

2.实战

server端代码,文件名为:select-server.c

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/types.h>

#define MAXBUF 1024
/************关于本文档********************************************
*filename: select-server.c
*purpose: 演示网络异步通讯、select用法,这是服务器端程序
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to: Google.com
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
*********************************************************************/

int main(int argc, char **argv)
{
    int sockfd, new_fd;
    socklen_t len;
    struct sockaddr_in my_addr, their_addr;
    unsigned int myport, lisnum;
    char buf[MAXBUF + 1];
    fd_set rfds;
    struct timeval tv;
    int retval, maxfd = -1;

    if (argv[1])
        myport = atoi(argv[1]);
    else
        myport = 7838;

    if (argv[2])
        lisnum = atoi(argv[2]);
    else
        lisnum = 2;

    if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }

    bzero(&my_addr, sizeof(my_addr));
    my_addr.sin_family = PF_INET;
    my_addr.sin_port = htons(myport);
    if (argv[3])
        my_addr.sin_addr.s_addr = inet_addr(argv[3]);
    else
        my_addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))
        == -1) {
        perror("bind");
        exit(1);
    }

    if (listen(sockfd, lisnum) == -1) {
        perror("listen");
        exit(1);
    }

    while (1) {
        printf
            ("\n----等待新的连接到来开始新一轮聊天……\n");
        len = sizeof(struct sockaddr);
        if ((new_fd =
             accept(sockfd, (struct sockaddr *) &their_addr,
                    &len)) == -1) {
            perror("accept");
            exit(errno);
        } else
            printf("server: got connection from %s, port %d, socket %d\n",
                   inet_ntoa(their_addr.sin_addr),
                   ntohs(their_addr.sin_port), new_fd);

        /* 开始处理每个新连接上的数据收发 */
        printf
            ("\n准备就绪,可以开始聊天了……直接输入消息回车即可发信息给对方\n");
        while (1) {
            /* 把集合清空 */
            FD_ZERO(&rfds);
            /* 把标准输入(stdin)句柄0加入到集合中 */
            FD_SET(0, &rfds);
            maxfd = 0;
            /* 把当前连接(socket)句柄new_fd加入到集合中 */
            FD_SET(new_fd, &rfds);
            if (new_fd > maxfd)
                maxfd = new_fd;
            /* 设置最大等待时间 */
            tv.tv_sec = 1;
            tv.tv_usec = 0;
            /* 开始等待 */
            retval = select(maxfd + 1, &rfds, NULL, NULL, &tv);
            if (retval == -1) {
                printf("将退出,select出错! %s", strerror(errno));
                break;
            } else if (retval == 0) {
                /* printf
                   ("没有任何消息到来,用户也没有按键,继续等待……\n"); */
                continue;
            } else {
		        /*判断当前IO是否是stdin*/
                if (FD_ISSET(0, &rfds)) {
                    /* 用户按键了,则读取用户输入的内容发送出去 */
                    bzero(buf, MAXBUF + 1);
                    fgets(buf, MAXBUF, stdin);
                    if (!strncasecmp(buf, "quit", 4)) {
                        printf("自己请求终止聊天!\n");
                        break;
                    }
                    len = send(new_fd, buf, strlen(buf) - 1, 0);
                    if (len > 0)
                        printf
                            ("消息:%s\t发送成功,共发送了%d个字节!\n",
                             buf, len);
                    else {
                        printf
                            ("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
                             buf, errno, strerror(errno));
                        break;
                    }
                }
		        /*判断当前IO是否是来自socket*/
                if (FD_ISSET(new_fd, &rfds)) {
                    /* 当前连接的socket上有消息到来则接收对方发过来的消息并显示 */
                    bzero(buf, MAXBUF + 1);
                    /* 接收客户端的消息 */
                    len = recv(new_fd, buf, MAXBUF, 0);
                    if (len > 0)
                        printf
                            ("接收消息成功:'%s',共%d个字节的数据\n",
                             buf, len);
                    else {
                        if (len < 0)
                            printf
                                ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
                                 errno, strerror(errno));
                        else
                            printf("对方退出了,聊天终止\n");
                        break;
                    }
                }
            }
        }
        close(new_fd);
        /* 处理每个新连接上的数据收发结束 */
        printf("还要和其它连接聊天吗?(no->退出)");
        fflush(stdout);
        bzero(buf, MAXBUF + 1);
        fgets(buf, MAXBUF, stdin);
        if (!strncasecmp(buf, "no", 2)) {
            printf("终止聊天!\n");
            break;
        }
    }

    close(sockfd);
    return 0;
}

client端代码,文件名为select-client.c

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>

#define MAXBUF 1024
/************关于本文档********************************************
// *filename: select-client.c
*purpose: 演示网络异步通讯,这是客户端程序
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to: Google.com
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
*********************************************************************/
int main(int argc, char **argv)
{
    int sockfd, len;
    struct sockaddr_in dest;
    char buffer[MAXBUF + 1];
    fd_set rfds;
    struct timeval tv;
    int retval, maxfd = -1;

    if (argc != 3) {
        printf
            ("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息",
             argv[0], argv[0]);
        exit(0);
    }
    /* 创建一个 socket 用于 tcp 通信 */
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket");
        exit(errno);
    }

    /* 初始化服务器端(对方)的地址和端口信息 */
    bzero(&dest, sizeof(dest));
    dest.sin_family = AF_INET;
    dest.sin_port = htons(atoi(argv[2]));
    if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
        perror(argv[1]);
        exit(errno);
    }

    /* 连接服务器 */
    if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
        perror("Connect ");
        exit(errno);
    }

    printf
        ("\n准备就绪,可以开始聊天了……直接输入消息回车即可发信息给对方\n");
    while (1) {
        /* 把集合清空 */
        FD_ZERO(&rfds);
        /* 把标准输入句柄0加入到集合中 */
        FD_SET(0, &rfds);
        maxfd = 0;
        /* 把当前连接句柄sockfd加入到集合中 */
        FD_SET(sockfd, &rfds);
        if (sockfd > maxfd)
            maxfd = sockfd;
        /* 设置最大等待时间 */
        tv.tv_sec = 1;
        tv.tv_usec = 0;
        /* 开始等待 */
        retval = select(maxfd + 1, &rfds, NULL, NULL, &tv);
        if (retval == -1) {
            printf("将退出,select出错! %s", strerror(errno));
            break;
        } else if (retval == 0) {
            /* printf
               ("没有任何消息到来,用户也没有按键,继续等待……\n"); */
            continue;
        } else {
            if (FD_ISSET(sockfd, &rfds)) {
                /* 连接的socket上有消息到来则接收对方发过来的消息并显示 */
                bzero(buffer, MAXBUF + 1);
                /* 接收对方发过来的消息,最多接收 MAXBUF 个字节 */
                len = recv(sockfd, buffer, MAXBUF, 0);
                if (len > 0)
                    printf
                        ("接收消息成功:'%s',共%d个字节的数据\n",
                         buffer, len);
                else {
                    if (len < 0)
                        printf
                            ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
                             errno, strerror(errno));
                    else
                        printf("对方退出了,聊天终止!\n");
                    break;
                }
            }
            if (FD_ISSET(0, &rfds)) {
                /* 用户按键了,则读取用户输入的内容发送出去 */
                bzero(buffer, MAXBUF + 1);
                fgets(buffer, MAXBUF, stdin);
                if (!strncasecmp(buffer, "quit", 4)) {
                    printf("自己请求终止聊天!\n");
                    break;
                }
                /* 发消息给服务器 */
                len = send(sockfd, buffer, strlen(buffer) - 1, 0);
                if (len < 0) {
                    printf
                        ("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
                         buffer, errno, strerror(errno));
                    break;
                } else
                    printf
                        ("消息:%s\t发送成功,共发送了%d个字节!\n",
                         buffer, len);
            }
        }
    }
    /* 关闭连接 */
    close(sockfd);
    return 0;
}

编译用如下命令:
gcc select-server.c -o server
gcc select-client.c -o client
运行用如下命令:
./server 7838 1
./client 127.0.0.1 7838

参考网址:http://zhoulifa.bokee.com/6068027.html

二.poll

1.概述

和select基本一样,除了poll没有使用低效的三个基于位的文件描述符set,而是采用了一个单独的结构体pollfd数组,由fds指针指向这个组。pollfd结构体定义如下:

#include <sys/poll.h>
struct pollfd{
    int fd;//文件描述符
    short events;//fd请求的事件。监视该文件描述符的事件掩码,由用户来设置。
    short revents;//fd发生的事件。文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。
}
特点:(a)它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历。如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或主动超时,被唤醒后它又要再次遍历fd;

(b)没有最大连接数的限制,原因是它是基于链表来存储的;

(c)大量的fd的数组被整体复制于用户态和内核地址空间;

(d)对fd的扫描是线性的;

(e)水平触发:如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

三.epoll

1.概述

epoll的接口非常简单,一共就三个函数:
(1). int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

(2). int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

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 */
};

events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。

(3). int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。


(4). 关于ET、LT两种工作模式:
可以得出这样的结论:
ET模式仅当状态发生变化的时候才获得通知,这里所谓的状态的变化并不包括缓冲区中还有未处理的数据,也就是说,如果要采用ET模式,需要一直read/write直到出错为止,很多人反映为什么采用ET模式只接收了一部分数据就再也得不到通知了,大多因为这样;而LT模式是只要有数据没有处理就会一直通知下去的.

那么究竟如何来使用epoll呢?其实非常简单。
通过在包含一个头文件#include <sys/epoll.h> 以及几个简单的API将可以大大的提高你的网络服务器的支持人数。

首先通过create_epoll(int maxfds)来创建一个epoll的句柄,其中maxfds为epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。

之后在你的网络主循环里面,每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写了。基本的语法为:
nfds = epoll_wait(epfd, events, maxevents, -1);
其中epfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是 epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件范围,为任意正整数的时候表示等这么长的时间,如果一直没有事件,则返回。一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率。

epoll_wait范围之后应该是一个循环,遍利所有的事件。

特点:(a)支持一个进程打开最大数目的socket描述符(FD)。所支持的FD上限是最大可以打开文件的数组,在1GB机器上,大约为10万左右;

(b)IO效率不随fd数目增加而线性下降;(select/poll每次调用都会线性扫描全部的集合;epoll中只有活跃的socket才会主动调用callback函数,其他idle状态的socket则不会)

(c)使用mmap减少复制开销,加速内核与用户空间的消息传递;(epoll是通过内核和用户空间共享同一块内存实现的)

(d)支持边缘触发,只告诉进程中哪些fd刚刚变为就绪态,并且只通知一次。(epoll使用事件的就绪通知方式,通过epoll_ctl函数注册fd。一旦该fd就绪,内核就会采用类似callback的回调机制激活该fd,epoll_wait便可以收到通知。)

2.具体使用步骤

epoll的实现原理图如下:


epoll模型主要负责对大量并发用户的请求进行及时处理,完成服务器与客户端的数据交互。其具体的实现步骤如下:
(a)使用epoll_create()函数创建文件描述,设定将可管理的最大socket描述符数目。
(b)创建与epoll关联的接收线程,应用程序可以创建多个接收线程来处理epoll上的读通知事件,线程的数量依赖于程序的具体需要。
(c)创建一个侦听socket描述符listenfd;将该描述符设定为非阻塞模式,调用listen()函数在套接字上侦听有无新的连接请求,在epoll_event结构中设置要处理的事件类型EPOLLIN,工作方式为 EPOLLET,以提高工作效率,同时使用epoll_ctl()注册事件,最后启动网络监视线程。
(d)网络监视线程启动循环,epoll_wait()等待epoll事件发生。
(e)如果epoll事件表明有新的连接请求,则调用accept()函数,将用户socket描述符添加到epoll_data联合体,同时设定该描述符为非阻塞,并在epoll_event结构中设置要处理的事件类型为读和写,工作方式为EPOLLET.
(f)如果epoll事件表明socket描述符上有数据可读,则将该socket描述符加入可读队列,通知接收线程读入数据,并将接收到的数据放入到接收数据的链表中,经逻辑处理后,将反馈的数据包放入到发送数据链表中,等待由发送线程发送。

参考文章:Epoll模型详解

这是epoll用法比较全面的一个例子的伪代码,几乎所有的epoll程序都使用下面的框架:

while (1) {
    nfds = epoll_wait(epfd, events, 20, 500);
    for (i = 0; i< nfds; i++) {//被内核IO事件唤醒的fd
        if (event[i].data.fd == listenfd) { //有新的连接
            connfd = accept(listenfd, (sockaddr *) &clientaddr, &clilen); //accept这个连接
            ev.data.fd = connfd;
            ev.events = EPOLLIN | EPOLLET;
            epoll_ctl = (epfd, EPOLL_CTL_ADD, connfd, &ev); //将新的fd添加到epoll的监听队列中
        } else if (event[i].events & EPOLLIN) { //接受到数据,读socket
            n = read(sockfd, line, MAXLINE); //读数据
            ev.data.ptr = md; //md为自定义的数据,添加数据
            ev.events = EPOLLOUT | EPOLLET;
            epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓
        } else if (event[i].events & EPOLLOUT) { //有数据要发送,写socket
            struct myepoll_data *md = (myepoll_data *)events[i].data.ptr;//取数据
            sockfd = md->fd;
            send(sockfd, md->ptr, strlen((char *)md->ptr), 0);
            ev.data.fd = sockfd;
            ev.events = EPOLLIN | EPOLLET;
            epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev); //修改标识符,等待下一个循环时接受数据
        } else {
            //其他处理
        }
    }
}

3.实战

client端代码还是用上面的select-client.c代码,服务器端程序需要改变,这里为epoll-server.c,如下:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <sys/epoll.h>

#define MAXBUF 1024
#define MAXEPOLLSIZE 10000

/*
setnonblocking - 设置句柄为非阻塞方式
*/
int setnonblocking(int sockfd)
{
    if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1) {
        return -1;
    }
    return 0;
}

/************关于本文档********************************************
*filename: epoll-server.c
*purpose: 演示epoll接受海量socket并进行处理响应的方法
*********************************************************************/
int main(int argc, char **argv)
{
    int listenfd, connfd, epfd, sockfd, nfds, n, curfds;
    socklen_t len;
    struct sockaddr_in my_addr, their_addr;
    unsigned int myport, lisnum;
    char buf[MAXBUF + 1];
    /*声明epoll_event结构体的变量,ev用于注册事件,events数组用于回传要处理的事件*/
    struct epoll_event ev;
    struct epoll_event events[MAXEPOLLSIZE];

    if (argv[1])
        myport = atoi(argv[1]);
    else
        myport = 7838;

    if (argv[2])
        lisnum = atoi(argv[2]);
    else
        lisnum = 2;

    /* 开启 socket 监听 */
    if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    } else
        printf("socket 创建成功!\n");

    /*把socket设置为非阻塞方式*/
    setnonblocking(listenfd);

    bzero(&my_addr, sizeof(my_addr));
    my_addr.sin_family = PF_INET;
    my_addr.sin_port = htons(myport);
    if (argv[3])
        my_addr.sin_addr.s_addr = inet_addr(argv[3]);
    else
        my_addr.sin_addr.s_addr = INADDR_ANY;

    if (bind
        (listenfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))
        == -1) {
        perror("bind");
        exit(1);
    } else
        printf("IP 地址和端口绑定成功\n");

    if (listen(listenfd, lisnum) == -1) {
        perror("listen");
        exit(1);
    } else
        printf("开启服务成功!\n");

    /* 创建 epoll 句柄,把监听 socket 加入到 epoll 集合里 */
    epfd = epoll_create(MAXEPOLLSIZE); /*epoll专用的文件描述符*/
    len = sizeof(struct sockaddr_in);
    ev.events = EPOLLIN|EPOLLET;
    ev.data.fd = listenfd;
    /*将listenfd注册到epoll事件*/
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev) < 0) {
        fprintf(stderr, "epoll set insertion error: fd=%d\n", listenfd);
        return -1;
    } else
        printf("监听 socket 加入 epoll 成功!\n");
    curfds = 1;
    while (1) {
        /* 等待有事件发生 */
        nfds = epoll_wait(epfd, events, curfds, -1);
        if (nfds == -1) {
            perror("epoll_wait");
            break;
        }
        /* 处理所有事件 */
        for (n = 0; n < nfds; ++n) {
	    /*如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。*/
            if (events[n].data.fd == listenfd) {
                len = sizeof(struct sockaddr);
                connfd = accept(listenfd, (struct sockaddr *) &their_addr, &len);
                if (connfd < 0) {
                    perror("accept");
                    continue;
                } else
                    printf("有连接来自于: %s:%d, 分配的 socket 为:%d\n", inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), connfd);

                setnonblocking(connfd);

                /*设置用于注册的读操作事件*/
                ev.events = EPOLLIN|EPOLLET;
                /*设置用于读操作的文件描述符*/
                ev.data.fd = connfd;
                /*注册ev*/
                epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);

                curfds++;
            } else if (events[n].events&EPOLLIN) {/*如果是已经连接的用户,并且收到数据,那么进行读入*/
                printf("EPOLLIN\n");
                if ((sockfd = events[n].data.fd) < 0)
                    continue;
                int len;
                bzero(buf, MAXBUF + 1);
                /* 接收客户端的消息 */
                /*len = read(sockfd, buf, MAXBUF);*/
                len = recv(sockfd, buf, MAXBUF, 0);
                if (len > 0)
                    printf
                         ("%d接收消息成功:'%s',共%d个字节的数据\n",
                          sockfd, buf, len);
                else {
                    if (len < 0) {
                        printf
                             ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
                              errno, strerror(errno));
                        epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, &ev);
                        curfds--;
                        continue;
                    }
                }

                /*设置用于写操作的文件描述符*/
		        ev.data.fd = sockfd;
                /*设置用于注册的写操作事件*/
		        ev.events = EPOLLOUT | EPOLLET;
                /*修改sockfd上要处理的事件为EPOLLOUT*/
		        epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
		
            } else if (events[n].events&EPOLLOUT) { /*如果有数据发送*/
		        printf("EPOLLOUT\n");
                sockfd = events[n].data.fd;
                bzero(buf, MAXBUF + 1);
                strcpy(buf, "Server already processes!");
                /*write(sockfd, buf, strlen(buf));*/
		        send(sockfd, buf, strlen(buf), 0);

                /*设置用于读操作的文件描述符*/
		        ev.data.fd = sockfd;
                /*设置用于注册的读操作事件*/
		        ev.events = EPOLLIN|EPOLLET;
                /*修改sockfd上要处理的事件为EPOLIN*/
		        epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
	        }
        }
    }
    close(listenfd);
    return 0;
}
编译此程序用命令:

gcc epoll-server.c -o server

gcc select-client.c -o client
运行此程序:
./server 7838 1

./client 127.0.0.1 7838

运行结果如下:

server端效果,接收到client1(127.0.0.1)的消息和client2(192.168.2.183)的消息,并对其响应“Server already processes!”消息:


client1(127.0.0.1)的运行效果,发送“111”,并接收到server端的消息“Server already processes!”:


client2(192.168.2.183)的运行效果,发送“222”,并接收到server端的消息“Server already processes!”:


通过测试这一个服务器可能同时处理10000 -3 = 9997 个连接!
如果这是一个在线服务系统,那么它可以支持9997人同时在线,比如游戏、聊天等。

参考网址:http://zhoulifa.bokee.com/6081520.html 

http://blog.csdn.net/ljx0305/article/details/4065058

epoll-server与select-server的区别是:

select:server每次都只能和一个client聊天,只有该client断开后,才处理另一个client的连接。
epoll:server可以同时响应多个client端发送来的消息。

四.总结

select、poll、epoll它们的职责不是做IO,而是帮助调用者寻找当前就绪的设备;linux中设备都被抽象成文件,所以设备在系统调用参数中的表示就是file description,fd其实就是一个整数(特别的,标准输入stdin、标准输出stdout、标准出错stderr分别对应的fd是0、1、2)。

epoll的回调函数,自己乖乖等待设备就绪时将它唤醒,然后它把这个设备fd放到一个指定的地方,同时唤醒可能在等待的进程,到这个指定的地方取fd就好了。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值