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
servidor concurrente multiproceso
servidor concurrente multiproceso
Multiplexación de E/S (multiplexación de E/S)
El epoll más importante (tomado por separado)
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 recursos2. 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; }