#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
nfds + 1(因为fd从0开始)FD_SETSIZE个fd(通常为1024)FD_ZERO(fd_set *set); // 清空集合
FD_SET(int fd, fd_set *set); // 添加fd到集合
FD_CLR(int fd, fd_set *set); // 从集合中移除fd
FD_ISSET(int fd, fd_set *set); // 检查fd是否在集合中
struct timeval {
long tv_sec; // 秒
long tv_usec; // 微秒
};
NULL:永久阻塞,直到有事件发生0, 0:非阻塞,立即返回>0:等待指定时间后返回#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define PORT 8888
#define BUF_SIZE 1024
#define MAX_CLIENTS 10
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len;
fd_set read_fds, temp_fds;
int max_fd, i, activity;
char buffer[BUF_SIZE];
int client_fds[MAX_CLIENTS];
int client_count = 0;
// 初始化客户端数组
for (i = 0; i < MAX_CLIENTS; i++) {
client_fds[i] = 0;
}
// 1. 创建服务器套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 2. 绑定地址和端口
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 3. 监听
if (listen(server_fd, 5) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server listening on port %d\n", PORT);
// 4. 初始化fd_set
FD_ZERO(&read_fds);
FD_SET(server_fd, &read_fds);
max_fd = server_fd;
// 5. 主循环
while (1) {
temp_fds = read_fds;
// select等待事件
activity = select(max_fd + 1, &temp_fds, NULL, NULL, NULL);
if (activity < 0) {
perror("select error");
continue;
}
// 检查服务器套接字是否有新连接
if (FD_ISSET(server_fd, &temp_fds)) {
client_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd < 0) {
perror("accept failed");
continue;
}
printf("New connection from %s:%d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
// 添加到客户端数组
for (i = 0; i < MAX_CLIENTS; i++) {
if (client_fds[i] == 0) {
client_fds[i] = client_fd;
FD_SET(client_fd, &read_fds);
if (client_fd > max_fd) {
max_fd = client_fd;
}
break;
}
}
}
// 检查客户端数据
for (i = 0; i < MAX_CLIENTS; i++) {
if (client_fds[i] > 0 && FD_ISSET(client_fds[i], &temp_fds)) {
int valread = read(client_fds[i], buffer, BUF_SIZE);
if (valread == 0) {
// 客户端断开连接
printf("Client disconnected\n");
close(client_fds[i]);
FD_CLR(client_fds[i], &read_fds);
client_fds[i] = 0;
} else {
// 回显数据
buffer[valread] = '\0';
printf("Received: %s", buffer);
send(client_fds[i], buffer, valread, 0);
}
}
}
}
close(server_fd);
return 0;
}
FD_SETSIZE通常为1024#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; // 文件描述符
short events; // 请求的事件
short revents; // 返回的事件
};
POLLIN:有数据可读POLLOUT:可以写入数据POLLERR:发生错误POLLHUP:挂起POLLPRI:有紧急数据POLLIN、POLLOUT等#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define PORT 8888
#define BUF_SIZE 1024
#define MAX_CLIENTS 10
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len;
struct pollfd fds[MAX_CLIENTS + 1];
int nfds = 1;
int timeout = 5000; // 5秒超时
char buffer[BUF_SIZE];
int i, ret;
// 1. 创建服务器套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 2. 绑定地址和端口
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 3. 监听
if (listen(server_fd, 5) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server listening on port %d\n", PORT);
// 4. 初始化pollfd数组
fds[0].fd = server_fd;
fds[0].events = POLLIN;
// 5. 主循环
while (1) {
ret = poll(fds, nfds, timeout);
if (ret < 0) {
perror("poll error");
continue;
}
if (ret == 0) {
printf("Poll timeout\n");
continue;
}
// 检查服务器套接字
if (fds[0].revents & POLLIN) {
client_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd < 0) {
perror("accept failed");
continue;
}
printf("New connection from %s:%d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
// 添加到pollfd数组
if (nfds < MAX_CLIENTS + 1) {
fds[nfds].fd = client_fd;
fds[nfds].events = POLLIN;
nfds++;
} else {
printf("Max clients reached\n");
close(client_fd);
}
}
// 检查客户端数据
for (i = 1; i < nfds; i++) {
if (fds[i].revents & POLLIN) {
int valread = read(fds[i].fd, buffer, BUF_SIZE);
if (valread == 0) {
// 客户端断开连接
printf("Client disconnected\n");
close(fds[i].fd);
// 移除客户端
fds[i] = fds[nfds - 1];
nfds--;
i--;
} else {
// 回显数据
buffer[valread] = '\0';
printf("Received: %s", buffer);
send(fds[i].fd, buffer, valread, 0);
}
}
}
}
close(server_fd);
return 0;
}
#include <sys/epoll.h>
int epoll_create(int size);
size:建议的大小(内核会动态调整)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd:epoll实例文件描述符op:操作类型
EPOLL_CTL_ADD:添加fdEPOLL_CTL_MOD:修改fdEPOLL_CTL_DEL:删除fdfd:目标文件描述符event:事件结构体int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
events:输出参数,存储发生的事件maxevents:最大事件数timeout:超时时间(毫秒)struct epoll_event {
uint32_t events; // 事件类型
epoll_data_t data; // 用户数据
};
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
EPOLLIN:有数据可读EPOLLOUT:可以写入数据EPOLLERR:发生错误EPOLLHUP:挂起EPOLLRDHUP:对端关闭连接EPOLLET:边缘触发模式#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define PORT 8888
#define BUF_SIZE 1024
#define MAX_EVENTS 10
int main() {
int server_fd, client_fd, epoll_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len;
struct epoll_event event, events[MAX_EVENTS];
char buffer[BUF_SIZE];
int i, nfds;
// 1. 创建服务器套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 2. 绑定地址和端口
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 3. 监听
if (listen(server_fd, 5) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server listening on port %d\n", PORT);
// 4. 创建epoll实例
epoll_fd = epoll_create1(0);
if (epoll_fd < 0) {
perror("epoll_create1 failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 5. 添加服务器套接字到epoll
event.events = EPOLLIN;
event.data.fd = server_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) < 0) {
perror("epoll_ctl failed");
close(epoll_fd);
close(server_fd);
exit(EXIT_FAILURE);
}
// 6. 主循环
while (1) {
nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (nfds < 0) {
perror("epoll_wait error");
continue;
}
for (i = 0; i < nfds; i++) {
if (events[i].data.fd == server_fd) {
// 新连接
client_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd < 0) {
perror("accept failed");
continue;
}
printf("New connection from %s:%d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
// 添加客户端到epoll
event.events = EPOLLIN;
event.data.fd = client_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) < 0) {
perror("epoll_ctl failed");
close(client_fd);
}
} else {
// 客户端数据
int valread = read(events[i].data.fd, buffer, BUF_SIZE);
if (valread == 0) {
// 客户端断开连接
printf("Client disconnected\n");
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
close(events[i].data.fd);
} else {
// 回显数据
buffer[valread] = '\0';
printf("Received: %s", buffer);
send(events[i].data.fd, buffer, valread, 0);
}
}
}
}
close(epoll_fd);
close(server_fd);
return 0;
}
EAGAIN判断是否读完#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define PORT 8888
#define BUF_SIZE 1024
// 设置非阻塞模式
int set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) return -1;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
int main() {
int server_fd, client_fd, epoll_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len;
struct epoll_event event, events[10];
char buffer[BUF_SIZE];
int i, nfds, n;
// 1. 创建服务器套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置非阻塞
set_nonblocking(server_fd);
// 2. 绑定地址和端口
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 3. 监听
if (listen(server_fd, 5) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server listening on port %d\n", PORT);
// 4. 创建epoll实例
epoll_fd = epoll_create1(0);
if (epoll_fd < 0) {
perror("epoll_create1 failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 5. 添加服务器套接字到epoll(边缘触发)
event.events = EPOLLIN | EPOLLET;
event.data.fd = server_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) < 0) {
perror("epoll_ctl failed");
close(epoll_fd);
close(server_fd);
exit(EXIT_FAILURE);
}
// 6. 主循环
while (1) {
nfds = epoll_wait(epoll_fd, events, 10, -1);
if (nfds < 0) {
perror("epoll_wait error");
continue;
}
for (i = 0; i < nfds; i++) {
if (events[i].data.fd == server_fd) {
// 新连接
while (1) {
client_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
break; // 没有更多连接
}
perror("accept failed");
break;
}
printf("New connection from %s:%d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
// 设置非阻塞
set_nonblocking(client_fd);
// 添加客户端到epoll(边缘触发)
event.events = EPOLLIN | EPOLLET;
event.data.fd = client_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) < 0) {
perror("epoll_ctl failed");
close(client_fd);
}
}
} else {
// 客户端数据(边缘触发,必须一次性读完)
while (1) {
n = read(events[i].data.fd, buffer, BUF_SIZE);
if (n == 0) {
// 客户端断开连接
printf("Client disconnected\n");
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
close(events[i].data.fd);
break;
} else if (n < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
break; // 没有更多数据
}
perror("read error");
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
close(events[i].data.fd);
break;
} else {
// 回显数据
buffer[n] = '\0';
printf("Received: %s", buffer);
send(events[i].data.fd, buffer, n, 0);
}
}
}
}
}
close(epoll_fd);
close(server_fd);
return 0;
}
| 特性 | select | poll | epoll |
|---|---|---|---|
| fd数量限制 | 1024(FD_SETSIZE) | 无限制 | 无限制 |
| 数据结构 | fd_set(位图) | pollfd数组 | 红黑树+就绪链表 |
| 触发方式 | 水平触发 | 水平触发 | 支持边缘触发 |
| 性能 | O(n) | O(n) | O(1) |
| 内核实现 | 遍历所有fd | 遍历所有fd | 回调机制 |
| 内存拷贝 | 每次拷贝fd_set | 每次拷贝pollfd | 仅首次添加 |
| 适用场景 | 少量fd | 中等数量fd | 大量fd |
| 跨平台 | 支持 | 支持 | 仅Linux |
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <fcntl.h>
#define SENSOR_COUNT 4
int main() {
int fds[SENSOR_COUNT];
fd_set read_fds;
int max_fd = 0;
int i, activity;
char buffer[1024];
// 打开传感器设备
for (i = 0; i < SENSOR_COUNT; i++) {
char device[64];
snprintf(device, sizeof(device), "/dev/sensor%d", i);
fds[i] = open(device, O_RDONLY | O_NONBLOCK);
if (fds[i] < 0) {
perror("open sensor failed");
continue;
}
if (fds[i] > max_fd) {
max_fd = fds[i];
}
}
// 主循环
while (1) {
FD_ZERO(&read_fds);
for (i = 0; i < SENSOR_COUNT; i++) {
if (fds[i] > 0) {
FD_SET(fds[i], &read_fds);
}
}
activity = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
if (activity < 0) {
perror("select error");
continue;
}
for (i = 0; i < SENSOR_COUNT; i++) {
if (fds[i] > 0 && FD_ISSET(fds[i], &read_fds)) {
int n = read(fds[i], buffer, sizeof(buffer));
if (n > 0) {
printf("Sensor %d data: %.*s\n", i, n, buffer);
}
}
}
}
// 关闭传感器
for (i = 0; i < SENSOR_COUNT; i++) {
if (fds[i] > 0) {
close(fds[i]);
}
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define PORT 8888
#define MAX_EVENTS 10
#define BUF_SIZE 1024
int main() {
int server_fd, client_fd, epoll_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len;
struct epoll_event event, events[MAX_EVENTS];
char buffer[BUF_SIZE];
int i, nfds;
// 创建服务器套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 绑定地址和端口
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 监听
if (listen(server_fd, 5) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Server listening on port %d\n", PORT);
// 创建epoll实例
epoll_fd = epoll_create1(0);
if (epoll_fd < 0) {
perror("epoll_create1 failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 添加服务器套接字到epoll
event.events = EPOLLIN;
event.data.fd = server_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) < 0) {
perror("epoll_ctl failed");
close(epoll_fd);
close(server_fd);
exit(EXIT_FAILURE);
}
// 主循环
while (1) {
nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (nfds < 0) {
perror("epoll_wait error");
continue;
}
for (i = 0; i < nfds; i++) {
if (events[i].data.fd == server_fd) {
// 新连接
client_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd < 0) {
perror("accept failed");
continue;
}
printf("New connection from %s:%d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
// 添加客户端到epoll
event.events = EPOLLIN;
event.data.fd = client_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) < 0) {
perror("epoll_ctl failed");
close(client_fd);
}
} else {
// 客户端数据
int valread = read(events[i].data.fd, buffer, BUF_SIZE);
if (valread == 0) {
// 客户端断开连接
printf("Client disconnected\n");
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
close(events[i].data.fd);
} else {
// 处理数据
buffer[valread] = '\0';
printf("Received from client %d: %s", events[i].data.fd, buffer);
// 广播给其他客户端
for (int j = 0; j < nfds; j++) {
if (events[j].data.fd != server_fd &&
events[j].data.fd != events[i].data.fd) {
send(events[j].data.fd, buffer, valread, 0);
}
}
}
}
}
}
close(epoll_fd);
close(server_fd);
return 0;
}
select、poll、epoll的区别是什么?
epoll的LT和ET模式有什么区别?
为什么epoll性能更好?
如何处理部分读写?
IO多路复用的适用场景有哪些?