原理差异
- select:
- select通过轮询方式检查文件描述符集合中是否有事件发生。它将文件描述符集合分成可读、可写和异常三个集合,每次调用select时,内核会遍历这些集合,检查是否有事件发生。
- 这种轮询方式在文件描述符数量较多时,效率会显著下降。
- epoll:
- epoll采用事件驱动机制。epoll有一个epoll内核对象,应用程序通过epoll_ctl函数向这个内核对象添加、修改或删除要监控的文件描述符。
- 当有事件发生时,内核会将这些事件通知给应用程序,应用程序通过epoll_wait函数获取这些事件,而不需要像select那样轮询所有文件描述符。
- kqueue:
- kqueue也是基于事件驱动,它在内核中维护一个事件队列。应用程序通过kevent函数注册要监控的事件,当事件发生时,内核将事件添加到事件队列中,应用程序通过kevent函数获取这些事件。
性能差异
- select:
- 性能随着文件描述符数量增加而下降,因为每次调用select都需要在内核态和用户态之间复制文件描述符集合,并且需要轮询所有文件描述符。
- 有文件描述符数量限制,通常为1024。
- epoll:
- 性能高,尤其是在处理大量并发连接时。epoll采用事件通知机制,减少了不必要的轮询开销。
- 没有文件描述符数量限制(理论上只受系统资源限制)。
- kqueue:
- 性能与epoll类似,同样基于事件驱动,避免了轮询开销。
- 可监控的事件数量也没有严格限制。
适用场景差异
- select:
- 适用于小规模网络应用,文件描述符数量较少且并发连接数不高的场景。因为其实现简单,跨平台性好。
- epoll:
- 适用于大规模网络服务器,需要处理大量并发连接的场景。在Linux系统下是高性能网络编程的首选。
- kqueue:
- 适用于FreeBSD、Mac OS X等系统,在这些系统下性能优异,同样适用于处理大量并发连接的场景。
选择依据
- 小规模并发场景:
- 如果是跨平台且并发连接数少,选择select。例如开发一个简单的网络工具,可能只需要处理少量的连接,select简单易用且跨平台,能满足需求。
- 大规模并发场景:
- 在Linux系统下,选择epoll。因为epoll在处理大量并发连接时性能高,能有效提升服务器性能。
- 在FreeBSD或Mac OS X系统下,选择kqueue,它在这些系统上有着出色的性能表现。
代码框架
- select代码框架:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#define PORT 8888
#define MAX_CLIENTS 1024
int main() {
int server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
fd_set read_fds, tmp_fds;
int fdmax;
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket < 0) {
perror("Socket creation failed");
return 1;
}
int opt = 1;
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
close(server_socket);
return 1;
}
if (listen(server_socket, MAX_CLIENTS) < 0) {
perror("Listen failed");
close(server_socket);
return 1;
}
FD_ZERO(&read_fds);
FD_ZERO(&tmp_fds);
FD_SET(server_socket, &read_fds);
fdmax = server_socket;
while (1) {
tmp_fds = read_fds;
int activity = select(fdmax + 1, &tmp_fds, NULL, NULL, NULL);
if (activity < 0) {
perror("Select error");
break;
} else if (activity > 0) {
if (FD_ISSET(server_socket, &tmp_fds)) {
client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_socket < 0) {
perror("Accept error");
continue;
}
FD_SET(client_socket, &read_fds);
if (client_socket > fdmax) {
fdmax = client_socket;
}
}
for (int i = 0; i <= fdmax; i++) {
if (FD_ISSET(i, &tmp_fds) && i != server_socket) {
char buffer[1024];
int bytes_read = recv(i, buffer, sizeof(buffer), 0);
if (bytes_read <= 0) {
if (bytes_read == 0) {
printf("Client disconnected\n");
} else {
perror("Recv error");
}
close(i);
FD_CLR(i, &read_fds);
} else {
buffer[bytes_read] = '\0';
printf("Received: %s\n", buffer);
send(i, buffer, bytes_read, 0);
}
}
}
}
}
close(server_socket);
return 0;
}
- epoll代码框架:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#define PORT 8888
#define MAX_EVENTS 1024
int main() {
int server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int epoll_fd;
struct epoll_event event, events[MAX_EVENTS];
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket < 0) {
perror("Socket creation failed");
return 1;
}
int opt = 1;
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
close(server_socket);
return 1;
}
if (listen(server_socket, MAX_EVENTS) < 0) {
perror("Listen failed");
close(server_socket);
return 1;
}
epoll_fd = epoll_create1(0);
if (epoll_fd < 0) {
perror("Epoll create failed");
close(server_socket);
return 1;
}
event.data.fd = server_socket;
event.events = EPOLLIN;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event) < 0) {
perror("Epoll ctl add server socket failed");
close(server_socket);
close(epoll_fd);
return 1;
}
while (1) {
int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (num_events < 0) {
perror("Epoll wait error");
break;
}
for (int i = 0; i < num_events; i++) {
if (events[i].data.fd == server_socket) {
client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_socket < 0) {
perror("Accept error");
continue;
}
int flags = fcntl(client_socket, F_GETFL, 0);
fcntl(client_socket, F_SETFL, flags | O_NONBLOCK);
event.data.fd = client_socket;
event.events = EPOLLIN | EPOLLET;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &event) < 0) {
perror("Epoll ctl add client socket failed");
close(client_socket);
}
} else {
client_socket = events[i].data.fd;
char buffer[1024];
int bytes_read = recv(client_socket, buffer, sizeof(buffer), 0);
if (bytes_read <= 0) {
if (bytes_read == 0) {
printf("Client disconnected\n");
} else {
perror("Recv error");
}
close(client_socket);
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_socket, NULL);
} else {
buffer[bytes_read] = '\0';
printf("Received: %s\n", buffer);
send(client_socket, buffer, bytes_read, 0);
}
}
}
}
close(server_socket);
close(epoll_fd);
return 0;
}
- kqueue代码框架(以FreeBSD为例):
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/event.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#define PORT 8888
#define MAX_EVENTS 1024
int main() {
int server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int kq;
struct kevent change_list[MAX_EVENTS], event_list[MAX_EVENTS];
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket < 0) {
perror("Socket creation failed");
return 1;
}
int opt = 1;
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
close(server_socket);
return 1;
}
if (listen(server_socket, MAX_EVENTS) < 0) {
perror("Listen failed");
close(server_socket);
return 1;
}
kq = kqueue();
if (kq < 0) {
perror("Kqueue creation failed");
close(server_socket);
return 1;
}
EV_SET(&change_list[0], server_socket, EVFILT_READ, EV_ADD, 0, 0, NULL);
while (1) {
int num_events = kevent(kq, change_list, 1, event_list, MAX_EVENTS, NULL);
if (num_events < 0) {
perror("Kevent error");
break;
}
for (int i = 0; i < num_events; i++) {
if (event_list[i].ident == server_socket) {
client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_socket < 0) {
perror("Accept error");
continue;
}
int flags = fcntl(client_socket, F_GETFL, 0);
fcntl(client_socket, F_SETFL, flags | O_NONBLOCK);
EV_SET(&change_list[0], client_socket, EVFILT_READ, EV_ADD, 0, 0, NULL);
} else {
client_socket = event_list[i].ident;
char buffer[1024];
int bytes_read = recv(client_socket, buffer, sizeof(buffer), 0);
if (bytes_read <= 0) {
if (bytes_read == 0) {
printf("Client disconnected\n");
} else {
perror("Recv error");
}
close(client_socket);
EV_SET(&change_list[0], client_socket, EVFILT_READ, EV_DELETE, 0, 0, NULL);
kevent(kq, change_list, 1, NULL, 0, NULL);
} else {
buffer[bytes_read] = '\0';
printf("Received: %s\n", buffer);
send(client_socket, buffer, bytes_read, 0);
}
}
}
}
close(server_socket);
close(kq);
return 0;
}