select、poll、epoll

首先看一下man文档中这三个函数的定义:

select函数:

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);
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); // 将一个集合请空

参数说明:

nfds:所要监视的文件描述符范围,即被监听的文件描述符最大值加一。

readfds:被监视的可读操作的文件描述符。

writefds:被监视的可写操作的文件描述符。

exceptfds:被监视的文件错误异常的文件描述符。

下面是使用select的一个echo服务端例子:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>
#include <iostream>
#define BUF_SIZE 100

void error_handling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char* argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    struct timeval timeout;
    fd_set reads, cpy_reads;

    socklen_t adr_sz;
    int fd_max, str_len, fd_num;
    char buf[BUF_SIZE];

    if ((serv_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket error");
	return 1;
    }

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(8000);

    if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1) {
        error_handling("bind error");
    }

    if (listen(serv_sock, 5) == -1) {
        error_handling("listen error");
    }

    FD_ZERO(&reads);  // 初始化
    // 注册serv_sock
    FD_SET(serv_sock, &reads);
    fd_max = serv_sock;

    while (true) {
        cpy_reads = reads;
	timeout.tv_sec = 5;
	timeout.tv_usec = 5000;

	// 监听服务端socket和与客户端连接的服务端socket的read事件
	if ((fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout)) == -1)
	    break;

	if (fd_num == 0)
	    continue;

	if (FD_ISSET(serv_sock, &cpy_reads)) {   // 受理客户端连接请求
	    adr_sz = sizeof(clnt_adr);
	    clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
	    FD_SET(clnt_sock, &reads);

	    if (fd_max < clnt_sock)
                fd_max = clnt_sock;

	    std::cout << "connected client: " << clnt_sock << std::endl;
	}
	else {   // 转发客户数据
	    str_len = read(clnt_sock, buf, BUF_SIZE);

	    if (str_len == 0) {  // 客户端发送退出EOF
	        // 取消注册该推出的套接字
		FD_CLR(clnt_sock, &reads);
		close(clnt_sock);
		std::cout << "closed client: " << clnt_sock << std::endl;
	    }
	    else {
		// 回写到客户端
	        write(clnt_sock, buf, str_len);
	    }
	}
    }

    close(serv_sock);
    return 0;
}

 

poll函数:

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

  struct pollfd {
    int fd; /* file descriptor */
    short events; /* requested events */
    short revents; /* returned events */
  };

参数说明:

fds:所监听的描述符。

nfds:所监听的文件描述符数目。

timeout:超时时间,单位是毫秒,设为-1表示永不超时。

下面是poll函数的事件标志符:

 

 

 epoll:

int epoll_create1(int flags); // 创建epoll实例
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // 管理epoll事件

函数参数:

  • epfd : epoll实例的fd
  • op : 操作标志,下文会描述
  • fd : 监控对象的fd
  • event : 事件的内容,下文描述

op可以有3个值,分别为:

  • EPOLL_CTL_ADD : 添加监听的事件
  • EPOLL_CTL_DEL : 删除监听的事件
  • EPOLL_CTL_MOD : 修改监听的事件

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

 

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); // 等待epoll事件

函数参数:

  • epfd : epoll实例的fd
  • events : 储存事件的数组首地址
  • maxevents : 最大事件的数量
  • timeout : 等待的最长时间

使用epoll的echo服务端例子:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <iostream>

#define BUF_SIZE 100
#define EPOLL_SIZE 50

void error_handling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char* argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t adr_sz;
    int str_len, i;
    char buf[BUF_SIZE];

    // 类似select的fd_set变量查看监视对象的状态变化
    // epoll_event结构体将发生变化的文件描述符集中到一起
    struct epoll_event* ep_events;
    struct epoll_event event;
    int epfd, event_cnt;

    if ((serv_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        error_handling("socket error");
    }

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(8000);

    if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) < 0) {
        error_handling("bind error");
    }

    if (listen(serv_sock, 5) < 0) {
        error_handling("listen error");
    }

    // 创建文件描述符的保存空间成为epoll例程
    epfd = epoll_create(EPOLL_SIZE);
    ep_events = (struct epoll_event*)malloc(sizeof(struct epoll_event) * EPOLL_SIZE);

    // 添加读取事件的监视(注册)
    event.events = EPOLLIN;   // 读取数据事件
    event.data.fd = serv_sock;
    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

    while (true) {
        // 响应事件 返回发生事件的文件描述符数
    event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);  // 传入-1 表示一直等待直到事件发生

    if (event_cnt == -1) {
        std::cout << "epoll_wait error" << std::endl;
        break;
    }
    
    // 服务端套接字个客户端套接字
    for (i = 0; i < event_cnt; i++) {
        if (ep_events[i].data.fd == serv_sock) {  // 服务端与客户端建立连接
            adr_sz = sizeof(clnt_adr);
        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
        event.events = EPOLLIN;
        event.data.fd = clnt_sock;
        epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
        std::cout << "connected client: " << clnt_sock << std::endl;
        }
        else {   // 连接后的客户端 传递数据
            str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);

        if (str_len == 0) {
            // 删除事件
            epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
            close(ep_events[i].data.fd);
            std::cout << "close client: " << ep_events[i].data.fd << std::endl;
        }
        else {
            write(ep_events[i].data.fd, buf, str_len);
        }
        }
    }
    }

    close(serv_sock);
    close(epfd);
    return 0;
}

select、poll、epoll比较:

 

 

 

posted @ 2021-03-12 19:13  荒唐了年少  阅读(62)  评论(0编辑  收藏  举报