Combate real del proyecto C ++: análisis detallado del servidor de alta concurrencia

El conjunto de funciones de socket en esta sección utiliza las previamente encapsuladas para el manejo de excepciones (opcional)

Enlace:  Proyecto C++ combate real - blog de programación de socket_干饭小白-blog de CSDN

Tabla de contenido

BIO modelo

modelo NIÑO

servidor concurrente multiproceso

servidor concurrente multiproceso 

modelo NIÑO 

Multiplexación de E/S (multiplexación de E/S)

seleccionar 

encuesta

El epoll más importante (tomado por separado)

Encuesta electrónica avanzada


BIO modelo

Bloqueo de espera: no ocupa valiosos segmentos de tiempo de la CPU, pero solo se puede procesar una operación a la vez

        

Modelo BIO: resuelva el defecto de que solo se puede procesar una operación a la vez a través de subprocesos múltiples/procesamiento múltiple. Pero el subproceso/proceso en sí necesita consumir recursos del sistema, y ​​la programación de subprocesos y procesos consume CPU.

Modelo BIO:
        1. Los hilos o procesos consumen recursos

         2. La programación de hilos o procesos consume CPU

modelo NIÑO

Sondeo ocupado sin bloqueo: recordatorios constantes o comprobación de si hay alguna operación cada dos veces

                              Mejora la eficiencia de ejecución del programa, pero consume muchos recursos de la CPU y del sistema

modelo NIÑO:

        

servidor concurrente multiproceso

Tenga en cuenta los siguientes puntos cuando utilice un servidor concurrente multiproceso:

1. El número máximo de descriptores de archivo en el proceso principal (el proceso principal debe cerrar el nuevo descriptor de archivo devuelto por aceptar)

2. El número de procesos creados por el sistema (relacionado con el tamaño de la memoria)

3. ¿La creación de demasiados procesos reducirá el rendimiento general del servidor (programación de procesos)?

Proceso principal: solía ser responsable de monitorear y asignar tareas a los procesos secundarios (el proceso secundario intercambia datos con el cliente)

Subproceso: intercambio de datos con el cliente

Reciclar proceso secundario: cuando finaliza cada subproceso secundario, el proceso principal aún puede aceptar (llamada lenta al sistema)

                      El proceso secundario necesita que el proceso principal se recicle. La señal SIGCHLD se enviará cuando finalice el proceso secundario. La acción de procesamiento predeterminada es ignorar, pero necesitamos capturar y procesar el proceso secundario a través de esta señal. 

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include "wrap.h"

#define MAXLINE     80              //最大的连接数
#define SERV_PORT   8080

int main(void)
{

   //创建socket
   int listenfd;
   listenfd = Socket(AF_INET,SOCK_STREAM,0);
   
   //bind 绑定端口和IP  sockfd
   struct sockaddr_in serveraddr;
   bzero(&serveraddr,sizeof(serveraddr));
   serveraddr.sin_port = htons(SERV_PORT);
   serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
   serveraddr.sin_family = AF_INET; 
   Bind(listenfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));

   //listen 设置监听的最大数量 
   listen(listenfd,20); 
   
   //accept 阻塞等待,连接
   int pid,n,i;
   struct sockaddr_in clientaddr;
   socklen_t clientlen;
   int connfd;
   char buf[MAXLINE];
   char str[INET_ADDRSTRLEN];   // INET --> IPV4   ADDR-->sockaddr  str  len 
   while(1)
   {
        clientlen = sizeof(clientaddr);
        //这个一定要放在while里面,因为多进程,可能连接的客户端不同,connfd是不同的
        connfd = Accept(listenfd,(struct sockaddr *)&clientaddr,&clientlen);

        pid = fork();
        if(pid == 0)    
        {
            //子进程读取数据和处理数据,不用做监听工作。监听工作交给父进程
            Close(listenfd);
            //读写数据,阻塞的(网络IO的数据准备就绪)
            while(1)
            {
                n = read(connfd,buf,MAXLINE);
                if(n == 0)  //说明有客户端关闭了,socket的对端关闭
                {
                    printf("the other side has been closed\n");
                    break;
                }
                //打印连接的客户端信息
                printf("received from %s at PORT %d\n",
                    inet_ntop(AF_INET,&clientaddr.sin_addr,str,sizeof(str)),ntohs(clientaddr.sin_port));
                //业务处理
                for(i = 0;i<n;++i)
                {
                    buf[i] = toupper(buf[i]);   //小写转大写
                }   
                write(connfd,buf,n);
            }
            //客户端关闭,关闭 connfd文件描述符
            Close(connfd);
            return 0;
        }
        else if(pid > 0)        //父线程不需要读写数据,fork之后,父子线程的文件描述符表是相同的
        {
            Close(connfd);
        }
        else        //出错了
        {
            perr_exit("fork");
        } 
   }

    Close(listenfd);
    return 0;

    return 0;
}
#include <stdio.h>
#include <netinet/in.h>
#include "wrap.h"
#include <string.h>
#include <unistd.h>

#define MAXLINE 80
#define SERV_PORT 8080
#define SERV_IP   "127.0.0.1"

int main(void)
{

    //创建socket
    int socketfd;   
    socketfd = Socket(AF_INET,SOCK_STREAM,0);
    
    //连接connect
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET,SERV_IP,&serveraddr.sin_addr);
    Connect(socketfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
    
    //读写数据
    char buf[MAXLINE];
    int  n;
    while(fgets(buf,MAXLINE,stdin) != NULL) //fgets从键盘键入数据
    {
        Write(socketfd,buf,sizeof(buf));
        n = Read(socketfd,buf,MAXLINE);         //一个socketfd操作读写两个缓冲区
        if(n == 0)  //对端已经关闭
        {
            printf("the other side has been closed..\n");
            break;
        }
        else
        {
            Write(STDOUT_FILENO,buf,n);     //向标准终端中写入数据
        }
    }

    Close(socketfd);
    return 0;

    return 0;
}
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <error.h>

void perr_exit(const char *s)
{
    perror(s);
    exit(1);
}
int  Accept(int fd,struct sockaddr *sa,socklen_t *salenptr)
{
    int n; 
    //accept:阻塞,是慢系统调用。可能会被信息中断
    again:
    if((n = accept(fd,sa,salenptr)) < 0)
    {
        if((errno == ECONNABORTED) || (errno == EINTR))
        {
            goto again;   //重启
        }
        else
        {
            perr_exit("accept error");
        }
    }
    return n;
}
int  Bind(int fd,const struct sockaddr *sa,socklen_t salen)
{
    int n;
    if((n = bind(fd,sa,salen)) < 0)
    {
        perr_exit("bind error");
    }
    return n;
}
int  Connect(int fd,const struct sockaddr *sa,socklen_t salen)
{
    int n;
    if((n = connect(fd,sa,salen)) < 0)
    {
        perr_exit("connect error");
    }
    return n;
}
int  Listen(int fd,int backlog)
{
    int n;
    if((n = listen(fd,backlog)) < 0)
    {
        perr_exit("listen error");
    }
    return n;
}
int  Socket(int family,int type,int protocol)
{
    int n;
    if((n = socket(family,type,protocol)) < 0)
    {
        perr_exit("socket error");
    }
    return n;
}
ssize_t Read(int fd,void *ptr,size_t nbytes)
{
    ssize_t n;
    again:
    if((n = read(fd,ptr,nbytes)) == -1)
    {
        if(errno == EINTR)//被中断
        {
            goto again;
        }
        else
        {
            return -1;
        }
    }
    return n;
}
ssize_t Write(int fd,const void *ptr,size_t nbytes)
{
    ssize_t n;
    again:
    if((n = write(fd,ptr,nbytes)) == -1)
    {
        if(errno == EINTR)
        {
            goto again;
        }
        else
        {
            return -1;
        }
    }
    return n;
}
int Close(int fd)
{
    int n;
    if((n = close(fd)) == -1)
    {
        perr_exit("close error");
    }
    return n;
}
ssize_t Readn(int fd,void *vptr,size_t n)
{
    size_t nleft;
    ssize_t nread;
    char *ptr;
    ptr = vptr;
    nleft = n;

    while(nleft > 0)
    {
        if((nleft = read(fd,ptr,nleft)) < 0)
        {
           if(errno == EINTR)
           {
                nread = 0;
           }
           else
           {
                return -1;
           }
        }
        else if(nread == 0)
        {
            break;
        }
        nleft -= nread;
        ptr += nread;
    }
    return n-nleft;

}
ssize_t Writen(int fd,const void *vptr,size_t n)
{
    size_t nleft;
    ssize_t nwritten;
    const char *ptr;
    ptr = vptr;
    nleft = n;

    while(nleft > 0)
    {
        if((nwritten = write(fd,ptr,nleft)) <= 0)
        {
            if(nwritten < 0 && errno == EINTR)
            {
                nwritten = 0;
            }
            else
            {
                return -1;
            }
        }
        nleft -= nwritten;
        ptr += nwritten;
    }
    return n;
}
static ssize_t my_read(int fd,char *ptr)
{
    static int read_cnt;
    static char *read_ptr;
    static char read_buf[100];

    if(read_cnt <= 0)
    {
        again:
            if((read_cnt = read(fd,read_buf,sizeof(read_buf))) < 0)
            {
                if(errno == EINTR)
                {
                    goto again;
                }
                return -1;
            }
            else if(read_cnt == 0)
            {
                return 0;
            }
            read_ptr = read_buf;
    }
    read_cnt--;
    *ptr = *read_ptr++;
    return 1;
}
ssize_t Readline(int fd,void *vptr,size_t maxlen)
{
    ssize_t n,rc;
    char c,*ptr;
    ptr = vptr;

    for(n=1;n<maxlen;n++)
    {
        if((rc = my_read(fd,&c)) == 1)
        {
            *ptr++ = c;
            if(c == '\n')
            {
                break;
            }
        }
        else if(rc == 0)
        {
            *ptr = 0;
            return n-1;
        }
        else
        {
            return -1;
        }
    }
    *ptr = 0;
    return n;
} 
#ifndef _WRAP_H_
#define _WRAP_H_

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

void perr_exit(const char *s);
int  Accept(int fd,struct sockaddr *sa,socklen_t *salenptr);
int  Bind(int fd,const struct sockaddr *sa,socklen_t salen);
int  Connect(int fd,const struct sockaddr *sa,socklen_t salen);
int  Listen(int fd,int backlog);
int  Socket(int family,int type,int protocol);
ssize_t Read(int fd,void *ptr,size_t nbytes);
ssize_t Write(int fd,const void *ptr,size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd,void *vptr,size_t n);
ssize_t Writen(int fd,const void *vptr,size_t n);
ssize_t my_read(int fd,char *ptr);
ssize_t Readline(int fd,void *vptr,size_t maxlen); 

#endif

servidor concurrente multiproceso 

Los siguientes problemas deben tenerse en cuenta al desarrollar un servidor utilizando el modelo de subprocesamiento:

        1. Ajuste el límite superior del descriptor de archivo máximo en el proceso

        2. Si los subprocesos tienen datos compartidos, se debe considerar la sincronización de subprocesos

        3. Salga del procesamiento cuando finalice el subproceso del cliente. (valor de salida, estado separado)

        4. La carga del sistema, a medida que aumenta la conexión del cliente, hace que otros subprocesos no lleguen a la CPU a tiempo

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <pthread.h>
#include <arpa/inet.h>
#include "wrap.h"


#define MAXLINE     80
#define SERV_PORT   8080

//一个连接对应一个客户端的信息
struct s_info
{
    struct sockaddr_in cliaddr;
    int    connfd;
};

//子线程处理的逻辑
void *do_work(void *arg)
{
    int n,i;
    //要进行业务处理的必要的信息  文件描述符  发送方的IP和动态端口
    struct s_info *ts = (struct s_info *)arg;
    //缓存
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN]; //存储点分十进制的 IP
    //设置线程分离
    pthread_detach(pthread_self());
    //业务处理
    while(1)
    {
        n = Read(ts->connfd,buf,MAXLINE);   //阻塞,阻塞状态不会消耗CPU
        if(n == 0)
        {
            printf("the other side has been closed.\n");
            break;
        }
    
    printf("recevied from %s at PORT %d\n",
                inet_ntop(AF_INET,&(*ts).cliaddr,str,sizeof(str)),ntohs((*ts).cliaddr.sin_port));

    //小写转大写
    for(i = 0;i < n; ++i)
    {
        buf[i] = toupper(buf[i]);
    }

    //传回给客户端
    Write(ts->connfd,buf,n);
    }
    
    Close(ts->connfd);

    return NULL;
}

int main(void)
{

    int i = 0;
    //创建套接字(监听)
    int listenfd;
    listenfd = Socket(AF_INET,SOCK_STREAM,0);
    //绑定
    struct sockaddr_in  servaddr;  //服务器端套接字
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);   //监听任何合理的IP
    Bind(listenfd,(struct servaddr *)&servaddr,sizeof(servaddr));
    //设置监听
    Listen(listenfd,20);
    //连接
    struct  s_info ts[256];     //最大的连接数 256 
    int connfd;
    struct sockaddr_in cliaddr;
    socklen_t cliaddr_len;
    pthread_t tid;
    while(1)
    {
        cliaddr_len = sizeof(cliaddr);
        connfd = Accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddr_len);
        ts[i].cliaddr = cliaddr;
        ts[i].connfd = connfd;
        //创建工作线程
        pthread_create(&tid,NULL,do_work,(void *)&ts[i]);
        i++;
        //为了安全起见
        if(i == 255)
        {
            break;
        }
    }

    return 0;
}
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "wrap.h"

#define MAXLINE     80
#define SERV_IP     "127.0.0.1"
#define SERV_PORT   8080

int main(void)
{
    
    //创建socket
    int sockfd;
    sockfd = Socket(AF_INET,SOCK_STREAM,0);
    //连接
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET,SERV_IP,&servaddr.sin_addr);
    Connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));

    //通信
    int n;
    char buf[MAXLINE];
    while(fgets(buf,MAXLINE,stdin) != NULL)
    {
        Write(sockfd,buf,sizeof(buf));
        n = Read(sockfd,buf,MAXLINE);
        if(n == 0)
        {
            printf("the other side has been closed\n");
        }
        else
        {
            Write(STDOUT_FILENO,buf,n);
        }
    }

    Close(sockfd);
    return 0;
}

modelo NIÑO 

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include "wrap.h"

#define MAXLINE     80
#define SERV_PORT   8080

int main(void)
{

    //创建socket
    int listenfd;
    listenfd = Socket(AF_INET,SOCK_STREAM,0);
    //将listenfd设置为非阻塞
    fcntl(listenfd,F_SETFD,fcntl(listenfd,F_GETFD,0) | O_NONBLOCK);
    //绑定
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);
    Bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
    //设置监听
    Listen(listenfd,20);
    //连接
    struct sockaddr_in cliaddr;
    socklen_t  cliaddr_len = sizeof(cliaddr);
    int connfd;
    char buf[MAXLINE];
    int n,i=0;
    while(1)
    {
        connfd = Accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len);
        n = Read(connfd,buf,MAXLINE);
        fcntl(connfd,F_SETFD,fcntl(connfd,F_GETFD,0) | O_NONBLOCK);
        if(n == -1)
        {
            if(errno == EAGAIN || errno == EWOULDBLOCK)
            {
                continue;   //再次启动read
            }
            //出错  Read会处理
        }
        else if(n == 0)
        {
            break;
        }
        else
        {
            for(i = 0;i<n;++i)
            {
                buf[i] = toupper(buf[i]);
            }
            Write(connfd,buf,n);
        }
    }

    Close(connfd);

    return 0;
}

El cliente es el mismo que los anteriores, así que no lo escribiré.

resumen:

        Ya sea multihilo o multiproceso arriba, se implementa en el modelo BIO, es decir, el método de bloqueo.Es fácil ver que el hilo principal (proceso principal) es responsable de monitorear, y el subhilo es responsable de leer y escribir datos y realizar el procesamiento lógico, a saber: Modelo de reactor

        La creación de un hilo o proceso consume recursos del sistema

        La programación (cambio) entre procesos (subprocesos) necesita consumir recursos del sistema

        La cancelación de hilos (threads) también consume recursos del sistema

       

        En comparación con el modelo NIO, NIO se implementa mediante sondeo y requiere mucha CPU  

        Ya sea el modelo NIO o el modelo BIO, solo se puede procesar una solicitud de conexión a la vez. ¿Hay alguna forma de monitorear varios descriptores de archivos al mismo tiempo? Veamos si se trata de monitorear el trabajo, la lectura y escritura de datos, o procesamiento de lógica de negocios. Lo realiza el proceso de usuario. De esta manera, la mayor parte del tiempo del proceso de usuario se utiliza para manejar el trabajo de monitoreo, lo cual no es rentable. ¿Podemos transferir el monitoreo al kernel? Estos y otros problemas pueden resolverse mediante la tecnología de multiplexación. La multiplexación incluye tres métodos: seleccionar, sondear y epoll. Vamos a explorarlos juntos.

Multiplexación de E/S (multiplexación de E/S)

        El servidor de transferencia de E/S multicanal también se denomina servidor de E/S multitarea. La idea principal de la implementación del servidor es que, en lugar de monitorear la conexión del cliente por la propia aplicación, el kernel monitorea el descriptor de archivo de la aplicación.

        La multiplexación de E/S permite que el programa monitoree varios descriptores de archivos al mismo tiempo, lo que puede mejorar el rendimiento del programa.El sistema requiere multiplexación de E/S en Linux que incluye: seleccionar, agrupar y epoll

        

        Da un ejemplo de la vida real para ayudar a entender. También podríamos pensar en el servidor como en nosotros mismos y en el núcleo como una estación de mensajería. Hay dos formas de recibir el mensajero. La primera es recibir el mensajero nosotros mismos, y la segunda es dejar que la estación de mensajería firme. para ello. Si tomamos el servicio de mensajería solos, debemos esperar al servicio de mensajería (bloqueando BIO en este momento) o llamar al servicio de mensajería cada 10 minutos (podemos limpiar el piso, cocinar y esperar a NIO dentro de los 10 minutos de cada recordatorio) . Si le pedimos a la estación de mensajería que firme en su nombre, podemos hacer otras cosas. Cuando llegue un mensajero, el personal de la estación de mensajería le notificará que tiene un mensajero. En este momento, puede optar por dejar que la estación de mensajería el personal lo entregará en su hogar (Asíncrono) Todavía puede continuar barriendo el piso, o puede ir directamente al sitio de mensajería para recogerlo (sincrónico). En este momento, no puede barrer el piso cuando va a recoger el mensajero.

seleccionar 

        Idea principal:

        1. Primero construya una lista de descriptores de archivos y agregue los descriptores de archivos a monitorear a la lista

        2. Llame a una función del sistema para monitorear los descriptores de archivo en la lista y la función no regresará hasta que uno o más de estos descriptores realicen operaciones de E/S.

                a. Esta función está bloqueando

                b. La operación de la función para detectar el descriptor del archivo la realiza el kernel

        3. Al regresar, le indicará al proceso cuántos descriptores realizar operaciones de E/S

       Análisis:
        1. La cantidad de descriptores de archivo que puede monitorear la selección está limitada por FD_SETSIZE, que generalmente es 1024. El simple hecho de cambiar la cantidad de descriptores de archivo abiertos por un proceso no puede cambiar la cantidad de archivos que monitorea la selección. [La cantidad de descriptores de archivo que se pueden abrir en el proceso predeterminado es 1024, el problema que quedó del historial: se puede resolver la recompilación del kernel de Linux]

        2. Es muy apropiado usar select al resolver clientes por debajo de 1024, pero si hay demasiados clientes conectados, select usa el modelo de sondeo (NIO), lo que reducirá en gran medida la eficiencia de respuesta del servidor [porque select no le dirá al proceso de aplicación, ya sea qué descriptor de archivo recibe datos, por lo que debe repetirse cada vez, por ejemplo, si hay 1000 conexiones de clientes, cada bucle necesita 1000 llamadas al sistema, lo que consume una gran cantidad de recursos]

        3. Hay mucho trabajo de copia en el proceso de trabajo

        4. Seleccione para comprender lo siguiente, no necesita pasar mucho tiempo aprendiendo, no es rentable

        Principio gráfico:

 API relacionadas

        int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timerval *timeval *timeout);

        nfds: el descriptor de archivo máximo en el conjunto de descriptores de archivo monitoreado se incrementa en 1, lo que le dice al kernel cuántos descriptores de archivo debe detectar

        readfds: supervisa la llegada de datos de lectura al conjunto de descriptores de archivos, parámetros entrantes y salientes ==> eventos legibles

        writefds: supervisa la llegada de datos de escritura al conjunto de descriptores de archivo, parámetros entrantes y salientes ==> eventos de escritura

        exceptfds: Supervise las ocurrencias anormales y alcance el conjunto de descriptores de archivos, parámetros entrantes y salientes ==> eventos anormales

        tiempo de espera: tiempo de monitoreo de bloqueo de tiempo

                      1. NULL espera eternamente hasta que se detecta un cambio en el descriptor del archivo

                      2. Configure timeval para esperar un tiempo fijo [- tv_sec> 0 tv_usec> 0, bloquee el tiempo correspondiente]

                      3. Establezca el timeval time en 0, regrese inmediatamente después de detectar el descriptor y sondee 

                                [- tv_sec = 0 tv_usec = 0, sin bloqueo]     

struct timeval {
    long tv_sec; /* seconds */
    long tv_usec; /* microseconds */
};

        void FD_CLR(int fd,fd_set *set); //Borrar fd en el descriptor de archivo establecido en 0

        void FD_ISSET(int fd,fd_set *set); //Comprobar si fd está establecido en 1 en el conjunto de descriptores de archivo

        void FD_SET(int fd,fd_set *set); //Establecer la posición fd en el descriptor de archivo establecido en 1

        void FD_ZERO(fd_set *set); //Establecer todos los 0 pendientes en el conjunto de descriptores de archivo

Algunas notas:

        Seleccionar devuelve el número total de condiciones que se cumplen en la colección monitoreada. A través de las cuatro funciones anteriores, puede juzgar los eventos que ocurren con miedo y cuál cumple con las condiciones. fd_set es un mecanismo de mapa de bits

        

Seleccione Desventajas:

        1. Cada vez que se llama a select, la colección fd debe copiarse del estado del usuario al estado del kernel. Esta sobrecarga será muy grande cuando haya muchos fds

        2. Al mismo tiempo, cada llamada a select necesita atravesar todos los fds pasados ​​en el kernel. Esta sobrecarga también es muy grande cuando hay muchos fds

        3. El número de descriptores de archivo admitidos por select es demasiado pequeño, el valor predeterminado es 1024

        4. La colección fds no se puede reutilizar y debe restablecerse cada vez

Ejemplo de código:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include "wrap.h"


#define MAXLINE     80
#define SERV_PORT   8080

int main(void)
{

    int i,n;
    //创建socket套接字
    int listenfd;
    listenfd = Socket(AF_INET,SOCK_STREAM,0);
    //绑定
    struct sockaddr_in servaddr;
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    Bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
    //设置
    Listen(listenfd,20);
    //连接前 ==> 先交给select
    int connfd,sockfd;
    fd_set allset,rset;         //allset保留原来的位,
    int nready;                 //select返回值,有事件发生的总数位图
    int maxfd = listenfd;                  //最大的文件描述符位置,告诉内核监听范围
    FD_ZERO(&allset);           //全部置为0
    FD_SET(listenfd,&allset);   //把监听描述符的位置 置1
    struct sockaddr_in cliaddr;
    socklen_t cliaddr_len;
    char str[INET_ADDRSTRLEN];   //保存客户端的 IP
    int  client[FD_SETSIZE];     //保存监听的位置   client[i] = 4  表示第4个文件描述符有事件发生
    int  maxi = -1;              //client 的最大下标
    char buf[MAXLINE];

    for(i = 0;i<FD_SETSIZE;++i)
    {
        client[i] = -1;         
    }

    while(1)
    {
        //监听读事件,阻塞。select最好不要设置为轮询的方式
        rset = allset;
        nready = select(maxfd+1,&rset,NULL,NULL,NULL); 

        if(nready < 0)      //失败,退出
        {
            // void perr_exit(const char *s)
            // {
            //     perror(s);
            //     exit(1);
            // }
            perr_exit("select error");
        }

        if(FD_ISSET(listenfd,&rset))    //如果为真,有新的连接到达  监听事件不放入client
        {
            //有新连接到达,我们需要把连接的客户端的信息拿到,后面要给它返回信息
            cliaddr_len = sizeof(cliaddr);
            //连接 accept,此时Accept不会发生阻塞等待,因为listenfd已经有事件发生
            connfd = Accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len);
            //打印连接的客户端信息
            printf("连接来自  %s 在  %d 端口\n",
                inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),ntohs(cliaddr.sin_port));
            //把客户端连接的文件描述符加入到监听队列中,监听该客户端是否有数据传来
            for(i = 0; i<FD_SETSIZE; ++i)   //通过 for循环总是能得到最小的空闲位置
            {
                if(client[i] < 0)
                {
                    client[i] = connfd;
                    break;
                }
            }


            //select能监听的文件个数达到上限 1024
            if(i == FD_SETSIZE)
            {
                fputs("select能监听的文件个数达到上限\n",stdout);   //向标准设备打印提示信息
                exit(1);
            }
            if(i > maxi)
            {
                maxi = i;
            }

            FD_SET(connfd,&allset);         //添加到监听信号集
            if(connfd > maxfd)
            {
                maxfd = connfd;             //maxfd做个迭代
            }

            if(--nready == 0)   //如果没有更多的就绪文件描述符,继续回到select阻塞监听
            {
                continue;
            }
        }

        for(i = 0;i <= maxi;++i)        //检测哪一个 clients有数据就绪
        {
            sockfd = client[i];
            if(client[i] < 0)          
            {
                continue;
            }
            if(FD_ISSET(sockfd,&rset))
            {
                if((n = Read(sockfd,buf,MAXLINE)) == 0) //与客户端关闭连接
                {
                    Close(sockfd);
                    FD_CLR(sockfd,&allset);       //解除select监听此文件描述符
                    client[i] = -1;
                }
                
                int j;
                for(j=0;j<n;++j)
                {
                    buf[j] = toupper(buf[j]);
                }
                Write(sockfd,buf,n);
                

                if(--nready == 0)
                {
                    break;
                }
            }   

        }
        
    }

    Close(listenfd);


    return 0;
}

El código de cliente es el mismo que el caso anterior


encuesta

        La encuesta solo es válida para Linux. El modelo de encuesta se basa en el límite máximo de descriptores de archivos de select. Es lo mismo que select, pero los tres descriptores de archivos basados ​​en bits (readfds/writefds/exceptfds) utilizados por select se encapsulan en un Luego use la forma de la matriz para superar el límite del descriptor de archivo máximo.

        #incluir <encuesta.h>

        int poll(struct pollfd *fds,nfds_t nfds,int timeout);

        parámetro:

                fds: la primera dirección de la matriz

                nfds_t: cuántos descriptores de archivo en la matriz de monitoreo deben monitorearse

                se acabó el tiempo: 

                                -1 espera de bloqueo

                                 0 regresa inmediatamente, no bloquea

                                >0 espere el número especificado de milisegundos, si la precisión de tiempo del sistema actual no es suficiente milisegundos, tome el valor

                encuesta de estructura

                {

                        int fd; //descriptor de archivo

                        eventos cortos; //Eventos monitoreados

                        short revents; //Eventos devueltos en el monitoreo de eventos que cumplen las condiciones

                }; 

                 - Valor de retorno: -1 : falla > 0(n)

                                 Correcto, n indica que se han detectado n descriptores de archivo en la colección que han cambiado

Si ya no supervisa un determinado descriptor de archivo, puede establecer pollfd, fd en -1, poll ya no supervisará este pollfd y establecer revents en 0 cuando regrese la próxima vez

Ejemplo de código:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
#include "wrap.h"

#define MAXLINE     80
#define SERV_PORT   8080
#define OPEN_MAX    1024        //监控的最多的数量


int main(void)
{

    int i,n,j;
    //创建socket
    int listenfd = Socket(AF_INET,SOCK_STREAM,0);
    //绑定
    struct sockaddr_in servaddr;
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);
    Bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
    //设置监听
    Listen(listenfd,20);
    //poll
    struct pollfd fds[OPEN_MAX];         //记录监听的文件描述符
    nfds_t maxi = -1;                          //最大的那个监听文件描述符
    int nready;

    //将设置为监听描述符
    fds[0].fd = listenfd;
    fds[0].events = POLLRDNORM;          //监听为普通事件
    maxi = 0;
    //client[i] = -1   表示文件描述符 i 不处于监听状态
    for(i=1;i<OPEN_MAX;++i)
    {
        fds[i].fd = -1;
    }

    //客户端信息
    struct sockaddr_in cliaddr;
    socklen_t  cliaddr_len;
    int connfd,sockfd;
    char str[INET_ADDRSTRLEN];
    char buf[MAXLINE];

    while(1)
    {
       
        nready = poll(fds,maxi+1,-1);    //-1表示阻塞等待
        if(fds[0].revents & POLLRDNORM)      // &位操作  有客户端连接请求
        {
            cliaddr_len = sizeof(cliaddr);
            connfd = Accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len);
            //打印客户端信息
            printf("连接来自 %s 在端口 %d\n",
                       inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)), ntohs(cliaddr.sin_port));
            //将 connfd 加入到监听数组
            for(i=1;i<OPEN_MAX;++i)
            {
                if(fds[i].fd < 0)
                {
                    fds[i].fd = connfd;
                    break;
                }
            }
            //判断监听事件是否超过最大限制
            if(i == OPEN_MAX)
            {
                perr_exit("too many clients\n");
            }

            fds[i].events = POLLRDNORM;
            if(i > maxi)
            {
                maxi = i;
            }
            if(--nready <= 0)
            {
                continue;
            }
        }   

        for(i = 1;i<= maxi;++i)
        {
            sockfd = fds[i].fd;
            if(fds[i].fd < 0)
            {
                continue;;
            }
            if(fds[i].revents & (POLLRDNORM|POLLERR))
            {
                if((n = Read(sockfd,buf,MAXLINE)) < 0)
                {
                    if(errno == ECONNRESET) //sockfd 不监听了
                    {
                        printf("fds[%d] aborted connection\n",i);
                        Close(sockfd);
                        fds[i].fd = -1;
                    }
                    else
                    {   
                        perr_exit("read error");
                    }
                }
                else if(n == 0)
                {
                    printf("fds[%d] closed connection\n",i);
                    Close(sockfd);
                    fds[i].fd = -1;
                }
                else
                {
                    for(j=0;j<n;++j)
                    {
                        buf[j] = toupper(buf[j]);
                    }
                    Writen(sockfd,buf,n);
                }
            
                if(--nready <= 0)
                {
                    break;
                }
            }
        }

    }

    return 0;
}

El epoll más importante (tomado por separado)

        epoll es una versión mejorada de la interfaz de E/S multiplexada select/poll bajo Linux. Puede mejorar significativamente la utilización de la CPU del sistema cuando solo hay unos pocos programas activos en una gran cantidad de conexiones simultáneas, porque multiplexará el conjunto de descriptores de archivos. para pasar Como resultado, los desarrolladores no tienen que volver a preparar el conjunto de descriptores de archivo para ser monitoreado cada vez antes de esperar un evento.Otro punto es que al obtener un evento, no es necesario recorrer todo el conjunto de descriptores para ser escuchado, siempre que atraviese los que utiliza el kernel IO El evento se activa de forma asincrónica y se une al conjunto de descriptores de la cola Listo.

        Actualmente, epoll es un primer modelo popular en los programas de red concurrentes a gran escala de Linux

        Además de proporcionar activación de nivel (LT) de eventos de E/S como selección/sondeo, epoll también proporciona activación de borde (ET). Esto hace posible que los programas de espacio de usuario almacenen en búfer el estado de E/S, reduzcan las llamadas a epoll_wait/epoll_pwait y mejoren la eficiencia de la aplicación.

        

Puede usar el comando cat para ver el límite superior de los descriptores de socket que puede abrir un proceso

gato /proc/sys/fs/file-max

También puede modificar el límite superior modificando el archivo de configuración

sudo usted /etc/security/limits.conf

Escriba la configuración al final del archivo, límite suave suave, límite duro duro

nofile suave 65536

duro nofile 100000

API básica

1. Cree un identificador de epoll, el tamaño del parámetro se usa para decirle al núcleo la cantidad de descriptores de archivos monitoreados, que está relacionado con el tamaño de la memoria

   #incluir <sys/epoll.h>

   int epoll_create(tamaño int); 

        - Parámetros: tamaño: actualmente sin sentido. Solo escribe un número, debe ser mayor que 0

        - Valor de retorno: -1: falla > 0: descriptor de archivo, instancia de epoll operativa

Cree una nueva instancia de epoll. Se crea un dato en el kernel. Hay dos datos importantes en estos datos. Uno es la información del descriptor de archivo (árbol rojo-negro) que necesita ser detectado, y el otro es la lista lista, que almacena los archivos que detectar el cambio de transmisión de datos Información del descriptor (lista doblemente enlazada)

2. Controle eventos en un descriptor de archivo monitoreado por epoll, registre, modifique, elimine

  int epoll_ctl(int dfp,int op,int fd,struct epoll_event *evento);

  dfp: manejador para epoll_create

  op: Indica una acción, representada por 3 macros

        EPOLL_CTL_ADD: registrar nuevo fd a dfpe

        EPOLL_CTL_MOD: Modificar el evento de escucha del fd registrado

        EPOLL_CTL_DEL: eliminar un fd de dfpe

  evento: Dígale al kernel los eventos que deben ser monitoreados

  estructura epoll_event{

          _uint32_t eventos; // Eventos de encuesta electrónica

          epoll_data datos; //variable de datos de usuario

  };            

  typedef union epoll_data {

        void *ptr; // función de devolución de llamada

        int fd;

        uint32_t u32;

        uint64_t u64;

} epoll_data_t;

Eventos comunes de detección de Epoll:

- EPOLLIN

- EPOLLOUT

- EPOLLERR

3. Espere a que se genere un evento en el descriptor de archivo monitoreado, similar a la llamada select()

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

- parámetros:

        - epfd: el descriptor de archivo correspondiente a la instancia de epoll

        - eventos: parámetro saliente, que guarda la información del descriptor de archivo que envió el cambio

        - maxevents: el tamaño de la segunda matriz de estructura de parámetros

        - tiempo de espera: tiempo de bloqueo

                0 : no bloquear -

                1: Bloquear hasta que se detecte un cambio en los datos fd y desbloquear

             > 0 : duración del bloqueo (milisegundos)

- valor de retorno:

        - Si tiene éxito, la cantidad de descriptores de archivo enviados para cambiar se devuelve> 0

        -fallo -1

Ejemplo de código:

#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include "wrap.h"

#define MAXLINE     80
#define SERV_PORT   8080
#define OPEN_MAX    1024


int main(void)
{

    int i,n,j,ret;
    //创建套接字
    int listenfd = Socket(AF_INET,SOCK_STREAM,0);
    //绑定
    struct sockaddr_in servaddr;
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    Bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
    //设置监听
    Listen(listenfd,20);
    //epoll
    int client[OPEN_MAX];
    int maxi = -1;

    for(i=0;i<OPEN_MAX;++i)
    {
        client[i] = -1;
    }
    maxi = -1;

    //创建一个 epoll 句柄
    int efd = epoll_create(OPEN_MAX);
    if(efd == -1)
    {
        perr_exit("epoll_create");
    }
    //设置连接
    int nready;
    struct epoll_event tep,ep[OPEN_MAX];
    tep.events = EPOLLIN;
    tep.data.fd = listenfd;
    ret = epoll_ctl(efd,EPOLL_CTL_ADD,listenfd,&tep);
    if(ret == -1)
    {
        perr_exit("epoll_ctl");
    }

    //客户端信息
    struct sockaddr_in cliaddr;
    socklen_t  cliaddr_len;
    char str[INET_ADDRSTRLEN];
    int connfd,sockfd;
    char buf[MAXLINE];

    while(1)
    {
        nready = epoll_wait(efd,ep,OPEN_MAX,-1);        //-1表示阻塞
        if(nready == -1)
        {
            perr_exit("epoll_wait");
        }

        for(i=0;i<nready;++i)
        {
            if(!ep[i].events & EPOLLIN)
            {
                continue;
            }
            if(ep[i].data.fd == listenfd)       //有新客户端连接
            {
                cliaddr_len = sizeof(cliaddr);
                connfd = Accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len);
                printf("连接来自 %s 在端口 %d\n",
                    inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),ntohs(cliaddr.sin_port));
                
                for(j=0;j<OPEN_MAX;++j)
                {
                    if(client[j] < 0)
                    {
                        client[j] = connfd;
                        break;
                    }
                }
                if(j == OPEN_MAX)
                {
                    perr_exit("too many clients");
                }
                if(j > maxi)
                {
                    maxi = j;
                }

                tep.events = EPOLLIN;
                tep.data.fd = connfd;
                ret = epoll_ctl(efd,EPOLL_CTL_ADD,connfd,&tep);
                if(ret == -1)
                {
                    perr_exit("epoll_ctl");
                }
                // if(--nready <= 0)
                // {
                //     continue;
                // }
            }
            else
            {
                sockfd = ep[i].data.fd;
                n = Read(sockfd,buf,MAXLINE);
                if(n == 0)
                {
                    for(j =0;j <= maxi;++j)
                    {
                        if(client[j] == sockfd)
                        {
                            client[j] = -1;
                            break;
                        }
                    }
                    ret = epoll_ctl(efd,EPOLL_CTL_DEL,sockfd,NULL);
                    if(ret == -1)
                    {
                        perr_exit("epoll_ctl");
                    }
                    Close(sockfd);
                    printf("cliend[%d] closed connect\n",j);
                }
                else
                {
                    for(j =0 ;j<n;++j)
                    {
                        buf[j] = toupper(buf[j]);
                    }
                    Writen(sockfd,buf,n);
                }
            }
        }
    }

    Close(listenfd);
    Close(efd);

    return 0;
}

Encuesta electrónica avanzada

modo de evento:

Hay dos modelos de eventos EPOLL ==>

        Activador de borde activado por borde (ET): dispara solo cuando llegan los datos, independientemente de si hay datos en el búfer

        Disparador de nivel disparado por nivel (LT): el disparador de nivel se disparará siempre que haya datos

caso:

 1. Suponga que hemos agregado un descriptor de archivo (RFD) que se usa para leer datos de la canalización al descriptor epoll

 2. El otro extremo de la canalización ha escrito 2 KB de datos

 3. Llame a epoll_wait y devolverá RFD, lo que indica que está listo para leer

 4. Leer 1 KB de datos

 5. Llame a epoll_wait...

En este proceso, hay dos modos de trabajo.

cliente ----> 1000B

epoll_wait(cfd);

read(500B)            ha leído 500B

Disparador de nivel: disparar epoll hasta leer

Edge trigger: no digas, no actives epoll, a menos que lleguen nuevos datos

modo ET

El modo ET es el modo de trabajo activado por borde

        Si usamos el indicador EPOLLET al agregar RFD al descriptor de epoll en el paso 1, habrá un bloqueo después de llamar a epoll_wait en el paso 5, porque los datos restantes aún existen en el búfer de entrada del archivo, y el remitente de datos todavía está esperando un mensaje de respuesta para los datos enviados. El modo de trabajo ET informará un evento solo cuando ocurra un evento en el identificador de archivo monitoreado. Por lo tanto, en el paso 5, la persona que llama puede dejar de esperar los datos restantes que aún existen en el búfer de entrada del archivo. Cuando epoll funciona en modo ET, se deben usar sockets sin bloqueo para evitar privar a la tarea de procesar múltiples descriptores de archivos debido a las operaciones de lectura y escritura bloqueadas de un identificador de archivo.

        1) Basado en el manejo de archivos sin bloqueo

        2) Suspender y esperar solo cuando la lectura o escritura devuelve EAGAIN (lectura sin bloqueo, sin datos temporalmente). Pero esto no significa que deba leer en un bucle cada vez que lea, y el procesamiento del evento no se considera completo hasta que se genera un EAGAIN.Cuando la longitud de los datos de lectura devueltos por read es menor que la longitud de datos solicitada, se puede determinar el buffer No hay datos en la zona, por lo que se puede considerar que el evento ha sido procesado

modo LT

        A diferencia del modo LT, cuando se llama a la interfaz epoll en el modo LT, es equivalente a un sondeo relativamente rápido, independientemente de si se utilizan o no los datos subsiguientes.

        LT: LT es el modo de trabajo predeterminado y admite enchufes con y sin bloqueo. En este enfoque, el kernel le dice si un descriptor de archivo está listo y luego puede acumular el fd listo para las operaciones de E/S. Si no hace nada, el núcleo seguirá notificándoselo, por lo que es menos probable que se produzcan errores de programación en este modo. La selección/encuesta tradicional son representantes de este modelo.

        ET: ET es un modo de trabajo de alta velocidad y solo admite enchufe sin bloqueo. En este modo, el kernel le informa a través de epoll cuando un descriptor pasa de estar nunca listo a estar listo. Luego asume que sabe que el descriptor de archivo está listo y no envía más notificaciones de listo para ese descriptor de archivo. Tenga en cuenta: si este fd no se ha operado en IO (para que ya no esté listo), el kernel no enviará más notificaciones.

 

Ejemplo de código:

        modo de disparo epoll ET basado en el modelo sin bloqueo de C/S de red

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>

#define MAXLINE     10
#define SERV_PORT   8080

int main(void)
{
    
    int res;
    //创建socket套接字
    int listenfd = socket(AF_INET,SOCK_STREAM,0);
    //绑定
    struct  sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);
    bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
    //设置监听
    listen(listenfd,20);
    //epoll + ET设置
    struct epoll_event event;
    struct epoll_event resevent[10];
    int efd = epoll_create(10);
    event.events = EPOLLIN | EPOLLET;   //ET边缘触发   默认是水平触发

    //保存客户端信息
    int connfd,len;
    char str[INET_ADDRSTRLEN];
    char buf[MAXLINE];
    struct sockaddr_in cliaddr;
    socklen_t cliaddr_len = sizeof(cliaddr);
    connfd = accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len);
    printf("连接的来自 %s 在端口 %d\n",
            inet_ntop(AF_INET,&servaddr.sin_addr,str,sizeof(str)),ntohs(cliaddr.sin_port));
    
    //设置connfd 非阻塞
    int flag = fcntl(connfd,F_GETFL);
    flag |= O_NONBLOCK;
    fcntl(connfd,F_SETFL,flag);
    event.data.fd = connfd;
    epoll_ctl(efd,EPOLL_CTL_ADD,connfd,&event);

    while(1)
    {
        printf("epoll_wait begin\n");
        res = epoll_wait(efd,resevent,10,-1);
        printf("epoll_wait end res %d\n",res);

        if(resevent[0].data.fd == connfd)
        {
            while((len = read(connfd,buf,MAXLINE/2)) > 0)
            {
                write(STDOUT_FILENO,buf,len);
            }
        }

    }

    return 0;
}
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"

#define MAXLINE     10
#define SERV_PORT   8080
#define SERV_IP     "127.0.0.1"

int main(void)
{

    int     i;
    char    ch = 'a';
    char    buf[MAXLINE];
    //创建套接字
    int sockfd;
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    //连接
    struct sockaddr_in servaddr;
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET,SERV_IP,&servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);   
    connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));

    while(1)
    {
        for(i=0;i<MAXLINE/2;++i)
        {
            buf[i] = ch;
        }
        buf[i-1] = '\n';
        ch++;

        for(;i<MAXLINE;++i)
        {
            buf[i] = ch;
        }
        buf[i-1] = '\n';
        ch++;

        write(sockfd,buf,sizeof(buf));
        sleep(10);
    }

    close(sockfd);

    return 0;
}

Supongo que te gusta

Origin blog.csdn.net/weixin_46120107/article/details/126559989
Recomendado
Clasificación