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 10

int 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 10

int 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 10

int 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

forgetable tree

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值