Linux socket编程(9):IO复用之poll和epoll详解

在之前的文章中,我们学习了IO复用模型之select原理及例子,但是select有监听描述符个数的限制,而且select的效率并不高,所以这篇文章就来学习效率更高的poll和Linux特有的epoll方法。

文章目录

  • 1 select/poll/epoll对比
  • 2 poll
    • 2.1 poll函数
    • 2.2 poll实战:实现多个套接字监听
      • 2.2.1 客户端
      • 2.2.2 服务端
      • 2.2.3 实验结果
      • 2.2.4 完整代码
  • 3 epoll
    • 3.1 相关函数
    • 3.2 epoll实战:实现多个套接字监听
      • 3.2.1 客户端
      • 3.2.2 服务端
      • 3.2.3 实验结果
      • 3.3.4 完整代码

1 select/poll/epoll对比

这三者都用于I/O多路复用来监视多个文件描述符。epoll的目的是取代较旧的POSIX中的selectpoll系统调用。

复杂性与可扩展性

  • selectpoll的时间复杂度为O(n),每次调用内核都需要遍历整个文件描述符
  • epoll的时间复杂度为O(1),它使用红黑树来跟踪当前被监视的所有文件描述符。epoll在文件描述符很多的情况下表现良好,且具有良好的可扩展性

可用性与可移植性

  • selectpoll在任何Unix系统上都可用

  • epoll是Linux特有的(在2.5.44版本之后可用)

  • poll是POSIX标准接口,因此在需要代码可移植时可以使用

poll和select

给定一组文件描述符,它们告诉你哪些文件描述符有可读/可写的数据。selectpoll从根本上基本使用相同的代码。poll对文件描述符返回一组可能的结果,如POLLRDNORMPOLLRDBANDPOLLINPOLLHUPPOLLERR,而select只告诉你有输入/输出/错误。

如果你有一个稀疏的文件描述符集(如设备长时间运行,在文件描述符回收和创建的过程中,可能一个描述符为1,一个描述符为1000),poll可以比select执行得更好。poll使用pollfd参数指定要监视的文件描述符;select使用位集并需要遍历整个范围。

select函数在某些系统上有文件描述符数量的限制,通常由文件描述符集的大小限制,例如 FD_SETSIZE。这个宏定义了文件描述符集的最大大小,通常是1024。而poll使用一个动态分配的数组来存储文件描述符集,因此理论上没有硬性的文件描述符数量限制。但在实际使用中,系统可能对单个进程所能打开的文件描述符总数有一定的限制,这是由操作系统的配置和资源限制决定的(可使用ulimit -n查看)。

2 poll

2.1 poll函数

poll允许程序监视多个文件描述符以确定是否可以进行I/O操作。

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • fds:指向pollfd结构体数组的指针,每个结构体表示一个要监视的文件描述符以及感兴趣的事件
  • nfds:数组中结构体的数量。
  • timeout:超时时间,单位是毫秒。传递负值表示无限超时,传递0表示立即返回。

pollfd结构体:

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

其中events/revents的取值可以为如下几个选项:

  • POLLIN:有数据可读。对于套接字来说,表示连接被对端关闭。
  • POLLPRI:有紧急数据可读。对于套接字来说,表示有带外数据。
  • POLLOUT:对端可写。
  • POLLRDHUP:对端挂起(连接关闭或半关闭)。
  • POLLERR:有错误发生。
  • POLLHUP:挂起事件。对于套接字来说,表示连接被挂起。
  • POLLNVAL:无效的请求,文件描述符未打开。

例如,如果你想监视可读和错误事件,可以将events设置为 POLLIN | POLLERR

注意

  • 如果revents中包含POLLNVAL,说明文件描述符无效或未打开,此时poll结果可能不可靠
  • revents中可能同时包含多个标志,需要使用位运算和上述常量进行判断
  • POLLRDHUPPOLLHUP标志在不同系统上可能有不同的行为,具体情况可以查看文档或相关头文件定义

2.2 poll实战:实现多个套接字监听

和之前select一样,这里就来实现一个服务端和客户端的模型,从代码中来深入理解poll函数的使用。

2.2.1 客户端

客户端需要能够监听标准输入stdin的消息,然后转发个服务端;还需要监听服务端的套接字,以接收服务端发来的消息。代码如下:

struct pollfd fds[2];
char buffer[1024];

fds[0].fd = 0;  // stdin
fds[0].events = POLLIN;
fds[1].fd = sock;
fds[1].events = POLLIN;

while(1)
{
    int ret = poll(fds, 2, -1);

    if (ret > 0)
    {
        if (fds[0].revents & POLLIN)
        {
            fgets(buffer, sizeof(buffer), stdin);
            send(sock, buffer, strlen(buffer), 0);
        }

        if (fds[1].revents & POLLIN)
        {
            int valread = read(sock, buffer, sizeof(buffer));
            if (valread > 0)
            {
                buffer[valread] = '\0';
                printf("Server says: %s", buffer);
            }
            else
            {
                // Server disconnected
                printf("Server disconnected\n");
                break;
            }
        }
    }
}

这里声明了一个 pollfd结构体变量fds,监听stdin和服务端的套接字。poll第三个超时参数为-1,表示无限等待。在poll返回之后,我们只需要判断对应fdsrevents对应的事件有没有置位就行了。

2.2.2 服务端

服务端则是一边要accept新的客户端连接请求,一边接收来自客户端的消息并回显回去。代码如下:

int client_sockets[MAX_CLIENTS] = {0};
struct pollfd fds[MAX_CLIENTS + 1];  // +1 for the listening socket

// Initialize the pollfd structure for the listening socket
fds[0].fd = server_fd;
fds[0].events = POLLIN;

while (1)
{
    activity = poll(fds, max_clients + 1, -1);
    
	if ((activity < 0) && (errno != EINTR))
    {
        perror("Poll error");
        exit(EXIT_FAILURE);
    }
    
    // Check for incoming connections on the listening socket
    if (fds[0].revents & POLLIN)
    {
        if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0)
        {
            perror("Accept failed");
            exit(EXIT_FAILURE);
        }
        printf("New connection, socket fd is %d, ip is: %s, port is: %d\n", new_socket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));
        // Add the new socket to the array of client sockets
        for (i = 1; i < max_clients + 1; i++)
        {
            if (client_sockets[i] == 0)
            {
                client_sockets[i] = new_socket;
                fds[i].fd = new_socket;
                fds[i].events = POLLIN;
                printf("Added new client to the list of sockets at index %d\n", i);
                break;
            }
        }
    }

    // Check for data from clients
    for (i = 1; i < max_clients + 1; i++)
    {
        sd = client_sockets[i];

        if (fds[i].revents & POLLIN)
        {
            if ((valread = read(sd, buffer, 1024)) == 0)
            {
                close(sd);
                client_sockets[i] = 0;
                printf("Client at index %d disconnected\n", i);
            }
            else
            {
                buffer[valread] = '\0';
                printf("Client at index %d says: %s\n", i, buffer);
            }
        }
    }
}

select一样,这里可以判断一下poll的返回值,小于0表示系统异常,但是如果errnoEINTR则表示进程被信号中断,继续下一次poll即可。

2.2.3 实验结果

首先打开服务端,再打开客户端,连接上后,客户端向服务端发送nohao,然后按Ctrl+C退出客户端,如下图所示:

在这里插入图片描述

2.2.4 完整代码

客户端

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

#define PORT 8080

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket creation error");
        exit(EXIT_FAILURE);
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // Convert IPv4 and IPv6 addresses from text to binary form
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        perror("Invalid address/ Address not supported");
        exit(EXIT_FAILURE);
    }

    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("Connection Failed");
        exit(EXIT_FAILURE);
    }

    struct pollfd fds[2];
    char buffer[1024];

    fds[0].fd = 0;  // stdin
    fds[0].events = POLLIN;
    fds[1].fd = sock;
    fds[1].events = POLLIN;

	while (1) {
		int ret = poll(fds, 2, -1);

		if (ret > 0) {
		    if (fds[0].revents & POLLIN) {
		        fgets(buffer, sizeof(buffer), stdin);
		        send(sock, buffer, strlen(buffer), 0);
		    }

		    if (fds[1].revents & POLLIN) {
		        int valread = read(sock, buffer, sizeof(buffer));
		        if (valread > 0) {
		            buffer[valread] = '\0';
		            printf("Server says: %s", buffer);
		        } else {
		            // Server disconnected
		            printf("Server disconnected\n");
		            break;
		        }
		    }
		}
	}

    close(sock);
    return 0;
}

服务端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <errno.h>

#define PORT 8080
#define MAX_CLIENTS 10

int main() {
    int server_fd, new_socket, max_clients = MAX_CLIENTS;
    int activity, i, valread;
    int sd, max_sd;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    char buffer[1024];

    // Create a socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    // Set up the server address struct
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // Bind the socket to the address
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("Bind failed");
        exit(EXIT_FAILURE);
    }

    // Listen for incoming connections
    if (listen(server_fd, 3) < 0) {
        perror("Listen failed");
        exit(EXIT_FAILURE);
    }

    int client_sockets[MAX_CLIENTS] = {0};
    struct pollfd fds[MAX_CLIENTS + 1];  // +1 for the listening socket

    // Initialize the pollfd structure for the listening socket
    fds[0].fd = server_fd;
    fds[0].events = POLLIN;

    printf("Waiting for connections...\n");

    while (1) {
        // Use poll to wait for events
        activity = poll(fds, max_clients + 1, -1);

        if ((activity < 0) && (errno != EINTR)) {
            perror("Poll error");
            exit(EXIT_FAILURE);
        }

        // Check for incoming connections on the listening socket
        if (fds[0].revents & POLLIN) {
            if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
                perror("Accept failed");
                exit(EXIT_FAILURE);
            }

            printf("New connection, socket fd is %d, ip is: %s, port is: %d\n", new_socket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));

            // Add the new socket to the array of client sockets
            for (i = 1; i < max_clients + 1; i++) {
                if (client_sockets[i] == 0) {
                    client_sockets[i] = new_socket;
                    fds[i].fd = new_socket;
                    fds[i].events = POLLIN;
                    printf("Added new client to the list of sockets at index %d\n", i);
                    break;
                }
            }
        }

        // Check for data from clients
        for (i = 1; i < max_clients + 1; i++) {
            sd = client_sockets[i];

            if (fds[i].revents & (POLLIN | POLLHUP | POLLERR)) {
                if ((valread = read(sd, buffer, 1024)) == 0) {
                    // Client disconnected
                    close(sd);
                    client_sockets[i] = 0;
                    printf("Client at index %d disconnected\n", i);
                } else {
                    // Process client message (in this example, just print it)
                    buffer[valread] = '\0';
                    printf("Client at index %d says: %s\n", i, buffer);
                }
            }
        }
    }

    return 0;
}

3 epoll

epollselectpoll更为灵活和高效,特别是在大量连接上的场景。

3.1 相关函数

来看一下与epoll相关的函数原型:

1、epoll_create和epoll_create1:创建epoll实例

int epoll_create(int size);
int epoll_create1(int flags);
  • epoll_create:创建一个epoll实例。size参数在大多数情况下会被忽略,可以设置为大于0的任何值。
  • epoll_create1:与epoll_create类似,但它支持flag设置为EPOLL_CLOEXEC,表示在调用 exec 进程时,epoll实例的文件描述符将会被关闭,以防止它在新程序中继续存在。这可以增强程序的安全性和可预测性。
    • 如果flags为 0,那么EPOLL_CLOEXEC标志将不会被设置。

2、epoll_ctl:在epoll实例中注册、修改或删除文件描述符的监听事件

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  • epfd:是一个由 epoll_createepoll_create1 返回的 epoll 实例的文件描述符。
  • op:是一个操作符,指定对 epoll 实例的操作类型。可以取以下值:
    • EPOLL_CTL_ADD:添加一个新的文件描述符到epoll实例中进行监听。
    • EPOLL_CTL_MOD:修改一个已经在epoll实例中的文件描述符的监听事件。
    • EPOLL_CTL_DEL:从 epoll 实例中删除一个文件描述符。
  • fd:是要进行操作的文件描述符,即要添加、修改或删除的文件描述符。
  • event:是一个指向struct epoll_event结构的指针,用于指定要监听的事件类型以及关联的数据。

3、epoll_wait:等待事件发生。返回发生的事件的数量,并将事件信息填充到提供的数组中。

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  • epfd:是一个由epoll_createepoll_create1返回的epoll实例的文件描述符。
  • events:是一个指向struct epoll_event结构的数组,用于存储发生事件的文件描述符和相关信息。
  • maxeventsevents数组的大小,即最多能存储多少个事件。
  • timeout:是等待的超时时间,以毫秒为单位。传递负值表示epoll_wait将一直阻塞,直到有事件发生。传递0表示立即返回,不管是否有事件发生。

其中 **struct epoll_event**结构体定义如下:

struct epoll_event {
    __uint32_t events;  // 要监视的事件
    epoll_data_t data;  // 用户数据
};

typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;
  • events字段:表示要监视的事件,可以是EPOLLIN(可读)、EPOLLOUT(可写)等。具体的事件常量可以查看 <sys/epoll.h>头文件。
  • data字段:用于保存用户数据。可以是文件描述符(fd)、指针(ptr)等,取决于epoll_data的类型。

3.2 epoll实战:实现多个套接字监听

这里用epoll来实现一个服务端和客户端的模型,通过代码来理解epoll的使用方法。

3.2.1 客户端

1、创建epoll实例

int epoll_fd = epoll_create1(0);

2、添加待监听的文件描述符

struct epoll_event event;

event.events = EPOLLIN;
event.data.fd = STDIN_FILENO;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event);

event.events = EPOLLIN;
event.data.fd = sock;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &event);

这里epoll_eventdata(用户数据)就保存文件描述符,用于后面判断是哪里来的消息。同时这里两个epoll_ctl的最后一个参数用了同一个变量event的地址传入,这是因为传入后函数内部会对数据进行拷贝。

3、等待和处理事件

struct epoll_event events[MAX_EVENTS];
while (1)
{
    int event_count = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
    for (int i = 0; i < event_count; i++)
    {
        if (events[i].data.fd == STDIN_FILENO)
        {
            fgets(buffer, sizeof(buffer), stdin);
            send(sock, buffer, strlen(buffer), 0);
        }
        else if (events[i].data.fd == sock)
        {
            ssize_t bytes_received = recv(sock, buffer, sizeof(buffer), 0);
            if (bytes_received <= 0)
            {
                printf("Server disconnected\n");
                close(sock);
                exit(EXIT_SUCCESS);
            }
            else
            {
                buffer[bytes_received] = '\0';
                printf("Server says: %s\n", buffer);
            }
        }
    }
}

epoll_wait会返回事件的个数,并将结果保存在events中,我们只需要遍历它即可。

3.2.2 服务端

1、创建epoll实例和添加文件描述符

epoll_fd = epoll_create1(0);

struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = server_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event);

2、等待和处理事件

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)
        {
            if ((new_socket = accept(server_fd, (struct sockaddr *)&address, &addrlen)) < 0)
            {
                perror("Accept failed");
                exit(EXIT_FAILURE);
            }
            event.events = EPOLLIN;
            event.data.fd = new_socket;
            if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_socket, &event) == -1)
            {
                perror("Failed to add new client socket to epoll");
                exit(EXIT_FAILURE);
            }

            printf("New connection, socket fd is %d, ip is: %s, port is: %d\n", new_socket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));
        }
        else
        {
            int client_socket = events[i].data.fd;
            ssize_t bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
            if (bytes_received <= 0)
            {
                printf("Client at socket fd %d disconnected\n", client_socket);
                close(client_socket);
                epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_socket, NULL);
            }
            else
            {
                buffer[bytes_received] = '\0';
                printf("Client at socket fd %d says: %s\n", client_socket, buffer);
            }
        }
    }
}

3.2.3 实验结果

首先打开服务端,再打开客户端,连接上后,客户端向服务端发送123,然后按Ctrl+C退出客户端,如下图所示:
在这里插入图片描述

3.3.4 完整代码

客户端

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

#define PORT 8080
#define MAX_EVENTS 10

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char buffer[1024];

    // Create a socket
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket creation error");
        exit(EXIT_FAILURE);
    }

    // Set up the server address struct
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // Convert IPv4 and IPv6 addresses from text to binary form
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        perror("Invalid address/ Address not supported");
        exit(EXIT_FAILURE);
    }

    // Connect to the server
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("Connection Failed");
        exit(EXIT_FAILURE);
    }

    // Create epoll instance
    int epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("Failed to create epoll instance");
        exit(EXIT_FAILURE);
    }

    // Add stdin and socket to epoll
    struct epoll_event events[MAX_EVENTS];
    struct epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = STDIN_FILENO;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) {
        perror("Failed to add stdin to epoll");
        exit(EXIT_FAILURE);
    }

    event.events = EPOLLIN;
    event.data.fd = sock;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &event) == -1) {
        perror("Failed to add socket to epoll");
        exit(EXIT_FAILURE);
    }

    while (1) {
        int event_count = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        for (int i = 0; i < event_count; i++) {
            if (events[i].data.fd == STDIN_FILENO) {
                // Data from stdin
                fgets(buffer, sizeof(buffer), stdin);
                send(sock, buffer, strlen(buffer), 0);
            } else if (events[i].data.fd == sock) {
                // Data from server
                ssize_t bytes_received = recv(sock, buffer, sizeof(buffer), 0);
                if (bytes_received <= 0) {
                    // Server disconnected
                    printf("Server disconnected\n");
                    close(sock);
                    exit(EXIT_SUCCESS);
                } else {
                    // Process server message (in this example, just print it)
                    buffer[bytes_received] = '\0';
                    printf("Server says: %s\n", buffer);
                }
            }
        }
    }

    close(sock);
    return 0;
}

服务端

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

#define PORT 8080
#define MAX_EVENTS 10

int main() {
    int server_fd, new_socket;
    int epoll_fd, event_count;
    struct sockaddr_in address;
    socklen_t addrlen = sizeof(address);
    char buffer[1024];

    // Create a socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    // Set up the server address struct
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // Bind the socket to the address
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("Bind failed");
        exit(EXIT_FAILURE);
    }

    // Listen for incoming connections
    if (listen(server_fd, 3) < 0) {
        perror("Listen failed");
        exit(EXIT_FAILURE);
    }

    // Create epoll instance
    epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("Failed to create epoll instance");
        exit(EXIT_FAILURE);
    }

    // Add the server socket to epoll
    struct epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = server_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) {
        perror("Failed to add server socket to epoll");
        exit(EXIT_FAILURE);
    }

    struct epoll_event events[MAX_EVENTS];

    printf("Waiting for connections...\n");

    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 client connection
                if ((new_socket = accept(server_fd, (struct sockaddr *)&address, &addrlen)) < 0) {
                    perror("Accept failed");
                    exit(EXIT_FAILURE);
                }

                // Add new client socket to epoll
                event.events = EPOLLIN;
                event.data.fd = new_socket;
                if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_socket, &event) == -1) {
                    perror("Failed to add new client socket to epoll");
                    exit(EXIT_FAILURE);
                }

                printf("New connection, socket fd is %d, ip is: %s, port is: %d\n", new_socket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));
            } else {
                // Data from client
                int client_socket = events[i].data.fd;
                ssize_t bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
                if (bytes_received <= 0) {
                    // Client disconnected
                    printf("Client at socket fd %d disconnected\n", client_socket);
                    close(client_socket);
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_socket, NULL);
                } else {
                    // Process client message (in this example, just print it)
                    buffer[bytes_received] = '\0';
                    printf("Client at socket fd %d says: %s\n", client_socket, buffer);
                }
            }
        }
    }

    close(server_fd);
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/214741.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【开源项目】Windows串口通信组件 -- Com.Gitusme.IO.Ports.SerialPort

目录 1、项目介绍 2、组件集成 1&#xff09;下载地址&#xff1a; 2&#xff09;添加项目依赖 3、使用方法 4、GitHub项目地址 1、项目介绍 Com.Gitusme.IO.Ports.SerialPort 是一款 Windows 串口通信组件&#xff0c;基于.Net Core 3.1 开发&#xff0c;支持 Console、Wi…

PostgreSQL有意思的现象:支持不带列的表

1、前言 以前从没有试过建一张表&#xff0c;不带任何列。在PG中却支持这种语法。这是个什么鬼? 最近&#xff0c;把PG源码扒了下&#xff0c;简单浏览了下最近的一些merge。其中有一个fix&#xff1a; eeb0ebad79 ("Fix the initial sync tables with no columns.&qu…

CRM简单小结

思想 对于三层架构&#xff0c;一个模块对应一个controller&#xff0c;controller实际就是Servlet&#xff1b;一张表对应一个domain类对应一个dao接口对应一个mapper文件&#xff1b;service层没有严格规定&#xff0c;如果两张表内容相近&#xff0c;用一个service接口也可以…

【C++练级之路】【Lv.2】类和对象(上)(类的定义,访问限定符,类的作用域,类的实例化,类的对象大小,this指针)

目录 一、面向过程和面向对象初步认识二、类的引入三、类的定义四、类的访问限定符及封装4.1 访问限定符4.2 封装 五、类的作用域六、类的实例化七、类的对象大小的计算7.1 类对象的存储方式猜测7.2 如何计算类对象的大小 八、类成员函数的this指针8.1 this指针的引出8.2 this指…

排序算法介绍(二)冒泡排序

0. 简介 冒泡排序&#xff08;Bubble Sort&#xff09;是一种简单的排序算法。它重复地遍历要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换&#xff0c;也就是说该数列已经排…

CentOS7搭建部署NTP服务器

服务端配置&#xff1a; yum install ntp ntpdate -y #下载安装包 修改配置文件&#xff0c;同步阿里的NTP服务器 vim /etc/ntp.conf systemctl start ntpd #启动该服务 ntpq -p #查看是否同步了阿里的NTP 服务端同步成功后&#xff0c;可以去新增…

移除元素、合并两个有序数组(leetcode)

一、移除元素 思路三&#xff1a; while(src<numsSize) 使用一个 while 循环来遍历数组。循环的条件是 src 必须小于 numsSize&#xff0c;以确保不会越界。 if(nums[src]!val) 如果当前 src 指向的元素不等于给定的值 val&#xff0c;则执行以下操作&#xff1a; nums[ds…

国际语音通知系统有哪些优点?国际语音通知系统有哪些应用场景?

国际语音通知是一种全球性的通信工具&#xff0c;它通过语音方式向用户发送各种重要信息和提示。无论是快递到货的取件提醒、机场航班的延误通知&#xff0c;还是银行账户的余额提醒&#xff0c;国际语音通知都能准确、迅速地将信息传达给用户。 三、国际语音通知系统有哪些优…

Jupyter NoteBook未授权访问漏洞

任务一&#xff1a; 复现未授权访问的漏洞 任务二&#xff1a; 利用Jupter Notebook控制台执行系统命令&#xff0c;读取/etc/passwd内容 1.搭建环境 2.new下面直接进入终端&#xff0c;而且也不需要登录&#xff0c;我就直接进入了管理界面 3.直接把指令输入进入&#xf…

【Unity动画】为一个动画片段添加事件Events

动画不管播放到那一帧&#xff0c;我们都可以在这里“埋伏”一个事件&#xff08;调用一个函数并且给函数传递一个参数&#xff0c;参数在外部设置&#xff0c;甚至传递一个物体&#xff09;&#xff01; 嗨&#xff0c;亲爱的Unity小伙伴们&#xff01;你是否曾想过为你的动画…

Java 数组另类用法(字符来当数组下标使用)

一、原因 看力扣的时候发现有位大佬使用字符来当数组下标使用。 class Solution {public int lengthOfLongestSubstring(String s) {int result 0;int[] hash new int[130];int i 0;for(int j 0; j < s.length(); j) {while(hash[s.charAt(j)] > 0) {hash[s.charAt…

基于SSM框架开发的酒店后台管理系统

基于SSM框架开发的酒店后台管理系统 文章目录 基于SSM框架开发的酒店后台管理系统 一.引言二.系统设计三.技术架构四.功能实现五.界面展示六.源码获取 一.引言 酒店管理系统是一个集客房预订、前台管理、客户服务、财务管理等功能于一体的综合性软件系统。它能够帮助酒店高效地…

C++面试宝典第1题:爬楼梯

题目 小乐爬楼梯&#xff0c;一次只能上1级或者2级台阶。楼梯一共有n级台阶&#xff0c;请问总共有多少种方法可以爬上楼&#xff1f; 解析 这道题虽然是一道编程题&#xff0c;但实际上更是一道数学题&#xff0c;着重考察应聘者的逻辑思维能力和分析解决问题的能力。 当楼梯只…

检测下我的饺子皮擀的怎么样(圆度)

&#x1f604;&#x1f60a;&#x1f606;&#x1f603;&#x1f604;&#x1f60a;&#x1f606;&#x1f603; 各位老铁周末愉快。 快乐的时间做充实的事&#xff0c;好久没有吃饺子了&#xff0c;俗话说好吃不过饺子。 我个人觉得会包饺子不算本事&#xff0c;会擀饺子皮…

一次电气——电抗器(一)

我之前的工作是在国外建联合循环电厂&#xff0c;现在的工作是研发一次电力设备。虽然仍是在电力行业发展&#xff0c;但这两份不同岗位不同职能的工作究其感受而言有很大的不同。相较于第一份工作&#xff0c;第二份工作带给我带来的更多的是一种由广及微&#xff0c;由浅入深…

Linux系统-----进程通讯

前言 本期我们来学习进程间的通讯&#xff0c;不同进程之间是可以去通过信号来去实现通讯交流的&#xff0c;下面我们就一起来看看多进程之间的通讯方式。 一、信号机制 1、信号的基本概念 每个信号都对应一个正整数常量(称为signal number,即信号编号。定义在系统头文件<…

Android开发,JNI开发项目创建

文章目录 Android开发&#xff0c;JNI开发项目创建1.jni是什么 Android开发&#xff0c;JNI开发项目创建 创建工程 1.jni是什么 使得java可以访问底层c语言&#xff0c;java本地化接口&#xff0c;是桥梁。 运行下我们的项目 出现这个就是我们的JNI开发环境已经配置好了 是…

算法通关村第七关—迭代实现二叉树的遍历(黄金)

迭代实现二叉树的遍历 迭代法实现前序遍历 前序遍历是中左右&#xff0c;如果还有左子树就一直向下找。完了之后再返回从最底层逐步向上向右找。不难写出如下代码&#xff1a;&#xff08;注意代码中&#xff0c;空节点不入栈&#xff09; public List<Integer>preorde…

组合总和II(回溯、去重)

40. 组合总和 II - 力扣&#xff08;LeetCode&#xff09; 题目描述 给定一个候选人编号的集合 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字在每个组合中只能使用 一次 。 注意&#xff1a…

Windows驱动中校验数字签名(使用 ci.dll)

1.背景 对于常规应用程序来说&#xff0c;校验数字签名认证在应用层可以使用 WinVerifyTrust, 在驱动层使用常规的 API无法使用&#xff0c;自己分析数据又太麻烦。 在内核中 ci.dll 包装了数据签名验证相关的功能&#xff0c;我们可以使用该 dll 来实现我们的数字签名验证。 详…
最新文章