Notas de lectura de "Programación de red TCP/IP" - Multiplexación de E/S

Tabla de contenido

1--Servidor basado en multiplexación de E/S

2 - función seleccionar ()

3--Servidor Echo basado en multiplexación de E/S

4--Opciones comunes para las funciones send() y recv()

5 - funciones readv() y writev()


1--Servidor basado en multiplexación de E/S

        El servidor multiproceso tiene las siguientes desventajas: cuando varios clientes inician solicitudes de conexión, se crearán múltiples procesos para manejar las solicitudes del cliente respectivamente.La creación de múltiples procesos a menudo requiere un costo enorme;

        El lado del servidor de la multiplexación de E/S puede reducir la cantidad de procesos: no importa cuántos clientes estén conectados, solo hay un proceso que proporciona servicios;

2 - función seleccionar ()

        La función select() puede reunir múltiples descriptores de archivos para un monitoreo unificado. Los descriptores de archivos de monitoreo pueden considerarse sockets de monitoreo ; al reunir varios descriptores de archivos, deben distinguirse según tres situaciones: recepción , transmisión y excepción ;

        select () realiza operaciones de monitoreo a través de la variable de matriz fd_set. Cuando el bit (valor) correspondiente al descriptor de archivo (índice) en fd_set se establece en 1, indica que el descriptor de archivo es el objeto de monitoreo;

// 对 fd_set 数组的常用操作
FD_ZERO(fd_set* fd_set); // 将 fd_set 变量的所有位初始化为0
FD_SET(int fd, fd_set* fdset); // 在参数 fdset 指向的变量中注册文件描述符fd的信息
FD_CLR(int fd, fd_set* fdset); // 从参数 fdset 指向的变量中消除文件描述符fd的信息
FD_ISSET(int fd, fd_set* fdset); // 若参数 fdset 指向的变量中包含文件描述符fd的信息,则返回true

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

int select(int maxfd, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout);
// 成功时返回大于 0 的值,值为发生事件的文件描述符数;失败时返回 -1;超时返回 0
// maxfd 表示监视对象文件描述符的数量
// readset 表示将所有关注“是否存在待读取数据”的文件描述符注册到fd_set型变量,并传递其地址值
// writeset 表示将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set型变量,并传递其地址值
// exceptset 表示将所有关注“是否发生异常”的文件描述符注册到fd_set型变量,并传递其地址值
// timeout 表示调用 select() 函数后,为防止陷入无限阻塞的状态,传递超时信息

        La función select() solo regresa cuando el descriptor del archivo monitoreado cambia. Si no hay cambios, entrará en el estado de bloqueo. Al especificar un evento de tiempo de espera, se puede evitar el bloqueo infinito. Incluso si el descriptor del archivo no cambia, cuando el el tiempo de espera excede la especificación del evento que regresará de la función y el valor de retorno es 0;

        Cuando se llama a la función select(), excepto el descriptor de archivo modificado, todos los bits con el valor original de 1 cambiarán a 0, por lo que se puede ver que el descriptor de archivo con el valor todavía 1 ha cambiado ;

// gcc select.c -o select
// ./select
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>

#define BUF_SIZE 30

int main(int argc, char* argv[]){
    fd_set reads, temps;
    int result, str_len;
    char buf[BUF_SIZE];
    struct timeval timeout;

    FD_ZERO(&reads); // 初始化fd_set变量
    FD_SET(0, &reads); // 将文件描述符 0 对应的位设置为1,表示监视标准输入(文件描述符0对应标准输入stdin)
    
    while(1){
        temps = reads; // 记录初始值,新循环时重新初始化为初始值
        timeout.tv_sec = 5; // 超时时间设置为 5s
        timeout.tv_usec = 0;
        result = select(1, &temps, 0, 0, &timeout); // 5s内监视是否有标准输入时间发生
        if(result == -1){
            puts("select() error!");
            break;
        }
        else if(result == 0){ // 返回值为0表示超时
            puts("Time-out!");
        }
        else{
            if(FD_ISSET(0, &temps)){ // 验证是否是标准输入发生了变化,打印标准输入的内容
                str_len = read(0, buf, BUF_SIZE);
                buf[str_len] = 0;
                printf("message from console: %s", buf);
            }
        }
    }
    return 0;
}

3--Servidor Echo basado en multiplexación de E/S

// gcc echo_selectserv.c -o echo_selectserv
// ./echo_selectserv 9190
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>

#define BUF_SIZE 100

void error_handling(char *buf){
    fputs(buf, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char* argv[]){
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    struct timeval timeout;
    fd_set reads, cpy_reads;

    socklen_t adr_sz;
    int fd_max, str_len, fd_num, i;
    char buf[BUF_SIZE];
    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");
    }

    FD_ZERO(&reads); // 初始化fd_set变量
    FD_SET(serv_sock, &reads); // 监视 serv_sock
    fd_max = serv_sock;

    while(1){
        cpy_reads = reads; // 记录初始值
        timeout.tv_sec = 5; // 设置超时时间
        timeout.tv_usec = 5000;

        if((fd_num = select(fd_max+1, &cpy_reads, 0, 0, &timeout)) == -1){
            break;
        }
        if(fd_num = 0) continue; // 判断是否是超时
        // 真的有事件发生,执行以下代码
        for(i = 0; i < fd_max + 1; i++){
            if(FD_ISSET(i, &cpy_reads)){ // 查找发生状态变化的文件描述符
                if(i == serv_sock){ // 服务器端socket有变化,执行受理连接请求
                    adr_sz = sizeof(clnt_adr);
                    clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
                    FD_SET(clnt_sock, &reads); // 将客户端socket注册到fd_set变量中
                    if(fd_max < clnt_sock){
                        fd_max = clnt_sock;
                    }
                    printf("connected client: %d \n", clnt_sock);
                }
                else{ // 不是服务器端socket发生变化,表明有要接收的数据
                    str_len = read(i, buf, BUF_SIZE);
                    if(str_len == 0){ // 接收的是 EOF,表明要断开连接
                        FD_CLR(i, &reads);
                        close(i);
                        printf("closed client: %d \n", i);
                    }
                    else{ // 接收的是真实数据
                        write(i, buf, str_len); // 将接收到的数据返回客户端,实现回声功能
                    }
                }
            }
        }
    }
    close(serv_sock);
    return 0;
}

4--Opciones comunes para las funciones send() y recv()

#include <sys/socket.h>
ssize_t send(int sockfd, const void* buf, size_t nbytes, int flags);
ssize_t recv(int sockfd, void* buf, size_t nbytes, int flags);
// flags 表示可选项信息

        Normalmente, estableceríamos los parámetros opcionales de send() y recv() en 0, pero en realidad tienen las siguientes opciones:

① MSG_OOB significa que se utiliza para transmitir datos fuera de banda (enviar, recibir)

② MSG_PEEK significa verificar si hay datos recibidos en el búfer de entrada (recv)

③ MSG_DONTROUTE significa que no se hace referencia a la tabla de enrutamiento durante la transmisión de datos y el destino (envío) se encuentra en la red local.

④ MSG_DONTWAIT significa no bloquear al llamar a la función de E / S y se usa para usar E / S sin bloqueo (enviar, recibir)

⑤ MSG_WAITALL significa evitar que la función regrese hasta que se reciban todos los bytes solicitados (recv)

        MSG_OOB se utiliza para enviar mensajes de emergencia para datos fuera de banda. Cuando el sistema operativo recibe el mensaje de emergencia, generará una señal SIGURG y llamará a la función de procesamiento de señales registradas;

// gcc oob_recv.c -o oob_recv
// ./oob_recv 9190

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>

#define BUF_SIZE 30
int acpt_sock;
int recv_sock;

void error_handling(char *message){
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

void urg_handler(int signo){
    int str_len;
    char buf[BUF_SIZE];
    str_len = recv(recv_sock, buf, sizeof(buf) - 1, MSG_OOB);
    buf[str_len] = 0;
    printf("Urgent message: %s \n", buf);
}

int main(int argc, char* argv[]){
    struct sockaddr_in recv_adr, serv_adr;
    int str_len, state;
    socklen_t serv_adr_sz;
    struct sigaction act;
    char buf[BUF_SIZE];

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

    act.sa_handler = urg_handler; //设置信号的处理函数
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    acpt_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&recv_adr, 0, sizeof(recv_adr));
    recv_adr.sin_family = AF_INET;
    recv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    recv_adr.sin_port = htons(atoi(argv[1]));

    if(bind(acpt_sock, (struct sockaddr*) &recv_adr, sizeof(recv_adr)) == -1){
        error_handling("bind() error"); 
    }
    listen(acpt_sock, 5); 

    serv_adr_sz = sizeof(serv_adr);
    recv_sock = accept(acpt_sock, (struct sockaddr*)&serv_adr, &serv_adr_sz);

    // getpid() 返回进程ID
    // recv_sock发生SIGUGR信号,需要有确定的处理进程(假设创建了多个进程)来调用信号处理函数
    // fcntl() 将 getpid() 返回的进程作为 SIGUGR 信号的处理进程
    fcntl(recv_sock, F_SETOWN, getpid());

    state = sigaction(SIGURG, &act, 0); // 发生SIGURG信号时,调用urg_handler()函数

    while((str_len = recv(recv_sock, buf, sizeof(buf)-1, 0)) != 0){
        if(str_len == -1){
            continue;
        }
        buf[str_len] = 0;
        puts(buf);
    }
    close(recv_sock);
    close(acpt_sock);
    return 0;
}

// gcc oob_send.c -o oob_send
// ./oob_send 127.0.0.1 9190

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

#define BUF_SIZE 30

void error_handling(char *message){
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char *argv[]){
    int sock;
    struct sockaddr_in recv_adr;
    if(argc != 3){
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&recv_adr, 0, sizeof(recv_adr));
    recv_adr.sin_family = AF_INET;
    recv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    recv_adr.sin_port = htons(atoi(argv[2]));

    if(connect(sock, (struct sockaddr*)&recv_adr, sizeof(recv_adr)) == -1){
        error_handling("connect() error!");
    }

    write(sock, "123", strlen("123")); 
    send(sock, "4", strlen("4"), MSG_OOB); // 紧急传输数据
    write(sock, "567", strlen("567"));
    send(sock, "890", strlen("890"), MSG_OOB); // 紧急传输数据
    close(sock);
    return 0;
}

        Configurar la opción MSG_PEEK y la opción MSG_DONTWAIT al mismo tiempo puede verificar si hay datos recibidos en el búfer de entrada. Al mismo tiempo, debido a que la opción MSG_PEEK está configurada, cuando se llama a la función recv, los datos en el búfer de entrada no se eliminará incluso si se leen los datos en el búfer de entrada (también es decir, la próxima vez que lea, aún puede leer los datos leídos la última vez);

// gcc peek_recv.c -o peek_recv
// ./peek_recv 9190

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

#define BUF_SIZE 30

void error_handling(char *message){
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char* argv[]){
    int acpt_sock, recv_sock;
    struct sockaddr_in acpt_adr, recv_adr;
    int str_len, state;
    socklen_t recv_adr_sz;
    char buf[BUF_SIZE];
    if(argc != 2){
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }

    acpt_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&acpt_adr, 0, sizeof(acpt_adr));
    acpt_adr.sin_family = AF_INET;
    acpt_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    acpt_adr.sin_port = htons(atoi(argv[1]));

    if(bind(acpt_sock, (struct sockaddr*) &acpt_adr, sizeof(acpt_adr)) == -1){
        error_handling("bind() error"); 
    }
    listen(acpt_sock, 5); 

    recv_adr_sz = sizeof(recv_adr);
    recv_sock = accept(acpt_sock, (struct sockaddr*)&recv_adr, &recv_adr_sz);

    while(1){
        // 设置 MSG_PEEK|MSG_DONTWAIT 选项,即使不存在待读取的数据,也不会进入阻塞状态
        // 假设存在待读取的数据,则读取且不删除输入缓冲的数据,因此下次仍可以读取
        str_len = recv(recv_sock, buf, sizeof(buf) - 1, MSG_PEEK|MSG_DONTWAIT);
        if(str_len > 0){
            break;
        }
    }
    buf[str_len] = 0;
    printf("Buffering %d bytes: %s \n", str_len, buf);
    // 读取上一次留在输入缓冲的数据
    str_len = recv(recv_sock, buf, sizeof(buf) - 1, 0);
    buf[str_len] = 0;
    printf("Read again: %s \n", buf);
    close(acpt_sock);
    close(recv_sock);
    return 0;
}
// gcc peek_send.c -o peek_send
// ./peek_send 127.0.0.1 9190

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

void error_handling(char *message){
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char *argv[]){
    int sock;
    struct sockaddr_in recv_adr;
    if(argc != 3){
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&recv_adr, 0, sizeof(recv_adr));
    recv_adr.sin_family = AF_INET;
    recv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    recv_adr.sin_port = htons(atoi(argv[2]));

    if(connect(sock, (struct sockaddr*)&recv_adr, sizeof(recv_adr)) == -1){
        error_handling("connect() error!");
    }

    write(sock, "123", strlen("123"));
    close(sock);
    return 0;
}

5 - funciones readv() y writev()

        Las funciones readv() y writev() integran los datos antes de leerlos y enviarlos; es decir, la función writev() puede enviar los datos dispersos en múltiples buffers juntos, y la función readv() puede enviar los datos de múltiples buffers juntos. Cada buffer se recibe por separado;

#include <sys/uio.h>

ssize_t writev(int filedes, const struct iovec* iov, int iovcnt);
// 成功时返回发送的字节数,失败时返回 -1
// filedes 表示文件描述符
// iov 表示 iovec 结构体数组的地址值
// iovcnt 表示向第二个参数传递的数组长度

ssize_t readv(int filedes, const struct iovec* iov, int iovcnt);
// 成功时返回接收的字节数,失败时返回 -1

// iovec结构体
struct iovec{
    void* iov_base; // 缓冲地址
    size_t iov_len; // 缓冲大小
}

Ejemplo de código:

// gcc writev.c -o write
// ./write

#include <stdio.h>
#include <sys/uio.h>

int main(int argc, char* argv[]){
    struct iovec vec[2];
    char buf1[] = "ABCDEFG";
    char buf2[] = "1234567";
    int str_len;

    vec[0].iov_base = buf1;
    vec[0].iov_len = 3; 
    vec[1].iov_base = buf2;
    vec[1].iov_len = 4;

    str_len = writev(1, vec, 2); // 向文件描述符1写数据,即向标准输出写数据
    puts("");
    printf("Write bytes: %d \n", str_len);
    return 0;
}

// gcc readv.c -o readv
// ./readv

#include <stdio.h>
#include <sys/uio.h>

#define BUF_SIZE 100

int main(int argc, char *argv[]){
    struct iovec vec[2];
    char buf1[BUF_SIZE] = {0,};
    char buf2[BUF_SIZE] = {0,};
    int str_len;

    vec[0].iov_base = buf1;
    vec[0].iov_len = 5; // 设置最多保存5个字节
    vec[1].iov_base = buf2;
    vec[1].iov_len = BUF_SIZE;

    str_len = readv(0, vec, 2); // 向标准输入(文件描述符0)读数据
    printf("Read bytes: %d \n", str_len);
    printf("First message: %s \n", buf1);
    printf("Second message: %s \n", buf2);
    return 0;
}

Supongo que te gusta

Origin blog.csdn.net/weixin_43863869/article/details/132794581
Recomendado
Clasificación