Multi-channel IO transfer server-epoll [Key]

1. Introduction to epoll

Epoll is an enhanced version of the multiplex io multiplexing interface select/poll under Linux. It can significantly improve the system CPU utilization of the program when a small amount of active in a large number of concurrent connections, because it reuses file descriptor sets to deliver results Instead of forcing the developer to prepare the set of file descriptors to be monitored every time before waiting for an event, another reason is that when getting the event, there is no need to traverse the entire set of file descriptors to be monitored, just traverse those that are to be monitored by the kernel. The IO event wakes up asynchronously and joins the descriptor set in the Ready queue. [What does it mean: such as teaching homework, teacher (transfer model), n multiple students (client), when a small number of students have finished their homework (there is an IO event), if the teacher is a select/poll model, then one by one Ask students, teacher: Have you finished writing? (How can the efficiency be high in this way), but if the teacher is an epoll model, this is the case. After a few students have finished writing their homework, they directly raise their hands to indicate that they are finished, and the teacher can go directly to him to collect it]

Currently epoll is the popular model of choice in linux large-scale concurrent network programs

In addition to providing level triggered for select / poll IO events, epoll also provides edge triggered [a separate explanation later ], which makes it possible for user space programs to cache IO status and reduce epoll_wait And the call of epoll_pwait improves the execution efficiency of the program.

 

Two.epoll related functions

1. Create an epoll handle , and the parameter size [recommended value: it doesn't matter if you exceed it] is used to tell the kernel to monitor the number of file descriptors, which is related to the memory size.

#include <sys/epoll.h>

int epoll_create(int size)    // size: the number of monitors

2. Control events on a file descriptor monitored by epoll : register, modify, delete

#include <sys/epoll.h>

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

epfd: epoll handle returned by epoll_create, ( root node fd )

op: represents the action, represented by three macros

      EPOLL_CTL_ADD: Add fd to the red-black tree [ register]

      EPOLL_CTL_DEL: Remove fd from the red-black tree 【Delete】

      EPOLL_CTL_MOD: modify the monitoring event of fd that is already on the red-black tree [modify]

event: tell the kernel to monitor the event [structure]

struct event{

        _uint32_t events; // monitor events

        epoll_data_t data; // user data

}

typedef union epoll_data{// There is only one type of union at the same time, which is a valid format. The previous C-based article has already introduced

        void * ptr;

        int fd;

        uint32_t   u32;

        uint64_t   u64;

}epoll_data_t

events listens for events Type:

EPOLLIN : Indicates the read event (including the normal closure of the opposite socket)

EPOLLOUT : Indicates write event  

EPOLLPRI: Indicates that the file descriptor has urgent data to read

EPOLLERR : Indicates that an error occurred in the file descriptor

EPOLLHUP: Indicates that the file descriptor is hung up

EPOLLET: indicates edge trigger

EPOLLONESHOT: Only listen to the event once. After listening to this event, if you need to continue to listen to this socket, you need to add this socket to epoll again

Note: The red part is the key to master

 

3. Wait for an event to occur on the monitored file descriptor, similar to select(...) / poll(....)

 #include <sys/epoll.h>

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

events: a collection of events used to store the kernel [outgoing parameters]

maxevents: used to tell the kernel how big the events (array) are

timeout: is the timeout

  •     -1: Block waiting for event to occur
  •     0: return immediately, non-blocking (epoll_wait: no event occurs, forget me, I won’t wait for you)
  •      >0: wait for the specified time

Return value: successfully return how many file descriptors are ready, return 0 when time is up, return -1 on error

 

Below I drew a schematic diagram [a bit rubbed, forgive me]

 

Code Demo

#include <iostream>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>

using namespace std;


#define MAXLINE 8192
#define SERVER_PORT 9999
#define OPEN_MAX 4000
#define LISTEN_NUM 100

static int Debug = 1;

void
perror_exit(const char* strerr)
{
	perror(strerr);
	exit(1);	
}


int
main(int argc, char*argv[])
{
	int i, listenfd, connfd, sockfd;
	int n, num = 0;
	ssize_t nready, efd, res;
	char buf[MAXLINE], str[INET_ADDRSTRLEN];
	socklen_t clie_addr_len;

	struct sockaddr_in clie_addr, serv_addr;
	struct epoll_event tep, ep[OPEN_MAX];

	listenfd = socket(AF_INET, SOCK_STREAM, 0);

	int opt = 1;
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
	
	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(SERVER_PORT);
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	
	bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
	listen(listenfd, LISTEN_NUM);
	
	if(Debug)printf("------listening------\n");

	efd = epoll_create(OPEN_MAX);
	if(efd == -1)
		perror_exit("create epoll error");

	tep.events = EPOLLIN; 
	tep.data.fd = listenfd;
	res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);
	if(res == -1)
		perror_exit("listenfd add error in epoll_ctl");

	while(true) {
		nready = epoll_wait(efd, ep, OPEN_MAX, -1);
		if(nready == -1)
			perror_exit("epoll_wait error");

		for(i=0; i<nready; i++){
			if(!(ep[i].events & EPOLLIN))   // 这里我们只处理  读事件  其他的都随浮云把
				continue;
			if(ep[i].data.fd == listenfd){ // have client connect event
				clie_addr_len = sizeof(clie_addr);
				connfd = accept(listenfd, (struct sockaddr*)&clie_addr, &clie_addr_len);
				if(Debug)printf("client IP:%s, PORT:%d \n",
					inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),
					ntohs(clie_addr.sin_port));
				
				num++;
				if(Debug)printf("connfd:%d ----- client %d \n", connfd, num);

				tep.events = EPOLLIN;
				tep.data.fd = connfd;
				res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
				if(res == -1)
					perror_exit("connfd add error in epoll_ctl");
			}else{
				sockfd = ep[i].data.fd;
				n = read(sockfd, buf, sizeof(buf));

				if(n == 0){
					res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
					if(res == -1)
						perror_exit("sockfd delete error in epoll_ctl");
					close(sockfd);
					printf("client[%d] closed connection \n", num);
					num--;					
				}else if(n < 0){
					perror("read n < 0 error");
					res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
					if(res == -1)
						perror_exit("sockfd delete error in epoll_ctl");
					close(sockfd);
					num--;
				}else{
					buf[n] = '\0';
					if(Debug)printf("test from client is %s \n", buf);
					for(i=0; i<n; i++)
						buf[i] = toupper(buf[i]);
					write(sockfd, buf, n);
					
				}
			}
		}
	}
	res = epoll_ctl(efd, EPOLL_CTL_DEL, listenfd, NULL);
	close(listenfd);
	return 0;
}

 

Guess you like

Origin blog.csdn.net/qq_44065088/article/details/109270124