C++ TinyWebServer项目总结(6. 高级 I/O 函数)

news2025/1/16 3:43:58

文件描述符

文件描述符(File Descriptor, FD)是操作系统中用于访问文件的一个抽象概念。它是一个非负整数,通常由操作系统分配,用来标识被打开的文件或输入输出资源(如管道、网络连接等)。文件描述符在操作系统和应用程序之间充当桥梁,允许程序通过文件描述符来读取、写入文件或进行其他I/O操作。

文件描述符的类型

文件描述符通常分为三类标准描述符:

  1. 标准输入(Standard Input,FD 0)
  • 默认情况下与键盘关联,通常用于从用户那里接收输入数据。
  1. 标准输出(Standard Output,FD 1)
  • 默认情况下与终端窗口关联,通常用于向用户显示输出数据。
  1. 标准错误(Standard Error,FD 2)
  • 默认情况下也与终端窗口关联,但通常用于显示错误消息或诊断信息。

文件描述符的使用

在UNIX和类UNIX操作系统中,文件描述符用于各种I/O操作,包括:

  • 打开文件open() 系统调用返回一个文件描述符,表示已打开的文件。
  • 读取文件read() 系统调用使用文件描述符从文件中读取数据。
  • 写入文件write() 系统调用使用文件描述符将数据写入文件。
  • 关闭文件close() 系统调用使用文件描述符关闭文件,以释放系统资源。

文件描述符不仅限于文件,还可以用于网络套接字(socket)、管道(pipe)、设备文件等各种输入输出资源。通过文件描述符,程序可以对这些资源进行抽象的统一操作。

socket 和文件描述符的关系

socket 和文件描述符之间有着密切的关系,特别是在 UNIX 和类 UNIX 操作系统中。简而言之,socket 是一种特殊类型的文件描述符,它用于网络通信。

Socket 与文件描述符的关系

  1. Socket 是文件描述符的一种
  • 在操作系统中,socket 被抽象为文件,这意味着每个 socket 都可以通过文件描述符进行标识和操作。文件描述符不仅用于文件,还可以用于其他 I/O 资源,如 socket、管道、设备文件等。
  1. Socket 的创建与文件描述符
  • 当你使用 socket() 系统调用创建一个 socket 时,操作系统会返回一个文件描述符,这个文件描述符代表了创建的 socket。例如:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);

这里,sockfd 就是一个文件描述符,后续的所有 socket 操作(如连接、发送、接收等)都将通过该文件描述符来进行。

  1. Socket 的操作类似于文件操作
  • 和普通文件一样,socket 的读写操作也是通过 read()write() 甚至是 send()recv() 等系统调用来完成的。你可以使用这些调用函数来向 socket 发送或接收数据。例如:
char buffer[1024];
int n = read(sockfd, buffer, sizeof(buffer));

在这个例子中,read() 函数通过 sockfd 文件描述符从 socket 中读取数据。

  1. Socket 的关闭
  • 当 socket 不再需要使用时,可以使用 close() 系统调用关闭它,就像关闭文件一样。关闭操作将释放与该文件描述符相关的所有资源:
close(sockfd);
  1. 重定向与 socket
  • 在某些高级应用中,可以通过重定向文件描述符来实现 socket 与其他文件描述符的交换。例如,将标准输入/输出重定向到一个 socket,从而通过网络连接来读写数据。

pipe 函数

用于创建管道,实现进程之间的通信。

#include <unistd.h>

//成功返回0,失败返回-1并设置errno
int pipe(int fd[2]);
  • fd[1]只能用于数据写入。
  • fd[0]只能用于数据读出。

socket 的基础 API 中有一个 socketpair 函数:

#include <sys/types.h>
#include <sys/socket.h>
//成功返回0,失败返回-1设置errno
int socketpair(int domain, int type, int protocol, int fd[2]);

domain只能使用UNIX本地协议族AF_UNIX,所以socketpair只能在本地使用,不过创建的这对文件描述符都是可读可写的。

dup函数和dup2函数

可以将标准输入重定向到一个文件,或者标准输出重定向到一个网络连接。

#include <unistd.h>
int dup(int file_descriptor);
int dup2(int file_descriptor_one, int file_descriptor_two);

以下程序使用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>
#include <libgen.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);

    // 监听套接字,最大等待连接队列的长度为5
    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 {
        // 先关闭标准输出文件描述符STDOUT_FILENO,其值为1
        close(STDOUT_FILENO);
        // 复制socket文件描述符connfd,由于dup函数总是返回系统中最小的可用文件描述符
        // 因此dup参数实际返回的是1,即之前关闭的标准输出文件描述符的值
        // 这样服务器输出到标准输出的内容会直接发送到与客户连接对应的socket上
        dup(connfd);
        printf("abcd\n");
        close(connfd);
    }

    close(sock);
    return 0;
}

代码首先关闭标准输出文件描述符STDOUT_FILENO,其值为1(由宏定义);

dup(connfd);复制客户端连接的文件描述符,并将其重定向为标准输出文件描述符1(即STDOUT_FILENO);

之后的printf("abcd\n");语句输出的内容将通过套接字发送给客户端(而非终端)。

char* a[3];char a[3];

char* a[3];char a[3]; 之间的主要区别在于:

1. 类型和结构的不同:

char a[3];:

  • 这是一个包含3个字符元素的字符数组。
  • 它占用3个字节的连续内存空间,每个元素都是一个char类型的字符。
  • 适用于存储一组字符,通常用于存储小的字符串或字符数据。

char* a[3];:

  • 这是一个包含3个元素的指针数组,每个元素都是指向char类型数据的指针。
  • 这意味着你可以在这个数组中存储3个字符指针,每个指针可以指向一个字符串或字符数组。
  • 适用于存储字符串的指针或一组字符数组的指针。

2. 内存布局的不同:

char a[3];:

  • 内存中会有3个连续的字节,每个字节存储一个字符。
  • 例如,a[0]a[1]a[2]都存储在相邻的内存地址中。

char* a[3];:

  • 内存中会有3个连续的指针,每个指针指向一个char类型的数据。
  • 这些指针本身占用空间(在32位系统中每个指针占用4个字节,在64位系统中每个指针占用8个字节),它们指向的内容可以是任意内存位置的字符或字符串。
  • 例如,a[0]a[1]a[2]存储的是指针,而不是字符本身。

3. 使用场景的不同:

char a[3];:

  • 适用于存储一小段字符数据,例如单个短字符串。
  • 例如,你可以用它来存储字符串 "Hi",并以 '\0' 结尾。

char* a[3];:

  • 适用于存储多个字符串的指针,通常用于二维字符数组或字符串数组的场景。
  • 例如:
char* a[3];
a[0] = "Hello";
a[1] = "World";
a[2] = "!";
  • 这里a[0]a[1]a[2]分别指向三个不同的字符串常量。

总结

char a[3]; 是一个存储字符的数组。

char* a[3]; 是一个存储字符指针的数组,通常用于存储字符串的指针。

readv函数和writev函数

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

#include <sys/uio.h>

//成功返回读写的字节数,失败返回-1并设置errno
ssize_t readv(int fd, const struct iovec* vector, int count); 	//分散读
ssize_t writev(int fd, const struct iovec* vector, int count); 	//集中写

结构体 iovec

struct iovec{
	void *iov_base;		//内存块起始地址
	size_t iov_len;		//内存块长度
};

sendfile函数

sendfile函数在两个文件描述符之间直接传递数据,完全在内核中操作,避免内核缓冲区和用户缓冲区之间的数据拷贝,效率高。这被称为零拷贝。

#include <sys/sendfile.h>

//成功返回传输的字节数,失败返回-1并设置errno
ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count);

参数:

  • in_fd:待读出内容的文件描述符
  • out_fd:待写入内容的文件描述符
  • offset:指定从读入文件流的哪个位置开始读,如果为空,则使用默认起始位置
  • count:指定in_fdout_fd之间传输的字节数。

mmap函数和munmap函数

mmap用于申请一段内存空间,这段内存可以用于进程间通信的共享内存,也可以直接将文件映射到其中,munmap用于释放这段空间。

#include <sys/mman.h>

//成功返回指向目标区域的指针,失败返回MAP_FAILED((void*) -1),并设置errno
void* mmap(void *start, size_t length, int port, int flags, int fd, off_t offset);
//成功返回0,失败返回-1并设置errno
int munmap(void *start, size_t length);

参数:

  • start :允许用户使用某一个特定的地址作为这段内存的起始地址,如果是设置为NULL,则系统自动分配。
  • length:指定这段内存的长度
  • port:设置内存段的访问权限。
    • PROT_READ,内存段可读。
    • PROT_WRITE,内存段可写。
    • PROT_EXEC,内存段可执行。
    • PROT_NONE,内存段不能被访问。
  • flags:控制内存段内容被修改后 程序的行为。
  • fd:是被映射文件的文件描述符,一般通过open系统调用获取
  • offset:设置从文件的何处开始映射。

splice函数

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

#include <fcntl.h>
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_outoff_out含义类似,用于输出数据流(数据写入)。
  • len:指定移动数据的长度。
  • flags:控制数据如何移动。

tee函数

tee函数在两个管道文件描述符之间复制数据,也是零拷贝操作,不消耗数据,而splice从管道中读取数据,也就是消耗数据。

#include <fcntl.h>
ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags);

参数:

  • fd_infd_out必须都是管道文件描述符

fcntl函数

fcntl函数,正如其名字(file control)描述的那样,提供了对文件描述符的各种控制,另一个常见的控制文件描述符属性和行为的系统调用是ioctl,且ioctl函数比fcntl函数能执行更多的控制,但控制文件描述符的常用属性和操作,fcntl函数是由 POSIX 规范指定的首选方法:

#include <fcntl.h>

//失败返回-1并设置errno
int fcntl(int fd, int cmd, ...);

参数:

  • fd:被操作的文件描述符
  • cmd:指定执行何种类型的操作
  • 由于操作类型的不同,可能需要第三个可选参数 arg

fcntl支持的常用操作:

在网络编程中,fcntl 函数常用于把文件描述符设置为非阻塞的:

int setnonblocking(int fd) {
    
	// 获取文件描述符状态标志
	int old_option = fcntl(fd, F_GETFL);
    
	// 设置非阻塞标志
	int new_option = old_option | O_NONBLOCK;
	fcntl(fd, F_SETFL, new_option);

    // 返回fd旧的状态标志,以便日后恢复该状态标志
	return old_option;
}

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

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

相关文章

苍穹外卖之员工管理、分类管理

新增员工 新增员工 需求分析和设计 产品原型&#xff1a; 接口设计&#xff1a; 数据库设计&#xff1a; 代码开发 根据新增员工接口设计对应的DTO&#xff1a; 这是实体类属性&#xff0c;但是由于属性差异过大&#xff0c;使用DTO封装数据为&#xff1a; 当前端提交的数据…

java常见面试题汇总

&#x1f30f;个人博客主页&#xff1a;意疏-CSDN博客 希望文章能够给到初学的你一些启发&#xff5e; 如果觉得文章对你有帮助的话&#xff0c;点赞 关注 收藏支持一下笔者吧&#xff5e; 阅读指南&#xff1a; 开篇说明一、封装 继承 多态1.封装2.继承3.多态 二、什么是重载…

非极大值抑制(Non-Maximum Suppression,简称 NMS)

本文为专栏《Python三维点云实战宝典》系列文章&#xff0c;专栏介绍地址“【python三维深度学习】python三维点云从基础到深度学习_python3d点云从基础到深度学习-CSDN博客”。配套书籍《人工智能点云处理及深度学习算法》提供更加全面和系统的解析。 非极大值抑制&#xff08…

揭开数字化转型的神秘面纱

​在当今快速发展的时代&#xff0c;数字化转型已成为众多企业和组织追求的目标。然而&#xff0c;对于许多人来说&#xff0c;数字化转型仍然笼罩着一层神秘的面纱。本研究报告旨在揭开数字化转型的神秘面纱&#xff0c;深入探讨其内涵、重要性以及实施策略。 一、数字化转型的…

开始尝试从0写一个项目--后端(四)

借出&#xff0c;归还&#xff0c;管理 学生和管理员登录分离 学生登录到用户界面 管理员到后台 后台和用户分离 添加代码 sems-server/src/main/java/com/ljc/controller/user/UserStudentController.java package com.ljc.controller.user;import com.ljc.constant.Jwt…

【PyCharm】配置“清华镜像”地址(pip源)

文章目录 前言一、清华镜像是什么&#xff1f;二、pip是什么&#xff1f;三、具体步骤1.复制镜像地址2.打开PyCharm&#xff0c;然后点击下图红框的选项3.在弹出的新窗口点击下图红框的选项进行添加4.在URL输入框中粘贴第一步复制的地址&#xff0c;名字可以不更改&#xff0c;…

leetcode371. 两整数之和,位运算

leetcode371. 两整数之和 给你两个整数 a 和 b &#xff0c;不使用 运算符 和 - ​​​​​​​&#xff0c;计算并返回两整数之和。 示例 1&#xff1a; 输入&#xff1a;a 1, b 2 输出&#xff1a;3 示例 2&#xff1a; 输入&#xff1a;a 2, b 3 输出&#xff1a;5 …

【Axure高保真原型】中继器表格——标签使用情况案例

今天和大家分享中继器表格——标签使用情况案例的原型模板&#xff0c;效果包括&#xff1a; 模糊搜索——输入标签编号或者标签名称&#xff0c;可以快速查找对应的数据 排序——点击排序按钮&#xff0c;可以按升序或降序排列 分页——点击上拉列表&#xff0c;可以选择表格…

gpu压力测试工具----gpu_burn的使用

背景&#xff1a; 我们平时想做gpu的压力测试&#xff0c;需要一个简单的可以一键执行的工具来测试&#xff0c;主要要来打满gpu的使用率和显存&#xff0c;这里以8卡4090机器为测试机&#xff0c;驱动版本550.54.14&#xff0c;cuda版本12.3。 开源方案&#xff1a; gpu-burn一…

oauth2.0学习

一、授权码模式 时序图 1、用户访问第三方服务&#xff0c;如果用到需要授权服务提供授权的资源&#xff0c;第三方服务生成获取授权码请求&#xff08;附带回调地址和用户在授权服务的确认身份的信息&#xff09;并页面发起请求 2、第三方授权服务根据请求生成对应的登录地址…

c++ 谷歌的招聘 题解

题目描述 2004 年 7 月,谷歌在硅谷的 101 号公路边竖立了一块巨大的广告牌(如下图)用于招聘 内容超级简单,就是一个以 .com 结尾的网址,而前面的网址是一个 1010 位素数,这个素数是自然常数 e 中最早出现的 10 位连续数字 能找出这个素数的人,就可以通过访问谷歌的这个网站进…

MySQL 亿级数据平滑迁移实战

作者&#xff1a;来自 vivo 互联网服务器团队- Li Gang 本文介绍了一次 MySQL 数据迁移的流程&#xff0c;通过方案选型、业务改造、双写迁移最终实现了亿级数据的迁移。 一、背景 预约业务是 vivo 游戏中心的重要业务之一。由于历史原因&#xff0c;预约业务数据表与其他业务…

springcloud集成seata实现分布式事务

Seata 是一款开源的分布式事务解决方案&#xff0c;致力于在微服务架构下提供高性能和简单易用的分布式事务服务。 官网&#xff1a;Apache Seata 文章目录 一、部署1.下载2.修改配置&#xff0c;nacos作注册中心&#xff0c;db存储 二、集成到springcloud项目1.引入依赖2.修改…

go设计模式——单例模式

概念 单例是一种创建型设计模式&#xff0c;它确保一个类在整个程序运行期间只有一个实例&#xff0c;并提供一个全局访问点来使用该实例。虽然单例模式在某些情况下非常有用&#xff0c;例如管理全局配置、日志记录或资源共享&#xff0c;但它也带来了与全局变量相似的问题。…

【CSS】什么是1px问题,前端如何去解决它,如何画出0.5px边框?

1px 问题概述 在移动端开发中&#xff0c;1px 的边框在高 DPI 屏幕上可能会显得过粗&#xff0c;这是因为移动设备的像素密度&#xff08;DPI&#xff09;通常比传统的计算机屏幕高。在高 DPI 屏幕上&#xff0c;1px 实际上可能会被渲染为 2px 或更多&#xff0c;这使得边框看…

华为手机换ip地址怎么换?手机换ip地址有什么影响

在数字化时代&#xff0c;网络已成为我们生活中不可或缺的一部分。无论是日常沟通、工作学习还是娱乐休闲&#xff0c;我们都离不开互联网。然而&#xff0c;随着网络安全问题的日益突出&#xff0c;如何保护个人隐私和信息安全成为了用户关注的焦点。更换手机IP地址作为提升网…

Vue3+Vite 解决“找不到模块“@/components/xxx.vue”或其相应的类型声明 ts(2307)”

1. 安装插件 pnpm i types/node -D2. 修改vite.config.ts文件 import path from path;resolve: {alias: {"": path.resolve(__dirname,"./src"),},},3. 修改tsconfig.app.json文件 别人教的都是修改tsconfig.json文件&#xff0c;但是我发现可能是因为版…

NVF04M录音芯片在宠物喂食器的应用:录音播放功能,内置SPI闪存

在现代社会中&#xff0c;宠物已经成为人们生活中的一部分&#xff0c;而宠物喂食器作为宠物养护的重要工具&#xff0c;也越来越受到人们的关注。为了满足人们对宠物喂食器的多样化需求&#xff0c;九芯电子供应商研发了一款NVF04M录音芯片。它在宠物喂食器中的作用主要是提供…

巧用PDF转Markdown插件,在扣子(Coze)手搓一个有趣好玩的AI Bot

近期&#xff0c;TextIn团队开发的PDF转Markdown插件已经上架Coze平台。 短短的时间内&#xff0c;已经有不少朋友愉快地和我们的工具开始玩耍。今天我们抛砖引玉&#xff0c;介&#xff08;an&#xff09;绍&#xff08;li&#xff09;几种PDF转Markdown插件的有趣玩法&#…

通用情商智商性格测试ACCESS\EXCEL数据库

今天这个数据库记录数不太多&#xff0c;是个可以进行智商和情商测试的数据&#xff0c;也可以体验比较有趣的测试体验&#xff0c;整个测试主要是以回答不同方面的问题来分析的。智商测试和情商测试均采用国际标准试题&#xff0c;采用国际标准测试题目&#xff0c;通过回答不…