Linux高性能服务器编程|阅读笔记:第6章 - 高级I/O函数

news2024/11/16 13:25:35

目录

  • 简介
  • 6.1 pipe函数
  • 6.2 dup函数和dup2函数
  • 6.3 readv函数和writev函数
  • 6.4 sendfile函数
  • 6.5 mmap函数和munmap函数
  • 6.6 splice函数
  • 6.7 tee函数
  • 6.8 fcntl函数
  • 结语

在这里插入图片描述

简介

Hello!
非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出~
 
ଘ(੭ˊᵕˋ)੭
昵称:海轰
标签:程序猿|C++选手|学生
简介:因C语言结识编程,随后转入计算机专业,获得过国家奖学金,有幸在竞赛中拿过一些国奖、省奖…已保研
学习经验:扎实基础 + 多做笔记 + 多敲代码 + 多思考 + 学好英语!
 
唯有努力💪
 
本文仅记录自己感兴趣的内容

6.1 pipe函数

pipe函数用于创建一个管道,实现进程间通信

在这里插入图片描述
参数fd[2]是一个包含两个int型整数的数组指针

通过pipe函数创建的这两个文件描述符fd[0]和fd[1]分别构成管道的两端,往fd[1]写入的数据可以从fd[0]读出。

  • fd[0]只能从管道读出数据
  • fd[1]只能往管道写入数据

如果要实现双向的数据传输,就应该使用两个管道。

自Linux2.6.11内核起,管道容量的大小默认是65536字节,可以使用fcntl函数来进行修改。

socketpair函数,作用:可以方便地创建双向管道

在这里插入图片描述

这里domain只能使用UNIX本地域协议族AF_UNIX,也就是只能在本地使用这个双向管道

6.2 dup函数和dup2函数

背景:把标准输入重定向到一个文件或者把标准输出重定向到一个网络连接(比如CGI编程)

这时候就可以使用dup或dup2函数进行文件描述符的复制
在这里插入图片描述

dup函数创建一个新的文件描述符,该文件描述符和原有文件描述符file_descriptor指向相同的文件、管道或网络连接,并且dup返回的文件描述符总是取当前系统可用的最小整数值

dup2函数与dup类似,不过它返回第一个不小于file_descriptor_two的整数值

注意:通过dup或dup2创建的文件描述符并不继承原文件描述符的属性,比如close-on-exec和non-blocking等


小测试:利用dup函数实现一个基本的CGI服务器

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

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 );
        dup( connfd );
        printf( "abcd\n" );
        close( connfd );
    }

    close( sock );
    return 0;
}

实验解释:

在这里插入图片描述

这里有点不懂?为什么printf输出到了客户端了呢?

6.3 readv函数和writev函数

readv函数:将数据从文件描述符读到分散的内存块中,即分散读

writev函数:将多块分散的内存数据一并写入文件描述符中,即集中写

在这里插入图片描述


小实验

背景:

  1. 当Web服务器解析完一个HTTP请求之后,如果目标文档存在且客户具有读取该文档的权限,那么它就需要发送一个HTTP应答来传输该文档。
  2. 这个HTTP应答包含1个状态行、多个头部字段、1个空行和文档的内容。其中,前3部分的内容可能被Wb服务器放置在一块内存中,而文档的内容则通常被读入到另外一块单独的内存中(通过read函数或mmap函数)
  3. 我们并不需要把这两部分内容拼接到一起再发送,而是可以使用writev函数将它们同时写出,如下代码所示
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

#define BUFFER_SIZE 1024
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
    {
        char header_buf[ BUFFER_SIZE ];
        memset( header_buf, '\0', BUFFER_SIZE );
        char* file_buf;
        struct stat file_stat;
        bool valid = true;
        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 )
            {
                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;
            }
        }
        
        if( valid )
        {
            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" );
            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;
}

个人理解:在http响应中,一般含有响应头部和数据,我们可以先把这些数据使用iovec内存结构体管理,然后再直接使用writev一并写入即可(就可以不用分开多次写了)

6.4 sendfile函数

作用:在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,也被称为零拷贝

在这里插入图片描述
注:

  • in_fd必须是一个支持类似mmap函数的文件描述符,即它必须指向真实的文件,不能是socket和管道
  • out_fd则必须是一个socket

由此可见,sendfile几乎为在网络上传输文件而设计的


小实验:利用sendfile函数将服务端上的一个文件传送给客户端

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sendfile.h>

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];

    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( sock );
    return 0;
}

实验解释:在上面实验中,服务端没有为目标文件分配任何用户空间的缓存,也没有执行读取文件的操作,但同样实现了文件的发送,效率显然更高(正常步骤就是先开一块缓存,再读文件到这个缓存,最后再发送出去)

6.5 mmap函数和munmap函数

mmap函数:用于申请一段内存空间,我们可以将这段空间作为进程间通信的共享内存

munmap函数:释放由mmap创建的这段内存空间

在这里插入图片描述

6.6 splice函数

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

在这里插入图片描述

使用splice函数时,fd_in和fd_out必须至少有一个是管道文件描述符


小实验:使用splice函数实现一个零拷贝的回射服务器,将客户端发送的数据原样返回给客户端

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

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];
        assert( ret != -1 );
        ret = pipe( pipefd );
        ret = splice( connfd, NULL, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE ); 
        assert( ret != -1 );
        ret = splice( pipefd[0], NULL, connfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );
        assert( ret != -1 );
        close( connfd );
    }

    close( sock );
    return 0;
}

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

6.7 tee函数

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

在这里插入图片描述


小实验:同时输出数据到终端和文件

#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

int main( int argc, char* argv[] )
{
	if ( argc != 2 )
	{
		printf( "usage: %s <file>\n", 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 );

	//close( STDIN_FILENO );
	// dup2( pipefd_stdout[1], STDIN_FILENO );
	//write( pipefd_stdout[1], "abc\n", 4 );
	ret = splice( STDIN_FILENO, NULL, pipefd_stdout[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );
	assert( ret != -1 );
	ret = tee( pipefd_stdout[0], pipefd_file[1], 32768, SPLICE_F_NONBLOCK ); 
	assert( ret != -1 );
	ret = splice( pipefd_file[0], NULL, filefd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );
	assert( ret != -1 );
	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;
}

6.8 fcntl函数

作用:提供对文件描述符的各种控制操作

在这里插入图片描述

在网络编程中,fcntl函数通常用来将一个文件描述符设置为非阻塞的

在这里插入图片描述

结语

文章仅作为个人学习笔记记录,记录从0到1的一个过程

希望对您有一点点帮助,如有错误欢迎小伙伴指正

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/497980.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

10-Vue技术栈之脚手架配置代理(解决跨域问题)+ GitHub用户搜索案例

目录 1、基本使用1.1 方法一1.2 方法二 2、GitHub用户搜索案例 1、基本使用 1.1 方法一 ​ 在vue.config.js中添加如下配置&#xff1a; devServer:{proxy:"http://localhost:5000" }说明&#xff1a; 优点&#xff1a;配置简单&#xff0c;请求资源时直接发给前…

用三角函数解决几何问题

如图&#xff0c;在 △ A B C \triangle ABC △ABC 中&#xff0c; A C > 5 , A B > A C AC>5,AB>AC AC>5,AB>AC&#xff0c;点 E E E 是 A B AB AB 上一点&#xff0c;链接 C E CE CE&#xff0c;将 △ B C E \triangle BCE △BCE 沿 C E CE CE 折叠&…

【unity之数据持久化】-Unity公共类PlayerPrefs

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

FreeRTOS 内存管理

文章目录 一、FreeRTOS 内存管理简介二、 内存碎片三、heap_1 内存分配方法1. 分配方法简介2. 内存申请函数详解3. 内存释放函数详解 四、heap_2 内存分配方法1. 分配方法简介2. 内存块详解3. 内存堆初始化函数详解4. 内存块插入函数详解5. 内存申请函数详解6. 内存释放函数详解…

操作系统考试复习——第四章 4.3连续分配存储管理方式

在这里的开头需要讲述一下碎片&#xff0c;碎片分为内碎片和外碎片两种。 内碎片&#xff1a;分区之内未被利用的空间外碎片&#xff1a;分区之间难以利用的空闲分区&#xff08;通常是小空闲分区&#xff09;。 连续分配存储管理方式: 为了能将用户程序装入内存&#xff0c…

力扣刷题Day12_2

144.二叉树的前序遍历 测试代码main() class TreeNode:def __init__(self, valNone, leftNone, rightNone):self.val valself.left leftself.right rightfrom typing import Listclass Solution:def preorderTraversal(self, root: TreeNode) -> List[int]:s Solution…

C++易错编程练习题(1)

0 编程练习 基础不牢靠&#xff0c;回头来补课。小白这个系列主要是为了重新打基础&#xff0c;为一些常见的易错编程练习题提供记录。其间若有错误&#xff0c;欢迎指出&#xff0c;轻喷勿骂。毕竟小白确实是基础不牢靠。 1 题目 自定义函数之整数处理。 题目描述 输入10个…

多视图局部共现和全局一致性学习提高乳腺图像分类的综合性

文章目录 Multi-view Local Co-occurrence and Global Consistency Learning Improve Mammogram Classification Generalisation摘要本文方法global consistency modulelocal co-occurrence module (LCM) 实验结果 Multi-view Local Co-occurrence and Global Consistency Lear…

okio篇3-超时机制

关于System.nanoTime System.currentTimeMills与System.nanoTime实际都是时间间隔&#xff0c;只不过两个时间的起始时间衡量不一致。 我们比较常用的&#xff0c;实际是System.currentTimeMills()&#xff0c;这个时间是以1970-01-01起始&#xff0c;到系统显示时间的间隔。…

聚浪成潮,网易数帆CodeWave智能开发平台开启低代码新时代

‍数据智能产业创新服务媒体 ——聚焦数智 改变商业 随着全球范围内新一代人工智能技术发展突飞猛进&#xff0c;社会各领域从数字化、网络化向智能化转变&#xff0c;如何进一步释放数据生产力、加速智能化转型已成为企业发展的必修课。 2023年4月25日&#xff0c;“网易数帆…

K8S管理系统项目实战[API开发]-2

后端: gogin 后端代码地址GitHub - yunixiangfeng/k8s-platform: K8s管理系统后端: gogin 5、存储与配置 5.1 ConfigMap 5.2 Secret 5.3 PersistentVolumeClaims 6、工作流 6.1 流程设计 6.2 数据库操作&#xff08;GORM&#xff09; &#xff08;1&#xff09;初始化…

Mysql Sharding-JDBC读写分离 原理

0 课程视频 深入Sharding-JDBC分库分表从入门到精通【黑马程序员】_哔哩哔哩_bilibili 1 基本概念 1.1应用逻辑 1.1.1 msyql 多库 多表 多服务器 1.1.2 通过Sharding-JDBC jar包->增强JDBC 访问多数据源 -> 自动处理成一个数据源 1.1.3 使用数据的人 -> 使用Sh…

Java面试题总结 | Java面试题总结12- 测试模块

测试 测试需要具备的素质 基础的理论知识、编程语言的功底、自动化测试工具、计算机基础知识 业务分析能力&#xff1a;分析业务的流程&#xff0c;分析被测业务数据、分析被测系统的框架、分析业务模块、分析测试所需资源、分析测试完成目标 缺陷洞察能力&#xff1a;一般…

【ChatGPT】吴恩达教程笔记(预备篇)

本文概要 众所周知&#xff0c;吴恩达老师与OpenAI联合推出了一门面向开发者的Prompt课程&#xff08;https://www.deeplearning.ai/short-courses/chatgpt-prompt-engineering-for-developers &#xff09;&#xff0c;时隔几天&#xff0c;吴恩达老师发推说已经有超过20万人…

Netty基础

2.1Netty是什么 是一个基于异步的&#xff08;多线程处理结果和接收&#xff09;、事件驱动的网络应用框架&#xff0c;用于基于快速开发可维护、高性能的网络服务器和客户端 异步是指调用时的异步&#xff0c;他的IO还是多路复用的IO 许多中间件都依赖与Netty zookperhado…

GUITAR PRO8吉他软件好不好用?值不值得下载

所谓“工欲善其事&#xff0c;必先利其器”&#xff0c;想成为一名专业甚至著名的音乐人&#xff0c;用到的工具软件非常多&#xff0c;在众多款软件工具中&#xff0c;Guitar Pro 8能满足乐谱创作者、学习者的所有需要。很多人在听到Guitar Pro这个名词时&#xff0c;本能反应…

spark的RDD算子计算

一、环境配置 import osfrom pyspark import SparkConf, SparkContextif __name__ __main__:os.environ[SPARK_HOME] /export/server/sparkos.environ["PYSPARK_PYTHON"] "/root/anaconda3/envs/pyspark_env/bin/python"os.environ["PYSPARK_DRIV…

JavaWeb07(MVC应用01[家居商城]连接数据库)

目录 一.什么是MVC设计模式&#xff1f; 1.2 MVC设计模式有什么优点&#xff1f; 二.MVC运用&#xff08;家居商城&#xff09; 2.1 实现登录 2.2 绑定轮播【随机三个商品】 2.2.1 效果预览 index.jsp 2.3 绑定最新上架&热门家居 2.3.1 效果预览 2.3.2 代码实现 数据…

linux进程基本知识

1.什么是程序&#xff0c;什么是进程&#xff1f; 程序是静态的概念&#xff0c;例如 gcc xx.c -o pro 磁盘中生成pro文件&#xff0c;叫做程序 进程是程序的一次运行活动&#xff0c;意思是程序跑起来了&#xff0c;系统中就多了一个进程 2.如何查看系统中有哪些进程&…

EMC VNX登录Unisphere错误 certificate has invalid date问题处理

经常有用户反应说&#xff0c;突然用浏览器登录EMC VNX或者Clarrion CX系统的时候出现“certificate has invalid date”的故障&#xff0c;然后无法正常登录图形界面。具体报错如下图所示&#xff1a; 导致这个问题的原因在于VNX系统中的certification认证过期&#xff0c;既然…