Programación de redes: 2. El principio y la implementación del reactor.

Programación de redes: el principio y la implementación del reactor.

1. ¿Qué es epoll?

epoll es una encuesta mejorada por el kernel de Linux para manejar una gran cantidad de descriptores de archivos (FD). Es una versión mejorada de la interfaz de E/S multiplexada select/poll bajo Linux. Puede mejorar significativamente el rendimiento de los programas que solo están activos en una gran cantidad de conexiones simultáneas La utilización de la CPU del sistema en las circunstancias.

2. Características de epoll

Al obtener un evento, no es necesario atravesar todo el conjunto de descriptores que se escuchan, solo atravesar el conjunto de descriptores que el evento de E/S del kernel activa de forma asincrónica y se agregan a la cola Listo.

De hecho, fue después de epoll que Linux comenzó a incendiarse.

3. ¿Cuáles son las interfaces de epoll?

  1. epoll_create(int)
    • Es equivalente a crear una caja Fengchao
  2. epoll_ctl(dfpe, OPERA, fd, ev)
    • Es equivalente a la comisión de gestión pagada por cada persona, y la alta y baja de gestores
    • El primero es para cada epoll, el segundo es para operaciones, el tercero es para cada fd y el cuarto es para cada evento.
  3. epoll_wait(epfd, eventos, evlength, timeout)
    • En comparación con el mensajero para recoger al mensajero, ¿con qué frecuencia pasa el tiempo de espera, una vez por la mañana y otra por la tarde?
    • events es la bolsa, y evlength es qué tan grande es la bolsa
    • El segundo es cuántos eventos se devuelven.
    • El tercero es la duración de los eventos.
    • El cuarto es el tiempo.

4. ejemplo de código de encuesta electrónica

#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<pthread.h>
#include<unistd.h>
#include<sys/epoll.h>
#include<string.h>

#define BUFFER_LENGTH 128
#define EVENTS_LENGTH 128

char rbuffer[BUFFER_LENGTH] = {
    
    0};
char wbuffer[BUFFER_LENGTH] = {
    
    0};

int main(){
    
    
    // fd创建的时候默认是阻塞的
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1) return -1;

    // listenfd绑定一个固定的地址
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(9999);

    if (-1 == bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr))){
    
    
        // 返回-1 失败
        return -2;
    }

    listen(listenfd, 10); // 相当于是迎宾的人

		// 传的参数只要大于0就可以了
    // 这个函数的参数一开始是设置就绪的最大数量的, 但其实用链表来串就绪集合就可以了
    int epfd = epoll_create(1); 
    struct epoll_event ev, events[EVENTS_LENGTH];
    // 为什么一开始要添加事件, 不应该是内核有事件通知我吗
    ev.events = EPOLLIN;
    ev.data.fd = listenfd;
    // 这个关注一个可读的事件,后面有事件了才会通知我们
    epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
    // printf("fd : %d \n", epfd);

    // 服务器 7 * 24 运行就是因为这个函数
    // 所有的循环都是这样操作的
    while(1){
    
     
        // 如果是-1,直到有事件才返回,没事件就会阻塞
        // 0代表立即返回, timeout时间是毫秒
        int nready = epoll_wait(epfd, events, EVENTS_LENGTH, 0);
        // nready 有数据的时候返回是大于0的数
        // printf("-----nready -> %d\n", nready);
        int i = 0;
        for(i = 0; i < nready; i++){
    
    
            int clientfd = events->data.fd;
            // 用accept处理
            if(listenfd == clientfd){
    
    
                struct sockaddr_in client;
                socklen_t len = sizeof(client);
                // 新增加的fd, 也加到可读的fd集合中
                int connfd = accept(listenfd, (struct sockaddr*)&client, &len);
                printf("accept: %d\n", connfd);
                ev.events = EPOLLIN;
                ev.data.fd = connfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
            }else if(events[i].events & EPOLLIN) {
    
     //clientfd
                // char rbuffer[BUFFER_LENGTH] = {0};
                int n = recv(clientfd, rbuffer, BUFFER_LENGTH, 0);
                if(n > 0){
    
    
                    rbuffer[n] = '\0';
                    printf("recv: %s, n: %d\n", rbuffer, n);
                    memcpy(wbuffer, rbuffer, BUFFER_LENGTH);
                    int j = 0;
                    for(j = 0; j < BUFFER_LENGTH; j++){
    
    
                        rbuffer[j] = 'A' + (j % 26);
                    }
                    // send
                    ev.events = EPOLLOUT;
                    ev.data.fd = clientfd;
                    epoll_ctl(epfd, EPOLL_CTL_MOD, clientfd, &ev);
                }
            }else if(events[i].events & EPOLLOUT){
    
    
                int sent = send(clientfd, wbuffer, BUFFER_LENGTH, 0);
                printf("sent: %d\n", sent);
            }
        }
    }
}

5. ¿Qué es el reactor?

El modo Reactor es un modo relativamente común para procesar E/S simultáneas. Se utiliza para E/S síncronas. La idea central es registrar todos los eventos de E/S para procesarlos en un multiplexor de E/S central y, al mismo tiempo, el hilo/proceso principal bloqueado en el multiplexor

Una vez que llega un evento de E/S o está listo (el descriptor de archivo o el socket se pueden leer o escribir), el multiplexor regresa y distribuye el evento de E/S correspondiente registrado con anterioridad al procesador correspondiente.

(Si varios fds comparten buffers, habrá un fenómeno de pegado de paquetes. Aquí es donde se crean los reactores, y los eventos correspondientes a cada fd deben ser propios, haciendo un aislamiento)

6. Ventajas del reactor

  1. Respuesta rápida, no es necesario bloquearlo con un solo tiempo de sincronización, aunque el propio Reactor sigue siendo síncrono
  2. La programación es relativamente simple, lo que puede evitar problemas complejos de sincronización y subprocesos múltiples en la mayor medida, y evitar la sobrecarga de conmutación de subprocesos/procesos múltiples.
  3. Escalabilidad, puede hacer un uso completo de los recursos de la CPU fácilmente al aumentar la cantidad de instancias de Reactor
  4. Reutilización, el marco del reactor en sí no tiene nada que ver con la lógica de procesamiento de eventos específicos y tiene una alta reutilización

7. Disparador de nivel y disparador de borde

activado por nivel

Mientras el búfer de lectura del kernel asociado con el descriptor de archivo no esté vacío y haya datos para leer, siempre enviará una señal legible para notificar. Cuando el búfer de escritura del kernel asociado con el descriptor de archivo no esté lleno (siempre que es espacio para escribir), siempre emitirá una notificación de señal de escritura

  1. epoll se activa horizontalmente por defecto
  2. Cuando los datos son pequeños, tiendo a LT y los leo todos a la vez.
  3. El disparador de nivel se puede usar para bloquear IO

Activado por flanco

Cuando el búfer del núcleo de lectura asociado con el descriptor de archivo cambia de vacío a no vacío , se envía una señal legible para notificar

Cuando el búfer del núcleo de escritura asociado con el descriptor de archivo cambia de completo a completo , se envía una señal de escritura para notificar

  1. Activar una vez, leer cíclicamente, hasta que no haya datos para leer
  2. los datos se almacenarán en el búfer
  3. Cuando se usa big data, ET en realidad no es diferente de LT
  4. El disparador de borde es un bucle para recibir, y no puede caer en el bloqueo de IO

Recomiendo un curso abierto y gratuito de Zero Sound Academy, personalmente creo que el profesor lo enseñó bien, así que me gustaría compartirlo con ustedes:

Linux, Nginx, ZeroMQ, MySQL, Redis, fastdfs, MongoDB, ZK, streaming media, CDN, P2P, K8S, Docker, TCP/IP, coroutines, DPDK y otro contenido técnico, aprenda ahora

Supongo que te gusta

Origin blog.csdn.net/weixin_44839362/article/details/128990675
Recomendado
Clasificación