• Linux高性能服务器编程——ch6笔记


    第6章 高级I/O函数

    6.1 pipe函数

    用于创建一个管道,以实现进程间通信
    int pipe(int fd[2]);

    读端文件描述符fd[0]和写端文件描述符fd[1]构成管道的两端,默认是阻塞的,fd[0]读出数据,fd[1]写入数据。管道内部传输的数据是字节流。
    如果fd[1]的引用计数减少至0,即没有任何进程需要往管道中写入数据,则针对f[0]的read操作将返回0,即读取到了文件结束标记(EOF);反之,如果fd[0]计数减少至 0,即没有任何进程需要从管道读取数据,则针对fd[1]的write操作将失败,并引发SIGPIPE信号。
    socketpair函数:双向管道,但仅能在本地使用。

    6.2 dup函数和dup2函数

    用于复制文件描述符,但不继承原文件描述符的属性。
    int dup(int file_descriptor);
    int dup2(int file_descriptor_one, int file_descriptor_two);

    dup函数创建一个新的文件描述符(系统当前可用的最小整数),与原有file_descriptor指向相同文件、管道或者网络连接。
    dup2函数类似,但返回第一个不小于file_descriptor_two的整数。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char *argv[])
    {
        if (argc < 2)
        {
            printf("usage: %s ip_address port_number\n", basename(argv[0]));
            return 1;
        }
    
        const char *ip = argv[1];
        int port = atoi(argv[2]);
    
        struct sockaddr_in address;
        bzero(&address, sizeof(address));
        address.sin_family = AF_INET;
        inet_pton(AF_INET, ip, &address.sin_addr);
        address.sin_port = htons(port);
    
        int sock = socket(PF_INET, SOCK_STREAM, 0);
        assert(sock > 0);
    
        int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
        assert(ret != -1);
    
        ret = listen(sock, 5);
        assert(ret != -1);
    
        struct sockaddr_in client;
        socklen_t client_addrlength = sizeof(client);
        int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
        if (connfd < 0)
        {
            printf("errno is : %d\n", errno);
        }
        else
        {
            close(STDOUT_FILENO);  //关闭标准输出文件描述符,值为1
            dup(connfd);  //返回最小可用文件描述符,返回1
    // 服务器输出到标准输出的内容就会直接发送到与客户连接对应的 socket 上
    // 因此 printf 的输出将被客户端获得(而不是显示在服务器程序的终端上)
            printf("abcd\n");
            close(connfd);
        }
    
        close(sock);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    6.3 readv函数和writev函数

    ssize_t readv(int fd, const struct iovec* vector, int count);
    ssize_t writev(int fd, const struct iovec* vector, int count);

    readv函数将数据从文件描述符读到分散的内存块中,即分散读;writev函数则将多块分散的内存数据一并写入文件描述符中,即集中写。
    当Web服务器解析完一个HTTP请求之后,如果目标文档存在,且客户具有读取该文档的权限,那么它就需要发送一个HTTP应答来传输该文档。这个HTTP应答包含1个状态行、多个头部字段、1个空行和文档的内容。前3部分的内容可能被Web服务器放置在一块内存中,而文档的内容则通常被读到另外一块单独的内存中(通过read函数或mmap函数)。我们并不需要把这两部分内容拼接到一起再发送,而是可以使用writev函数将它们同时写出。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define BUFFER_SIZE 1024
    
    /* 定义两种HTTP状态吗和状态信息 */
    static const char *status_line[2] = {"200 OK", "500 Internal server error"};
    
    int main(int argc, char *argv[])
    {
        if (argc < 3)
        {
            printf("usage: %s ip_address port_number filename\n", basename(argv[0]));
            return 1;
        }
    
        const char *ip = argv[1];
        int port = atoi(argv[2]);
        /* 将目标文件作为程序的第三个参数传入 */
        const char *file_name = argv[3];
    
        struct sockaddr_in address;
        bzero(&address, sizeof(address));
        address.sin_family = AF_INET;
        inet_pton(AF_INET, ip, &address.sin_addr);
        address.sin_port = htons(port);
    
        int sock = socket(PF_INET, SOCK_STREAM, 0);
        assert(sock >= 0);
    
        int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
        assert(ret != -1);
    
        ret = listen(sock, 5);
        assert(ret != -1);
    
        struct sockaddr_in client;
        socklen_t client_addrlength = sizeof(client);
        int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
        if (connfd < 0)
        {
            printf("errno is : %d\n", errno);
        }
        else
        {
            /* 用于保存HTTP应答的状态行、头部字段和一个空行的缓冲区 */
            char header_buf[BUFFER_SIZE];
            memset(header_buf, '\0', BUFFER_SIZE);
            /* 用于存放目标文件内容的应用程序缓存 */
            char *file_buf = NULL;
            /* 用于获取目标文件的属性,比如是否为目录,文件大小等 */
            struct stat file_stat;
            /* 记录目标文件是否是有效文件 */
            bool valid = true;
            /* 缓冲区header_buf目前已经使用了多少字节的空间 */
            int len = 0;
            if (stat(file_name, &file_stat) < 0) /* 目标文件不存在 */
            {
                valid = false;
            }
            else
            {
                if (S_ISDIR(file_stat.st_mode)) /* 目标文件是一个目录 */
                {
                    valid = false;
                }
                else if (file_stat.st_mode & S_IROTH) /* 当前用户有读取目标文件的权限 */
                {
                    /* 动态分配缓存区file_buf, 并制定其大小为目标文件的大小
                     * file_stat.st_size 加1, 然后将目标文件读入缓存区file_buf中 */
                    int fd = open(file_name, O_RDONLY);
                    file_buf = new char [file_stat.st_size + 1];
                    memset(file_buf, '\0', file_stat.st_size + 1);
                    if (read(fd, file_buf, file_stat.st_size) < 0)
                    {
                        valid = false;
                    }
                }
                else
                {
                    valid = false;
                }
            }
    
            /* 如果目标文件有效,则发送正常的HTTP应答 */
            if (valid)
            {
               /* 下面这部分内容将HTTP应答的状态行、“Content-Length”头部字段和一个空行
                * 依次加入header_buf中 */
                ret = snprintf(header_buf, BUFFER_SIZE - 1, "%s %s\r\n",
                        "HTTP/1.1", status_line[0]);
                len += ret;
                ret = snprintf(header_buf + len, BUFFER_SIZE - 1 - len,
                        "Content-Length: %d\r\n", file_stat.st_size);
                len += ret;
                ret = snprintf(header_buf + len, BUFFER_SIZE - 1 - len,
                        "%s", "\r\n");
                /* 利用writev将header_buf和file_buf的内容一并写出 */
                struct iovec iv[2];
                iv[0].iov_base = header_buf;
                iv[0].iov_len = strlen(header_buf);
                iv[1].iov_base = file_buf;
                iv[1].iov_len = file_stat.st_size;
                ret = writev(connfd, iv, 2);
            }
            else /* 如果目标文件无效,则通知客户端服务器发生了"内部错误" */
            {
                ret = snprintf(header_buf, BUFFER_SIZE - 1, "%s %s\r\n",
                        "HTTP/1.1", status_line[1]);
                len += ret;
                ret = snprintf(header_buf + len, BUFFER_SIZE - 1 - len, "%s",
                        "\r\n");
                send(connfd, header_buf, strlen(header_buf), 0);
            }
    
            close(connfd);
            delete[] file_buf;
        }
    
        close(sock);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133

    省略了HTTP请求的接收及解析,该代码只关注HTTP应答的发送。

    6.4 sendfile函数

    在内核中操作,在两个文件描述符之间直接传递数据。避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这被称为零拷贝。
    ssize_t sendlile(int out_fd, int in_fd, off_t* offset, size_t count);

    in_fd(待读出)必须是一个支持类似mmap函数的文件描述符,即它必须指向真实的文件,不能是socket和管道;而out_fd(待写入)则必须是一个socket。由此可见,sendfile几乎是专门为在网络上传输文件而设计的。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char *argv[])
    {
        if (argc <= 3)
        {
            printf("usage: %s ip_address port_numberr filename\n", basename(argv[0]));
            return 1;
        }
    
        const char *ip = argv[1];
        int port = atoi(argv[2]);
        const char *file_name = argv[3];
    
        int filefd = open(file_name, O_RDONLY);
        assert(filefd > 0);
        struct stat stat_buf;
        fstat(filefd, &stat_buf);
    
        struct sockaddr_in address;
        bzero(&address, sizeof(address));
        address.sin_family = AF_INET;
        inet_pton(AF_INET, ip, &address.sin_addr);
        address.sin_port = htons(port);
    
        int sock = socket(PF_INET, SOCK_STREAM, 0);
        assert(sock > 0);
    
        int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
        assert(ret != -1);
    
        ret = listen(sock, 5);
        assert(ret != -1);
    
        struct sockaddr_in client;
        socklen_t client_addrlength = sizeof(client);
        int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
        if (connfd < 0)
        {
            printf("errno is : %d\n", errno);
        }
        else
        {
            sendfile(connfd, filefd, NULL, stat_buf.st_size);
            close(connfd);
            close(filefd);
        }
    
        close(sock);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    没有为目标文件分配任何用户空间的缓存,也没有执行读取文件的操作,但同样实现 了文件的发送。

    6.5 mmap函数和munmap函数

    mmap函数用于申请一段内存空间。可以将这段内存作为进程间通信的共享(也可为调用进程所私有)内存,
    也可以将文件内接映射到其中。munmap函数则释放由mmap创建的这段内存空间。

    void* mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
    int munmap(void *start, size_t length);

    6.6 splice函数

    用于在两个文件描述符之间移动数据,也是零拷贝。

    ssize_t splice(int fd_in, loff_t* off_in, int fd_out, loff_t* off_out, size_t len, unsigned int flags);

    fd_in和fd_out必须至少有一个是管道文件描述符。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char *argv[])
    {
        if (argc <= 2)
        {
            printf("usage: %s ip_address port_number\n", basename(argv[0]));
            return 1;
        }
    
        const char *ip = argv[1];
        int port = atoi(argv[2]);
    
        struct sockaddr_in address;
        bzero(&address, sizeof(address));
        address.sin_family = AF_INET;
        inet_pton(AF_INET, ip, &address.sin_addr);
        address.sin_port = htons(port);
    
        int sock = socket(PF_INET, SOCK_STREAM, 0);
        assert(sock >= 0);
    
        int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
        assert(ret != -1);
    
        ret = listen(sock, 5);
        assert(ret != -1);
    
        struct sockaddr_in client;
        socklen_t client_addrlength = sizeof(client);
        int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
        if (connfd < 0)
        {
            printf("errno is : %d\n", errno);
        }
        else
        {
            int pipefd[2];
    
            ret = pipe(pipefd); /* 创建管道 */
            assert(ret != -1);
            /* 将connfd上流入的客户数据定向到管道中 */
            ret = splice(connfd, NULL, pipefd[1], NULL, 32768,
                    SPLICE_F_MORE | SPLICE_F_MOVE);
            assert(ret != -1);
            /* 将管道的输出定向到connfd客户连接文件描述符 */
            ret = splice(pipefd[0], NULL, connfd, NULL, 32768,
                    SPLICE_F_MORE | SPLICE_F_MOVE);
            assert(ret != -1);
            close(connfd);
        }
    
        close(sock);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    通过splice函数将客户端的内容读入到pipefd[1]中,然后再使用splice函数从pipefd[0]中读出该内容到客户端,从而实现了简单高效的回射服务。整个过程未执行recv或send操作,因此也未涉及用户空间和内核空间之间的数据拷贝。

    6.7 tee函数

    在两个管道文件描述符之间复制数据,也是零拷贝操作。它不消耗数据,因此源文件描述符上的数据仍然可以用于后续的读操作。

    ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags);

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char *argv[])
    {
        if (argc != 2)
        {
            printf("usage: %s \n", basename(argv[0]));
            return 1;
        }
    
        int filefd = open(argv[1], O_CREAT | O_WRONLY | O_TRUNC, 0666);
        assert(filefd > 0);
    
        int pipefd_stdout[2];
        int ret = pipe(pipefd_stdout);
        assert(ret != -1);
    
        int pipefd_file[2];
        ret = pipe(pipefd_file);
        assert(ret != -1);
    
        /* 将标准输入内容输入管道pipefd_stdout */
        ret = splice(STDIN_FILENO, NULL, pipefd_stdout[1], NULL,
                32768, SPLICE_F_MORE | SPLICE_F_MOVE);
        assert(ret != -1);
    
        /* 将管道pipefd_stdout 的输出复制到管道pipefd_file的输入端 */
        ret = tee(pipefd_stdout[0], pipefd_file[1], 32768, SPLICE_F_NONBLOCK);
        assert(ret != -1);
        /* 将管道pipefd_file的输出定向到文件描述符filefd上,从而将标准输入的内容写入文件 */
        ret = splice(pipefd_file[0], NULL, filefd, NULL,
                32768, SPLICE_F_MORE | SPLICE_F_MOVE);
        assert(ret != -1);
        /* 将管道pipefd_stdout 的输出定向到标准输出,其内容和写入文件的内容完全一致 */
        ret = splice(pipefd_stdout[0], NULL, STDOUT_FILENO, NULL,
                32768, SPLICE_F_MORE | SPLICE_F_MOVE);
        assert(ret != -1);
    
        close(filefd);
        close(pipefd_stdout[0]);
        close(pipefd_stdout[1]);
        close(pipefd_file[0]);
        close(pipefd_file[1]);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    6.8 fcntl函数

    提供了对文件描述符的各种控制操作,另外一个常见的控制文件描述符属性和行为的系统调用是ioctl,而且ioctl比fcntl能够执行更多的控制。但是,对于控制文件描述符常用的属性和行为,fcntl函数是由POSIX规范指定的首选方法。
    网络编程中,fcntl函数通常用来将一个文件描述符设置为非阻塞的。
    SIGIO和SIGURG这两个信号与其他Linux信号不同,它们必须与某个文件描述符相关联方可使用:当被关联的文件描述符可读或可写时,系统将触发 SIGIO信号;当被关联的文件描述符(而且必须是一个socket)上有带外数据可读时,系统将触发 SIGURG信号。将信号和文件描述符关联的方法,就是使用fcntl函数为目标文件描述符指定宿主进程或进程组,那么被指定的宿主进程或进程组将捕获这两个信号。使用 SIGIO时,还需要利用fcntl设置其O_ASYNC标志(异步I/O标志,不过SIGIO信号模型并非真正意义上的异步I/O模型)。

  • 相关阅读:
    Nginx基础
    VMware云数据中心中常用的术语清单
    8.CF446C DZY Loves Fibonacci Numbers 线段树Lazy标记
    AERMOD模型在大气环境影响评价中的实践
    实操Java 诊断工具 Arthas
    el-table展开和合并都触发的事件方法
    linux centos9
    sql注入之高权限注入和文件读写
    【微服务】SpringCloud-Feign远程调用
    两名高管遭解雇,Twitter:只出不进
  • 原文地址:https://blog.csdn.net/qq_43680965/article/details/133978078