Improvement of epoll--working mode

Keywords: socket

  • Horizontal Trigger Mode--This is the default mode (as described in the previous article)
  • Edge Blocking Trigger Mode
  • Edge Non-Blocking Mode--Highest Efficiency

Let's start with a need:

For one client (interprocess pipeline communication) for one server
If the client sends 100 bytes of information and the server receives only 50 bytes at a time, what about the remaining 50 bytes?

Analysis:

  1. Default Execution Flow: The corresponding buffer holds 100 bytes sent, the system epoll listens for changes in the corresponding file descriptor, the server reads the data, but only reads 50 bytes, then there are 50 bytes left in the buffer
    There are two statements at this point: Fact is the second
    1. To improve efficiency, the epoll_wait function will not be called anymore, then 50 bytes of data will only be received when the next client sends information (to improve efficiency, reduce the number of calls to the function)
    2. The epoll_wait function will be called again to read the data. (Code will be attached later)
  2. Edge Blocking Trigger Mode: Like the first scenario above, but it causes data to remain in the buffer every time, and more and more...
  3. Edge non-blocking (O_NONBLOCK) trigger mode: This is most efficient mainly because the file descriptor corresponding to the client, buffer (pipeline), is set to non-blocking mode. When receiving (reading) information, it needs to be read in a loop. When read/recv returns 0, it means the read is complete. In addition, edge mode (calling epoll_wait function only once) is efficient
    Set non-blocking:
    1. Set parameters when opening;
    2.fcntl settings

 

//Modify file attributes after opening the file to win the set attributes flags
//Get flags:
 int flags = fcntl(fd, F_GETFL); 
//Set flags:
flags = flags | O_NONBLOCK;
fcntl(fd, F_SETFL, flags);

Code for the three modes:

  1. The first two modes are implemented using pipe-to-parent process communication:
    Toggle where comments are made in the code, and the output is formatted as described above, where 10 and 5 bytes are simulated

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <error.h>
int main(int argc, char *argv[]) {
    char buf[10];

    //Using pipeline implementations, pipeline creation requires an array, one for reading and one for writing
    int pfd[2];
    //Create anonymous pipe
    pipe(pfd);

    //Create Subprocess
    pid_t pid = fork();

    if(pid == 0) { //Subprocess
        //No read operation is required, close the read file descriptor to ensure that the pipeline transmits data individually
        close(pfd[0]);
        while(1) {
            int i = 0;
            for(i = 0; i < 10/2; i++) {
                buf[i] = 'a';
            }
            buf[i-1] = '\n';
            for(; i < 10; i++) {
                buf[i] = 'b';
            }
            buf[i-1] = '\n';
            //The array now holds aaaa\nbbn
            //Send 10 bytes at a time
            write(pfd[1], buf, sizeof(buf));
            sleep(3);
        }
        close(pfd[1]);
    } else if(pid > 0) {//Parent Process

        close(pfd[1]);
        //Create epoll model, point to root node, handle
        int efd = epoll_create(10);
        //Mount the monitor on the root node
        struct epoll_event event;
        //Setting the edge triggers as follows:
        event.events = EPOLLIN | EPOLLET;
        /*
         * //The default is to trigger horizontally
         * event.events = EPOLLIN;
         */
        event.data.fd = pfd[0];//Writing end
        epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);

        struct epoll_event resevents[10];
        char readbuf[5];
        while(1) {
            int res = epoll_wait(efd, resevents, 10, -1);
            printf("res:%d\n", res);
            if(resevents[0].data.fd == pfd[0]) {
                int len = read(pfd[0], readbuf, 5);//Read 5 bytes at a time
                write(STDOUT_FILENO, readbuf, len);
            }
        }
        close(pfd[0]);
        close(efd);
    } else {
        perror("fork error");
        exit(1);
    }
    return 0;
}
  1. Edge blocking trigger implemented with c/s model, what we do here is listen on only one client

 

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#define MAXLINE 10
#define SERV_PORT 9000
int main(void) {
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];

    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    listen(listenfd, 20);

    struct epoll_event event;
    struct epoll_event resevent[10];
    int res, len;

    printf("Accepting connections ...\n");

    cliaddr_len = sizeof(cliaddr);
    connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
    printf("received from %s at PORT %d\n",
            inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
            ntohs(cliaddr.sin_port));

    // Create a red-black tree root node
    int efd = epoll_create(10);
    // Detected Event Settings
#if 0
    /* ET Edge Trigger */
    event.events = EPOLLIN | EPOLLET;     
#else
    /* Default LT Horizontal Trigger */
    event.events = EPOLLIN;                 
#endif
    // File descriptors that need to be detected
    event.data.fd = connfd;
    epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);

    while (1) {
        res = epoll_wait(efd, resevent, 10, -1);

        printf("========res %d\n", res);
        if (resevent[0].data.fd == connfd) {
            len = read(connfd, buf, MAXLINE/2);        
            write(STDOUT_FILENO, buf, len);
        }
    }
    return 0;
}
  1. Just like 2, only listen on the client, but here's what to note is that you can no longer tell if the client disconnected or failed to read or what you read based on the return value of read. You need to think about designing programs, such as using the void * pointer inside the data to store a time, disconnecting if you don't connect for a long time, or using the heartbeat package.

 

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#define MAXLINE 10
#define SERV_PORT 8000
int main(void) {
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];

    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    listen(listenfd, 20);

    ///////////////////////////////////////////////////////////////////////
    struct epoll_event event;
    struct epoll_event resevent[10];

    int efd = epoll_create(10);

    //event.events = EPOLLIN;
    printf("Accepting connections ...\n");
    cliaddr_len = sizeof(cliaddr);
    connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
    printf("received from %s at PORT %d\n",
            inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
            ntohs(cliaddr.sin_port));

    /* Modify connfd for non-blocking read */
    int flag = fcntl(connfd, F_GETFL);          
    flag |= O_NONBLOCK;
    fcntl(connfd, F_SETFL, flag);

    /* ET Edge trigger, default is horizontal trigger */
    event.events = EPOLLIN | EPOLLET;     
    event.data.fd = connfd;
    //Add connfd to listen on red and black trees
    epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);      

    while (1) {
        int len = 0;
        printf("epoll_wait begin\n");
        //Up to 10, blocking listening
        int res = epoll_wait(efd, resevent, 10, -1);        
        printf("epoll_wait end res %d\n", res);

        if (resevent[0].data.fd == connfd) {
            // Non-blocking read, polling
            // epoll_wait is triggered once, remaining data is read in a loop
            while ((len = read(connfd, buf, MAXLINE/2)) >0 ) {
                write(STDOUT_FILENO, buf, len);
            }
        }
    }
    return 0;
}



Author: dab61956e53d
Link: https://www.jianshu.com/p/59ab5ecbaa76
Source: Short Book
Copyright belongs to the author.For commercial reprinting, please contact the author for authorization. For non-commercial reprinting, please indicate the source.

171 original articles published. 61% praised. 180,000 visits+
Private letter follow

Posted by thebluebus on Tue, 03 Mar 2020 19:13:24 -0800