首先看一下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(' ', 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(' ', 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; }