欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 美食 > c语言使用select进行IO多路复用

c语言使用select进行IO多路复用

2024/12/1 5:30:40 来源:https://blog.csdn.net/unforgetablebaby/article/details/141172228  浏览:    关键词:c语言使用select进行IO多路复用

使用select缺点:readfds这个数据会从用户态拷贝到内核态,长度有限制1024

每次select调用前都要进行初始化【重新设置要监听的位置】,内核态会把数据拷贝回来会覆盖(用户态拷贝到内核态,内核态拷贝到用户态)

#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>#define PORT 8080
#define MAX_CLIENTS 10int main() {int server_fd, new_socket, client_sockets[MAX_CLIENTS], max_sd, activity;struct sockaddr_in address;fd_set readfds;// 创建套接字server_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_fd == 0) {perror("socket failed");exit(EXIT_FAILURE);}address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);// 绑定套接字bind(server_fd, (struct sockaddr *)&address, sizeof(address));// 监听套接字listen(server_fd, 3);// 初始化客户端套接字列表for (int i = 0; i < MAX_CLIENTS; i++) {client_sockets[i] = 0;}while (1) {// 清空文件描述符集合FD_ZERO(&readfds);// 添加服务器套接字到集合中FD_SET(server_fd, &readfds);max_sd = server_fd;// 添加客户端套接字到集合中for (int i = 0; i < MAX_CLIENTS; i++) {int sd = client_sockets[i];if (sd > 0) FD_SET(sd, &readfds);if (sd > max_sd) max_sd = sd;}// 使用 select 监视文件描述符集合activity = select(max_sd + 1, &readfds, NULL, NULL, NULL);// 处理新的连接if (FD_ISSET(server_fd, &readfds)) {new_socket = accept(server_fd, NULL, NULL);for (int i = 0; i < MAX_CLIENTS; i++) {if (client_sockets[i] == 0) {client_sockets[i] = new_socket;break;}}}// 处理客户端消息for (int i = 0; i < MAX_CLIENTS; i++) {int sd = client_sockets[i];if (FD_ISSET(sd, &readfds)) {char buffer[1024];int valread = read(sd, buffer, 1024);if (valread == 0) {close(sd);client_sockets[i] = 0;} else {buffer[valread] = '\0';printf("Client message: %s\n", buffer);}}}}return 0;
}

poll使用pollfd 结构体,解除了1024这个数量限制, 你可以自己定义数量

struct pollfd{int fd; /*文件描述符,如建立socket后获取的fd, 此处表示想查询的文件描述符*/short events;	/*等待的事件,就是要监测的感兴趣的事情*/short revents;	/*实际发生了的事情*/
};

但是依然存在用户态、内核态相互拷贝的问题,内核态会修改revents拷贝回用户态,在用户态需要改成0(重新初始化)

时间复杂度是O(n): 每个都要判断

#include <stdio.h>
#include <stdlib.h>
#include <poll.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>#define PORT 8080
#define MAX_CLIENTS 10int main() {int server_fd, new_socket, client_sockets[MAX_CLIENTS];struct sockaddr_in address;struct pollfd fds[MAX_CLIENTS + 1];int nfds;// 创建套接字server_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_fd == 0) {perror("socket failed");exit(EXIT_FAILURE);}address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);// 绑定套接字bind(server_fd, (struct sockaddr *)&address, sizeof(address));// 监听套接字listen(server_fd, 3);// 初始化客户端套接字列表for (int i = 0; i < MAX_CLIENTS; i++) {client_sockets[i] = 0;}while (1) {nfds = 0;// 添加服务器套接字到 pollfd 数组中fds[nfds].fd = server_fd;fds[nfds].events = POLLIN;nfds++;// 添加客户端套接字到 pollfd 数组中for (int i = 0; i < MAX_CLIENTS; i++) {if (client_sockets[i] > 0) {fds[nfds].fd = client_sockets[i];fds[nfds].events = POLLIN;nfds++;}}// 使用 poll 监视文件描述符集合int activity = poll(fds, nfds, -1);// 处理新的连接if (fds[0].revents & POLLIN) {new_socket = accept(server_fd, NULL, NULL);for (int i = 0; i < MAX_CLIENTS; i++) {if (client_sockets[i] == 0) {client_sockets[i] = new_socket;break;}}}// 处理客户端消息for (int i = 1; i < nfds; i++) {if (fds[i].revents & POLLIN) {char buffer[1024];int valread = read(fds[i].fd, buffer, 1024);if (valread == 0) {close(fds[i].fd);client_sockets[i - 1] = 0;} else {buffer[valread] = '\0';printf("Client message: %s\n", buffer);}}}}return 0;
}

epoll使用O(1)的时间复杂度,不需要从内核态到用户态来回拷贝

epoll 提供了三个基本操作:

epoll_create:创建一个 epoll 实例,返回一个 epoll 文件描述符,用于管理后续的 epoll 操作。
epoll_ctl:向 epoll 实例中添加、修改或删除文件描述符,并指定感兴趣的事件(如读、写、异常等)。
epoll_wait:等待注册的文件描述符上发生的事件,返回发生事件的文件描述符列表。

epoll 的核心数据结构是一个红黑树(Red-Black Tree)和一个双向链表:

红黑树:用于存储和快速查找所有注册的文件描述符。当使用 epoll_ctl 添加、删除或修改文件描述符时,会在红黑树上进行相应的操作。这使得这些操作的时间复杂度为 O(log N)。
双向链表:用于存储已经准备好(即发生了事件)的文件描述符。在事件发生时,内核会将对应的文件描述符从红黑树移到这个链表中,从而使得 epoll_wait 调用可以高效地返回事件。

  1. 创建 epoll 实例:使用 epoll_create 创建一个 epoll 实例,并得到一个 epoll 文件描述符。
  2. 注册文件描述符:使用 epoll_ctl 将感兴趣的文件描述符添加到 epoll 实例中。此时,epoll 会将这些文件描述符插入到内核的红黑树中,并指定感兴趣的事件。
  3. 等待事件发生:应用程序调用 epoll_wait,该调用会挂起并等待注册的文件描述符上发生感兴趣的事件。内核会监控这些文件描述符,一旦有事件发生,就将对应的文件描述符移到双向链表中,并唤醒 epoll_wait 调用。
  4. 处理事件:epoll_wait 返回准备好的文件描述符列表,应用程序可以对这些文件描述符进行相应的读写操作。
  5. 循环处理:应用程序可以持续调用 epoll_wait,循环处理新的事件。
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>#define PORT 8080
#define MAX_EVENTS 10
#define MAX_CLIENTS 10int main() {int server_fd, new_socket, epoll_fd, event_count;struct sockaddr_in address;struct epoll_event event, events[MAX_EVENTS];int client_sockets[MAX_CLIENTS];// 创建套接字server_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_fd == 0) {perror("socket failed");exit(EXIT_FAILURE);}address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);// 绑定套接字bind(server_fd, (struct sockaddr *)&address, sizeof(address));// 监听套接字listen(server_fd, 3);// 创建 epoll 实例epoll_fd = epoll_create1(0);if (epoll_fd == -1) {perror("epoll_create1 failed");exit(EXIT_FAILURE);}// 添加服务器套接字到 epoll 实例中event.events = EPOLLIN;event.data.fd = server_fd;epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event);// 初始化客户端套接字列表for (int i = 0; i < MAX_CLIENTS; i++) {client_sockets[i] = 0;}while (1) {// 等待事件发生event_count = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);// 处理每个事件for (int i = 0; i < event_count; i++) {if (events[i].data.fd == server_fd) {// 处理新的连接new_socket = accept(server_fd, NULL, NULL);event.events = EPOLLIN;event.data.fd = new_socket;epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_socket, &event);} else {// 处理客户端消息char buffer[1024];int valread = read(events[i].data.fd, buffer, 1024);if (valread == 0) {close(events[i].data.fd);} else {buffer[valread] = '\0';printf("Client message: %s\n", buffer);}}}}return 0;
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com