Linux network programming (use of epoll function)


Preface

In this article, we explain how to use the epoll function. Compared with poll, epoll has improved performance.

1. Explanation of the concept and characteristics of epoll

epoll is a high-performance multiplexing mechanism on Linux that monitors a large number of file descriptors and notifies applications when they are ready. It is further optimized and improved on the basis of select and poll.

epoll 的主要特点包括:

1. No limit on the number of file descriptors: Unlike select and poll, epoll uses an event-based readiness notification mechanism, has no predefined limit on the number of file descriptors, and can support larger concurrent connections.

2. Efficient event notification: epoll uses the event data structure shared by the kernel and user space to register the event of the file descriptor to the kernel space. When the event is ready, the kernel directly notifies the ready event to the user space, avoiding every Each call requires the performance overhead of traversing the entire file descriptor array.

3. Separate ready event set: epoll copies ready events from kernel space to user space to form a separate ready event set. Users can directly traverse this set to process ready events without traversing the entire file descriptor array .

4. Supports edge triggering and horizontal triggering: epoll provides two modes to handle events, one is the edge triggering mode (EPOLLET), which only notifies the application when the state changes, and the other is the horizontal triggering mode (default). The application is notified all the time that the event is ready.

5. Lower memory copy overhead: epoll uses memory mapping technology to avoid the overhead of copying event data from the kernel to user space for each call, thereby reducing the number of system calls and the overhead of memory copying.

6. Supports higher-precision timeout control: Unlike poll, epoll's timeout parameters are in milliseconds and nanoseconds, providing higher-precision timeout control.

总体来说,epoll 在性能上相比于 select 和 poll 有较大的优势,特别适用于高并发场景下的网络编程。它的高效事件就绪通知、支持大规模并发连接、较低的内存拷贝开销以及较高的超时精度,使得它成为开发高性能服务器和网络应用的首选机制。

2. epoll implementation mechanism

epoll is implemented using Red-Black Tree. epoll is an efficient event notification mechanism provided by the Linux operating system, used to handle a large number of concurrent connections. It can monitor the status changes of multiple file descriptors and perform corresponding processing through callback functions when the file descriptors are ready.

In the Linux kernel, epoll uses a red-black tree as its main data structure to maintain a set of registered file descriptors. Red-black tree is a self-balancing binary search tree with fast time complexity for insertion, deletion and search operations. By using red-black trees, epoll can efficiently retrieve and manage large numbers of file descriptors.

When an event occurs on the file descriptor, epoll quickly locates the corresponding node through the search operation of the red-black tree, and triggers the registered callback function for event processing. The reason for using a red-black tree is that it can maintain a good balance and ensure that the worst-case time complexity of search, insertion, and deletion operations is O(log n), thereby ensuring the high performance and scalability of epoll.

In summary, epoll is implemented using red-black trees as its underlying data structure, which enables it to provide an efficient event notification mechanism when handling a large number of concurrent connections.

3. Explanation of epoll related functions

1.epoll_create function

Function prototype:

int epoll_create(int size);

The epoll_create function creates an epoll instance and returns a file descriptor used to identify the epoll instance. The parameter size is a hint, indicating the upper limit of the number of file descriptors that the epoll instance can monitor. But in most cases, this parameter will be ignored and any value can be passed.

The returned file descriptor can be used to perform subsequent operations on the epoll instance, such as registering, modifying, and deleting file descriptor events.

It should be noted that the epoll_create function returns a non-negative integer when successful, indicating the file descriptor of the epoll instance. If an error occurs, the return value is -1, and the errno error code is set to indicate the specific error type.

2.epoll_ctl function

Function prototype:

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

The epoll_ctl function operates a specific epoll instance through the specified epfd parameter. The op parameter indicates the operation type, which can be one of the following three:

EPOLL_CTL_ADD: Add the specified file descriptor fd to the epoll instance and register the corresponding event. This way, the application is notified when an event on that file descriptor is ready.

EPOLL_CTL_MOD: Modify the event corresponding to the file descriptor fd registered in the epoll instance. You can modify the type of event, events of concern, associated user data, etc.

EPOLL_CTL_DEL: Delete the file descriptor fd registered in the epoll instance.

The fd parameter is the target file descriptor, which is used to specify the file descriptor that needs to be operated.

The event parameter is a pointer to the struct epoll_event structure, used to specify event-related configuration. This structure contains two members:

uint32_t events: Indicates the registered event type, which can be EPOLLIN (readable event), EPOLLOUT (writable event), EPOLLRDHUP (the peer closes the connection), EPOLLPRI (emergency data is readable), EPOLLERR (error event), etc. Can be combined using bitmasks.

epoll_data_t data: used to store user data information, which can be the value of the file descriptor itself or a user-defined data structure pointer.

The return value of the function is 0, which means the operation was successful, and -1, which means an error occurred. The specific error information can be obtained by checking the errno variable.

3.epoll_wait function

Function prototype:

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

The epfd parameter is the descriptor of the epoll instance, which specifies the epoll instance to wait for events.

The events parameter is an array used to store event information. Each array element is a structure of type struct epoll_event, used to store ready file descriptors and corresponding event information.

The maxevents parameter indicates the size of the events array, that is, the maximum number of events that can be waited for.

The timeout parameter is the timeout in milliseconds. It determines the blocking behavior of the epoll_wait function:

If timeout is set to -1, it means blocking indefinitely until an event occurs.

If timeout is set to 0, it means non-blocking and the currently ready event will be returned immediately. If no event is ready, 0 will be returned.

If timeout is set to a positive integer, it means blocking and waiting for the specified time before returning. If the event is not ready within the timeout period, 0 will be returned.

The epoll_wait function returns the number of file descriptors of the ready event when successful, returns -1 if an error occurs, and sets the errno error code to indicate the specific error type.

4. epoll realizes concurrent server

当使用epoll实现并发服务器时,通常的步骤包括以下几个主要环节:

1. Create socket: Use the socket function to create a listening socket to accept client connection requests.

2. Bind socket: Use the bind function to bind the listening socket to a specific IP address and port.

3. Listen for connections: Use the listen function to start listening for connection requests and specify the maximum number of connections the server can accept.

4. Create an epoll instance: Use the epoll_create function to create an epoll instance and return a file descriptor.

5. Add the listening socket to the epoll instance: Use the epoll_ctl function to add the listening socket to the epoll instance and register attention to the read event.

6. Enter the event loop: Call the epoll_wait function in a loop to wait for the event to occur. This function will block the current thread until an event occurs. Once an event occurs, it will return a list of ready events.

7. Process ready events: Traverse the ready event list and process each event. Depending on the event type, operations such as accepting connections, reading data, sending data, or closing connections can be performed.

8. Add or delete file descriptors as needed: After processing an event, you can use the epoll_ctl function to dynamically add or delete file descriptors as needed to continue listening to other events.

9. Repeat steps 6-8: Continue to execute steps 6-8 in a loop to process new ready events until the server actively shuts down or an error condition occurs.

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

#define MAX_EVENTS  1024 //最多可以等待多少个事件

int main()
{
    
    
    int server = 0;
    struct sockaddr_in saddr = {
    
    0};
    int client = 0;
    struct sockaddr_in caddr = {
    
    0};
    socklen_t asize = 0;
    int len = 0;
    char buf[32] = {
    
    0};
    int maxfd;
    int ret = 0;
    int i = 0;

    server = socket(PF_INET, SOCK_STREAM, 0);

    if( server == -1 )
    {
    
    
        printf("server socket error\n");
        return -1;
    }

    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    saddr.sin_port = htons(8888);

    if( bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1 )
    {
    
    
        printf("server bind error\n");
        return -1;
    }

    if( listen(server, 128) == -1 )
    {
    
    
        printf("server listen error\n");
        return -1;
    }

    printf("server start success\n");


    struct epoll_event event, events[MAX_EVENTS];
    /*创建epoll*/
    int epollInstance = epoll_create1(0);
    if (epollInstance == -1) 
    {
    
    
        printf("Failed to create epoll instance\n");
    }
    /*将服务器添加进入event中*/
    event.events = EPOLLIN;
    event.data.fd = server;

    if (epoll_ctl(epollInstance, EPOLL_CTL_ADD, server, &event) == -1) 
    {
    
    
        printf("Failed to add server socket to epoll instance");
    }        

    while( 1 )
    {
    
            
        int numEventsReady = epoll_wait(epollInstance, events, MAX_EVENTS, -1);
        if (numEventsReady == -1) 
        {
    
    
            printf("Failed to wait for events");
            return -1;
        }

        for(i = 0; i < numEventsReady; i++)
        {
    
    
            if(events[i].data.fd == server)
            {
    
    
                /*有客户端连接上来了*/
                asize = sizeof(caddr);  
                client = accept(server, (struct sockaddr*)&caddr, &asize);
                printf("client is connect\n");

                event.events = EPOLLIN | EPOLLET;
                event.data.fd = client;

                if (epoll_ctl(epollInstance, EPOLL_CTL_ADD, client, &event) == -1) 
                {
    
    
                    printf("Failed to add client socket to epoll instance");
                    return -1;
                }                
            }
            else
            {
    
    
                /*处理客户端的请求*/
                len = read(events[i].data.fd, buf, 1024);
                if(len == 0)
                {
    
    
                    printf("client is disconnect\n");
                    close(events[i].data.fd);
                }
                else
                {
    
    
                    /*对接收到的数据进行处理*/
                    printf("read buf : %s\n", buf);
                    write(events[i].data.fd, buf, len);
                }
            }
        }        


    }
    
    close(server);

    return 0;
}

Summarize

This article ends here, and the next article will continue to explain the knowledge of Linux network programming.

Guess you like

Origin blog.csdn.net/m0_49476241/article/details/132376808