[TCP/IP] Usando tecnologia de multiplexação de E/S para implementar servidores simultâneos - epoll

Índice

Defeitos de seleção

função epoll

epoll_create

epoll_ctl

epoll_wait

Implementação de servidor Echo baseada em epoll


Defeitos de seleção

        Anteriormente, usamos a função select para completar a reutilização de E/S do lado do servidor echo, mas ainda há falhas no código, focando principalmente em:

  • Cada vez que você chama a função select, você precisa passar as informações do objeto de monitoramento para a função.
  • Depois de chamar select, projete uma instrução de loop em fd_set para monitorar todas as variáveis ​​do objeto

       Essas duas operações apresentam uma grande perda de desempenho. E deve-se notar que os soquetes são gerenciados pelo sistema operacional. Em cenários onde a função select precisa ser chamada com freqüência, a função select requer que o objeto de monitoramento (soquete) seja passado para ela, o que consumirá uma grande quantidade de tempo e espaço.

        No entanto, esse problema pode ser otimizado por meio de estratégias apropriadas - passando apenas uma vez o descritor do arquivo do objeto de monitoramento para o sistema operacional e notificando os eventos de interesse apenas quando o objeto de monitoramento for alterado.

        No Linux, o epoll pode suportar esta operação.

função epoll

*Kernels Linux anteriores à versão 2.5.44 não podem usar a função epoll

        Ao implementar o epoll, você precisa usar algumas outras funções e estruturas associadas:

  • epoll_create: Crie um espaço para salvar o descritor do arquivo epoll.
  • epoll_ctl: Registra e cancela o registro de descritores de arquivo com o espaço.
  • epoll_wait: semelhante à função select, aguarde a alteração do descritor do arquivo.

        Comparado ao uso da variável fd_set para armazenar o descritor do arquivo do objeto de monitoramento no método select, o epoll usa a função epoll_create para solicitar ao sistema operacional que salve o descritor do arquivo do objeto.

As funções macro FD_SET FD_CLR          precisam ser utilizadas no método select  , enquanto no método epoll, você só precisa  solicitar a conclusão do sistema operacional  por meio da função epoll_ctl  .

        Em termos de monitoramento de alterações nos descritores de arquivo, epoll chama a função epoll_wait para monitorar alterações nos descritores de arquivo e, ao mesmo tempo, usa o tipo de estrutura epoll_event para reunir os descritores de arquivo alterados.

        O tipo de estrutura epoll_event é definido da seguinte forma:

typedef union epoll_data
{
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;

struct epoll_event
{
  uint32_t events;	/* 欲监视的 Epoll 事件 */
  epoll_data_t data;	/* 用户数据变量 */
}

        Após declarar a estrutura epoll_event, passe-a para a função epoll_wait, e os descritores de arquivo alterados serão preenchidos na estrutura, sem a necessidade de usar um loop para verificar e monitorar todos os descritores de arquivo no intervalo.

epoll_create

        A função epoll_create é usada para criar uma instância epoll . Seu arquivo de cabeçalho de referência e estrutura de função são os seguintes:

#include <sys/epoll.h>

int epoll_create(int size);

/* 成功时返回 epoll 文件描述符,失败时返回-1。
   
   参数size 用来传递epoll实例的大小 */

        Deve-se observar que o valor do parâmetro size na função não controla o tamanho real da instância epoll.Este valor é responsável apenas por informar ao sistema operacional o valor recomendado para o tamanho da instância para referência do sistema operacional.

        A instância criada pela função epoll_create é essencialmente um soquete, portanto o descritor de arquivo também será retornado.Ao mesmo tempo, quando a operação no soquete for concluída, a função close deve ser chamada para fechá-lo.

expandir:

        As versões do kernel posteriores ao Linux 2.6.8 irão ignorar o significado do parâmetro size em epoll_create, ou seja, o parâmetro size não tem mais significado prático, e o sistema operacional ajustará o tamanho da instância epoll de acordo com a situação.

epoll_ctl

        Depois de criar uma instância epoll, você precisa registrar o descritor de arquivo do objeto de monitoramento por meio da função epoll_ctl .

        O arquivo de cabeçalho de referência e a estrutura da função epoll_ctl são os seguintes:

#include <sys/epoll.h>

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

//成功时返回 0 ,失败时返回 -1

/* 参数含义 */
/* 
   epfd: 用于注册监视对象epoll实例的文件描述符
   op: 用于指定监视对象的添加、删除或修改等操作
   fd: 需要注册的监视对象文件描述符
   event: 监视对象的时间类型
*/

        O parâmetro op possui uma macro projetada para representar operações de adição, exclusão ou modificação:

  • EPOLL_CTL_ADD: Registre o descritor de arquivo na instância epoll 
  • EPOLL_CTL_DEL: Exclua o descritor de arquivo na instância epoll 
  • EPOLL_CTL_MOD: Altera a ocorrência de eventos de interesse nos descritores de arquivos cadastrados

        Ao usar EPOLL_CTL_DEL , NULL deve ser passado para o quarto parâmetro (evento).

Exemplo:

//从实例 A 中删除文件描述符SOCK_1
epoll_ctl(A , EPOLL_CTL_DEL , SOCK_1, NULL);

//向实例 A 中注册文件描述符SOCK_1,并监视 EVENT中 EPOLLIN 事件的发生与否
struct epoll_event EVENT;
EVENT.events=EPOLLIN;
EVENT.data.fd=sockfd;
epoll_ctl(A , EPOLL_CTL_ADD , SOCK_1, &EVENT);

        No exemplo, EPOLLIN é um tipo de evento usado para representar “ uma situação onde os dados precisam ser lidos ”. Existem outros tipos de eventos, como segue:

  • EPOLLIN: Quando os dados precisam ser lidos.
  • EPOLLOUT: O buffer de saída está vazio e os dados podem ser enviados imediatamente.
  • EPOLLPRI: Situação em que são recebidos dados OOB.
  • EPOLLRDHUP: Situação desconectada ou semifechada (comumente usada no modo edge trigger).
  • EPOLLERR: Ocorreu uma condição de erro.
  • EPOLLET: Receba notificações de eventos de maneira acionada pela borda.
  • EPOLLONESHOT: Após a ocorrência de um evento, o descritor de arquivo correspondente não recebe mais notificações de eventos. (Você precisa passar EPOLL_CTL_MOD para o segundo parâmetro da função epoll_ctL e definir o evento novamente)

epoll_wait

        epoll_wait é usado para completar o monitoramento final do descritor de arquivo objeto .

        O arquivo de cabeçalho de referência e a estrutura da função epoll_wait são os seguintes:

#include <sys/epoll.h>

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

// 成功时返回发生事件的文件描述符数,失败时返回 -1。

/* 参数含义 */
/* 
   epfd: 表示事件发生监视范围的 epoll 实例 的文件描述符
   events: 保存发生事件的文件描述符集合的结构体地址值
   maxevents: 可以保存的最大事件数 (对应第二个参数events)
   timeout: 以 1/ 1000秒为单位的等待时间,传递 -1 时代表一直等待到事件的发生。
*/

        Deve-se observar que a variável events (segundo parâmetro) na função epoll_wait precisa usar malloc para abrir espaço.

por exemplo:

struct epoll_event * EVENTS;

//EPOLL_SIZE是宏常量,其值代表欲开辟的实例数。注意最后要对类型进行强转,因为malloc返回的是void*
EVENTS = (struct epoll_event *) malloc(sizeof(struct epoll_event) * EPOLL_SIZE); 

epoll_wait(SOCK, EVENTS, EPOLL_SIZE, -1)

        A seguir, vamos tentar usar o epoll para implementar o servidor de eco anterior

Implementação de servidor Echo baseada em epoll

Exemplo de código:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#define BUF_SIZE 1024
#define EPOLL_SIZE 5

void Sender_message(char *message)
{
    puts(message);
    exit(1);
}

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t adr_sz;
    int str_len, i;
    char buf[BUF_SIZE];

    struct epoll_event *ep_events;
    struct epoll_event event;
    int epfd, event_cnt;

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)
        Sender_message((char *)"sock creation error");

    memset(&serv_adr, 0, sizeof(serv_adr));

    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
        Sender_message((char *)"bind error");

    if (listen(serv_sock, 5) == -1)
        Sender_message((char *)"listen error");

    epfd = epoll_create(EPOLL_SIZE);
    ep_events = (struct epoll_event *)malloc(sizeof(struct epoll_event) * EPOLL_SIZE);

    event.events = EPOLLIN; //声明欲监视的事件为需要读取数据的情况
    event.data.fd = serv_sock; //将服务器端套接字保存至epoll_event结构体中
    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

    while (1)
    {
        event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
        if (event_cnt == -1)
        {
            puts((char *)"supervise error");
            break;
        }

        for (i = 0; i < event_cnt; i++)
        {
            if (ep_events[i].data.fd == serv_sock) //若找到的文件描述符是服务器端的
            {
                adr_sz = sizeof(clnt_adr);
                clnt_sock =
                    accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);
                event.events = EPOLLIN;
                event.data.fd = clnt_sock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
                printf((char *)"connected client: %d \n", clnt_sock);
            }
            else
            {
                str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
                if (str_len == 0) // 关闭请求
                {
                    epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
                    close(ep_events[i].data.fd);
                    printf((char *)"closed client: %d \n", ep_events[i].data.fd);
                }
                else
                {
                    write(ep_events[i].data.fd, buf, str_len); //写数据
                }
            }
        }
    }
    close(serv_sock);
    close(epfd);
    return 0;
}

resultado da operação:

 Ser verificado

Acho que você gosta

Origin blog.csdn.net/weixin_42839065/article/details/131427564
Recomendado
Clasificación