Red de conocimiento informático - Problemas con los teléfonos móviles - [Original] Introducción a varios modelos del lado del servidor: modelo de reactor (introducción a epoll)

[Original] Introducción a varios modelos del lado del servidor: modelo de reactor (introducción a epoll)

Introducción: el artículo anterior habló sobre el método del grupo de subprocesos para manejar la concurrencia del lado del servidor y proporcionó una solución del grupo de subprocesos (método semisincrónico y semiasincrónico). Hablando de IO asíncrona, en realidad es difícil lograr una verdadera asincrónica ahora. En la mayoría de los casos, aún necesita bloquear una función multiplexada, como select o epoll, para obtener el descriptor listo y luego llamar a la función correspondiente registrada en el. función de devolución de llamada. Este enfoque es la idea básica detrás del diseño actual de los reactores. A continuación les mostraré un diagrama esquemático del modelo del reactor. Este diagrama está tomado del artículo del reactor sobre el servidor retorcido de Python, pero tiene aproximadamente la misma idea que necesitamos. El bucle de eventos se bloquea para ver si el descriptor está listo y devuelve un descriptor legible o escribible cuando está listo, ya sea fuera de banda o por error. Debido a que select se ha introducido en muchos artículos, tomaré epoll como ejemplo. Parece que la reutilización de IO se agregó en 2.4.6 o alguna versión posterior. Sin mencionar algunas ventajas de epoll sobre select. El kernel utiliza un mecanismo de árbol rojo-negro, lo que mejora enormemente el rendimiento de epoll. Los famosos libevent y Nginx utilizan este mecanismo internamente. La función principal de epoll: int epoll_create (int size); en la versión actual de Linux, el tamaño no es importante y el valor predeterminado no excede el valor máximo. El valor de retorno de la función es un descriptor (identificador) y crear epoll es muy simple. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); el primer parámetro es el descriptor devuelto por epoll_create, y el segundo parámetro son varios valores definidos por la macro EPOLL_CTL_ADD: EPOLL_CTL_MOD: modifica el evento de registrado fd Escriba EPOLL_CTL_DEL: elimine un descriptor de la cola de escucha de epoll. El tercer parámetro es el descriptor que se agregará al cuarto parámetro. es un parámetro estructural, la estructura es la siguiente struct epoll_event { __uint32_t events; epoll_data_t data }; typedef union epoll_data { void *ptr; __uint32_t u3; 2 ; __uint64_t u64; } epoll_data_t u64; } t u64; el evento dentro de la estructura epoll_event representa el tipo de evento devuelto o el tipo de evento cuando se agrega. También podrían ser datos fuera de banda o errores, etc. Está definido por varias macros: EPOLLIN: un evento de lectura en el descriptor de archivo EPOLLOUT: un evento de escritura en el descriptor de archivo EPOLLPRI: el descriptor tiene datos urgentes para leer (esto debería indicar la llegada de datos fuera de banda EPOLLERR); : Error de descriptor; EPOLLHUP: el descriptor está suspendido; EPOLLET: modo activado por borde EPOLLONESHOT: solo escuche el evento una vez, si necesita continuar monitoreando el socket, debe restablecer el socket. interfaces a la cola EPOLL Vale la pena mencionar que muchos artículos no mencionan que esta macro puede ser cambiada por usted mismo, a través de epoll_ctl, o el sistema operativo cuando regresa epoll_wait, porque el descriptor puede ser incorrecto, etc.

En términos generales, para los descriptores, puede cambiarlos usted mismo a través de epoll_ctl. En términos generales, para un descriptor, puede utilizar la operación | para combinarlos. Agregue un descriptor y escuche para ver si se puede leer o escribir. EPOLLIN | EPOLLOUT Tenga en cuenta ptr o fd en epoll_data_t, no ptr y fd. La estructura solo puede contener uno de estos, por lo que al registrar un evento en el descriptor correspondiente, registre el descriptor correspondiente, fd, o registre el contenedor de eventos correspondiente. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); el primer parámetro es el descriptor de epoll y el segundo parámetro es un puntero a struct epoll_event, que debe pasarse en forma de matriz. , con tipo epoll_event. El tercer parámetro es el valor máximo de la matriz de eventos de escucha. El cuarto es el tiempo de espera. Para Nginx o muchas formas de administración de tiempo de espera, como libevent, se utilizan árboles rojo-negro y montón mínimo para administrar el tiempo de espera. Escribiré una publicación de blog para presentarlo en el futuro. Aquí solo necesita saber el tiempo de espera. Es el valor máximo del bloqueo epoll_wait. Si excede este valor independientemente de si se devuelve un evento, 0 significa regresar inmediatamente, es decir, no se devuelve ningún evento y -1 es permanente. . Evento o no, 0 significa retorno inmediato, es decir, retorno si hay evento o no, -1 significa bloqueo permanente.

Una estructura de demostración de epoll simple epoll_event ev, events[1024]; epfd=epoll_create(1024); for(;;) { nfds = epoll_wait(epfd, events, 1024, time_value); ) { if(events[i].data.fd==listenfd) /* Si hay eventos en el descriptor de escucha agregado*/ { connfd = aceptar(listenfd, (sockaddr *)amp; clientaddr, amp; clilen) /; * Acepte la conexión y obtenga el descriptor del enlace, agregue el descriptor a la cola de eventos de escucha de epoll */

setnonblocking(connfd); ev.data.fd=connfd ev.data.fd=EPOLLIN| }; ev.data.fd=EPOLLIN|EPOLLIN}; ev.data.fd=EPOLLIN|EPOLLIN}; ev.data.fd=EPOLLIN|EPOLLINEvents=EPOLLIN|EPOLLET /* Leer eventos**/ epoll_ctl (epfd, EPOLL_CTL_ADD; , connfd, amp; ev); /* Agregar nuevo fd a la cola de escucha de epoll */ } else if(events[i].eventsamp; EPOLLIN) //recibir datos, leer socket { n = read(sockfd, line, MAXLINE) ) lt; 0 ev.data.ptr = mi_ev; // ev.data.ptr = mi_ev; // ev.data.ptr = mi_ev; // ev.data.ptr = mi_ev; my_ev; // ev.data.ptr = my_ev; // ev.data.ptr = my_evptr = my_ev; //ev puede ser un contenedor de eventos personalizado o fd ev.events=EPOLLOUT|EPOLLET( epfd, EPOLL_CTL_MOD, sockfd , amp; ev); /*Modifica el identificador y espera el siguiente ciclo para enviar datos*/ } else if(events[i].eventsamp; EPOLLOUT) /* Se puede escribir el descriptor correspondiente, es decir, struct my_event* my_ev= (my_event*)eventos[i].data.ptr; sockfd = my_ev-gt; fd; enviar( sockfd, ev-gt; ptr, strlen((char*)my_ev-gt; ptr), 0) ; .data.fd=sockfd; ev.events=EPOLLIN|EPOLLET; epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, amp; ev); else { // }.}} EPOLL aún no se ha aclarado, y hay muchos más Otro contenido. Solo quiero que los lectores que no conocen los eventos asincrónicos y el patrón del reactor comprendan este patrón.

Cabe señalar que en este modo, el descriptor de conexión debe configurarse en modo sin bloqueo y luego la función de operación de IO debe registrar el estado de cada lectura y escritura. Si el búfer está lleno, el estado debe ser. Cuando se registra y se devuelve a este descriptor la próxima vez, la transmisión o lectura continuará desde el estado anterior, porque el búfer del socket lee los datos de la capa de aplicación y, si los datos de la capa TCP son relativamente grandes, se producirá fragmentación. Si los datos de la capa TCP son grandes, la fragmentación hará que el búfer del socket no pueda leer o escribir todos los datos a la vez y el búfer del socket se llenará. El modo que debe seleccionar es el modo de disparo horizontal LT. Si es el modo de activación de borde ET, después de leer o escribir en el socket una vez, si el búfer está lleno y la escritura no puede continuar, epoll_wait ya no continuará regresando y no se requiere grabación de la máquina de estado. Resumen: lo anterior es solo una breve introducción a epoll. Si hay algún error, hágamelo saber. Espero que a los expertos no les importe. Después de comenzar, habrá una introducción al marco del reactor. Si se utiliza la encapsulación de eventos y la configuración de funciones de devolución de llamada, esto es solo una demostración, no escrita por mí. Eso es todo por hoy