IO多路复用

目录

前言

 一、select

二、poll 

poll 和 select 的区别

三、epoll 

epoll 的优点


前言

        I/O多路复用(I/O Multiplexing)是一种在操作系统中处理多个I/O操作的技术,主要用于网络编程中处理多个网络连接或文件描述符的读写操作。I/O多路复用允许单个线程或进程同时监控多个I/O事件(如读、写、异常等),从而有效地管理多个并发的I/O操作,而不需要为每个I/O操作分配一个独立的线程或进程。

      在Linux和类Unix系统中,I/O多路复用主要通过以下几种机制实现:

  1. select:最早的I/O多路复用接口。
  2. poll:改进版的select,可以处理更多的文件描述符。
  3. epoll:Linux特有的高效I/O多路复用机制,适用于大规模并发连接。

 一、select

        select是最早的I/O多路复用机制之一,用于在单个线程中监控多个文件描述符(如套接字、文件等)上的I/O事件(如可读、可写、异常等)。尽管select已经被更现代的机制如pollepoll所取代,但它依然是理解I/O多路复用基础原理的重要工具。 

        

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>

#define SERVERADDR "192.168.125.86"
#define PORT 8888

int main(int argc, const char *argv[]) {
    // 创建套接字
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0) {
        perror("socket failed");
        return -1;
    }

    // 绑定套接字
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(PORT);
    serveraddr.sin_addr.s_addr = inet_addr(SERVERADDR);
    if (bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0) {
        perror("bind failed");
        return -1;
    }

    // 监听套接字
    if (listen(listenfd, 20) < 0) {
        perror("listen failed");
        return -1;
    }
	//定义文件描述符表
    fd_set master_fds, read_fds;
    int max_fd = listenfd;
	//清空文件描述符表
    FD_ZERO(&master_fds);
	//将监听套接字加入文件描述符表
    FD_SET(listenfd, &master_fds);

    while (1) {
        read_fds = master_fds;

        // 监视文件描述符集,进程通知内核监视的内核是否准备就绪(是否有IO事件)
        int activity = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
        if (activity < 0) {
            perror("select failed");
            return -1;
        }

        // 处理新连接
        if (FD_ISSET(listenfd, &read_fds)) {
            struct sockaddr_in clientaddr;
            socklen_t len = sizeof(clientaddr);
            int client_fd = accept(listenfd, (struct sockaddr*)&clientaddr, &len);
            if (client_fd < 0) {
                perror("accept failed");
                continue;
            }
            printf("New connection from %s:%hu\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
            //将新创建的客户端文件描述符加入到文件描述符表当中
			FD_SET(client_fd, &master_fds);
            if (client_fd > max_fd) {
                max_fd = client_fd;
            }
        }

        // 处理已连接客户端的数据
        for (int i = listenfd + 1; i <= max_fd; ++i) {
            if (FD_ISSET(i, &read_fds)) {
                char buf[100];
                bzero(buf, 100);
                int ret = read(i, buf, 100);
                if (ret <= 0) {
                    // 客户端关闭了连接
                    close(i);
                    FD_CLR(i, &master_fds);
                } else {
                    // 处理接收到的数据
                    printf("Received: %s\n", buf);
                    write(i, buf, ret);
                }
            }
        }
    }

    close(listenfd);
    return 0;
}

二、poll 

        

pollselect 的区别

  1. 文件描述符数量限制

    • select:对文件描述符数量有限制,通常是1024(可以通过编译时调整)。
    • poll:没有文件描述符数量的限制,仅受系统可用资源限制。
  2. 接口设计

    • select:使用fd_set结构体,通过宏操作(如FD_SETFD_CLR等)对集合进行操作。
    • poll:使用pollfd结构体数组,结构更加直观,不需要手动设置和重置集合。
  3. 性能

    • select:每次调用都需要遍历所有的文件描述符,性能随着文件描述符数量增加而下降。
    • poll:同样需要遍历,但由于其数据结构设计上的优势,处理大量文件描述符时性能稍好。
  4. 修改集合

    • select:每次调用都需要重新初始化fd_set,因为select会改变fd_set的内容。
    • poll:直接修改pollfd数组即可,无需像select那样重新设置集合。

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <poll.h>

#define SERVERADDR "192.168.125.86"
#define PORT 8888
#define MAX_CLIENTS 1024

int main(int argc, const char *argv[]) {
    // 创建监听套接字
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0) {
        perror("socket failed");
        return -1;
    }

    // 绑定套接字
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(PORT);
    serveraddr.sin_addr.s_addr = inet_addr(SERVERADDR);
    if (bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0) {
        perror("bind failed");
        return -1;
    }

    // 监听套接字
    if (listen(listenfd, 20) < 0) {
        perror("listen failed");
        return -1;
    }

    // 初始化pollfd数组
    struct pollfd fds[MAX_CLIENTS];
    for (int i = 0; i < MAX_CLIENTS; i++) {
        fds[i].fd = -1; // 初始化为-1表示未使用
    }
    fds[0].fd = listenfd;
    fds[0].events = POLLIN;

    int nfds = 1; // 当前监控的文件描述符数量

    while (1) {
        int activity = poll(fds, nfds, -1); // 阻塞等待事件
        if (activity < 0) {
            perror("poll failed");
            return -1;
        }

        // 处理新连接
        if (fds[0].revents & POLLIN) {
            struct sockaddr_in clientaddr;
            socklen_t len = sizeof(clientaddr);
            int client_fd = accept(listenfd, (struct sockaddr*)&clientaddr, &len);
            if (client_fd < 0) {
                perror("accept failed");
                continue;
            }
            printf("New connection from %s:%hu\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

            // 将新连接的客户端添加到pollfd数组
            for (int i = 1; i < MAX_CLIENTS; i++) {
                if (fds[i].fd == -1) { // 找到第一个空闲位置
                    fds[i].fd = client_fd;
                    fds[i].events = POLLIN;
                    if (i >= nfds) {
                        nfds = i + 1; // 更新nfds
                    }
                    break;
                }
            }
        }

        // 处理已连接客户端的数据
        for (int i = 1; i < nfds; i++) {
            if (fds[i].fd == -1) {
                continue;
            }
            if (fds[i].revents & POLLIN) {
                char buf[100];
                bzero(buf, 100);
                int ret = read(fds[i].fd, buf, 100);
                if (ret <= 0) {
                    // 客户端关闭了连接
                    close(fds[i].fd);
                    fds[i].fd = -1;
                } else {
                    // 处理接收到的数据
                    printf("Received: %s\n", buf);
                    write(fds[i].fd, buf, ret);
                }
            }
        }
    }

    close(listenfd);
    return 0;
}

三、epoll 

        

epoll 的优点

  1. 高效性epoll使用内核事件表来管理文件描述符,只通知实际发生事件的文件描述符,避免了selectpoll中遍历所有文件描述符的开销。

  2. 扩展性:可以处理数以万计的并发连接,而selectpoll在文件描述符数量多时性能会显著下降。

  3. 边缘触发与水平触发epoll支持边缘触发(Edge Triggered, ET)和水平触发(Level Triggered, LT)模式,提供更灵活的事件通知机制。

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <sys/epoll.h>

#define SERVERADDR "192.168.125.86"
#define PORT 8888
#define MAX_EVENTS 1024

int main(int argc, const char *argv[]) {
    // 创建监听套接字
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0) {
        perror("socket failed");
        return -1;
    }

    // 绑定套接字
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(PORT);
    serveraddr.sin_addr.s_addr = inet_addr(SERVERADDR);
    if (bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0) {
        perror("bind failed");
        return -1;
    }

    // 监听套接字
    if (listen(listenfd, 20) < 0) {
        perror("listen failed");
        return -1;
    }

    // 创建epoll实例,使用epoll_create(0),0表示标准实例
    int epollfd = epoll_create1(0);
    if (epollfd < 0) {
        perror("epoll_create1 failed");
        return -1;
    }

    // 添加监听套接字到epoll实例
    struct epoll_event event, events[MAX_EVENTS];
    event.events = EPOLLIN;
    event.data.fd = listenfd;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event) < 0) {
        perror("epoll_ctl: listenfd");
        return -1;
    }

    while (1) {
        // 等待事件发生
        int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
        if (nfds < 0) {
            perror("epoll_wait failed");
            return -1;
        }

        // 处理事件
        for (int i = 0; i < nfds; i++) {
            if (events[i].data.fd == listenfd) {
                // 处理新连接
                struct sockaddr_in clientaddr;
                socklen_t len = sizeof(clientaddr);
                int client_fd = accept(listenfd, (struct sockaddr*)&clientaddr, &len);
                if (client_fd < 0) {
                    perror("accept failed");
                    continue;
                }
                printf("New connection from %s:%hu\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

                // 将新客户端文件描述符添加到epoll实例
                event.events = EPOLLIN;
                event.data.fd = client_fd;
                if (epoll_ctl(epollfd, EPOLL_CTL_ADD, client_fd, &event) < 0) {
                    perror("epoll_ctl: client_fd");
                    close(client_fd);
                }
            } else {
                // 处理客户端数据
                int client_fd = events[i].data.fd;
                char buf[100];
                bzero(buf, 100);
                int ret = read(client_fd, buf, 100);
                if (ret <= 0) {
                    // 客户端关闭了连接
                    close(client_fd);
                    epoll_ctl(epollfd, EPOLL_CTL_DEL, client_fd, NULL);
                } else {
                    // 处理接收到的数据
                    printf("Received: %s\n", buf);
                    write(client_fd, buf, ret);
                }
            }
        }
    }

    close(listenfd);
    close(epollfd);
    return 0;
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值