"TCP/IP Network Programming" Reading Notes--I/O Multiplexing

Table of contents

1--Server based on I/O multiplexing

2--select() function

3--Echo server based on I/O multiplexing

4--Common options for send() and recv() functions

5--readv() and writev() functions


1--Server based on I/O multiplexing

        The multi-process server has the following disadvantages: when multiple clients initiate connection requests, multiple processes will be created to handle the client's requests respectively. Creating multiple processes often requires a huge cost;

        The server side of I/O multiplexing can reduce the number of processes. No matter how many clients are connected, there is only one process providing services;

2--select() function

        The select() function can gather multiple file descriptors together for unified monitoring. Monitoring file descriptors can be regarded as monitoring sockets ; when gathering multiple file descriptors, they need to be distinguished according to three situations: reception , transmission and exception ;

        select() performs monitoring operations through the fd_set array variable. When the bit (value) corresponding to the file descriptor (index) in fd_set is set to 1, it indicates that the file descriptor is the monitoring object;

// 对 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() 函数后,为防止陷入无限阻塞的状态,传递超时信息

        The select() function only returns when the monitored file descriptor changes. If there is no change, it will enter the blocking state. By specifying a timeout event, infinite blocking can be prevented. Even if the file descriptor does not change, when the timeout exceeds Specifying the event will return from the function, and the return value is 0;

        When the select() function is called, except for the changed file descriptor, all the bits with the original value of 1 will change to 0, so it can be seen that the file descriptor with the value still 1 has changed ;

// 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--Echo server based on I/O multiplexing

// 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--Common options for send() and recv() functions

#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 表示可选项信息

        Normally, we would set the optional parameters of send() and recv() to 0, but they actually have the following options:

① MSG_OOB means used to transmit out-of-band data (send, recv)

② MSG_PEEK means verifying whether there is received data in the input buffer (recv)

③ MSG_DONTROUTE means that the routing table is not referenced during data transmission and the destination (send) is found in the local network.

④ MSG_DONTWAIT means not blocking when calling the I/O function and is used to use non-blocking I/O (send, recv)

⑤ MSG_WAITALL means to prevent the function from returning until all requested bytes are received (recv)

        MSG_OOB is used to send emergency messages for out-of-band data. When the operating system receives the emergency message, it will generate a SIGURG signal and call the registered signal processing function;

// 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;
}

        Setting the MSG_PEEK option and the MSG_DONTWAIT option at the same time can verify whether there is received data in the input buffer. At the same time, because the MSG_PEEK option is set, when the recv function is called, the data in the input buffer will not be deleted even if the data in the input buffer is read (also That is to say, when reading next time, you can still read the data read last time);

// 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--readv() and writev() functions

        The readv() and writev() functions integrate the data before reading and sending it; that is, the writev() function can send the data scattered in multiple buffers together, and the readv() function can send the data from multiple buffers together. Each buffer is received separately;

#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; // 缓冲大小
}

Code example:

// 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;
}

Guess you like

Origin blog.csdn.net/weixin_43863869/article/details/132794581