首页 > 系统相关 >Linux下的I/O多路复用

Linux下的I/O多路复用

时间:2024-03-20 20:02:06浏览次数:25  
标签:socket 多路复用 epoll address server failed Linux include

在 I/O 多路复用中,epoll、poll 和 select 是常用的三种机制,它们都可以用于实现事件驱动的网络编程。

select

select 是 Unix 系统最早引入的 I/O 多路复用函数,它允许一个进程监视多个文件描述符,当其中任何一个文件描述符准备好进行 I/O 操作时,select 函数就会返回。

  • 优点:跨平台支持好,几乎所有的操作系统都支持 select。
  • 缺点:效率较低,因为在调用 select 函数后,内核需要遍历所有的文件描述符来检查状态变化,同时 select 函数有最大文件描述符数量的限制。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>

int main() {
    int server_socket, client_socket, max_sd, activity;
    struct sockaddr_in address;
    fd_set readfds;
    char buffer[1025];

    // 创建 TCP 套接字
    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 绑定地址和端口
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(8888);
    if (bind(server_socket, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听
    if (listen(server_socket, 5) < 0) {
        perror("listen failed");
        exit(EXIT_FAILURE);
    }

    // 接受连接
    client_socket = accept(server_socket, (struct sockaddr *)NULL, NULL);

    while (1) {
        FD_ZERO(&readfds);
        FD_SET(client_socket, &readfds);
        max_sd = client_socket;

        // 监视文件描述符变化
        activity = select(max_sd + 1, &readfds, NULL, NULL, NULL);
        
        if ((activity < 0) && (errno != EINTR)) {
            printf("select error");
        }

        if (FD_ISSET(client_socket, &readfds)) {
            // 读取数据
            int valread = read(client_socket, buffer, 1024);
            if (valread == 0) {
                printf("客户端关闭连接\n");
                break;
            } else {
                buffer[valread] = '\0';
                printf("收到数据: %s\n", buffer);
            }
        }
    }

    close(server_socket);
    return 0;
}

poll

poll 是对 select 的改进,它也能够监视多个文件描述符,并在其中任何一个准备好进行 I/O 操作时返回。

  • 优点:没有最大文件描述符数量的限制,相比 select 更加灵活。
  • 缺点:效率仍然有限,因为调用 poll 函数后,内核必须遍历所有的文件描述符来检查状态变化。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <poll.h>

int main() {
    int server_socket, client_socket, i, activity;
    struct sockaddr_in address;
    struct pollfd fds[1];
    char buffer[1025];

    // 创建 TCP 套接字
    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 绑定地址和端口
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(8888);
    if (bind(server_socket, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听
    if (listen(server_socket, 5) < 0) {
        perror("listen failed");
        exit(EXIT_FAILURE);
    }

    // 接受连接
    client_socket = accept(server_socket, (struct sockaddr *)NULL, NULL);

    fds[0].fd = client_socket;
    fds[0].events = POLLIN;

    while (1) {
        activity = poll(fds, 1, -1);
        if (activity < 0) {
            perror("poll failed");
            break;
        }

        if (fds[0].revents & POLLIN) {
            int valread = read(client_socket, buffer, 1024);
            if (valread == 0) {
                printf("客户端关闭连接\n");
                break;
            } else {
                buffer[valread] = '\0';
                printf("收到数据: %s\n", buffer);
            }
        }
    }

    close(server_socket);
    return 0;
}

epoll

epoll 是 Linux 特有的高性能 I/O 多路复用机制,可以显著提升大规模并发连接的性能。

  • 优点:使用红黑树结构存储文件描述符,只有活跃的文件描述符才会被放入红黑树中,因此效率很高。同时,epoll 支持水平触发和边缘触发两种模式,可以更精确地控制事件通知。
  • 缺点:只能在 Linux 系统上使用,不具有跨平台特性。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/epoll.h>

int main() {
    int server_socket, client_socket, i, activity;
    struct sockaddr_in address;
    struct epoll_event event, events[1];
    char buffer[1025];

    // 创建 TCP 套接字
    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 绑定地址和端口
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(8888);
    if (bind(server_socket, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听
    if (listen(server_socket, 5) < 0) {
        perror("listen failed");
        exit(EXIT_FAILURE);
    }

    // 创建 epoll 实例
    int epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("epoll_create1 failed");
        exit(EXIT_FAILURE);
    }

    event.events = EPOLLIN;
    event.data.fd = server_socket;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event) == -1) {
        perror("epoll_ctl failed");
        exit(EXIT_FAILURE);
    }

    while (1) {
        activity = epoll_wait(epoll_fd, events, 1, -1);
        if (activity < 0) {
            perror("epoll_wait failed");
            break;
        }

        for (i = 0; i < activity; i++) {
            if (events[i].data.fd == server_socket) {
                client_socket = accept(server_socket, (struct sockaddr *)NULL, NULL);
                event.events = EPOLLIN;
                event.data.fd = client_socket;
                if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &event) == -1) {
                    perror("epoll_ctl failed");
                    exit(EXIT_FAILURE);
                }
            } else {
                int valread = read(events[i].data.fd, buffer, 1024);
                if (valread == 0) {
                    printf("客户端关闭连接\n");
                    close(events[i].data.fd);
                } else {
                    buffer[valread] = '\0';
                    printf("收到数据: %s\n", buffer);
                }
            }
        }
    }

    close(server_socket);
    close(epoll_fd);
    return 0;
}

总的来说,select 和 poll 在处理大量并发连接时效率较低,而 epoll 则在 Linux 系统上表现更好,因此在高性能网络编程中更常用。选择合适的 I/O 多路复用机制取决于具体的应用场景和目标平台。

标签:socket,多路复用,epoll,address,server,failed,Linux,include
From: https://www.cnblogs.com/JasenChao/p/18085955

相关文章

  • Linux下生成核心转储core
    为了方便进行分析调试,希望当程序发生崩溃或者收到SIGSEGV、SIGABRT等信号时,系统会生成相应的核心转储文件。核心转储大小限制首先,要检查核心转储的大小限制。可以使用ulimit命令来查看当前用户的核心转储大小限制:ulimit-c如果输出为0,则表示不生成核心转储文件。可以使......
  • Linux Interview questions
    @@用户管理面试题:开机bios自检,检测硬件的问题主板CPU内存硬盘电源在企业中出问题最多的硬件:硬件服务器IDC机房自建机房1.磁盘出了问题怎么办?磁盘的详细属性互联网公司:表现的有经验1).是否在保质期内3年如果保质期3年内,联系售后直接换新的2).过了保质期,......
  • Linux基础命令
    一.Linux的目录结构Linux的目录结构是一个树型结构Windows系统可以拥有多个盘符,如C盘、D盘、E盘Linux没有盘符这个概念,只有一个根目录/所有文件都在它下面二.Linux命令1.Linux命令基础格式command[-options][parameter]command命令本身options:[可选,非必填]命......
  • Linux 文件权限
    查看文件权限: ls-lfile_name -rw-r--r--12linuxizeusers12.0KApr2810:10file_name |[-][-][-]- [------][---] |||||  |   | |||||  |   +----------->7.Group用户组 |||||  +------------------->......
  • Linux网络编程: TCP协议首部与可选项简述
    一、TCP/IP五层模型物理层(PhysicalLayer):物理层是最底层,负责传输比特流(bitstream)以及物理介质的传输方式。它定义了如何在物理媒介上传输原始的比特流,例如通过电缆、光纤或无线传输等。数据链路层(DataLinkLayer):数据链路层位于物理层之上,负责在直接相连的节点之间传输......
  • 在Linux 中,如何配置网桥?如何配置虚拟网络?
    本章主要学习的是linux中如何设置网桥和虚拟网络的配置一、网桥的配置在Linux系统中配置一个新的网桥主要涉及以下几个步骤:为yum仓库做准备,安装组件epel-releasesudoyum-yinstallepel-release在yum仓库中安装bridge-utilscd/etc/yum.repos.d/sudoyum-yi......
  • linux: nohup & 重定向
    linux:nohup&重定向背景今天在进行一些spark-hive​的操作时,因为对此完全不了解,所以找好兄弟咨询了下,他给了我一串我完全看不懂的shell命令,本文就是专门用来解释这个命令的问题​nohup​以及>​代码这段神奇的代码:nohupspark-hive-fxx.sql>xx.log2>&1......
  • linux系统kubernetes的资源对象secret
    资源对象-secretSecret实现场景解释內建的Secrets创建自己的Secretsecret使用使用Secret加密流程创建secret加密数据挂载到pod容器以变量形式挂载以Volume数据卷形式挂载案例Secret实现作用:加密数据,存储在etcd中,让pod容器,以挂载Volume方式进行访问场景凭证......
  • linux系统kubernetes容器检查和恢复机制
    容器检查和恢复机制容器检查和恢复机制命令模式探针httpget方式探针POD的恢复策略容器检查和恢复机制在kubernetes中,可以为容器定义一个健康探针,kubelet就会根据这个Probe的返回值决定这个容器的状态,而不是直接以容器是否运行(来自Docker返回的信息)作为依据......
  • Linux进程控制
    1.进程创建fork函数#include<unistd.h>pid_tfork(void);返回值:自进程中返回0,父进程返回子进程id,出错返回-1进程调用fork,当控制转移到内核中的fork代码后,内核做:1.分配新的内存块和内核数据结构给子进程2.将父进程部分数据结构内容拷贝至子......