当前位置: 首页 > article >正文

C语言-IO

一,阻塞IO与非阻塞IO

简介:

        IO的本质是基于操作系统接口来控制底层的硬件之间数据传输,并且在操作系统中实现了多种不同的 IO 方式(模型),比较常见的有下列三种
                阻塞型IO模型
                非阻塞型IO模型
                多路复用IO模型

在 C 语言中,阻塞 I/O 和非阻塞 I/O 是两种不同的输入 / 输出操作方式,它们在程序的行为和性能方面有很大的区别。

一、阻塞 I/O

  1. 概念:

    • 当一个进程进行阻塞 I/O 操作时,如果数据尚未准备好或者输出缓冲区已满,进程会被阻塞,暂停执行,直到 I/O 操作完成。
    • 例如,当使用read函数从一个文件描述符读取数据时,如果没有数据可读,进程会一直等待,直到有数据到达或者文件描述符被关闭。

  1. 特点:

    • 简单直观:编程模型相对简单,容易理解和实现。
    • 同步操作:进程在进行 I/O 操作时会等待操作完成,因此是一种同步的操作方式。
    • 低并发性:由于进程在进行 I/O 操作时会被阻塞,因此在一个单线程程序中,只能同时进行一个 I/O 操作,降低了系统的并发性。
  2. 示例代码:

#include <stdio.h>
#include <unistd.h>

int main() {
    int fd = open("test.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    char buffer[1024];
    ssize_t bytesRead = read(fd, buffer, sizeof(buffer));
    if (bytesRead == -1) {
        perror("read");
        return 1;
    }

    close(fd);
    return 0;
}

在这个例子中,如果test.txt文件中没有数据可读,read函数会阻塞进程,直到有数据可读或者文件描述符被关闭。

二、非阻塞 I/O

  1. 概念:

    • 非阻塞 I/O 允许进程在进行 I/O 操作时不会被阻塞。如果数据尚未准备好或者输出缓冲区已满,I/O 函数会立即返回一个错误码,表示操作无法立即完成。
    • 进程可以通过轮询的方式不断检查 I/O 状态,直到数据准备好或者操作可以完成。

  1. 特点:

    • 高并发性:进程在进行 I/O 操作时不会被阻塞,因此可以同时进行多个 I/O 操作,提高了系统的并发性。
    • 复杂编程模型:需要进程不断地进行轮询,增加了编程的复杂性。
    • 可能浪费 CPU 时间:如果数据一直不可用,进程会不断地进行轮询,浪费 CPU 时间。
  2. 示例代码:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int main() {
    int fd = open("test.txt", O_RDONLY | O_NONBLOCK);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    char buffer[1024];
    ssize_t bytesRead = 0;
    while ((bytesRead = read(fd, buffer, sizeof(buffer))) == -1 && errno == EAGAIN) {
        // 文件不可读,继续轮询
    }

    if (bytesRead > 0) {
        // 处理读取到的数据
    } else {
        if (bytesRead == 0) {
            // 到达文件末尾
        } else {
            perror("read");
        }
    }

    close(fd);
    return 0;
}

在这个例子中,使用O_NONBLOCK标志打开文件,使文件描述符处于非阻塞模式。如果文件中没有数据可读,read函数会立即返回-1,并且errno被设置为EAGAIN,表示文件不可读。进程可以通过不断地轮询来检查文件是否可读,直到有数据可读或者文件描述符被关闭。

三、阻塞 I/O 和非阻塞 I/O 的选择

  1. 应用场景:

    • 阻塞 I/O 适用于简单的程序,其中 I/O 操作相对较少,并且不需要高并发性。例如,一个命令行工具,只需要从标准输入读取数据并进行处理,然后输出结果。
    • 非阻塞 I/O 适用于需要高并发性的程序,其中多个 I/O 操作可以同时进行。例如,一个网络服务器,需要同时处理多个客户端的连接请求,并且不能因为一个客户端的 I/O 操作而阻塞其他客户端的请求处理。
  2. 性能考虑:

    • 阻塞 I/O 在 I/O 操作完成之前会阻塞进程,因此可能会导致程序的响应时间较长。但是,由于进程在进行 I/O 操作时不会消耗 CPU 时间,因此在 I/O 操作频繁的情况下,可能会比非阻塞 I/O 更高效。
    • 非阻塞 I/O 需要进程不断地进行轮询,因此会消耗一定的 CPU 时间。但是,由于进程在进行 I/O 操作时不会被阻塞,因此可以同时进行多个 I/O 操作,提高了系统的并发性。在 I/O 操作不频繁的情况下,非阻塞 I/O 可能会比阻塞 I/O 更高效。

总之,阻塞 I/O 和非阻塞 I/O 是两种不同的输入 / 输出操作方式,它们在程序的行为和性能方面有很大的区别。在选择使用哪种方式时,需要根据具体的应用场景和性能要求进行考虑。

二,多路复用IO-select

在 C 语言中,多路复用 I/O(I/O multiplexing)是一种可以同时监视多个文件描述符(file descriptor)的输入 / 输出状态的技术。其中,select函数是一种常用的实现多路复用 I/O 的方法。

简介

        本质上就是通过复用一个进程来处理多个 IO 请求 本质上就是通过 复用 个进程来处理多个 IO 请求
        基本思想:由内核来监控多个文件描述符是否可以进行 I/O 操作,如果有就绪的文件描述符,将结果
        告知给用户进程,则用户进程在进行相应的I/O 操作

类似于下图的老师检查学生作业

一、select函数的概念和用法

  1. 函数原型:
   int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

  1. 参数说明:

    • nfds:要监视的文件描述符的范围,通常设置为最高文件描述符值加 1。
    • readfdswritefdsexceptfds:分别是指向要监视的可读、可写和异常文件描述符集合的指针。可以为NULL,表示不监视相应类型的文件描述符。
    • timeout:指定等待的时间限制。可以为NULL,表示无限期等待;或者设置一个特定的时间值,表示等待的最长时间。
  2. 返回值:

    • 返回值表示就绪的文件描述符的数量。如果在等待时间内没有任何文件描述符就绪,select返回 0。如果发生错误,返回 -1,并设置errno
  3. 单进程可以处理,但是需要不断检测客户端是否发出 IO 请求,需要不断占用 cpu ,消耗 cpu 资源

二、使用步骤

  1. 初始化文件描述符集合:
    • 使用fd_set类型的变量来表示文件描述符集合。可以使用FD_ZERO宏初始化一个空集合,使用FD_SET宏将特定的文件描述符添加到集合中。
   fd_set readfds;
   FD_ZERO(&readfds);
   FD_SET(socket_fd, &readfds);

  1. 调用select函数:
    • 将初始化后的文件描述符集合作为参数传递给select函数,并设置适当的超时时间。
   struct timeval timeout;
   timeout.tv_sec = 5;
   timeout.tv_usec = 0;
   int ready = select(nfds, &readfds, NULL, NULL, &timeout);

  1. 检查就绪的文件描述符:
    • 根据select的返回值,检查哪些文件描述符就绪。可以使用FD_ISSET宏来测试特定的文件描述符是否在就绪集合中。
   if (ready > 0) {
       if (FD_ISSET(socket_fd, &readfds)) {
           // 处理可读的文件描述符
       }
   } else if (ready == 0) {
       // 超时
   } else {
       // 错误处理
   }

三、示例代码

以下是一个使用select函数实现简单服务器的示例,该服务器可以同时处理多个客户端连接:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/time.h>

#define PORT 8888
#define MAX_CLIENTS 10

void handleClient(int client_fd) {
    char buffer[1024];
    ssize_t bytesRead;
    while ((bytesRead = read(client_fd, buffer, sizeof(buffer))) > 0) {
        // 处理客户端请求
        write(client_fd, buffer, bytesRead);
    }
    close(client_fd);
}

int main() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    fd_set readfds;
    int max_fd;
    int i;

    // 创建服务器套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    // 绑定服务器套接字
    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    // 监听连接请求
    if (listen(server_fd, MAX_CLIENTS) == -1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d...\n", PORT);

    // 初始化文件描述符集合
    FD_ZERO(&readfds);
    FD_SET(server_fd, &readfds);
    max_fd = server_fd;

    while (1) {
        fd_set tmpfds = readfds;
        int ready = select(max_fd + 1, &tmpfds, NULL, NULL, NULL);
        if (ready == -1) {
            perror("select");
            exit(EXIT_FAILURE);
        }

        if (FD_ISSET(server_fd, &tmpfds)) {
            // 有新的连接请求
            client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
            if (client_fd == -1) {
                perror("accept");
                continue;
            }
            FD_SET(client_fd, &readfds);
            if (client_fd > max_fd) {
                max_fd = client_fd;
            }
            printf("New client connected.\n");
        } else {
            // 处理已连接的客户端
            for (i = 0; i <= max_fd; i++) {
                if (FD_ISSET(i, &tmpfds)) {
                    if (i!= server_fd) {
                        handleClient(i);
                        FD_CLR(i, &readfds);
                        if (i == max_fd) {
                            while (FD_ISSET(max_fd, &readfds) == 0 && max_fd > server_fd) {
                                max_fd--;
                            }
                        }
                    }
                }
            }
        }
    }

    close(server_fd);
    return 0;
}

在这个例子中,服务器使用select函数来监视服务器套接字和已连接的客户端套接字。当有新的连接请求时,服务器接受连接并将新的客户端套接字添加到文件描述符集合中。当有客户端发送数据时,服务器读取数据并将其回显给客户端。

四、注意事项

  1. 文件描述符限制:select函数的最大文件描述符数量通常受到系统限制。可以使用FD_SETSIZE宏来查看系统支持的最大文件描述符数量。
  2. 性能问题:select函数在每次调用时都需要重新设置文件描述符集合,并且在返回时需要遍历所有的文件描述符来确定哪些是就绪的。这可能会导致性能问题,特别是在处理大量文件描述符时。
  3. 超时处理:可以使用select函数的timeout参数来设置超时时间,以避免无限期地等待。如果超时时间到达,select将返回 0,表示没有文件描述符就绪。

在 C 语言中,poll是另一种实现多路复用 I/O 的方法。与select相比,poll在一些方面有改进。

三,多路复用IO-poll

简介:

多路复用 poll 的方式与 select 多路复用原理类似,但有很多地方不同,下面是具体的对比
       1. 在应用层是以结构体struct pollfd 数组的形式来进行管理文件描述符,在内核中基于链表对数组进
       2 .行扩展;select 方式以集合的形式管理文件描述符且最大支持 1024 个文件描述
        3.poll将请求与就绪事件通过结构体进行分开
        4.  select将请求与就绪文件描述符存储在同一个集合中,导致每次都需要进行重新赋值才能进行下一
次的监控
      5.  在内核中仍然使用的是轮询的方式,与 select 相同,当文件描述符越来越多时 , 则会影响效率

一、poll函数的概念和用法

  1. 函数原型:
   int poll(struct pollfd *fds, nfds_t nfds, int timeout);

  1. 参数说明:

    • fds:是一个pollfd结构数组的指针,每个结构表示一个要监视的文件描述符及其事件。
    • nfds:是要监视的文件描述符数组的长度。
    • timeout:指定等待的时间限制,以毫秒为单位。可以为负值,表示无限期等待;为 0 表示立即返回;为正值表示等待指定的时间。
  2. 返回值:

    • 返回值表示就绪的文件描述符的数量。如果在等待时间内没有任何文件描述符就绪,poll返回 0。如果发生错误,返回 -1,并设置errno

二、pollfd结构

pollfd结构通常定义如下:

struct pollfd {
    int fd;         // 文件描述符
    short events;   // 要监视的事件
    short revents;  // 实际发生的事件
};

其中,events成员用于指定要监视的事件类型,revents成员在poll返回时被设置为实际发生的事件类型。

常见的事件类型有:

  • POLLIN:表示文件描述符可读。
  • POLLOUT:表示文件描述符可写。
  • POLLPRI:表示有紧急数据可读。
  • POLLERR:表示发生错误。
  • POLLHUP:表示挂起。

三、使用步骤

  1. 定义pollfd结构数组并初始化:

   struct pollfd fds[10];
   fds[0].fd = socket_fd;
   fds[0].events = POLLIN;

  1. 调用poll函数:

   int ready = poll(fds, 10, -1);

  1. 检查就绪的文件描述符:

   if (ready > 0) {
       if (fds[i].revents & POLLIN) {
           // 处理可读的文件描述符
       }
   } else if (ready == 0) {
       // 超时
   } else {
       // 错误处理
   }

四、示例代码

以下是一个使用poll实现简单服务器的示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <poll.h>

#define PORT 8888
#define MAX_CLIENTS 10

void handleClient(int client_fd) {
    char buffer[1024];
    ssize_t bytesRead;
    while ((bytesRead = read(client_fd, buffer, sizeof(buffer))) > 0) {
        // 处理客户端请求
        write(client_fd, buffer, bytesRead);
    }
    close(client_fd);
}

int main() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    struct pollfd fds[MAX_CLIENTS + 1];
    int nfds = 1;

    // 创建服务器套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    // 绑定服务器套接字
    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    // 监听连接请求
    if (listen(server_fd, MAX_CLIENTS) == -1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d...\n", PORT);

    // 初始化文件描述符数组
    fds[0].fd = server_fd;
    fds[0].events = POLLIN;

    while (1) {
        int ready = poll(fds, nfds, -1);
        if (ready == -1) {
            perror("poll");
            exit(EXIT_FAILURE);
        }

        if (fds[0].revents & POLLIN) {
            // 有新的连接请求
            client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
            if (client_fd == -1) {
                perror("accept");
                continue;
            }
            if (nfds == MAX_CLIENTS + 1) {
                fprintf(stderr, "Too many clients.\n");
                close(client_fd);
            } else {
                fds[nfds].fd = client_fd;
                fds[nfds].events = POLLIN;
                nfds++;
                printf("New client connected.\n");
            }
        } else {
            // 处理已连接的客户端
            for (int i = 1; i < nfds; i++) {
                if (fds[i].revents & POLLIN) {
                    handleClient(fds[i].fd);
                    fds[i].fd = -1;
                    fds[i].events = 0;
                    if (i == nfds - 1) {
                        nfds--;
                    }
                }
            }
        }
    }

    close(server_fd);
    return 0;
}

在这个例子中,服务器使用poll来监视服务器套接字和已连接的客户端套接字。当有新的连接请求时,服务器接受连接并将新的客户端套接字添加到pollfd结构数组中。当有客户端发送数据时,服务器读取数据并将其回显给客户端。

五、pollselect的比较

  1. 可监视的文件描述符数量:poll没有最大文件描述符数量的限制,而select通常受到系统限制。
  2. 参数传递:poll使用pollfd结构数组,不需要像select那样每次重新设置文件描述符集合,减少了一些开销。
  3. 可移植性:select在不同的平台上更具可移植性,而poll在一些特定的系统上可能表现更好。

总之,poll是一种实现多路复用 I/O 的有效方法,在某些情况下比select更具优势。但在实际应用中,还可以考虑使用更高效的多路复用技术,如epoll

四,多路复用IO-epoll

在 C 语言中,epoll是一种高效的 I/O 多路复用机制,它克服了传统的selectpoll函数在处理大量文件描述符时的性能瓶颈。

简介:

        epoll相对于 select poll 有较大的不同,主要是针对前面两种多路复用 IO 接口的不足
select/poll的不足:
        select 方案使用数组存储文件描述符,最大支持 1024
        select 每次调用都需要将文件描述符集合拷贝到内核中,非常消耗资源
        poll 方案解决文件描述符存储数量限制问题,但其他问题没有得到解决
        select / poll 底层使用轮询的方式检测文件描述符是否就绪,文件描述符越多,则效率越低
epoll优点:
        epoll底层使用红黑树,没有文件描述符数量的限制,并且可以动态增加与删除节点,不用重复拷贝
        epoll底层使用 callback 机制,没有采用遍历所有描述符的方式,效率较高

select/poll 方案
epoll 方案

一、epoll的概念和特点

  1. epoll的工作原理:

    • epoll通过在内核中维护一个事件表,将需要监视的文件描述符及其感兴趣的事件注册到这个事件表中。
    • 当文件描述符上有事件发生时,内核会将这些事件通知给应用程序,应用程序可以根据这些通知进行相应的 I/O 操作。
  2. selectpoll的比较:

    • selectpoll在每次调用时都需要遍历所有的文件描述符,检查它们是否有事件发生,这种方式在处理大量文件描述符时效率低下。
    • epoll只需要在文件描述符状态发生变化时才会通知应用程序,避免了不必要的遍历,因此在处理大量文件描述符时具有更高的性能。
  3. epoll的事件触发模式:

    • epoll支持两种事件触发模式:水平触发(Level Triggered,LT)和边缘触发(Edge Triggered,ET)。
    • 在水平触发模式下,只要文件描述符上有事件发生,epoll就会不断地通知应用程序,直到应用程序对该事件进行处理。
    • 在边缘触发模式下,只有当文件描述符的状态从不可读 / 不可写变为可读 / 可写时,epoll才会通知应用程序。这种模式需要应用程序在一次通知中尽可能多地处理事件,以避免丢失事件。

二、使用epoll的步骤

  1. 创建epoll实例:

    • 使用epoll_create函数创建一个epoll实例,该函数返回一个文件描述符,用于后续的epoll操作。
    • 函数原型:int epoll_create(int size);
    • 参数size是一个提示性参数,表示epoll实例可以处理的最大文件描述符数量。这个参数在现代 Linux 内核中已经被忽略,但仍然需要提供一个大于 0 的值。
  2. 注册文件描述符和事件:

    • 使用epoll_ctl函数将需要监视的文件描述符及其感兴趣的事件注册到epoll实例中。
    • 函数原型:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    • 参数说明:
      • epfdepoll实例的文件描述符。
      • op是操作类型,可以是EPOLL_CTL_ADD(添加文件描述符)、EPOLL_CTL_MOD(修改文件描述符的事件)或EPOLL_CTL_DEL(删除文件描述符)。
      • fd是要注册的文件描述符。
      • event是一个指向epoll_event结构的指针,用于指定要监视的事件类型和相关的数据。
  3. 等待事件发生:

    • 使用epoll_wait函数等待epoll实例上的事件发生。
    • 函数原型:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    • 参数说明:
      • epfdepoll实例的文件描述符。
      • events是一个指向epoll_event结构数组的指针,用于存储发生的事件。
      • maxeventsevents数组的大小,表示最多可以返回的事件数量。
      • timeout是等待事件发生的超时时间,以毫秒为单位。可以设置为-1表示无限期等待。
  4. 处理事件:

    • epoll_wait函数返回时,应用程序可以根据events数组中的事件进行相应的 I/O 操作。
    • epoll_event结构中的events成员表示发生的事件类型,可以是EPOLLIN(可读事件)、EPOLLOUT(可写事件)等。

三、示例代码

以下是一个使用epoll实现简单服务器的示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>

#define PORT 8888
#define MAX_EVENTS 10

void handleClient(int client_fd) {
    char buffer[1024];
    ssize_t bytesRead;
    while ((bytesRead = read(client_fd, buffer, sizeof(buffer))) > 0) {
        // 处理客户端请求
        write(client_fd, buffer, bytesRead);
    }
    close(client_fd);
}

int main() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    struct epoll_event event, events[MAX_EVENTS];
    int epoll_fd, nfds;

    // 创建服务器套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    // 绑定服务器套接字
    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    // 监听连接请求
    if (listen(server_fd, SOMAXCONN) == -1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d...\n", PORT);

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

    // 将服务器套接字添加到 epoll 实例中,监视可读事件
    event.events = EPOLLIN;
    event.data.fd = server_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) {
        perror("epoll_ctl");
        exit(EXIT_FAILURE);
    }

    while (1) {
        // 等待事件发生
        nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (nfds == -1) {
            perror("epoll_wait");
            exit(EXIT_FAILURE);
        }

        // 处理发生的事件
        for (int i = 0; i < nfds; i++) {
            if (events[i].data.fd == server_fd) {
                // 有新的连接请求
                client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
                if (client_fd == -1) {
                    perror("accept");
                    continue;
                }

                // 将新的客户端套接字添加到 epoll 实例中,监视可读事件
                event.events = EPOLLIN;
                event.data.fd = client_fd;
                if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) == -1) {
                    perror("epoll_ctl");
                    close(client_fd);
                }
            } else {
                // 处理客户端的请求
                handleClient(events[i].data.fd);

                // 从 epoll 实例中删除客户端套接字
                if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL) == -1) {
                    perror("epoll_ctl");
                }
            }
        }
    }

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

在这个例子中,服务器使用epoll来监视服务器套接字和已连接的客户端套接字。当有新的连接请求时,服务器接受连接并将新的客户端套接字添加到epoll实例中。当有客户端发送数据时,服务器读取数据并将其回显给客户端。

四、注意事项

  1. 错误处理:

    • 在使用epoll函数时,要注意检查返回值并进行适当的错误处理。
    • 如果epoll_create1epoll_ctlepoll_wait函数返回错误,应该根据错误码进行相应的处理。
  2. 事件触发模式:

    • 根据应用程序的需求选择合适的事件触发模式。水平触发模式相对简单,但可能会导致频繁的通知;边缘触发模式需要应用程序更加小心地处理事件,以避免丢失事件。
  3. 资源管理:

    • 在使用完epoll实例后,应该及时关闭对应的文件描述符,以释放系统资源。

总之,epoll是一种高效的 I/O 多路复用机制,在处理大量文件描述符时具有明显的优势。通过正确地使用epoll,可以提高应用程序的性能和并发性。


http://www.kler.cn/a/322434.html

相关文章:

  • VSCode+ESP-IDF开发ESP32-S3-DevKitC-1(2)第一个工程 LED心跳灯
  • 【青牛科技】视频监控器应用
  • html实体字符
  • 5G的SUCI、SUPI、5G-GUTI使用场景及关系
  • 【Qt】QTreeWidget的简单使用
  • 数据库基本概念学习笔记
  • 卷积神经网络-最优模型
  • SSH 安全实战:保护您的远程访问
  • 嵌入式的核心能力-Debug调试能力(一)
  • 【分布式微服务】探索微服务架构下的服务治理
  • 1.MySQL的安装
  • AcWing 835. Trie字符串统计
  • 设计模式介绍
  • OJ在线评测系统 将代码沙箱开放为API 跑通前端后端整个项目 请求对接口
  • 后端开发刷题 | 没有重复项数字的全排列
  • 家庭网络的ip安全性高吗
  • 为什么IP首部的源IP地址和目的IP地址不变而MAC层的源MAC地址和目的MAC地址变
  • Spring Boot电商开发:购物商城系统
  • F28335 的 EPWM 外设
  • 鸿蒙_异步详解
  • Python知识点:如何使用Python进行卫星数据分析
  • 如何选择数据库架构
  • Redis 的 Java 客户端有哪些?官方推荐哪个?
  • socket.io-client实现实前后端时通信功能
  • LeetCode[中等] 78.子集
  • 基于SpringBoot+Vue+MySQL的旅游推荐管理系统