Practical development of Android Framework - detailed explanation of epoll for cross process communication

Keywords: Android Framework udp

csdn online learning course, course consultation and Q & A and new course information: QQ communication group: 422901085 for course discussion

android cross process communication practice video course (add group to get preferential)

1. Background of epoll

First, understand two basic concepts:
blocking IO:
The blocking mode of socket means that IO operations (including errors) must be completed before returning.
blocking IO:
In non blocking mode, Chengdu will return immediately whether the operation is completed or not. It is necessary to judge whether the specific operation is successful through other methods.
Think back to our demo of network communication last class:
For the Server side, it does the following:

 while(1) {
        cliun_len = sizeof(cliun);
        if ((connfd = accept(listenfd, (struct sockaddr *)&cliun, &cliun_len)) < 0){
            perror("accept error");
            continue;
        }
        printf("new client connect to server,client sockaddr === %s \n",((struct sockaddr *)&cliun)->sa_data);
        while(1) {
            memset(buf,0,sizeof(buf));
            n = read(connfd, buf, sizeof(buf));
            if (n < 0) {
                perror("read error");
                break;
            } else if(n == 0) {
                printf("EOF\n");
                break;
            }

            printf("received: %s\n", buf);

            for(i = 0; i < n; i++) {
                buf[i] = toupper(buf[i]);
            }
            write(connfd, buf, n);
        }
        close(connfd);
    }

The above code is actually a process:
accept has been waiting for the client to connect
—>
After the client is connected, it has been waiting to read the data sent by the client
accept and read here are generally blocking methods by default, that is, the so-called blocking IO. When calling read, it will not return until the data is read. When there is no data, the program can only stay in the read method all the time and cannot continue to the next step.
Do you obviously find a problem? At present, it seems that this thread can only serve one client, but in the real scene, there are likely to be many clients connected to the server. How can the server communicate with the client at the same time? What methods can you think of?
Idea 1. We can consider the method of one thread for one client, so that each thread can read and reply to data with one client respectively. In fact, this is completely possible when the number of clients does not exceed 1-2, but once the number of clients is more than one?
Suppose there are 20 clients communicating with the server, does the server have 20 threads or even more 20000 clients? That can't be 20000 threads

Idea 2. You may wonder whether the read method can be made into the nonlocking IO mode. When calling read, if it is found that the system has no data, it will return immediately and read the data. What I tell you here is that there is such a non blocking mode. The specific setting method can be referred to as follows:
The fcntl function can set a socket handle to non blocking mode:

flags = fcntl(sockfd, F_GETFL, 0); //Gets the flags value of the file. 
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); 
//Set to non blocking mode;
flags = fcntl(sockfd,F_GETFL,0);
fcntl(sockfd,F_SETFL,flags&~O_NONBLOCK); 
//Set to blocking mode;

When the read method is non blocking, can we repeatedly call the data of each client in a thread, and the code may become like this:

while(1) {
	read(fd1...);//Customer 1
	read(fd2...);//Customer 2
	read(fd3...);//Customer 3
}

However, the above methods also have some serious problems:
For example, when the customer has no data, the thread has been idling and calling read data in an endless loop. In fact, there is no data. A lot of cpu is wasted. Some students will say that it is OK to delay while? In fact, this kind of thinking is not good, because you can't confirm the appropriate delay. If it is small, it will consume cpu. If it is set to be large, it will cause the delay of receiving data.

Due to the shortcomings of the above scheme, I/O multiplexing technology is introduced. Multiplexing refers to processing multiple I / OS in the same process (thread), and multiplexing refers to multiple file descriptors. Its core idea collects all descriptors that the process is interested in, and then calls a function. When one or more of these descriptors are ready for I/O, the function returns and tells which descriptors the process is ready. At this time, the process only needs to operate on these prepared descriptors. At present, select, poll and epoll are the most used methods to implement multiplexing on linux. At present, most of android systems are epoll, so here we focus on epoll.

2. epoll main methods

2. epoll interface

epoll operation requires three interfaces, as follows:

#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
(1) int epoll_create(int size);

Create an epoll handle, and size is used to tell the kernel how many listeners there are. This parameter is different from the first parameter in select(). It gives the maximum listening fd+1 value. It should be noted that when the epoll handle is created, it will occupy an fd value. Under linux, if you view / proc / process id/fd /, you can see this fd. Therefore, after using epoll, you must call close() to close it, otherwise fd may be exhausted.

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

Epoll's event registration function is different from select() which tells the kernel what type of event to listen for when listening for events. Epoll's event registration function is different from select() which tells the kernel what type of event to listen for when listening for events, but registers the event type to listen for here. The first parameter is epoll_ The return value of create(). The second parameter represents the action, which is represented by three macros:
EPOLL_CTL_ADD: register a new fd into epfd;
EPOLL_CTL_MOD: modify the listening event of the registered fd;
EPOLL_CTL_DEL: delete an fd from epfd;
The third parameter is fd that needs to be monitored, and the fourth parameter tells the kernel what needs to be monitored, struct epoll_ The event structure is as follows:

struct epoll_event {
  __uint32_t events;  /* Epoll events */
  epoll_data_t data;  /* User data variable */
};

events can be a collection of the following macros:
Epolin: indicates that the corresponding file descriptor can be read (including the normal shutdown of the opposite SOCKET);
EPOLLOUT: indicates that the corresponding file descriptor can be written;
EPOLLPRI: indicates that the corresponding file descriptor has urgent data readability (here it should indicate the arrival of out of band data);
EPOLLERR: indicates that an error occurred in the corresponding file descriptor;
EPOLLHUP: indicates that the corresponding file descriptor is hung up;
EPOLLET: set EPOLL to edge triggered mode, which is relative to level triggered.
EPOLLONESHOT: only listen to the event once. After listening to the event, if you need to continue listening to the socket, you need to add the socket to the EPOLL queue again

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

Wait for the event to occur, similar to the select() call. The parameter events is used to get the set of events from the kernel. Maxevents tells the kernel how big the events are. The value of maxevents cannot be greater than the value of the created epoll_ The size of create(), and the parameter timeout is the timeout (milliseconds, 0 will be returned immediately, and - 1 will be uncertain. It is also said that it is permanently blocked). This function returns the number of events to be processed. If 0 is returned, it indicates that it has timed out.

3. Working mode

epoll operates on file descriptors in two modes: LT (level trigger) and ET (edge trigger). LT mode is the default mode. The differences between LT mode and et mode are as follows:

LT mode: when epoll_wait detects the occurrence of a descriptor event and notifies the application of this event. The application may not process the event immediately. Next call epoll_ When you wait, the application responds again and notifies you of this event.

ET mode: when epoll_wait detects the occurrence of a descriptor event and notifies the application of this event, which must be handled immediately. If not, epoll will be called next time_ When waiting, the application will not respond again and notify this event.

ET mode reduces the number of epoll events repeatedly triggered to a great extent, so the efficiency is higher than LT mode. When epoll works in ET mode, a non blocking socket must be used to avoid starving the task of processing multiple file descriptors due to the blocking read / write operation of a file handle.

3. Actual demo of epoll

Server epoll:

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/epoll.h>
#define MAXLINE 80

char *socket_path = "server-socket";

int main()
{
    struct sockaddr_un serun, cliun;
    socklen_t cliun_len;
    int listenfd, connfd, size;
    char buf[MAXLINE];
    int i, n;

    if ((listenfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        perror("socket error");
        exit(1);
    }

    memset(&serun, 0, sizeof(serun));
    serun.sun_family = AF_UNIX;
    strncpy(serun.sun_path,socket_path ,
                   sizeof(serun.sun_path) - 1);
    unlink(socket_path);
    if (bind(listenfd, (struct sockaddr *)&serun, sizeof(struct sockaddr_un)) < 0) {
        perror("bind error");
        exit(1);
    }
    printf("UNIX domain socket bound\n");

    if (listen(listenfd, 20) < 0) {
        perror("listen error");
        exit(1);
    }
    printf("Accepting connections ...\n");
     // 4. Create epoll tree
    int epfd = epoll_create(1000);
    if(epfd == -1)
    {
        perror("epoll_create");
        exit(-1);
    }
    //5. Hang the lfd used for listening on the epoll tree (red black tree)
    struct epoll_event ev;//This structure records what events detect which file descriptors
    ev.events = EPOLLIN;
    ev.data.fd = listenfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);//What events are recorded in ev to detect lfd
    // Loop detection is delegated to the kernel for processing
    struct epoll_event events[1024];//When the kernel detects the arrival of an event, it will write the event to the structure array


    while(1) {
        int num = epoll_wait(epfd, events, sizeof(events)/sizeof(events[0]), -1);//The last parameter indicates blocking
        for (int i = 0;i<num;i++) {
            if(events[i].data.fd == listenfd)//A connection request has arrived
            {

                int len = sizeof(cliun);
                int connfd = accept(listenfd, (struct sockaddr *)&cliun, &len);
                if(connfd == -1)
                {
                    perror("accept");
                    exit(-1);
                }
                printf("a new client connected! \n");
                //Hang the file descriptor used for communication on the epoll tree
                ev.data.fd = connfd;
                ev.events = EPOLLIN;
                epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
            } else {
                 //Communication can also be a write event
                if(events[i].events & EPOLLOUT)
                {
                    //Ignore the write event first
                    continue;
                }
                char buf[1024]={0};
                int count = read(events[i].data.fd, buf, sizeof(buf));
                if(count == 0)//The client closed the connection
                {
                    printf("The client closed the connection....\n");
                    //Remove the corresponding file descriptor from the epoll tree
                    close(events->data.fd);
                    epoll_ctl(epfd, EPOLL_CTL_DEL, events->data.fd, NULL);
                }
                else
                {
                    if(count == -1)
                    {
                        perror("read");
                        exit(-1);
                    }
                    else
                    {
                        //Normal communication
                        printf("client say: %s\n" ,buf);
                        write(events[i].data.fd, buf, strlen(buf)+1);
                    }
                }

            }

        }
    }
    close(listenfd);
    return 0;
}

epoll implementation of client:

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#define MAXLINE 80

char *client_path = "client-socket";
char *server_path = "server-socket";

int main() {
        struct  sockaddr_un cliun, serun;
        int len;
        char buf[100];
        int sockfd, n;

        if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0){
                perror("client socket error");
                exit(1);
        }
//    signal(SIGPIPE, SIG_IGN);
    memset(&serun, 0, sizeof(serun));
    serun.sun_family = AF_UNIX;
    strncpy(serun.sun_path,server_path ,
                   sizeof(serun.sun_path) - 1);
    if (connect(sockfd, (struct sockaddr *)&serun, sizeof(struct sockaddr_un)) < 0){
        perror("connect error");
        exit(1);
    }
    printf("please input send char:");
    while(fgets(buf, MAXLINE, stdin) != NULL) {
         write(sockfd, buf, strlen(buf));
         n = read(sockfd, buf, MAXLINE);
         if ( n <= 0 ) {
            printf("the other side has been closed.\n");
            break;
         }else {
            printf("received from server: %s \n",buf);
         }
         printf("please input send char:");
    }
    printf("end server  date");
    close(sockfd);
    return 0;
}

Posted by dgudema on Mon, 04 Oct 2021 13:57:02 -0700