C语言-IO

news2024/11/18 13:55:01

一,阻塞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.coloradmin.cn/o/2172833.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

牛客SQL练习详解 02:条件查询

牛客SQL练习详解 02&#xff1a;条件查询 1、基础排序sql36 查找后排序sql37 查找后多列排序sql38 查找后降序排列 2、基础操作符sql6 查找学校时北大的学生信息sql7 查找年龄大于24岁的用户信息sql8 查找某个年龄段的用户信息sql9 查找chuchu 3、高级操作符sql11 高级操作符练…

认知杂谈91《菜鸟的自我修炼:减少过度干预》

内容摘要&#xff1a;          在投资和生活中&#xff0c;动作过多往往因情绪波动和缺乏计划而引发亏损。历史上的安史之乱和现代投资中的频繁交易都是例证。要管理情绪&#xff0c;首先要认识自己的情绪模式&#xff0c;然后改变消极的思考方式&#xff0c;并通过合…

『USB3.0Cypress』QT基于cyusb_linux_1.0.5开发上位机

文章目录 1.CyUSB Suite2.搭建开发环境3.Cyusb的应用4.疑问解决5.传送门1.CyUSB Suite CyUSB Suite for Linux是一个围绕现有开源用户空间USB库libusb的wrapper。CyUSB套件通过围绕libusb的简化包装器以及在下载固件后提供用于测试外围设备的基础设施,让您快速入门。换句话说…

1.6 物理层

欢迎大家订阅【计算机网络】学习专栏&#xff0c;开启你的计算机网络学习之旅&#xff01; 文章目录 前言1 物理层的基本概念1.1 定义1.2 作用1.3 物理层的主要任务 2 数据通信的基础知识2.1 常用术语2.2 信号2.3 码元2.4 信道2.5 数据通信系统模型 3 信道的极限容量3.1 基本术…

LabVIEW提高开发效率技巧----合理管理程序架构

在LabVIEW开发中&#xff0c;合理管理程序架构是保持项目可维护性和扩展性的关键。随着项目复杂度的增加&#xff0c;良好的架构设计可以避免代码混乱&#xff0c;并且便于后期的修改和扩展。以下是两种常见且有效的架构管理方式&#xff1a; 1. 面向对象编程&#xff08;OOP&a…

Sony IMX334LQR-C 1/1.8寸 8.42 M像素

索尼IMX334LQR宽动态超星光级交通监测CMOS 封装&#xff1a;LGA 对⾓线&#xff1a;8.86 mm&#xff08;类型1/1.8&#xff09; 索尼IMX334LQR宽动态超星光级交通监测CMOS的参数及规格书资料&#xff1a; IMX334LQR-C是⼀个对⾓线8.86 mm&#xff08;类型1/1.8&#xff09;的…

安科瑞Acrel-1000DP分布式光伏监控系统在鄂尔多斯市鄂托克旗巴音乌苏六保煤矿5MW分布式光伏项目中的应用

安科瑞 华楠 摘 要&#xff1a;分布式光伏发电就是将太阳能光伏板分散布置在各个区域&#xff0c;通过小规模、模块化的方式实现电能的并网或独立使用&#xff0c;这种发电方式具有就近发电、就近并网、就近转换、就近使用的特点。近年来&#xff0c;技术进步和政策支持推动了光…

8086介绍

内部结构 执行部件EU&#xff08;Execution Unit&#xff09; 包含运算器、通用寄存器组、EU控制单元。 只负责控制&#xff0c;不和外部总线打交道 总线接口部件BIU&#xff08;Bus Interface Unit&#xff09; 包含指令队列缓冲器、16位指令指针寄存器IP、16位段寄存器&am…

TypeScript入门 (五)异步编程与前后端交互

引言 大家好&#xff0c;我是GISer Liu&#x1f601;&#xff0c;一名热爱AI技术的GIS开发者。本系列文章是我跟随DataWhale 2024年9月学习赛的TypeScript学习总结文档。本文旨在全面介绍 TypeScript 中的异步编程与网络请求&#xff0c;帮助读者深入理解 TypeScript 中的 asyn…

Colorful/七彩虹将星X17 XS 22 Win11原厂OEM系统 带COLORFUL一键还原

安装完毕自带原厂驱动和预装软件以及一键恢复功能&#xff0c;自动重建COLORFUL RECOVERY功能&#xff0c;恢复到新机开箱状态。 【格式】&#xff1a;iso 【系统类型】&#xff1a;Windows11 原厂系统下载网址&#xff1a;http://www.bioxt.cn 注意&#xff1a;安装系统会…

Redis 五大基本数据类型及其应用场景进阶(缓存预热、雪崩 、穿透 、击穿)

Redis 数据类型及其应用场景 Redis 是什么? Redis是一个使用C语言编写的高性能的基于内存的非关系型数据库&#xff0c;基于Key/Value结构存储数据&#xff0c;通常用来 缓解高并发场景下对某一资源的频繁请求 &#xff0c;减轻数据库的压力。它支持多种数据类型,如字符串、…

Linux安装JDK及配置环境变量超详细教程

微服务Linux解析部署使用全流程 linux系统的常用命令 Linux安装vim超详细教程 Linux安装tomcat及配置环境变量超详细教程 1、上传压缩包 统一创建目录&#xff1a;/usr/local/jdk&#xff0c;将压缩包上传到这个目录下。拖动文件到这个目录下即可。 2、执行解压命令 先进…

ELMO理论

目录 1 优点 2 缺点 3.知识点个人笔记 2018年3月份&#xff0c;ELMo出世&#xff0c;该paper是NAACL18 Best Paper。在之前2013年的word2vec及2014年的GloVe的工作中&#xff0c;每个词对应一个vector&#xff0c;对于多义词无能为力。ELMo的工作对于此&#xff0c;提出了一…

在 Gitlab 中使用 ChatGPT 进行 CodeReview

ChatGPT集成Gitlab&#xff0c;实现自动代码审计并进行评论&#xff0c;为软件开发团队提供高效、智能的代码审查解决方案。支持其他模型如通义千问等 自动触发与及时响应&#xff1a;利用Gitlab的Webhook功能&#xff0c;实现代码提交、合并请求和标签创建等事件的自动触发。一…

安全帽检测系统丨OPENAIGC开发者大赛高校组AI创作力奖

在第二届拯救者杯OPENAIGC开发者大赛中&#xff0c;涌现出一批技术突出、创意卓越的作品。为了让这些优秀项目被更多人看到&#xff0c;我们特意开设了优秀作品报道专栏&#xff0c;旨在展示其独特之处和开发者的精彩故事。 无论您是技术专家还是爱好者&#xff0c;希望能带给…

国产化低功耗低延时广覆盖物联网无线通讯方案_LAKI模组

01 物联网系统中为什么要使用LAKI模组。 物联网系统中使用LAKI模组的原因可以归结为以下几个方面&#xff1a; 技术先进性 广覆盖能力&#xff1a;LAKI模组具有卓越的广覆盖能力&#xff0c;其射频SoC芯片接收灵敏度小于-120dBm125kbps&#xff0c;系统通讯距离可达5千米以上…

一款好用的多种格式电子书制作软件

在数字化阅读日益普及的今天&#xff0c;电子书已经成为人们日常生活中不可或缺的一部分。而一款功能强大、操作简便的电子书制作软件&#xff0c;无疑是满足广大用户需求的最佳选择。 这款软件名为“FLBOOK在线制作电子杂志平台”&#xff0c;它支持多种格式输入&#xff0c;如…

设计模式、系统设计 record part02

软件设计模式&#xff1a; 1.应对重复发生的问题 2.解决方案 3.可以反复使用 1.本质是面向对象 2.优点很多 1.创建型-创建和使用分离 2.结构型-组合 3.行为型-协作 571123种模式 UML-统一建模语言-Unified Modeling Language 1.可视化&#xff0c;图形化 2.各种图&#xff08;9…

Python编程:08- pycharm使用技巧

新建文件时,自动填充代码 设置方法&#xff1a; settings→editor→file and code templates,选择python script #${NAME} 文件名 #${DATE} 日期自动补齐 if name ‘main’: # 先输入main,然后按tab键自动补齐自定义的段落 settings→editor→live templates,在右侧点击号…

C语言进阶版第12课—字符函数和字符串函数1

文章目录 1. 字符分类函数1.1 库函数iscntrl1.2 库函数isspace1.3 库函数islower和isupper 2. 字符转换函数3. strlen函数的使用和模拟实现3.1 strlen函数的使用3.2 strlen函数的模拟实现 4. strcpy函数的使用和模拟实现4.1 strcpy函数的使用4.2 strcpy函数的模拟实现 5. strca…