Análisis profundo del mecanismo de multiplexación IO

Hola, soy Weiki y bienvenido a ape java.

La tecnología de multiplexación de IO, ya sea una entrevista o la acumulación de tecnología habitual, es un punto de conocimiento importante y muchos marcos técnicos de alto rendimiento lo tienen. Entonces, ¿qué es la multiplexación IO? ¿Qué problema resuelve la multiplexación IO? Echémosle un vistazo juntos hoy.

Declaración: Este artículo está basado en el sistema Linux

Hoy en día, la capa inferior de muchos sistemas o marcos utiliza el modelo de programación de Socket para realizar la comunicación de red. Por lo tanto, antes de explicar la multiplexación de IO, primero presentamos el conocimiento sobre Socket para que todos puedan comprender mejor la multiplexación de IO. puede consultar mi publicación de blog anterior: Ape java: ¿Sabe cómo se crea Socket? Si ya tienes conocimientos de Scoket puedes saltarte este paso.

Con la comprensión de socket, veamos el modelo IO común:

1. Modelos IO comunes

Los modelos de E/S de red comunes se dividen en cuatro tipos: E/S de bloqueo síncrono (E/S de bloqueo, BIO), E/S sincrónico sin bloqueo (NIO), multiplexación de E/S, E/S sin bloqueo asíncrono (E/S asíncrono, AIO), donde AIO es E/S asíncrono, otros son IO sincrónicos.

1.1 Bloqueo síncrono IO-BIO

E/S de bloqueo síncrono: durante el procesamiento de subprocesos, si hay operaciones de E/S involucradas, el subproceso actual se bloqueará hasta que se complete el procesamiento de E/S antes de que el subproceso proceda a procesar el proceso posterior. Como se muestra en la figura a continuación, el servidor asignará un nuevo subproceso para cada socket del cliente. El procesamiento comercial de cada subproceso se divide en 2 pasos. Cuando se completa el procesamiento del paso 1 y la operación de E/S (como: cargar archivos), en este momento, el subproceso actual se bloqueará hasta que se complete la operación IO, el subproceso continuará procesando el paso 2.
img.png
Escenario de uso real: el uso del grupo de subprocesos en Java para conectarse a la base de datos utiliza el modelo de E/S de bloqueo síncrono.

Desventajas del modelo: debido a que cada cliente requiere un nuevo subproceso, inevitablemente conducirá a frecuentes bloqueos y cambios de subprocesos, lo que generará una sobrecarga.

1.2 IO-NIO síncrono sin bloqueo (Nuevo IO)

E/S sin bloqueo síncrono: durante el procesamiento de subprocesos, si hay operaciones de E/S involucradas, el subproceso actual no se bloqueará, pero procesará otros códigos comerciales y luego esperará un momento antes de verificar si la interacción de E/S se completó. Como se muestra en la siguiente figura: El búfer es un búfer que se usa para almacenar en caché los datos leídos y escritos; El canal es un canal responsable de conectar los datos de E/S en segundo plano; y la función principal implementada por Selector es consultar activamente qué canales están listos . Selector reutiliza un subproceso para consultar los canales listos, lo que reduce en gran medida la sobrecarga del cambio frecuente de subprocesos causado por las interacciones de IO.
img_1.png

Escenarios de uso real: Java NIO se basa en este modelo de interacción de E/S para admitir códigos comerciales para implementar diseños sincrónicos y sin bloqueo para E/S, lo que reduce la sobrecarga del bloqueo frecuente de subprocesos y el cambio de bandas en el proceso de interacción de E/S de bloqueo síncrono tradicional original. El caso clásico utilizado por NIO es el marco Netty, y en realidad se adopta el mecanismo subyacente de Elasticsearch.

1.3 Multiplexación de E/S

se explicará en detalle a continuación

1.4 IO-AIO asíncrono sin bloqueo

AIO es la abreviatura de Asynchronous IO, es decir, Asynchronized IO. Para AIO, en lugar de notificar al subproceso cuando el IO está listo, notifica al subproceso después de que se haya completado la operación de IO. Por lo tanto, AIO es completamente sin bloqueo. En este punto, nuestra lógica comercial se convertirá en una función de devolución de llamada, que el sistema activará automáticamente una vez que se complete la operación IO. AIO se usó en netty5, pero requirió mucho esfuerzo, y el rendimiento de netty5 no logró dar un gran salto sobre netty4, por lo que netty5 finalmente estaba fuera de línea.

Lo siguiente es la aparición de nuestro multiplexado IO protagonista

2. ¿Qué es la multiplexación de E/S?

Presumiblemente, cuando estamos aprendiendo una nueva tecnología o concepto, la pregunta más importante es el concepto en sí mismo, y la multiplexación de IO no es una excepción. Si desea averiguar qué es la multiplexación de IO, puede comenzar con la multiplexación de IO El "camino" para comenzar.

Camino: el significado original es el camino, como: el camino asfaltado en la ciudad, el camino de tierra en el campo, debe estar familiarizado con estos.
Entonces: ¿cuál es el camino en IO?

No se preocupe, veamos primero qué es IO.

En una computadora, IO es entrada y salida (Entrada/Salida), y la interacción de información directa se realiza a través del dispositivo IO subyacente. Para diferentes objetos de operación, se puede dividir en E/S de disco, E/S de red, E/S mapeada en memoria, etc. Siempre que un sistema interactivo con tipos de entrada y salida se pueda considerar un sistema de E/S.
Finalmente, veamos "carretera" y "vías múltiples" juntos

En la programación de sockets, [ClientIp, ClientPort, ServerIp, ServerPort, Protocol] 5 elementos pueden identificar de manera única una conexión de socket.Basado en esta premisa, un determinado puerto de un mismo servicio puede establecer conexiones de socket con n clientes, como se muestra en la siguiente figura Para describir aproximadamente:
img_2.png

Por lo tanto, la conexión de socket entre cada cliente y servidor se puede considerar como "unidireccional", y la conexión de socket entre varios clientes y el servidor es "de múltiples maneras". Por lo tanto, IO multivía es el flujo de entrada y salida en conexiones de múltiples sockets. La multiplexación significa que los flujos de entrada y salida en varias conexiones de socket son procesados ​​por un hilo. Entonces, la multiplexación IO se puede definir de la siguiente manera:

La multiplexación de IO en Linux se refiere a: un hilo procesa múltiples flujos de IO.

3. ¿Cuáles son los mecanismos de implementación de la multiplexación IO?

Primero mire el modelo del socket básico, para compararlo con el mecanismo de multiplexación IO a continuación, el pseudocódigo se implementa de la siguiente manera

listenSocket = socket(); //系统调用socket()函数,调用创建一个主动socket
bind(listenSocket);  //给主动socket绑定地址和端口
listen(listenSocket); //将默认的主动socket转换为服务器使用的被动socket(也叫监听socket)
while (true) { //循环监听客户端连接请求
   connSocket = accept(listenSocket); //接受客户端连接,获取已连接socket
   recv(connsocket); //从客户端读取数据,只能同时处理一个客户端
   send(connsocket); //给客户端返回数据,只能同时处理一个客户端
}

Realice el proceso de comunicación de red como se muestra en la siguiente figura
img_3.png

El modelo de socket básico puede realizar la comunicación entre el servidor y el cliente, pero cada vez que el programa llama a la función de aceptación, solo puede manejar una conexión de cliente. Cuando hay una gran cantidad de conexiones de clientes, el rendimiento de procesamiento de este modelo es relativamente pobre Por lo tanto, Linux proporciona un mecanismo de multiplexación de E/S de alto rendimiento para resolver este dilema.

En Linux, el sistema operativo proporciona tres mecanismos de multiplexación de IO: select, poll y epoll Analizamos principalmente los principios de los tres mecanismos de multiplexación basados ​​en los siguientes cuatro aspectos:

¿Cuántos sockets puede monitorear la multiplexación IO?
¿Qué eventos en el socket pueden ser monitoreados por multiplexación IO?
¿Cómo detecta la multiplexación IO el descriptor de archivo listo fd?
¿Cómo implementa la comunicación de red la multiplexación de IO?

3.1 mecanismo de selección

Una función importante en el mecanismo de selección es select(). La función tiene 4 parámetros de entrada y devuelve un número entero. El prototipo y los parámetros de select() son los siguientes:

/**
*  参数说明
*  监听的文件描述符数量__nfds、
*  被监听描述符的三个集合*__readfds,*__writefds和*__exceptfds
*  监听时阻塞等待的超时时长*__timeout
*  返回值:返回一个socket对应的文件描述符
   */
   int select(int __nfds, fd_set * __readfds, fd_set * __writefds, fd_set * __exceptfds, struct timeval * __timeout)
   

¿Cuántos enchufes se pueden seleccionar para escuchar?

Respuesta: 1024

¿Qué eventos del socket puede seleccionar escuchar?

Respuesta: La función select() tiene tres conjuntos fd_set, que representan tres tipos de eventos para monitorear, a saber, eventos de datos de lectura (conjunto __readfds), eventos de datos de escritura (conjunto __writefds) y eventos anormales (conjunto __exceptfds). Cuando el conjunto es NULL , representa No hay necesidad de manejar el evento correspondiente.

¿Cómo percibe select el fd listo?

Respuesta: Es necesario recorrer la colección fd para encontrar un descriptor listo.

¿Cómo realiza el mecanismo de selección la comunicación de red?

Código

int sock_fd,conn_fd; //监听socket和已连接socket的变量
sock_fd = socket() //创建socket
bind(sock_fd)   //绑定socket
listen(sock_fd) //在socket上进行监听,将socket转为监听socket

fd_set rset;  //被监听的描述符集合,关注描述符上的读事件
int max_fd = sock_fd

//初始化rset数组,使用FD_ZERO宏设置每个元素为0
FD_ZERO(&rset);
//使用FD_SET宏设置rset数组中位置为sock_fd的文件描述符为1,表示需要监听该文件描述符
FD_SET(sock_fd,&rset);

//设置超时时间
struct timeval timeout;
timeout.tv_sec = 3;
timeout.tv_usec = 0;
while(1) {
//调用select函数,检测rset数组保存的文件描述符是否已有读事件就绪,返回就绪的文件描述符个数
n = select(max_fd+1, &rset, NULL, NULL, &timeout);

//调用FD_ISSET宏,在rset数组中检测sock_fd对应的文件描述符是否就绪
if (FD_ISSET(sock_fd, &rset)) {
//如果sock_fd已经就绪,表明已有客户端连接;调用accept函数建立连接
conn_fd = accept();
//设置rset数组中位置为conn_fd的文件描述符为1,表示需要监听该文件描述符
FD_SET(conn_fd, &rset);
}

//依次检查已连接套接字的文件描述符
for (i = 0; i < maxfd; i++) {
     //调用FD_ISSET宏,在rset数组中检测文件描述符是否就绪
    if (FD_ISSET(i, &rset)) {
    //有数据可读,进行读数据处理
   }
  }
}

Select realiza el proceso de comunicación de red como se muestra en la siguiente figura:
img_4.png

Las deficiencias de la función de selección.

En primer lugar, la función select() tiene un límite en la cantidad de descriptores de archivos que un solo proceso puede monitorear. La cantidad de descriptores de archivos que puede monitorear está determinada por __FD_SETSIZE, y el valor predeterminado es 1024.

En segundo lugar, después de que regresa la función de selección, es necesario recorrer la colección de descriptores para encontrar descriptores listos. Este proceso transversal generará cierta sobrecarga, lo que reducirá el rendimiento del programa.

3.2 Mecanismo de encuesta

La función principal del mecanismo de encuesta es la función poll() y la definición prototipo de la función poll()

/**
* 参数 *__fds 是 pollfd 结构体数组,pollfd 结构体里包含了要监听的描述符,以及该描述符上要监听的事件类型
* 参数 __nfds 表示的是 *__fds 数组的元素个数
*  __timeout 表示 poll 函数阻塞的超时时间
   */
   int poll (struct pollfd *__fds, nfds_t __nfds, int __timeout);
   pollfd结构体的定义

   struct pollfd {
      int fd;         //进行监听的文件描述符
      short int events;       //要监听的事件类型
      short int revents;      //实际发生的事件类型
   };

La estructura pollfd contiene tres variables miembro fd, events y revents, que representan respectivamente el descriptor de archivo que se monitoreará, el tipo de evento que se monitoreará y el tipo de evento que realmente ocurrió.

¿Cuántos sockets puede escuchar la encuesta?

Respuesta: personalizado, pero el sistema debe poder soportar

¿Qué eventos en el socket pueden sondear el monitor?

Los tipos de eventos a ser monitoreados y realmente ocurridos en la estructura pollfd están representados por las siguientes tres definiciones de macro, a saber, POLLRDNORM, POLLWRNORM y POLLERR, que representan eventos legibles, escribibles y de error respectivamente.

#define POLLRDNORM 0x040 //可读事件
#define POLLWRNORM 0x100 //可写事件
#define POLLERR 0x008 //错误事件

¿Cómo se prepara la encuesta fd?

Respuesta: Similar a select, necesita recorrer la colección fd para encontrar un descriptor listo.

¿Cómo realiza el mecanismo de sondeo la comunicación de red?

código de implementación de la encuesta

int sock_fd,conn_fd; //监听套接字和已连接套接字的变量
sock_fd = socket() //创建套接字
bind(sock_fd)   //绑定套接字
listen(sock_fd) //在套接字上进行监听,将套接字转为监听套接字

//poll函数可以监听的文件描述符数量,可以大于1024
#define MAX_OPEN = 2048

//pollfd结构体数组,对应文件描述符
struct pollfd client[MAX_OPEN];

//将创建的监听套接字加入pollfd数组,并监听其可读事件
client[0].fd = sock_fd;
client[0].events = POLLRDNORM;
maxfd = 0;

//初始化client数组其他元素为-1
for (i = 1; i < MAX_OPEN; i++)
   client[i].fd = -1;

while(1) {
    //调用poll函数,检测client数组里的文件描述符是否有就绪的,返回就绪的文件描述符个数
    n = poll(client, maxfd+1, &timeout);
    //如果监听套件字的文件描述符有可读事件,则进行处理
if (client[0].revents & POLLRDNORM) {
     //有客户端连接;调用accept函数建立连接
     conn_fd = accept();

       //保存已建立连接套接字
       for (i = 1; i < MAX_OPEN; i++){
         if (client[i].fd < 0) {
           client[i].fd = conn_fd; //将已建立连接的文件描述符保存到client数组
           client[i].events = POLLRDNORM; //设置该文件描述符监听可读事件
           break;
          }
       }
       maxfd = i; 
}

//依次检查已连接套接字的文件描述符
for (i = 1; i < MAX_OPEN; i++) {
   if (client[i].revents & (POLLRDNORM | POLLERR)) {
       //有数据可读或发生错误,进行读数据处理或错误处理
    }
  }
}

Poll realiza el proceso de comunicación de la red como se muestra en la siguiente figura:
img_5.png

El mecanismo de sondeo resuelve la limitación de que un solo proceso de selección solo puede escuchar un máximo de 1024 sockets, pero no resuelve el problema del sondeo para obtener ready fd.

3.3 mecanismo de encuesta electrónica

epoll se propone en el kernel 2.6, usando la estructura epoll_event para registrar el fd a monitorear y el tipo de evento a monitorear.

Definición de estructura epoll_event y estructura epoll_data

typedef union epoll_data
{
    ...
    int fd;  //记录文件描述符
     ...
} epoll_data_t;


struct epoll_event
{
    uint32_t events;  //epoll监听的事件类型
    epoll_data_t data; //应用程序数据
};

La interfaz de epoll es relativamente simple y hay tres funciones en total:
int epoll_create(int size);
Crea un identificador de epoll, y size se usa para decirle al kernel cuántos monitores hay. La instancia de epoll mantiene internamente dos estructuras, que registran el fd a monitorear y el fd listo, y para los descriptores de archivo listos, se devolverán al programa de usuario para su procesamiento.
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
función de registro de eventos epoll, epoll_ctl agrega, modifica o elimina eventos interesados ​​en el objeto epoll, devuelve 0 si tiene éxito, de lo contrario devuelve –1. En este caso, debe juzgar el tipo de error según el código de error errno. Es diferente de select() en que le dice al núcleo qué tipo de evento debe escuchar cuando escucha el evento, pero primero registra el tipo de evento que debe escuchar aquí. El evento devuelto por el método epoll_wait debe agregarse a epoll a través de epoll_ctl.
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
Espere a que se genere el evento, similar a la llamada select(). El parámetro events se usa para obtener la colección de eventos del núcleo, maxevents es el tamaño de la colección de eventos y no es mayor que el tamaño de epoll_create(), el parámetro timeout es el tiempo de espera (milisegundos, 0 volverá inmediatamente, -1 será incierto, también hay dichos bloqueados permanentemente). La función devuelve el número de eventos que deben procesarse. Devolver 0 significa tiempo de espera y devolver -1 significa error. Debe verificar el código de error errno para determinar el tipo de error.
Acerca de los modos de trabajo ET y LT de epoll

epoll tiene dos modos de trabajo: modo LT (disparador de nivel) y modo ET (disparador de borde).

De manera predeterminada, epoll funciona en modo LT y puede manejar tanto sockets bloqueantes como no bloqueantes, mientras que EPOLLET en la tabla anterior indica que un evento se puede cambiar a modo ET. El modo ET es más eficiente que el modo LT y solo admite enchufes sin bloqueo.

La diferencia entre el modo ET y el modo LT
Cuando llega un nuevo evento, el evento se puede obtener de la llamada epoll_wait en el modo ET, pero si el búfer del socket correspondiente al evento no se ha procesado esta vez, en este socket Cuando no hay un nuevo evento vuelve, el evento no se puede obtener de la llamada epoll_wait nuevamente en modo ET; por el contrario, en modo LT, siempre que el búfer del socket correspondiente a un evento todavía tenga datos, siempre se puede recuperar de epoll_wait para obtener este evento . Por lo tanto, desarrollar aplicaciones basadas en epoll en modo LT es más simple y menos propenso a errores. Sin embargo, cuando ocurre un evento en modo ET, si los datos del búfer no se procesan por completo, la solicitud del usuario en el búfer se perderá. Sin respuesta.

¿Cuántos enchufes puede monitorear epoll?

Respuesta: personalizado, pero el sistema debe poder soportar

¿Cómo se prepara epoll fd?

Respuesta: La instancia de epoll mantiene dos estructuras internamente, que registran el fd que se monitoreará y el fd que está listo, y puede monitorear el fd que está listo

¿Cómo implementa epllo la comunicación en red?

Código

int sock_fd,conn_fd; //监听socket和已连接socket的变量
sock_fd = socket() //创建主动socket
bind(sock_fd)   //绑定socket
listen(sock_fd) //在socket进行监听,将socket转为监听socket

epfd = epoll_create(EPOLL_SIZE); //创建epoll实例,
//创建epoll_event结构体数组,保存socket对应文件描述符和监听事件类型    
ep_events = (epoll_event*)malloc(sizeof(epoll_event) * EPOLL_SIZE);

//创建epoll_event变量
struct epoll_event ee
//监听读事件
ee.events = EPOLLIN;
//监听的文件描述符是刚创建的监听socket
ee.data.fd = sock_fd;

//将监听socket加入到监听列表中    
epoll_ctl(epfd, EPOLL_CTL_ADD, sock_fd, &ee);

while (1) {
//等待返回已经就绪的描述符
n = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
//遍历所有就绪的描述符     
for (int i = 0; i < n; i++) {
      //如果是监听socket描述符就绪,表明有一个新客户端连接到来
     if (ep_events[i].data.fd == sock_fd) {
        conn_fd = accept(sock_fd); //调用accept()建立连接
        ee.events = EPOLLIN;  
        ee.data.fd = conn_fd;
        //添加对新创建的已连接socket描述符的监听,监听后续在已连接socket上的读事件      
        epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ee);

       } else { //如果是已连接socket描述符就绪,则可以读数据
           ...//读取数据并处理
       }
    }
}

El flujo de comunicación de red de epoll es el siguiente:
img_6.png

La diferencia entre los tres

mecanismo de multiplexación IO Límite máximo del descriptor de archivo de escucha Cómo encontrar descriptores de archivos listos
seleccionar 1024 Iterar a través de la colección de descriptores de archivo
encuesta personalizar Iterar a través de la colección de descriptores de archivo
encuesta personalizar epoll_wait devuelve descriptores de archivos listos

Realice el cuadro de comparación de la comunicación en red, para que todos puedan ver la diferencia.
img_7.png

El diagrama de control de la realización de la comunicación de red.

4. Uso del marco técnico de multiplexación IO

redis: los archivos ae_select.c y ae_epoll.c de Redis utilizan los mecanismos select y epoll respectivamente para realizar la multiplexación de E/S;

nginx: Nginx admite varios métodos de multiplexación de E/S en diferentes sistemas operativos, como epoll, select y kqueue; Nginx usa epoll a través del modo ET.

Reactor framework, netty: independientemente de C++ o Java, la mayoría de los frameworks de programación de red de alto rendimiento se basan en el modo Reactor, el más típico de los cuales es el framework Netty de Java, y el modo Reactor se basa en la multiplexación IO de;

La explicación de la multiplexación de IO ha terminado, porque el modelo de multiplexación de IO es muy útil para comprender marcos de trabajo de alto rendimiento como Redis y Nginx, por lo que le sugiero que consulte el código fuente e intente resolverlo. Si tiene alguna pregunta, también puede agregarme en WeChat: MrWeiki, bienvenido a discutir y progresar juntos.

Este artículo es un artículo original, indique la fuente al reimprimirlo.

Enlace a este artículo: http://www.yuanjava.cn/linux/2022/01/01/iomultiplexing.html

Este artículo proviene del blog de ape java.

por fin

Este artículo explica aproximadamente varios usos de lambda del sitio web oficial de Oracle, y algunos usos son razonables. En los siguientes artículos, analizaremos los principios de lambda uno por uno.

Enlace original: Análisis profundo del mecanismo de multiplexación IO

Supongo que te gusta

Origin blog.csdn.net/m0_54369189/article/details/126089798
Recomendado
Clasificación