[Desenvolvimento Linux – reutilização de E/S]

1. Fundo

Desvantagens dos servidores multiprocessos:

- 1,需要大量的运算
- 2,需要大量的内存空间

Insira a descrição da imagem aqui

Aplicação da tecnologia de reutilização no lado do servidor:

Insira a descrição da imagem aqui

2. Reutilização de E/S

  • O significado de reutilização:
    • 1. Melhorar a eficiência dos equipamentos físicos
    • 2. Use o menor número de elementos físicos para fornecer o máximo de dados

Para mais comparações entre select e epoll, eu pessoalmente li as postagens de outras pessoas. Você pode consultar a comparação entre select e epoll.

1.Selecione o modelo

0. Vantagens e Desvantagens:

  • Vantagens: Um único thread pode atender vários clientes.
  • deficiência:
    • 1. O número de clientes do serviço é limitado e não pode exceder 1.024.
    • 2. A aceitação precisa ser pesquisada e monitorada. Se o número de conexões for relativamente grande, consumirá muito desempenho.

1. Compreendendo o modelo selecionado:

Insira a descrição da imagem aqui

1. fd_set define o descritor de arquivo:

  • A função select monitora vários descritores de arquivo (não mais que 1024)
    Insira a descrição da imagem aqui
  • estrutura fd_set
  • FD_ZERO (fd set *fdset): inicializa todos os bits da variável fd set como 0.
  • FD_SET (int fd, fd set*fdset): Registra as informações do descritor de arquivo fd na variável apontada pelo parâmetro dset.
  • FD_CLR (int fd, fd set*fdset): Limpa as informações do descritor de arquivo fd da variável apontada pelo parâmetro fdset.
  • FD_ISSET (int fd, fd_set*fdset): Se a variável apontada pelo parâmetro fdset contém informações sobre o descritor de arquivo fd, ele retorna "true".

2. Chame a função select:

#include<sys/select.h>
#include <sys/time.h>
int select(int nfds, fd_set*readset, fd_set* writeset, fd_set*exceptset, const struct timeval * timeout);	
// →成功时返回大于0的值,失败时返回-1。
  • nfds : o número de descritores de arquivo de objeto de monitoramento.
  • readset : usado para verificar a legibilidade,
  • writeset : usado para verificar a capacidade de escrita,
  • exceptset : usado para verificar dados fora de banda,
  • timeout : um ponteiro para uma estrutura timeval, usada para determinar o tempo máximo de espera de seleção por E/S. Se estiver vazio, esperará para sempre .

2. Selecione a instância do modelo:

select foca no lado do servidor

void select_cs_connect(char* arg)
{
    
    
    if (strcmp(arg, "s") == 0)//如果输入s,走服务端路线
    {
    
    
        select_server();
    }
    else
    {
    
    
        fputs("Please input your name:", stdout);
        scanf("%s", name);
        select_client();
    }
}

1. Servidor

//select函数
#include<sys/select.h>
#include <sys/time.h>

void select_server()
{
    
    

    int serv_sock;
    struct sockaddr_in serv_adr;
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)
    {
    
    
        printf("create socket error:%d %s\n", errno, strerror(errno));
        return;
    }

    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(9527);

    if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
    {
    
    
        error_handling("thread server bind error");
        printf("bind error msg:%d %s\n", errno, strerror(errno));
        return;
    }

    if (listen(serv_sock, 5) == -1)
    {
    
    
        error_handling("thread server listen error");
        printf("listen error msg:%d %s\n", errno, strerror(errno));
        return;
    }

    printf("create server socket success!\n");

    int clnt_sock;
    struct sockaddr_in clnt_adr;
    socklen_t clnt_adr_sz = sizeof(clnt_adr);
    
    fd_set reads, copy_reads;
    FD_ZERO(&reads);
    FD_SET(serv_sock, &reads);
    timeval timeout = {
    
    5, 5000};//设置5.5秒超时,结构体中,第二个参数为微秒结构0.5秒 = 5000000
    int fd_max = serv_sock;
    
    while (1)
    {
    
    
        copy_reads = reads;
        int fd_num = select(fd_max + 1, &copy_reads, NULL, NULL, &timeout);//套接字最大再加1,范围全部覆盖
        if (fd_num == -1)
        {
    
    
            printf("select error msg:%d %s\n", errno, strerror(errno));

            close(serv_sock);
            return;
        }
        if (fd_num == 0) continue;

        printf("fd_num is:%d\n",fd_num);
		//轮询
        for (int i = 0; i < fd_max + 1; i++)
        {
    
    
            if (FD_ISSET(i, &copy_reads))
            {
    
    
                if (i == serv_sock)     // connection request!
                {
    
    
                    clnt_sock =
                        accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
                    FD_SET(clnt_sock, &reads);
                    if (fd_max < clnt_sock)
                        fd_max = clnt_sock;
                    printf("connected client: %d \n", clnt_sock);
                }
                else    // read message!
                {
    
    
                    char buf[256] = "";
                    ssize_t str_len = read(i, buf, BUF_SIZE);
                    if (str_len == 0)    // close request!
                    {
    
    
                        FD_CLR(i, &reads);
                        close(i);
                        printf("closed client: %d \n", i);
                    }
                    else
                    {
    
    
                        write(i, buf, str_len);    // echo!
                    }
                }
            }
        }
    }

    close(serv_sock);
}

2. Cliente

// 客户端 发送消息
char name[64] = "[MOON]";
void* client_send_msg(void* arg)
{
    
    
    pthread_detach(pthread_self());
    int clnt_sock = *(int*)arg;//取出当前线程的socket
    char msg[256] = "";
    char buffer[1024];
    while (1)
    {
    
    
        memset(buffer, 0, sizeof(buffer));
        fgets(msg, sizeof(msg), stdin);//对文件的标准输入流操作 读取buffer的256字节
        if (strcmp(msg, "q\n") == 0 || (strcmp(msg, "Q\n") == 0)) {
    
    
            break;
        }

        
        if (strcmp(msg, "") == 0)
        {
    
    
            continue;
        }

        snprintf(buffer, sizeof(buffer), "%s: %s", name, msg);
        size_t len = strlen(buffer);
        size_t send_len = 0;

        //当数据量很大时,并不能一次把所有数据全部发送完,因此需要分包发送
        while (send_len < len)
        {
    
    
            ssize_t ret = write(clnt_sock, buffer + send_len, len - send_len);//send_len 记录分包的标记
            if (ret <= 0) {
    
    //连接出了问题
                fputs("may be connect newwork failed,make client write failed!\n", stdout);
                break;
            }
            send_len += (size_t)ret;
        }       
    };

    sem_post(&semid);
    pthread_exit(NULL);
}
//客户端-接收消息
void* client_recv_msg(void* arg)
{
    
    
    pthread_detach(pthread_self());
    int clnt_sock = *(int*)arg;//取出当前线程的socket
    char buffer[1024] = "";
    while (1)
    {
    
    
        size_t ret = read(clnt_sock, buffer, sizeof(buffer));
        if (ret <= 0) {
    
    //连接出了问题
            fputs("client read failed!\n", stdout);
            break;
        }
        fputs(buffer, stdout);
        memset(buffer, 0, ret);//处理完消息及时重置内存
    };
    sem_post(&semid);
    pthread_exit(NULL);
}

void select_client()
{
    
    
    struct sockaddr_in clnt_adr;

    socklen_t clnt_adr_sz = sizeof(clnt_adr);

    int clnt_sock = socket(PF_INET, SOCK_STREAM, 0);

    memset(&clnt_adr, 0, clnt_adr_sz);
    clnt_adr.sin_family = AF_INET;
    clnt_adr.sin_addr.s_addr = inet_addr("127.0.0.1");
    clnt_adr.sin_port = htons(9527);
    if (connect(clnt_sock, (struct sockaddr*)&clnt_adr, clnt_adr_sz) == -1)
    {
    
    
        printf("connect error msg:%d %s\n", errno, strerror(errno));
        return;
    }

    pthread_t thread_send, thread_recv;

    sem_init(&semid, 0, -1);
    pthread_create(&thread_send, NULL, client_send_msg, &clnt_sock);//消息clnt_sock为局部变量
    pthread_create(&thread_recv, NULL, client_recv_msg, &clnt_sock);//消息clnt_sock为局部变量


    sem_wait(&semid);

    close(clnt_sock);
}

2. Modelo Epoll

  • Resolva as deficiências do modelo selecionado (o dispositivo de pesquisa selecionado consome desempenho e o problema do número limitado de dispositivos conectados)
  • Desvantagens do modelo Select:
    • A instrução de loop comum para todos os descritores de arquivo após chamar a função select . Se houver muitos usuários inativos, a eficiência da seleção será relativamente baixa. Se houver muitos usuários ativos, a eficiência da seleção será maior que a do epoll.
      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 a função select, os descritores de arquivo alterados não são reunidos individualmente, mas os descritores de arquivo alterados são encontrados observando as alterações na variável fd_set como o objeto de monitoramento, portanto, o loop para todos os objetos de monitoramento não pode ser evitado. Além disso, a variável fd_set como objeto de monitoramento será alterada, portanto, as informações originais devem ser copiadas e salvas antes de chamar a função select, e novas informações do objeto de monitoramento devem ser passadas sempre que a função select for chamada.
  • solução:
    • "Transfira o objeto de monitoramento para o sistema operacional uma vez. Se o escopo ou o conteúdo do monitoramento mudar, apenas os assuntos alterados serão notificados. "Dessa forma
      , não há necessidade de passar as informações do objeto de monitoramento para o sistema operacional toda vez que a seleção A função é chamada, mas a premissa é que o sistema operacional suporte essa forma de tratamento (quanto e como cada sistema operacional suporta é diferente). O método de suporte para Linux é epoll e o método de suporte para Windows é IOCP . epoll这个是Linux独有的函数.

1. Compreendendo o modelo Epoll

As três funções principais do Epoll: epoll_create, epoll_wait, epoll_ctl

1,epoll_create

#include<sys/epoll.h>
int epoll_create(int size);
//→成功时返回epoll文件描述符,失败时返回-1。
  • size : o tamanho da instância epoll.
    Esta função foi adicionada desde a versão 2.3.2, e a versão 2.6 introduziu o kernel. A
    última versão estável do kernel do Linux atingiu 5.8.14 e a versão de suporte de longo prazo atingiu 5.4.70.
    Linux a partir de 2.6 O kernel .8 irá ignorar este parâmetro, mas deve ser maior que 0.
    Esta é uma função exclusiva do Linux.

2,epoll_ctl

#include<sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
//→成功时返回0,失败时返回-1。
  • O descritor de arquivo usado pelo epfd para registrar a rotina epoll do objeto monitorado.
  • op é usado para especificar operações como adicionar, excluir ou alterar objetos de monitoramento.
    • Aumento de EPOLL_CTL_ADD
    • Modificação EPOLL_CTL_MOD
    • Exclusão de EPOLL_CTL_DEL
  • fd precisa registrar o descritor de arquivo do objeto de monitoramento.
  • O tipo de evento do objeto de monitoramento de eventos .
    • EPOLLIN : Quando os dados precisam ser lidos.
    • EPOLLOUT : O buffer de saída está vazio e os dados podem ser enviados imediatamente.
    • EPOLLPRI : Quando dados OOB são recebidos.
    • EPOLLRDHUP : Situação desconectada ou semifechada, muito útil no modo edge trigger.
    • EPOLLERR : Quando ocorre um 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. Portanto, você precisa passar EPOLLCTL_MOD para o segundo parâmetro da função epoll_ctl e definir o evento novamente.

3, epoll_wait:

#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event*events,int maxevents,int timeout);
//→成功时返回发生事件的文件描述符数,失败时返回-1。
  • epfd representa o descritor de arquivo da rotina epol na faixa de monitoramento de eventos.
  • events armazena o valor do endereço da estrutura da coleção de descritores de arquivo onde o evento ocorreu.
  • O número máximo de eventos que podem ser salvos no segundo parâmetro de maxevents .
  • Tempo limite : o tempo de espera em unidades de 1/1000 segundos. Quando -1 for ultrapassado, aguarde até que o evento ocorra.

2. Exemplo de modelo Epoll

1. Servidor

// epoll模型
#include <sys/epoll.h>
void epoll_server()
{
    
    
    int serv_sock;
    struct sockaddr_in serv_adr;
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)
    {
    
    
        printf("create socket error:%d %s\n", errno, strerror(errno));
        close(serv_sock);
        return;
    }

    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(9527);

    if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
    {
    
    
        error_handling("thread server bind error");
        printf("bind error msg:%d %s\n", errno, strerror(errno));
        close(serv_sock);
        return;
    }

    if (listen(serv_sock, 5) == -1)
    {
    
    
        error_handling("thread server listen error");
        printf("listen error msg:%d %s\n", errno, strerror(errno));
        close(serv_sock);
        return;
    }

    printf("create server socket success!\n");

    int clnt_sock;
    struct sockaddr_in clnt_adr;
    socklen_t clnt_adr_sz = sizeof(clnt_adr);

    epoll_event event;
    int event_cnt;
    int epfd = epoll_create(1);
    if (epfd == -1)
    {
    
    
        printf("epoll_create error msg:%d %s\n", errno, strerror(errno));
        close(serv_sock);
        return;
    }

    epoll_event* all_events = new epoll_event[100];

    event.events = EPOLLIN;
    event.data.fd = serv_sock;
    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);
    while (true)
    {
    
    
        event_cnt = epoll_wait(epfd, all_events, 100, 1000);//1000:等待1秒
        if (event_cnt == -1)
            printf("epoll_wait error msg:%d %s\n", errno, strerror(errno));
            break;

        if (event_cnt == 0)continue;
        for (int i = 0; i < event_cnt; i++)
        {
    
    
            int event_data_fd = all_events[i].data.fd;
            if (event_data_fd == serv_sock) {
    
    
                clnt_sock = accept(serv_sock, (sockaddr*)&clnt_adr, &clnt_adr_sz);
                event.events = EPOLLIN;
                event.data.fd = clnt_sock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
                printf("client is connect success! %d\n", clnt_sock);
            }
            else
            {
    
    
                char buf[256] = "";
                ssize_t str_len = read(event_data_fd, buf, sizeof(buf));
                if (str_len <= 0)    // close request!
                {
    
    
                    epoll_ctl(epfd, EPOLL_CTL_DEL, event_data_fd, NULL);
                    close(event_data_fd);
                    
                    printf("closed client: %d \n", i);
                }
                else
                {
    
    
                    write(event_data_fd, buf, str_len);    // echo!
                }
            }
        }

    }
    delete[] all_events;
    close(serv_sock);
    close(epfd);
}

void epoll_cs_connect(char* arg)
{
    
    
    if (strcmp(arg, "s") == 0)//如果输入s,走服务端路线
    {
    
    
        epoll_server();
    }
    else
    {
    
    
        fputs("Please input your name:", stdout);
        scanf("%s", name);
        select_client();
    }
}

2. Cliente

  • A lógica do código do cliente é igual ao código do cliente na instância do modelo selecionado.

3. Acionamento de borda e acionamento condicional

1. Definição

  • Acionamento condicional (acionado por nível, também conhecido como acionamento de nível) LT :
    Enquanto a condição for atendida, um evento será acionado (enquanto houver dados que não foram obtidos, o kernel continuará a notificá-lo)

  • ET acionado por borda (acionado por borda) :
    sempre que o estado muda, um evento é acionado.
    "Tomemos um exemplo de leitura de um soquete. Suponha que, após um longo período de silêncio, 100 bytes estejam chegando. Neste momento, independentemente de acionamento de borda ou acionamento condicional, uma notificação será gerada informando que o aplicativo está legível. O aplicativo lê 50 bytes e, em seguida, chama a API novamente para aguardar o evento io. Nesse momento, a API acionada horizontalmente retornará imediatamente
    uma leitura pronta notificação ao usuário porque ainda há 50 bytes para ler. .
    A API acionada por borda entrará em uma longa espera porque o status legível não mudou . Portanto, ao usar a API acionada por borda, tome cuidado para ler o soquete a cada tempo e retorne EWOULDBLOCK, caso contrário o soquete será considerado inútil. E ao usar uma API acionada condicionalmente, se o aplicativo não precisar gravar, não preste atenção ao evento gravável do soquete, caso contrário, uma notificação de gravação pronta será retornada imediatamente e infinitamente.

  • Select é um gatilho condicional típico .

  • Se os eventos forem definidos como EPOLLET no epoll, eles serão acionados por borda.

2. Caso de gatilho de borda epoll:

边缘触发:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#define BUF_SIZE 4
#define EPOLL_SIZE 50
void setnonblockingmode(int fd);
void error_handling(char *buf);

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;

	if(argc!=2) {
    
    
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
	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)
		error_handling("bind() error");
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");

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

	setnonblockingmode(serv_sock);
	event.events=EPOLLIN;
	event.data.fd=serv_sock;	
	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("epoll_wait() error");
			break;
		}

		puts("return epoll_wait");
		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);
				setnonblockingmode(clnt_sock);
				event.events=EPOLLIN|EPOLLET;
				event.data.fd=clnt_sock;
				epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
				printf("connected client: %d \n", clnt_sock);
			}
			else
			{
    
    
					while(1)
					{
    
    
						str_len=read(ep_events[i].data.fd, buf, BUF_SIZE);
						if(str_len==0)    // close request!
						{
    
    
							epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
							close(ep_events[i].data.fd);
							printf("closed client: %d \n", ep_events[i].data.fd);
							break;
						}
						else if(str_len<0)
						{
    
    
							if(errno==EAGAIN)
								break;
						}
						else
						{
    
    
							write(ep_events[i].data.fd, buf, str_len);    // echo!
						}
				}
			}
		}
	}
	close(serv_sock);
	close(epfd);
	return 0;
}

void setnonblockingmode(int fd)
{
    
    
	int flag=fcntl(fd, F_GETFL, 0);
	fcntl(fd, F_SETFL, flag|O_NONBLOCK);
}
void error_handling(char *buf)
{
    
    
	fputs(buf, stderr);
	fputc('\n', stderr);
	exit(1);
}

Acho que você gosta

Origin blog.csdn.net/MOON_YZM/article/details/131014381
Recomendado
Clasificación