多路复用技术是一种通过共享物理信道实现多路信号传输的通信技术,其核心原理是利用复用器将多路信号合成单一信号传输,接收端通过分用器分离还原。该技术主要包括频分复用(FDM)、时分复用(TDM)、波分复用(WDM)、码分多址(CDMA)和空分多址(SDMA)等类型,其中FDM通过划分频段实现ADSL数据传输,TDM采用时分片机制支持T1载波传输,WDM通过不同波长光信号在光纤中实现双向通信。(该部分来源于百度百科)
select的概念和原理
select是一种事件通知机制,通常用于阻塞式 I/O 操作中,它允许程序在多个文件描述符上进行监视,一旦某个文件描述符变得可读、可写或出现异常,select会通知程序进行相应的处理。
select会在多个文件描述符上设置监视(例如,读、写、异常状态),然后它会阻塞程序,直到以下事件之一发生:
- 读事件:某个文件描述符准备好读取数据(如套接字接收到数据)。
- 写事件:某个文件描述符准备好写入数据(如套接字可以发送数据)。
- 异常事件:文件描述符处于异常状态(如套接字连接关闭)。
select会返回发生事件的文件描述符列表,程序可以通过这些文件描述符来进一步处理数据。
select原型
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds:最大文件描述符值加1。通常可以传入所有需要监视的文件描述符的最大值加1。readfds:用于监视哪些文件描述符可以读取(即数据已准备好读取)。writefds:用于监视哪些文件描述符可以写入。exceptfds:用于监视哪些文件描述符发生了异常。timeout:指定等待事件的最长时间。如果为NULL,则select会阻塞直到有事件发生;如果设置为0,则为非阻塞模式;否则,设置为一个timeval结构体表示的超时时间。
select的使用
struct timeval timeout;
timeout.tv_sec = 5; // 等待最多5秒
timeout.tv_usec = 0;
int ready = select(socket_fd + 1, &readfds, NULL, NULL, &timeout);
if (ready < 0) {
perror("select error");
} else if (ready == 0) {
printf("timeout\n");
} else {
if (FD_ISSET(socket_fd, &readfds)) {
// 如果 socket_fd 可读,进行读取操作
char buffer[1024];
int len = read(socket_fd, buffer, sizeof(buffer));
if (len > 0) {
printf("Received data: %s\n", buffer);
}
}
}
select的不足
文件描述符数量限制
select的最大文件描述符数目是由fd_set中的位数限制的,通常在 1024 或 4096 左右。超出这个数量,select将无法处理。因此,在高并发的应用场景下,select可能不够高效。性能瓶颈 当需要监视大量的文件描述符时,
select会不断地遍历所有文件描述符,检查哪些描述符有事件发生。这可能导致性能瓶颈,特别是在大规模的并发连接情况下。无法支持优先级
select不支持文件描述符的优先级调度,因此它处理多个文件描述符时是公平的,不会根据优先级处理某些请求。
poll的概念和原理
poll是select的一个改进版本,常用于多路复用(I/O多路复用)的场景,它允许程序监控多个文件描述符的状态(如是否可以读写)。
poll原型
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds:一个pollfd结构体数组,表示要监视的文件描述符及其关注的事件。nfds:要监视的文件描述符的数量,即fds数组的大小。timeout:指定poll阻塞等待的时间(单位是毫秒)。如果为负数,则阻塞直到有事件发生;如果为 0,则立即返回;如果为正数,则等待指定的时间后返回。
poll的使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <poll.h>
#define PORT 8080
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int addr_len = sizeof(address);
// 创建服务器套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定套接字
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听客户端连接
if (listen(server_fd, 3) < 0) {
perror("listen failed");
exit(EXIT_FAILURE);
}
// 创建 pollfd 结构体
struct pollfd fds[1];
fds[0].fd = server_fd;
fds[0].events = POLLIN; // 关注可读事件
printf("Server is listening on port %d...\n", PORT);
while (1) {
// 调用 poll,等待事件
int ret = poll(fds, 1, -1); // -1 表示无限阻塞
if (ret < 0) {
perror("poll failed");
exit(EXIT_FAILURE);
}
// 检查是否有可读事件
if (fds[0].revents & POLLIN) {
new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addr_len);
if (new_socket < 0) {
perror("accept failed");
continue;
}
printf("New connection established\n");
// 读取数据
char buffer[1024] = {0};
int valread = read(new_socket, buffer, sizeof(buffer));
if (valread > 0) {
printf("Received message: %s\n", buffer);
}
close(new_socket);
}
}
return 0;
}
poll的缺点和优点
优点:
- 没有文件描述符数量的限制:不像
select限制了文件描述符数量,poll可以处理更大规模的文件描述符。- 灵活的事件选择:通过
pollfd可以灵活地指定感兴趣的事件,并且能够处理多种类型的事件。缺点:
- 性能瓶颈:在大量文件描述符的情况下,
poll仍然会遍历所有文件描述符,因此它在大规模并发连接时的性能较低。- 没有优先级:
poll处理事件的方式是均等的,没有优先级调度功能。
epoll的概念和原理
epoll是 Linux 下提供的高效的 I/O 多路复用机制,特别适用于处理大量并发连接。与select和poll相比,epoll提供了更高效的方式来管理大量文件描述符。
epoll相关函数
epoll_create:创建一个 epoll 实例,返回一个文件描述符。 int epoll_create(int size); size 参数已被废弃,但它仍然被要求传递。通常传入一个大于零的数。 epoll_ctl:用于添加、删除或修改事件。 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); epfd:epoll_create 返回的 epoll 文件描述符。 op:操作类型(EPOLL_CTL_ADD、EPOLL_CTL_DEL、EPOLL_CTL_MOD)。 fd:待操作的文件描述符。 event:待监控的事件结构。 epoll_wait:等待事件发生,并返回就绪的文件描述符。 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); epfd:epoll_create 返回的 epoll 文件描述符。 events:用于存放就绪事件的数组。 maxevents:数组大小。 timeout:等待时间(单位毫秒),可以设置为 -1(永久阻塞),0(非阻塞),或正数(指定超时时间)。
epoll的使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#define PORT 8080
#define MAX_EVENTS 10
int main() {
int server_fd, client_fd, epfd, n;
struct sockaddr_in address;
struct epoll_event ev, events[MAX_EVENTS];
int addr_len = sizeof(address);
// 创建服务器套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定套接字
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听客户端连接
if (listen(server_fd, 3) < 0) {
perror("listen failed");
exit(EXIT_FAILURE);
}
// 创建 epoll 实例
epfd = epoll_create1(0);
if (epfd == -1) {
perror("epoll_create failed");
exit(EXIT_FAILURE);
}
// 将 server_fd 添加到 epoll 监控列表中
ev.events = EPOLLIN;
ev.data.fd = server_fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, server_fd, &ev) == -1) {
perror("epoll_ctl failed");
exit(EXIT_FAILURE);
}
printf("Server is listening on port %d...\n", PORT);
while (1) {
// 等待事件发生
n = epoll_wait(epfd, events, MAX_EVENTS, -1);
if (n == -1) {
perror("epoll_wait failed");
exit(EXIT_FAILURE);
}
// 处理所有就绪事件
for (int i = 0; i < n; i++) {
if (events[i].data.fd == server_fd) {
// 新连接到达
client_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addr_len);
if (client_fd == -1) {
perror("accept failed");
continue;
}
// 将 client_fd 添加到 epoll 监控列表中
ev.events = EPOLLIN;
ev.data.fd = client_fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &ev) == -1) {
perror("epoll_ctl failed");
exit(EXIT_FAILURE);
}
printf("New connection established\n");
} else if (events[i].events & EPOLLIN) {
// 有数据可读
char buffer[1024] = {0};
int len = read(events[i].data.fd, buffer, sizeof(buffer));
if (len > 0) {
printf("Received: %s\n", buffer);
} else {
// 客户端关闭连接
printf("Closing connection\n");
close(events[i].data.fd);
}
}
}
}
close(server_fd);
close(epfd);
return 0;
}
epoll的优点
- 高效性:
epoll使用事件驱动模式,当有文件描述符就绪时才通知应用程序,因此避免了poll和select中每次都要遍历所有文件描述符的性能问题。- 支持大规模并发:在大量并发连接的情况下,
epoll处理效率更高,尤其是当活跃的文件描述符很少时,它只会通知那些就绪的文件描述符,避免了不必要的检查。- 支持边缘触发和水平触发:
epoll提供了两种触发模式,可以根据需要选择。
总结
特性 selectpollepoll最大文件描述符 限制(通常 1024) 没有最大文件描述符限制 没有文件描述符数量的限制 效率 随着文件描述符增加,性能下降 随着文件描述符增加,性能下降 高效,性能不随文件描述符数量变化 API设计 每次调用时需要重设文件描述符 使用结构体数组 使用事件驱动,灵活且高效 操作方式 阻塞或非阻塞 阻塞或非阻塞 阻塞、非阻塞、事件驱动 适用场景 文件描述符数量较少,简单应用 文件描述符数量适中,但不高 高并发场景,如大规模网络服务 优点 简单易用 较灵活,解决了 select 限制 极高的性能,无限制的并发处理

919

被折叠的 条评论
为什么被折叠?



