Linux I/O related functions

The accept function

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sys/socket.h>

int accept4(int sockfd, struct sockaddr *addr,
            socklen_t *addrlen, int flags);
Copy the code

The accept system call is used for the basic socket types (SOCK_STREAM, SOCK_SEQPACKET). It retrieves the first connection request on the pending connection queue (full connection queue) of the listening socket (SOCKFD) and creates a new connection socket. It then returns a new file descriptor that references the socket. The newly created socket is not in the listening state (ESTABLISHED). The raw socket sockFD is not affected by this call.

The sockfd parameter is a socket. It is created by the socket function, binds to a local address and port number using the bind function, and listens for connections after the LISTEN function. It is in a listening state.

Addr is the pointer to the sockaddr_in structure. Addrlen is the length of the parameter addr, which can be obtained from sizeof(). Addr holds the IP address and port number of the client.

If there are no pending connections in the full connection queue and the socket is not marked as nonblocking, accept() blocks the caller until a connection appears. If the socket is marked nonblocking and there are no pending connections in the queue, accept() fails with an EAGAIN or EWOULDBLOCK error.

To notify incoming connections on the socket, select(2), poll(2), or epoll(7) can be used. When a new connection is attempted, a readable event is passed, and accept() can then be called to obtain the socket for that connection.

The select function

#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
Copy the code

Select () allows a program to monitor multiple file descriptors until one or more file descriptors are ready for some type of I/O operation. A file descriptor is considered ready if it can perform the corresponding I/O operation (for example, read() or write()) without blocking.

Select () can only listen on file descriptors smaller than FD_SETSIZE. Poll and epoll do not have this limitation.

The main arguments to select() are three file descriptor “collections” (declared with type fd_set) that allow the caller to wait for three class events on the specified set of file descriptors. If the file descriptor for the corresponding event class is not monitored, then each fd_set parameter can be specified as NULL.

When select() returns, each set of file descriptors is modified to indicate which file descriptors are currently “ready,” so if select() is used in the loop, fd_set must be reinitialized before each call.

On success, select() returns the number of ready file descriptors (the total number of digits set in readFds, Writefds, and ExcepTFds) in the three descriptor sets. If the timeout ends before any file descriptors are ready, the return value may be zero.

The macro

  1. FD_ZERO(fd_set *) clears a set of file descriptors;

  2. FD_SET(int, FD_SET *) adds a file descriptor to a specified set of file descriptors;

  3. FD_CLR(int,fd_set*) removes a given file descriptor from the collection;

  4. FD_ISSET(int,fd_set*) checks whether the file descriptor specified in the collection can be read or written.

Bugs

The select() operation is not affected by O_NONBLOCK. On Linux, however, select() may report a socket file descriptor as “ready for Reading “, but it is still possible to block when reading data. This can happen, for example, when the data has arrived, but a checksum error is found during inspection and discarded. In other cases, file descriptors may also be falsely reported as ready. Therefore, it is safer to use a socket in a non-blocking mode.

The Linux kernel has no fixed limit on fd_set, but the glibc implementation makes fd_set size fixed. FD_SETSIZE is defined as 1024, and poll or epoll is required to monitor file descriptors greater than 1023.

The poll function

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

#define _GNU_SOURCE  /* See feature_test_macros(7) */
#include <signal.h>
#include <poll.h>

int ppoll(struct pollfd *fds, nfds_t nfds,
 const struct timespec *tmo_p, const sigset_t *sigmask);
Copy the code

The poll function is similar to the select function in that it waits for ready I/O in a set of FDS.

The fd collection to monitor is specified in the FDS argument, which is an array of structures as follows:

struct pollfd {
    int   fd;         /* file descriptor */
    short events;     /* requested events */
    short revents;    /* returned events */
};
Copy the code

Field fd contains the file descriptor for the open file. If this field is negative, the corresponding Events field is ignored, and the Revents field returns zero

The field Events is an input parameter that is a bitmask to specify the events that the application is interested in with the file descriptor FD. This field can be specified as 0, in which case the only events that revents can return are POLLHUP, POLLERR, and POLLNVAL.

The field Revents is an output parameter populated by the kernel for the actual event that occurred. The bits returned in Revents can include any of the bits specified in events, or one of the POLLERR, POLLHUP, or POLLNVAL values.

If no requested events (and no errors) occur at any of the file descriptors, poll() blocks until one of those events occurs.

On success, poll() returns a non-negative value, which is the number of elements in the PollFds where the Revents field has been set to a non-zero value (representing events or errors). A return value of 0 means that the system call times out before reading any file descriptors.

epoll API

Epoll () performs a similar task to poll() : it monitors multiple file descriptors to see if there is I/O on any one of them. The epoll API can be used as both edge-triggered and Level-triggered interfaces, and extends nicely to a large number of monitored file descriptors.

The core concept of the ePoll API is the EPoll instance, which is a kernel data structure that, from a user-space perspective, can be thought of as a container for two lists:

  • Interest List: A set of file descriptors that a process registers to monitor
  • Ready List: Set of file descriptors ready for I/O. A ready list is a list of interests (interestA subset of file descriptors (or, more accurately, a set of references to them) in. The ready list is dynamically populated by the kernel as a result of the I/O activity on those file descriptors.

The following system calls are made to create and manage epoll instances:

  • epoll_create(2)Create a new epoll instance and return the file descriptor that references the instance.
  • Then throughepoll_ctl(2) registers an interest in a particular file descriptor, which adds the item to the interest list of the epoll instance
  • epoll_wait(2)Waits for I/O events and blocks the calling thread if no events are currently available. (This system call can be thought of as fetching the item from the ready list of the epoll instance.)

Horizontal triggering (LT) and edge triggering (ET)

The EPoll event distribution interface can act as both an edge trigger (ET) and a level trigger (LT). The differences between the two mechanisms can be described as follows. Suppose this happens:

  1. Register a file descriptor (rfd) to the epoll instance, indicatingread sideThe pipe.
  2. In the pipelinewrite sideWrite 2KB data to the pipe.
  3. callepoll_wait()Waiting for therfdThe file descriptor is ready.
  4. fromrfdRead 1KB data.
  5. Complete theepoll_wait()The call.

If the RFD file descriptor has been added to the epoll interface using the EPOLLET (edge-triggered) flag, the call to epoll_wait() in Step 5 May hang even though the available data still exists in the file input buffer; Meanwhile, the remote peer might expect a response based on the data it has already sent. The reason for this is that edge-triggered mode only sends events when the file descriptor being monitored changes. Therefore, in Step 5, the caller might end up waiting for some data that already exists in the input buffer (because the file descriptor RFD has not changed at this point). In the above example, an event on the RFD will be generated as a result of the write operation in Step 2, and the event will be consumed in Step 3. Because the read performed in Step 4 does not consume the entire buffer data, the call to epoll_wait in Step 5 May block indefinitely.

Applications that use the EPOLLET flag should use a noblocking file descriptor to avoid blocking reads or writes and starving tasks that are processing multiple file descriptors. The recommended epoll interfaces are as follows:

  1. Use non-blocking file descriptors
  2. by waiting for an event only after read(2) or write(2) return EAGAIN.

By contrast, if epoll is used as an interface for horizontal triggering (LT) (by default, when EPOLLET is not specified), epoll is just a faster poll() that can be used anywhere the latter is used because they have the same semantics.

If multiple threads are blocked in epoll_wait(), the file descriptor in the list of interests waiting for notification of the same epoll file descriptor is ready and marked as an edge trigger (EPOLLET).

Then only one thread (or process) wakes up from epoll_wait(). This provides a useful optimization to avoid “scare effect” awakenings in some scenarios.

epoll_create

#include <sys/epoll.h>

int epoll_create(int size);
int epoll_create1(int flags);
Copy the code

Create an epoll instance. There is no difference between epoll_create1(0) and ‘epoll_create

epoll_ctl

#include <sys/epoll.h>

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

The epoll_ctl system call is used to add, modify, or delete entries in the interest list of the epoll instance referenced by the file descriptor EPfd. It requests operation op on the target file descriptor fd.

Op parameter

  • EPOLL_CTL_ADD: Adds an entry to the interest list of the epoll instance referenced by epFD. This entry includes the file descriptor, fd, a reference to the description of the corresponding open file (see epoll(7) and open(2)), and the Settings specified in the event.

  • EPOLL_CTL_MOD: Changes the Settings associated with fd in the interest list to the new Settings specified in event.

  • EPOLL_CTL_DEL: Remove (unregister) the object file descriptor fd from the list of interests

The event parameter

The event parameter describes the object linked to the file descriptor fd. The epoll_event structure is defined as

 typedef union epoll_data {
 void        *ptr;
 int          fd;
 uint32_t     u32;
 uint64_t     u64;
} epoll_data_t;

struct epoll_event {
 uint32_t     events;      /* Epoll events */
 epoll_data_t data;        /* User data variable */
};
Copy the code

The data member of the epoll_event structure specifies that the kernel should save and return (via epoll_wait(2)) the data when the file descriptor is ready

The events member of the epoll_event structure is a bitmask consisting of zero or more of the following available event types combined:

  • EPOLLIN: Related files can be used for read operations
  • EPOLLOUT: Related files can be used for write operations

Epoll_ctl returns 0 on success and -1 on error.

epoll_wait

#include <sys/epoll.h>

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

The epoll_wait() system call waits for events on the epoll instance referenced by the file descriptor EPfd. The buffer to which Events points is used to return information from the ready list of file descriptors with some events available in the list of interests. Epoll_wait () returns the maximum maxEvents. The maxEvents parameter must be greater than 0.

The timeout argument is used to specify when epoll_wait() will block. Calling epoll_wait() will block until:

  • The file descriptor passes an event;
  • Call interrupted
  • timeout

The data field of each returned epoll_event structure contains the same data as the corresponding open file descriptor specified in the most recent call to EPOLL_CTL_ADD (EPOLL_CTL_MOD).

On success, epoll_wait() returns the number of file descriptors ready for the requested I/O, or 0 if no file descriptors are ready during the timeout milliseconds of the request. When an error occurs, epoll_wait() returns -1 and sets errno.

Blocking I/O problems

The service side

#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <netinet/in.h> #define BUF_SIZE 1024 int main() { // 1. Int serv_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); printf("server_socket_fd: %d\n", serv_sock); Struct sockaddr_in serv_addr; // initialize memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; // Use the IPv4 address serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // The specific IP address serv_addr. Sin_port = htons(8081); Bind bind(struct sockaddr *)&serv_addr, sizeof(serv_addr)); // 4.listen listen(serv_sock, 1024); Struct sockaddr_in clnt_addr; socklen_t clnt_addr_size = sizeof(clnt_addr); // Set read buffer char buffer[BUF_SIZE]; // 6. accept printf("accept... \n"); int clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size); printf("clnt_socket_fd: %d\n", clnt_sock); // 7. read printf("read... \n"); read(clnt_sock, buffer, sizeof(buffer) - 1); printf("Message form server: %s\n", buffer); // 8. write char str[] = "Hello World!" ; write(clnt_sock, str, sizeof(str)); //9. Close (clnT_sock); close(serv_sock); return 0; }Copy the code

After starting the server, the following output is displayed

server_socket_fd: 3
accept...
Copy the code

This indicates that the Accept function is blocking and will block the current process if no new connections are established.

Next, use the nc localhost 8081 command to establish a connection. The server output is as follows

server_socket_fd: 3
accept...
clnt_socket_fd: 4
read...
Copy the code

It then sends a bit of data to the server, as follows

nc localhost 8081
niubi
Hello World!
Copy the code

The server prints niuBI and immediately exits, with the following output

server_socket_fd: 3
accept...
clnt_socket_fd: 4
read...
Message form server: niubi
Copy the code

Another problem with this is that the read function blocks the current process when there is no data in the socket buffer.

Since both the Accept and read functions block, this can be a problem. There is no way for a single thread to accept the creation of multiple sockets while reading socket data. Therefore, each socket needs to create a thread to send and receive data, and the main thread continues to handle the establishment of new connections. However, the cost of thread creation is relatively high. The pressure of thread context switch on CPU, and the memory pressure required to create thread are linear increase.

Non-blocking I/O

Set the socket to non-blocking by using the FCNTL function or the SOCK_NONBLOCK flag. In non-blocking mode, neither accept nor read will block the current thread without a new connection or the socket buffer.

server_socket_fd: 3
accept...
clnt_socket_fd: -1
read...
Message form server:
Copy the code

The accept and read functions do not block, which makes it possible to use a single thread to process multiple connections, but this is still not feasible. Why? Because the application does not know which connection is readable, it must traverse all connections and call the read function. There are too many system calls.

I/O multiplexing

Use select function

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

int fd[1024]; // Join the fd array
#define BUF_SIZE 1024

int main(void)
{
    int yes = 1;

    int sock_fd; // Listen on the socket

    // 1. Establish a sock_fd socket
    if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) = =- 1)
    {
        perror("socket error");
        exit(1);
    }
    printf("server_sock_fd = %d\n", sock_fd);

    // Set the socket option SO_REUSEADDR to allow multiple instances of the server to be started on the same port
    // SOL SOCKET, the second parameter of setsockopt, specifies the level of the explanation option in the system for common sockets
    if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) = =- 1)
    {
        perror("setsockopt error \n");
        exit(1);
    }

    // 2. Declare the server addr structure and the client addr structure
    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    socklen_t serv_addr_size = sizeof(serv_addr);
    socklen_t clnt_addr_size = sizeof(clnt_addr);
    // Initializes the structure
    memset(&serv_addr, 0.sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;                     // Use an IPv4 address
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // Specify the IP address
    serv_addr.sin_port = htons(8081);                   / / port

    // 3.bind
    bind(sock_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    // 4.listen
    listen(sock_fd, 1024);

    // Define the file descriptor set
    fd_set fdsr;
    int maxsock = sock_fd;
    struct timeval tv;
    // Set the read buffer
    char buffer[BUF_SIZE];
    int conn_amount = 0;

    while (1)
    {
        // Clear the descriptor set
        FD_ZERO(&fdsr);
        // Add server_sock_fd to the descriptor set
        FD_SET(sock_fd, &fdsr);
        // Set the timeout to block 30 seconds, and set this parameter to NULL, indicating that select is in block mode
        tv.tv_sec = 30;
        tv.tv_usec = 0;

        // Add all descriptors to the descriptor collection
        for (int i = 0; i < conn_amount; i++)
        {
            if(fd[i] ! =0)
            {
                FD_SET(fd[i], &fdsr); }}// If there is a connection request in the file descriptor, it will do the corresponding processing, to achieve the multiplexing of I/O multi-user connection communication
        printf("select... \n");

        int res = select(maxsock + 1, &fdsr, NULL.NULL, &tv);
        if (res < 0) // No valid connection was found
        {
            perror("select error! \n");
            break;
        }
        else if (res == 0) // When the specified time is up,
        {
            printf("timeout \n");
            continue;
        }

        printf("select result is %d\n", res);

        if (FD_ISSET(sock_fd, &fdsr))
        {
            int clnt_sock = accept(sock_fd, (struct sockaddr *)&clnt_addr, &clnt_addr_size);

            printf("accpet a new client fd=[%d]: %s:%d\n", clnt_sock, inet_ntoa(clnt_addr.sin_addr), clnt_addr.sin_port);
            // Store the socket in an array
            fd[conn_amount++] = clnt_sock;

            if(clnt_sock > maxsock) { maxsock = clnt_sock; }}// The following loop is necessary because you don't know which connection sent the data, so you have to find it one by one.
        for (int i = 0; i < conn_amount; i++)
        {
            if (FD_ISSET(fd[i], &fdsr))
            {
                res = recv(fd[i], buffer, sizeof(buffer), 0);
                // If the client disconnects, there will be four waves, a signal will be sent, at which point the corresponding socket will return data, telling SELECT, My client is disconnected, you return -1

                if (res <= 0) // The client connection is closed, clearing the corresponding bit in the file descriptor set
                {
                    printf("client fd=[%d] close\n", fd[i]);
                    close(fd[i]);
                    FD_CLR(fd[i], &fdsr);
                    fd[i] = 0;
                    conn_amount--;
                }
                // Otherwise, corresponding data will be sent for corresponding processing
                else
                {
                    if (res < BUF_SIZE)
                        memset(&buffer[res], '\ 0'.1);
                    printf("clint fd=[%d] recv message: %s\n", fd[i], buffer);

                    char str[] = "Hello World!";
                    write(fd[i], str, sizeof(str));

                    printf("clint fd=[%d] send message: %s\n", fd[i], str);
                }
            }
        }
    }
}
Copy the code

The select function uses three fd_sets to monitor whether a socket is “ready”. It avoids the problem of all sockets making system calls and allows us to handle many connections with only one thread. But it also has some problems.

  1. The gliBC standard libraryfd_setIt’s actually oneThe bitmap, its maximum value is 1024. Therefore, a maximum of 1024 file descriptors can be manipulated.
  2. Each loop requires all FDS to be copied from user space to kernel space.
  3. Reinitialization is required before each loopfd_setAnd add all socket FDS back tofd_setThis is also a considerable overhead when there are many FD characters.
  4. Only connections are “ready”, but we do not know which connection is ready, and we need to traverse all FDS to determine which connection is ready.
  5. The kernel checks by looping throughfd_setIf no fd is found, the current process will be suspended to the wait queue. When a FD is ready or an active timeout occurs, the kernel will wake up the process through the callback function, and the process will be traversed again after being woken upfd_set, CPU consumption increases linearly with the number of FDS monitored.
  6. False readiness bug

Using the poll function

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

#define READ_BUF_LEN 256
#define BACK_LOG 1024
#define OPEN_MAX 1024

int main(a)
{

    int sock_fd; // Listen on the socket

    // 1. Establish a sock_fd socket
    if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) = =- 1)
    {
        perror("socket error");
        exit(1);
    }
    printf("server_sock_fd = %d\n", sock_fd);

    int on = 1;
    // Set the socket option SO_REUSEADDR to allow multiple instances of the server to be started on the same port
    // SOL SOCKET, the second parameter of setsockopt, specifies the level of the explanation option in the system for common sockets
    if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)) = =- 1)
    {
        perror("setsockopt error \n");
        exit(1);
    }

    // 2. Declare the server addr structure and the client addr structure
    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    socklen_t serv_addr_size = sizeof(serv_addr);
    socklen_t clnt_addr_size = sizeof(clnt_addr);

    // Initializes the structure
    memset(&serv_addr, 0.sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;                     // Use an IPv4 address
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // Specify the IP address
    serv_addr.sin_port = htons(8081);                   / / port

    // 3.bind
    bind(sock_fd, (struct sockaddr *)&serv_addr, serv_addr_size);

    // 4.listen
    listen(sock_fd, BACK_LOG);

    struct pollfd client[OPEN_MAX];
    client[0].fd = sock_fd;
    client[0].events = POLLIN;
    int conn_amount = 1;
    int sockfd, ret;
    // Set the read buffer
    char buffer[READ_BUF_LEN];
    while (1)
    {
        printf("poll ... \n");
        int res = poll(client, conn_amount + 1.- 1);
        printf("poll result is %d\n", res);
        // A new connection is added
        if (client[0].revents & POLLIN)
        {
            int clnt_sock = accept(sock_fd, (struct sockaddr *)&clnt_addr, &clnt_addr_size);

            printf("accpet a new client fd=[%d]: %s:%d\n", clnt_sock, inet_ntoa(clnt_addr.sin_addr), clnt_addr.sin_port);
            // Store the socket in an array
            conn_amount++;
            client[conn_amount].fd = clnt_sock;
            client[conn_amount].events = POLLIN;
        }
        // Iterate over all connections
        for (int i = 1; i <= conn_amount; i++)
        {
            if ((sockfd = client[i].fd) < 0)
                continue;
            if (client[i].revents & POLLIN)
            {
                if ((ret = read(sockfd, buffer, sizeof(buffer))) <= 0)
                {
                    printf("client fd=[%d] close\n", sock_fd);
                    client[i].fd = - 1;

                    close(sockfd);
                }
                else
                {
                    printf("clint fd=[%d] recv message: %s\n", sockfd, buffer);

                    char str[] = "Hello World!";
                    write(sockfd, str, sizeof(str));
                    printf("clint fd=[%d] send message: %s\n", sockfd, str);
                }
            }
        }
    }
}
Copy the code

Advantages of the poll function

  1. The poll function uses arrays to store file descriptors, and there is no limit to the maximum number.
  2. The input and output of the poll function are separate (event and revent). There is no need to copy all file descriptors from user state to kernel state at call time.

The disadvantage is that, like select, all file descriptors need to be iterated after the return to get the socket ready. In fact, with a large number of client connections there are only a few ready connections at any one time. As the number of monitor file descriptors increases, the performance duration decreases linearly.

Using epoll API

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

#define MAX_EVENT 20
#define READ_BUF_LEN 256
#define BACK_LOG 1024

void setNonblocking(int sockfd)
{
    int opts;
    opts = fcntl(sockfd, F_GETFL);
    if (opts < 0)
    {
        perror("fcntl(sock,GETFL)");
        return;
    } //if

    opts = opts | O_NONBLOCK;
    if (fcntl(sockfd, F_SETFL, opts) < 0)
    {
        perror("fcntl(sock,SETFL,opts)");
        return;
    } //if
}

int main(a)
{

    int sock_fd; // Listen on the socket

    // 1. Establish a sock_fd socket
    if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) = =- 1)
    {
        perror("socket error");
        exit(1);
    }
    printf("server_sock_fd = %d\n", sock_fd);

    int on = 1;
    // Set the socket option SO_REUSEADDR to allow multiple instances of the server to be started on the same port
    // SOL SOCKET, the second parameter of setsockopt, specifies the level of the explanation option in the system for common sockets
    if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)) = =- 1)
    {
        perror("setsockopt error \n");
        exit(1);
    }

    // 2. Declare the server addr structure and the client addr structure
    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    socklen_t serv_addr_size = sizeof(serv_addr);
    socklen_t clnt_addr_size = sizeof(clnt_addr);

    // Initializes the structure
    memset(&serv_addr, 0.sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;                     // Use an IPv4 address
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // Specify the IP address
    serv_addr.sin_port = htons(8081);                   / / port

    // 3.bind
    bind(sock_fd, (struct sockaddr *)&serv_addr, serv_addr_size);

    // 4.listen
    listen(sock_fd, BACK_LOG);

    setNonblocking(sock_fd);

    /* Declare the epoll_event structure variable, ev for registering the event, and array for sending back the event to be processed */
    struct epoll_event ev.events[MAX_EVENT];
    // 5. Create an epoll instance
    int epfd = epoll_create1(0);

    /* Sets the listener descriptor */
    ev.data.fd = sock_fd;
    /* Sets the processing event type to edge trigger */
    ev.events = EPOLLIN | EPOLLET;

    /* Register event */
    epoll_ctl(epfd, EPOLL_CTL_ADD, sock_fd, &ev);
    int sockfd;
    // Set the read buffer
    char buffer[READ_BUF_LEN];
    char str[] = "Hello World!";
    ssize_t n, ret;

    for (;;)
    {
        printf("epoll wait... \n");
        int nfds = epoll_wait(epfd, events, MAX_EVENT, - 1);
        if (nfds <= 0)
            continue;
        printf("nfds = %d\n", nfds);

        for (int i = 0; i < nfds; i++)
        {
            // A new connection is added
            if (events[i].data.fd == sock_fd)
            {
                int clnt_sock = accept(sock_fd, (struct sockaddr *)&clnt_addr, &clnt_addr_size);
                printf("accpet a new client fd=[%d]: %s:%d\n", clnt_sock, inet_ntoa(clnt_addr.sin_addr), clnt_addr.sin_port);
                // Set non-blocking
                setNonblocking(clnt_sock);
                /* Sets the listener descriptor */
                ev.data.fd = clnt_sock;
                /* Sets the processing event type to edge trigger */
                ev.events = EPOLLIN | EPOLLET;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &ev);
            }
            else if (events[i].events & EPOLLIN)
            { //
                if ((sockfd = events[i].data.fd) < 0)
                    continue;
                if ((ret = read(sockfd, buffer, sizeof(buffer))) <= 0)
                {
                    printf("client fd=[%d] close\n", sock_fd);
                    close(sockfd);
                    events[i].data.fd = - 1;
                }
                else
                {
                    printf("clint fd=[%d] recv message: %s\n", sock_fd, buffer);

                    /* Set to register write operation file descriptors and events */
                    ev.data.fd = sockfd;
                    ev.events = EPOLLOUT | EPOLLET;
                    epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev); }}else if (events[i].events & EPOLLOUT)
            {
                if ((sockfd = events[i].data.fd) < 0)
                    continue;
                write(sockfd, str, sizeof(str));
                printf("clint fd=[%d] send message: %s\n", sock_fd, str);
                //if
                /* Sets the file descriptors and events used for reading */
                ev.data.fd = sockfd;
                ev.events = EPOLLIN | EPOLLET;
                / * modify * /
                epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev); }}}}Copy the code

The advantages of the epoll

  1. There is no limit to the number of file descriptors monitored.
  2. event-driven
  3. Only ready socket file descriptors are returned
  4. Epoll instances use red-black trees