lv8 Embedded Development-Network Programming Development 16 Multiplexing poll and epoll

Table of Contents

1 Multiple implementation methods of multiplexing

2 poll

2.1 poll function application

3 epoll function family (most efficient)

3.1 epoll_create creates epoll handle

3.2 epoll_ctl epoll handle control interface

3.3 epoll_wait waits for I/O events on the epoll file descriptor

3.4 epoll function application


1 Multiple implementation methods of multiplexing

2 poll

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

struct pollfd {
    int d; /* file descriptor */
    short events; /* Requested event */
    short revents; /*Returned events */
};

The poll() function is a system call that monitors a set of file descriptors and waits for one or more of them to become ready for read and write operations. It is similar to select(), but poll() has no limit on the maximum number of file descriptors and is more efficient. Here is what the parameters mean:

  • fds: Pointer to the pollfd structure array, which stores the file descriptors that need to be monitored and the events that each file descriptor is concerned about.
  • nfds: The number of file descriptors.
  • timeout: timeout time (unit: milliseconds), if it is 0, it means returning immediately, if it is -1, it means waiting forever.

The return value of poll() indicates the number of ready file descriptors. If the return value is 0, it indicates a timeout. If the return value is -1, it means an error occurred, and you can use errno to view the specific error information.

Event type events:

  • POLLIN: There is data to read
  • POLLPRI: Urgent data needs to be read
  • POLLOUT: file is writable
  • …..

2.1 poll function application

server.c

#include "net.h"
#include <poll.h>

#define MAX_SOCK_FD 1024
int main(int argc, char *argv[])
{
int i, j, fd, newfd;
nfds_t nfds = 1;
struct pollfd fds[MAX_SOCK_FD] = {};
Addr_in addr;
socklen_t addrlen = sizeof(Addr_in);
/*Check parameters, if less than 3, exit the process directly*/
Argment(argc, argv);
/*Create a socket with listening mode set*/
fd = CreateSocket(argv);
fds[0].fd = fd;
fds[0].events = POLLIN;
while(1){
if( poll(fds, nfds, -1) < 0)
ErrExit("poll");
for(i = 0; i < nfds; i + + ){
/*Receive client connection and generate new file descriptor*/
if(fds[i].fd == fd & amp; & amp; fds[i].revents & amp; POLLIN){ //Determine whether the server's fd has data arriving, that is, the 0th one, and the file descriptor is data If it is readable, then execute it to connect the client.
if( (newfd = accept(fd, (Addr *) & amp;addr, & amp;addrlen) ) < 0)
perror("accept");
fds[nfds].fd = newfd; //Add to check list
fds[nfds + + ].events = POLLIN;
printf("[%s:%d][nfds=%lu] connection successful.\\
",
inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), nfds);
}
/*Process client data*/
if(i > 0 & amp; & amp; fds[i].revents & amp; POLLIN){ //The server does not count, it needs to start with 1
if(DataHandle(fds[i].fd) <= 0){
if( getpeername(fds[i].fd, (Addr *) & amp;addr, & amp;addrlen) < 0)
perror("getpeername");
printf("[%s:%d][fd=%d] exited.\\
",
inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), fds[i].fd);
close(fds[i].fd);
for(j=i; j<nfds-1; j + + ) //Deleting the target of the check is equivalent to moving the following array forward by 1
fds[j] = fds[j + 1];
nfds--;
i--;
}
}
}
}
close(fd);
return 0;
}

socket.c

#include "net.h"

void Argment(int argc, char *argv[]){
if(argc < 3){
fprintf(stderr, "%s<addr><port>\\
", argv[0]);
exit(0);
}
}
int CreateSocket(char *argv[]){
/*Create socket*/
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0)
ErrExit("socket");
/*Allow addresses to be reused quickly*/
int flag = 1;
if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, & amp;flag, sizeof(flag) ) )
perror("setsockopt");
/*Set communication structure*/
Addr_in addr;
bzero( & amp;addr, sizeof(addr) );
addr.sin_family = AF_INET;
addr.sin_port = htons( atoi(argv[2]) );
/*Bind communication structure*/
if( bind(fd, (Addr *) & amp;addr, sizeof(Addr_in) ) )
ErrExit("bind");
/*Set the socket to listening mode*/
if( listen(fd, BACKLOG) )
ErrExit("listen");
return fd;
}
int DataHandle(int fd){
char buf[BUFSIZ] = {};
Addr_in peeraddr;
socklen_t peerlen = sizeof(Addr_in);
if( getpeername(fd, (Addr *) & amp;peeraddr, & amp;peerlen) )
perror("getpeername");
int ret = recv(fd, buf, BUFSIZ, 0);
if(ret < 0)
perror("recv");
if(ret>0){
printf("[%s:%d]data: %s\\
",
inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);
}
return ret;
}

net.h

#ifndef _NET_H_
#define _NET_H_

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
#include <errno.h>

typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
#define BACKLOG 5
#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)

void Argment(int argc, char *argv[]);
int CreateSocket(char *argv[]);
int DataHandle(int fd);


#endif

3 epoll function family (the most efficient)

/*Create epoll handle*/
int epoll_create(int size); //The size parameter has actually been deprecated

/*Control interface of epoll handle*/
int epoll_ctl(int epfd, int op, int fd,
                         struct epoll_event *event);

/*Wait for I/O events on the epoll file descriptor*/
int epoll_wait(int epfd, struct epoll_event *events,
                         int maxevents, int timeout);

3.1 epoll_create Create epoll handle

int epoll_create(int size); //The size parameter has actually been deprecated

The epoll_create() function creates an epoll instance and returns a file descriptor associated with the instance. Here is what the parameters mean:

  • size: has been deprecated. It was originally used to specify the size of the epoll instance, but is no longer used after kernel version 2.6.8.

The return value of epoll_create() is a non-negative integer representing the file descriptor associated with the epoll instance. If the return value is -1, it means the creation failed, and you can use errno to view the specific error information.

After the call is successful, you can use the returned file descriptor to perform subsequent operations, such as registration, modification, waiting for events on the file descriptor, etc.

3.2 epoll_ctl epoll handle control interface

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

The epoll_ctl() function is used to register, modify or delete file descriptor events to the epoll instance. It is the control interface for the epoll system call. Here is what the parameters mean:

  • epfd: The file descriptor of the epoll instance, created by epoll_create().
  • op: Operation type, which can be one of the following values:
    • EPOLL_CTL_ADD: Adds the file descriptor fd to the epoll instance and associates an event structure.
    • EPOLL_CTL_MOD: Modifies the event structure associated with the file descriptor fd in the epoll instance.
    • EPOLL_CTL_DEL: Removes the file descriptor fd from the epoll instance.
  • fd: File descriptor that needs to be registered, modified or deleted.
  • event: Pointer to the struct epoll_event structure, used to specify the event type that the file descriptor cares about.

The return value of epoll_ctl() is 0, which means the operation was successful, and -1, which means the operation failed. In this case, you can use errno to view the specific error information.

epoll_event structure

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 are used to describe events that occur on file descriptors*/
   epoll_data_t data; /* User data variable can be used to store user data information related to the event. */
};
  • EPOLLIN: Indicates that the corresponding file descriptor can be read (including the normal closing of the peer SOCKET);
  • EPOLLOUT: Indicates that the corresponding file descriptor can be written;
  • EPOLLPRI: Indicates that the corresponding file descriptor has urgent data to read (this 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 Trigger mode, which is relative to Level Trigger.
  • EPOLLONESHOT: Only listens to an event once. After listening to this event, if you still need to continue to monitor this socket, you need to add this socket to the EPOLL queue again.

3.3 epoll_wait Waits for I/O events on the epoll file descriptor

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

The epoll_wait() function is used to wait for an event to occur on the file descriptor in the epoll instance. It will block until at least one file descriptor has a ready event. Here is what the parameters mean:

  • epfd: The file descriptor of the epoll instance, created by epoll_create().
  • events: Pointer to the struct epoll_event structure array, used to return the file descriptor information that triggered the event.
  • maxevents: Indicates the maximum size of the events array, that is, the maximum number of events that can be returned.
  • timeout: timeout time (unit: milliseconds), if it is 0, it means returning immediately, if it is -1, it means waiting forever.

The return value of epoll_wait() indicates the number of ready file descriptors. If the return value is 0, it indicates a timeout. If the return value is -1, it means an error occurred, and you can use errno to view the specific error information. On a successful return, the events array is populated with the file descriptor information that triggered the event.

3.4 epoll function application

server.c

#include "net.h"
#include <sys/epoll.h>

#define MAX_SOCK_FD 1024

int main(int argc, char *argv[])
{
int i, nfds, fd, epfd, newfd;
Addr_in addr;
socklen_t addrlen = sizeof(Addr_in);
struct epoll_event tmp, events[MAX_SOCK_FD] = {};
/*Check parameters, if less than 3, exit the process directly*/
Argment(argc, argv);
/*Create a socket with listening mode set*/
fd = CreateSocket(argv);

if( (epfd = epoll_create(1)) < 0) //Parameter 1 is meaningless
ErrExit("epoll_create");
tmp.events = EPOLLIN;
tmp.data.fd = fd;
if( epoll_ctl(epfd, EPOLL_CTL_ADD, fd, & amp;tmp) )
ErrExit("epoll_ctl");

while(1) {
if( (nfds = epoll_wait(epfd, events, MAX_SOCK_FD, -1) ) < 0)
ErrExit("epoll_wait");
printf("nfds = %d\\
", nfds);

for(i = 0; i < nfds; i + + ) {
if(events[i].data.fd == fd){
/*Receive client connection and generate new file descriptor*/
if( (newfd = accept(fd, (Addr *) & amp;addr, & amp;addrlen) ) < 0)
perror("accept");
printf("[%s:%d] connection.\\
", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) );
tmp.events = EPOLLIN;
tmp.data.fd = newfd;
if( epoll_ctl(epfd, EPOLL_CTL_ADD, newfd, & amp;tmp) )
ErrExit("epoll_ctl");
}else{/*Process client data*/
if(DataHandle(events[i].data.fd) <= 0){
if( epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL) )
ErrExit("epoll_ctl");
if( getpeername(events[i].data.fd, (Addr *) & amp;addr, & amp;addrlen) )
perror("getpeername");
printf("[%s:%d] exited.\\
", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) );
close(events[i].data.fd);
}
}
}
}
close(epfd);
close(fd);
return 0;
}

socket.c

#include "net.h"

void Argment(int argc, char *argv[]){
if(argc < 3){
fprintf(stderr, "%s<addr><port>\\
", argv[0]);
exit(0);
}
}
int CreateSocket(char *argv[]){
/*Create socket*/
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0)
ErrExit("socket");
/*Allow addresses to be reused quickly*/
int flag = 1;
if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, & amp;flag, sizeof(flag) ) )
perror("setsockopt");
/*Set communication structure*/
Addr_in addr;
bzero( & amp;addr, sizeof(addr) );
addr.sin_family = AF_INET;
addr.sin_port = htons( atoi(argv[2]) );
/*Bind communication structure*/
if( bind(fd, (Addr *) & amp;addr, sizeof(Addr_in) ) )
ErrExit("bind");
/*Set the socket to listening mode*/
if( listen(fd, BACKLOG) )
ErrExit("listen");
return fd;
}
int DataHandle(int fd){
char buf[BUFSIZ] = {};
Addr_in peeraddr;
socklen_t peerlen = sizeof(Addr_in);
if( getpeername(fd, (Addr *) & amp;peeraddr, & amp;peerlen) )
perror("getpeername");
int ret = recv(fd, buf, BUFSIZ, 0);
if(ret < 0)
perror("recv");
if(ret>0){
printf("[%s:%d]data: %s\\
",
inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);
}
return ret;
}

net.h

#ifndef _NET_H_
#define _NET_H_

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
#include <errno.h>

typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
#define BACKLOG 5
#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)

void Argment(int argc, char *argv[]);
int CreateSocket(char *argv[]);
int DataHandle(int fd);


#endif

4 Exercise

Summarize the advantages and disadvantages of select, poll and epoll

select:

advantage:

  1. There is no limit on the number of file descriptors that can be processed, which is suitable for low concurrency situations.
  2. On some platforms (such as Windows), select is the only multiplexing mechanism available.

shortcoming:

  1. Each call to select requires copying all file descriptors from user mode to kernel mode, which results in poor performance.
  2. In the case of high concurrency, each check requires polling the entire fd_set, which takes up a lot of system resources.
  3. It cannot monitor whether the location of the file descriptor has been modified.

poll:

advantage:

  1. There is no limit on the number of file descriptors that can be processed, which is suitable for low concurrency situations.
  2. Compared with select, poll does not need to copy file descriptors and has better performance.

shortcoming:

  1. In the case of high concurrency, each check requires polling the entire pollfd array, which takes up a lot of system resources.
  2. It cannot monitor whether the location of the file descriptor has been modified.

epoll:

advantage:

  1. Supports ET (edge trigger) mode, which is more efficient.
  2. It will not slow down as the number of listening file descriptors increases, and is suitable for high concurrency situations.
  3. Supports detection of whether the file descriptor location has been modified.

shortcoming:

  1. The number of file descriptors that can be processed is limited, but the number is usually large and generally does not have a big impact on usage.

To sum up, epoll is a more efficient and flexible I/O multiplexing mechanism that can better adapt to high concurrency scenarios. Select and poll are suitable for low concurrency situations.