在Unix网络编程中,select、poll和epoll都是用于多路复用的I/O模型,可以同时监视多个文件描述符的可读、可写和异常事件。它们之间的主要区别如下:
1. select的文件描述符集合是使用数组来表示,而poll使用链表,epoll使用红黑树,因此在文件描述符数量较大的情况下,select和poll的效率会随着文件描述符数量的增加而降低,而epoll则不会有太大的性能影响。
2. select和poll每次调用时都需要将文件描述符集合从用户态拷贝到内核态,而epoll只需要在第一次调用时将文件描述符集合拷贝到内核态,之后只需要在需要修改时再次拷贝,因此epoll的性能更优。
3. select和poll对于同一个文件描述符,如果既可以读也可以写,需要分别在读集合和写集合中进行标记,而epoll则可以在注册时指定感兴趣的事件类型,不需要分别标记。
下面是一个使用select、poll和epoll进行通信的demo,该程序可以监听本地端口,并接受客户端的连接请求,然后将客户端发送的数据原样返回。
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#define PORT 8888
#define MAX_EVENTS 10
#define BUF_SIZE 1024
int main(int argc, char *argv[]) {
int listen_fd, conn_fd;
struct sockaddr_in serv_addr, cli_addr;
socklen_t cli_len;
char buf[BUF_SIZE];
int i, n;
// 创建监听socket
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
// 绑定地址和端口
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(PORT);
if (bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("bind");
exit(EXIT_FAILURE);
}
// 监听端口
if (listen(listen_fd, 10) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
// 使用select进行通信
// fd_set read_fds;
// FD_ZERO(&read_fds);
// FD_SET(listen_fd, &read_fds);
// while (1) {
// fd_set tmp_fds = read_fds;
// if (select(FD_SETSIZE, &tmp_fds, NULL, NULL, NULL) < 0) {
// perror("select");
// exit(EXIT_FAILURE);
// }
// for (i = 0; i < FD_SETSIZE; i++) {
// if (FD_ISSET(i, &tmp_fds)) {
// if (i == listen_fd) {
// // 有新连接
// cli_len = sizeof(cli_addr);
// conn_fd = accept(listen_fd, (struct sockaddr *)&cli_addr, &cli_len);
// if (conn_fd < 0) {
// perror("accept");
// exit(EXIT_FAILURE);
// }
// printf("New connection from %s:%d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
// FD_SET(conn_fd, &read_fds);
// } else {
// // 有数据可读
// n = read(i, buf, BUF_SIZE);
// if (n <= 0) {
// // 连接关闭
// close(i);
// FD_CLR(i, &read_fds);
// printf("Connection closed\n");
// } else {
// // 发送数据
// write(i, buf, n);
// }
// }
// }
// }
// }
// 使用poll进行通信
// struct pollfd fds[MAX_EVENTS];
// fds[0].fd = listen_fd;
// fds[0].events = POLLIN;
// while (1) {
// if (poll(fds, MAX_EVENTS, -1) < 0) {
// perror("poll");
// exit(EXIT_FAILURE);
// }
// for (i = 0; i < MAX_EVENTS; i++) {
// if (fds[i].revents & POLLIN) {
// if (i == 0) {
// // 有新连接
// cli_len = sizeof(cli_addr);
// conn_fd = accept(listen_fd, (struct sockaddr *)&cli_addr, &cli_len);
// if (conn_fd < 0) {
// perror("accept");
// exit(EXIT_FAILURE);
// }
// printf("New connection from %s:%d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
// fds[i].fd = conn_fd;
// fds[i].events = POLLIN;
// } else {
// // 有数据可读
// n = read(fds[i].fd, buf, BUF_SIZE);
// if (n <= 0) {
// // 连接关闭
// close(fds[i].fd);
// printf("Connection closed\n");
// fds[i].fd = -1;
// } else {
// // 发送数据
// write(fds[i].fd, buf, n);
// }
// }
// }
// }
// }
// 使用epoll进行通信
int epfd, nfds;
struct epoll_event ev, events[MAX_EVENTS];
epfd = epoll_create(MAX_EVENTS);
ev.events = EPOLLIN;
ev.data.fd = listen_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);
while (1) {
nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
for (i = 0; i < nfds; i++) {
if (events[i].data.fd == listen_fd) {
// 有新连接
cli_len = sizeof(cli_addr);
conn_fd = accept(listen_fd, (struct sockaddr *)&cli_addr, &cli_len);
if (conn_fd < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
printf("New connection from %s:%d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
ev.events = EPOLLIN;
ev.data.fd = conn_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev);
} else {
// 有数据可读
n = read(events[i].data.fd, buf, BUF_SIZE);
if (n <= 0) {
// 连接关闭
close(events[i].data.fd);
printf("Connection closed\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
} else {
// 发送数据
write(events[i].data.fd, buf, n);
}
}
}
}
return 0;
}
```
其中,使用注释掉的代码分别实现了使用select、poll和epoll进行通信的功能。可以通过注释掉相应的代码来切换使用不同的I/O模型。