Linux 高并发服务器实战 - 1 Linux系统编程入门

Linux 高并发服务器实战-1Linux系统编程入门

在本机和服务器端设置公共密钥(配置免密登录)

  • 在本机cmd里输入 ssh-keygen -t rsa,生成本机的公密钥
  • 在服务器端里也配置 ssh-keygen -t rsa
  • 进入服务器ssh目录创建authorized_key 把本机的公钥复制进去
  • (id_rsa 是私钥 id_rsa.pub是公钥)

GCC

原名为GUN C Compiler -> GUN C语言 编译器

后面更名为 GUN Compiler Collection -> Gun 编译套件

安装GCC和g++:

sudo apt install gcc g++

查看版本:

gcc/g++ -v/--version

清空linux命令行:Ctrl + L

在这里插入图片描述

在这里插入图片描述

选项-o用于指定要生成的结果文件,后面跟的就是结果文件名字

o是output的意思,不是目标的意思。

结果文件可能是预处理文件、汇编文件、目标文件或者最终可执行文件

不用-o的话,一般会在当前文件夹下生成默认的a.out文件作为可执行程序。

  • -E 预处理

  • -S 编译

  • -c 编译 + 汇编 只是有了目标代码obj文件 但是不进行链接

  • 如果直接 -s test.s 就是编译和链接都做 生成可执行程序

  • -I directory 指定include 搜索的目录

  • -g 在编译的时候,生成调试信息

  • -D 在程序编译的时候,指定一个宏

  • -Wall 生成警告信息

Tips:

  • 后缀为.c 时,gcc认为是c程序,g++认为是c++程序
  • 后缀为.cpp时,两者都认为是c++程序
  • g++调用时,底层其实也调用了gcc来编译、最后还得需要用g++来链接

静态库

静态库和动态库的区别:

静态库是在程序链接的阶段被复制到了程序中

动态库是在程序运行时由系统动态加载到了内存中使用

静态库命名规则:

Linux: libxxx.a (前缀lib固定,后缀.a固定)

Windows: libxxx.lib

静态库制作:

(1)gcc 获得 .o 文件

(2)将.o 文件打包,使用ar工具(archive)

​ ar rcs libxxx.a xxx.o xxx.o

​ r - 将文件插入备存文件中

​ c - 建立备存文件

​ s - 索引

arc rcs libxxx.a xxx.o xxx.o

项目目录介绍:

  • include 放头文件
  • lib 放库文件
  • src …其余cpp
  • main.cpp 主函数

把刚刚生成好的静态库文件放到lib里

注意,给了库文件的同时,需要把库文件里所引入的头文件也要放在项目里,否则无法看到他们的声明

在这里插入图片描述

在gcc mian.c 时,发现找不到head.h,因为他们不是在同一层级的目录下的

因此要用:

gcc main.c -o app -I ./include/

在这里插入图片描述

结果显示没有定义这几个函数

也就是我们没有把库引入进来,所以

-l 表示我们要加载哪个库,这里我们要加载的库的名称是calc(注意和库文件名的区别) -L 是说要到哪个目录里去找

gcc main.c -o app -I ./include/ -l calc -L ./lib

在这里插入图片描述

动态库的制作

命名:

  • Linux : libxxx.so
  • windows: libxxx.dll

制作:

(1)gcc 得到 .o 文件 得到和位置无关的代码

gcc中 加 -fpic 得到和位置无关的代码

​ gcc -c -fpic/-fPIC a.c b.c

(2)gcc 得到动态库(也成为共享库)

​ gcc -shared a.o b.o -o libcalc.so

在这里插入图片描述

在calc里制作好libcalc.so后,把它拷贝到library下面
在这里插入图片描述

编译main.c 注意写好以下:

  • -I 要引入的头文件的目录
  • -L 要引入的库文件的目录
  • -l 要引入的库文件的名称

在这里插入图片描述

说找不到这个库文件!

动态库加载原理、如何解决动态库加载失败?

静态库动态库原理:

  • 静态库在链接阶段会把二进制代码直接打包到可执行程序当中
  • 动态库链接时,动态库代码不会打包到可执行程序中(因此会加载失败!)

**动态库是在程序运行起来的时候,当调用api时,通过动态载入器来获取它的路径 **

ldd (list dynamic dependencies): 查看动态库的依赖关系

在这里插入图片描述

如何定位共享库文件?

通过动态载入器来获取该绝对路径,对于elf格式的可执行程序,通过ld-linux.so完成

它先后搜索elf文件的:

(1)DT_RPATH段**(每个程序运行起来是一个进程,linux系统会为他分配虚拟地址空间,DT_RPATH就是在这里面)**

(2)环境变量LD_LIBRARY_PATH

(3)/etc/ld.so.cache

(4)/lib/, /usr/lib

查找到绝对路径后,把动态库加载到内存当中,上面就不是not fund 了,就可以在程序运行的时候调用

第一种配置方式:

因为DT_RPATH是不可改的,因此我很要给环境变量LD_LIBRARY_PATH进行定义

在这里插入图片描述

export配置环境变量:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:(刚刚lib的目录)

echo 查看环境变量

当再打开一个新的终端时,设置的那个环境变量会被清除

因此我们应该用 用户级别的配置 / 系统级别的配置

  • 用户级别

    (1)进入home目录下配置 .bashrc:vim .bashrc

    在最后一行加入:

    在这里插入图片描述

    (2)让刚刚的修改生效:. .bashrc 或者 source .bashrc

  • 系统级别

    (1)sudo vim /etc/profile

    (2)最后一行加入 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/root/gjcc/lesson06/library/lib

    (3)让刚刚的修改生效 . /etc/profile

第二种配置方式:修改/etc/ld.so.cache

不能直接修改它,要间接修改他,通过:/etc/ld.so.conf

(1)vim 它,在里面直接加上要的路径

(2)更新它:sudo ldconfig

最后一种配置方式,把动态库文件放在那两个文件下(不推荐),里面好多系统自带的库文件,可能会冲突替换

静态库的优缺点

在这里插入图片描述

动态库:进程间资源共享(已经动态加载到内存中了)

在这里插入图片描述

Makefile

Makefile 文件定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于更复杂的功能操作。带来的好处 -> 自动化编译,只需要一个make命令,整个工程完全自动编译,极大提高了软件开发的效率。

解压文件: tar -zxvf xxx

在这里插入图片描述

redis下的源代码文件是很多的,如果让你自己去编译的话是很难做到的

所以用makefile文件

在这里插入图片描述

目标: 依赖

​ (Tab) shell命令生成目标

执行:输入make

makefile工作原理

在这里插入图片描述

如果把makefile里 app目标下的依赖换成 xxx.o 并且 gcc xxx.o 得到app

发现没有这个依赖,那么就检查其他的规则,看看能否生成这个依赖

在这里插入图片描述

Makefile里的其他规则,一般都是为第一条规则服务的,如果说下面的规则和第条规则没有任何的关系,那么是不会执行的

检测更新:依赖比目标的时间要晚,需要重新生成目标

比方说我们新修改了main.c 他的时间要比目标 main.o晚了,那就需要重新生成main.o,再执行所gcc xxx.o -o app,其他的.o不用动

如果我们直接一句 gcc xxx.c xxx.c -o app 它每次都要重新全部执行

如果要按第二种方式来写,只需要更新待更新的.o,并重新链接

如果有100个.c 也要写出那100个.o的规则吗?

在这里插入图片描述

自动变量只能在规则的命令中使用

模式匹配

在这里插入图片描述

makefile中的函数

wildcard 是获得.c文件的,我们是要.o文件

在这里插入图片描述

因此使用patsubst (把那一串.c 的字符串 变成.o的)

在这里插入图片描述

最后:

在这里插入图片描述

在此基础上,我们发现生成了很多并不需要的.o文件,可以定义一个新的语句删掉所有.o

但是!默认调用make是不执行clean语句的,因为它只执行第一个规则,并调用它所需要的下面的规则

如果我们想要执行那句clean 需要:make clean

在这里插入图片描述

如果我们在目录下touch 一个叫做clean的文件,因为Makefile中,clean并没有依赖,那么它的时间是永远晚于它的依赖,也就意味着调用make clean 它并不会调用它下面的命令。因此我们应该把它改成一个伪目标

在这里插入图片描述

GDB调试

GDB 是由 GUN 软件系统社区提供的调试工具,同GCC配套组成一套完整开发环境。

在编译的时候,要打开调试选项 ‘-g’

gcc -g -Wall program.c -o program

-g 是在可执行文件中加入源代码信息,如可执行文件中第几条机器指令对应源代码的第几行,并不是把整个源文件嵌入到可执行文件中

在这里插入图片描述

要用gdb 调试,必须在gcc的时候加+g参数 生成可以被调试的可执行程序

输入list后,就可以显示出它的代码、list后也可以跟行号、函数名

如果有多个.cpp代码 把他们编译成一个可执行程序之后,用list 文件名: 行号/函数名

可以set list 每次展示的行数:set listsize xxx

在这里插入图片描述

设置条件断点break 10 if i==5

在这里插入图片描述

n会一行一行的执行,遇到函数并不会进入函数体

s(单步调试)的话会遇到函数进入函数体

print 可以输出当前变量的值,但是如果你走一行print一行,也是太慢了,所以可以定义自动变量

如果要删除的话,可以用undisplay

标准C库IO函数和Linux系统IO函数对比

C库的IO 可以跨平台(Linux/Windows都可以用)——> 是另一种跨平台实现方式,用不同平台的API

Java的跨平台是通过JVM实现的

Linux的IO的API是低级的,标准C库的IO函数的效率是更高的,更高级的

在这里插入图片描述

缓冲区的作用->提高效率

先进缓冲区,再一次性把缓冲区里的放到磁盘里,避免和硬件的多次交流,降低效率

什么时候从内存刷新到磁盘?

  • 缓冲区已满
  • 刷新缓冲区:fflush
  • 正常关闭文件

缓冲区就是内存

缓冲区默认大小:8K

标准C库IO和Linux系统IO的关系

调用和被调用的关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m5qGMb2D-1664182380192)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20220912174312782.png)]

磁盘和buffer之间的读写是Linux系统来做的

虚拟地址空间

  • 文件描述符 -> 指向要操作的文件
  • 文件读写指针 -> 操作文件中的数据(有读指针、写指针)
  • I/O缓冲区 提高效率

在这里插入图片描述

虚拟地址空间解决了进程加载到内存中的问题

0-3G 用户区

3-4G 内核区

虚拟空间里的数据会被cpu中的MMU(内存管理单元) 映射到真实的物理内存上

理想状态占4G,实际上不会占用物理内存4G

内核区普通用户无法操作的

用户区介绍:

  • null、nullptr都是在受保护的地址里
  • 程序加载到内存当中 -> 到代码段
  • .data 已初始化全局变量
  • .bss 未初始化全局变量
  • 共享库
  • 堆空间 从低地址往高地址存
  • 栈空间 从高地址往低地址存 -> 局部变量 等等

(1)堆栈 -> 本地变量(函数内部产生的变量)

(2)堆 -> 有且仅有 new 出来的

(3)全局数据区 全局变量、静态本地变量、静态成员变量

内核区:

调用系统API 对内核里的数据进行操作

  • 内存管理
  • 进程管理
  • 设备驱动管理
  • VFS虚拟文件系统

文件描述符

fopen之后 会返回一个file指针,里面包括一个文件描述符,可以定位文件

文件描述符是在进程的内核区,由内核进行管理

由PCB进程控制块管理文件描述符 文件描述符表是一个数组 数组大小默认是1024 前三个默认被 标准输入 标准输出 标准错误 ,而且是默认已经打开的。他们和当前终端是绑定在一起的

在这里插入图片描述

可以用多个fopen符打开一个文件,当然他们对应的文件描述符是不一样的

Linux系统怎么得到文件描述符?

​ 利用Linux系统API:open()、read()、write() 结合文件描述符去操作这个文件

标准C库的fopen 会调用linux系统的api open()

open函数两个参数的是打开文件,不会创建新文件

带三个参数的是创建新的文件

在这里插入图片描述

close:关闭一个文件描述符,并且让他不能再指向其他的文件,文件描述符还能被重用

perror,打印出错误信息

//三个参数的 是创建文件
/*
    -pathname -> 要创建的文件的路径
    -flags -> 对文件的操作权限和其他的设置
            必选项:O_RDONLY, O_WRONLY, O_RDWR
            可选项:O_CREAT 文件不存在,创建新文件
    -mode -> 八进制的数,表示创建出的新文件的操作权限,比如0775
    最终的权限是mode & ~umask
            umask 是 0002 取反是 0775
            传入是0777 可是最后变成了0775
            umask的作用是抹去某些权限
    
    flags参数是一个int类型的数据,占4个字节,32位
    flags 32个位, 每一个位就是一个标志位
    所以这里是用或运算O_RDWR | O_CREAT
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
//int open(const char *pathname, int flags, mode_t mode);

int main()
{
    //创建一个新的文件
    //注意最后出来不是0777 因为它还要和umask做掩码操作
    int fd = open("create.txt", O_RDWR | O_CREAT, 0777);
    if(fd == -1)
    {
        perror("open");
    }
    //关闭
    close(fd);
    return 0;
}

read、write函数(读到内存中,写到文件里)

/*
    #include<unistd.h>
    ssize_t read(int fd, void *buf, size_t count)
        参数:
            -fd 文件描述符,open得到的,通过这个文件描述符操作某个文件
            -buf: 需要读取数据存放的地方,数组的地址(传出参数)
            -count: 指定的数组的大小
        并不是说每次读取的数据都是数组大小的数据,有可能没装满
        返回值:
            -成功:
                >0: 返回实际的读取到的字节数
                =0: 文件已经读取完了
            -失败: -1,并设置erron
    

    #include <unistd.h>
    ssize_t write(int fd, const void *buf, size_t count);
    参数:
            -fd 文件描述符,open得到的,通过这个文件描述符操作某个文件
            -buf: 要往磁盘写入的数据,数据
            -count: 要写的数据的实际的大小
    返回值:
        成功: 实际返回的字节数
        失败:返回-1, 并设置erron
*/

#include<unistd.h>
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    //1. 通过open打开english.txt文件
    int srcfd = open("english.txt", O_RDONLY);
    if(srcfd == -1)
    {
        perror("open");
        return -1;
    }
    //2. 创建一个新的文件(拷贝文件)
    int destfd = open("cpy.txt", O_WRONLY | O_CREAT, 0664);
    if(srcfd == -1)
    {
        perror("open");
        return -1;
    }
    //3. 频繁的读写操作
    char buf[1024] = {0};
    int len = 0;
    while((len = read(srcfd, buf, sizeof(buf)-1)) > 0) {
        buf[len] = '\0';
        write(destfd, buf, len);
    }
    //4. 关闭文件
    close(destfd);
    close(srcfd); 

    return 0;
}

注意在读写操作的那部分,因为我们都是以字符串的形式在读写

输入字符串中含有空格,通俗地说就是到了字符串末尾时,read()函数分不清这是个空格还是结束符,然后就跟着一串乱码出来,如果你的字符串没有空格的话,它是不会出现乱码的。

所以为了解决这个问题,我们要以’\0’来给字符串做结尾、仿照上面、在写之前,buf后面都要加’\0’

read函数的长度最后是 sizeof(buf) - 1

lseek函数

off_t lseek(int fd, off_t offset, int whence);
        参数:
            - fd:文件描述符,通过open得到的,通过这个fd操作某个文件
            - offset:偏移量
            - whence:
                SEEK_SET
                    设置文件指针的偏移量
                SEEK_CUR
                    设置偏移量:当前位置 + 第二个参数offset的值
                SEEK_END
                    设置偏移量:文件大小 + 第二个参数offset的值
        返回值:返回文件指针的位置

作用:

  1. 移动文件指针到文件头(不用关了再重新打开了)

    lseek(fd, 0, SEEK_SET);

  2. 获取当前文件指针的位置

    lseek(fd, 0, SEEK_CUR);

  3. 获取文件长度

    lseek(fd, 0, SEEK_END);

  4. 扩展文件的长度,当前文件10b,110b,增加了100个字节

    lseek(fd, 100, SEEK_END);

扩展的字节是空字符

/*  
    标准C库的函数
    #include <stdio.h>
    int fseek(FILE *stream, long offset, int whence);

    Linux系统函数
    #include <sys/types.h>
    #include <unistd.h>
    off_t lseek(int fd, off_t offset, int whence);
        参数:
            - fd:文件描述符,通过open得到的,通过这个fd操作某个文件
            - offset:偏移量
            - whence:
                SEEK_SET
                    设置文件指针的偏移量
                SEEK_CUR
                    设置偏移量:当前位置 + 第二个参数offset的值
                SEEK_END
                    设置偏移量:文件大小 + 第二个参数offset的值
        返回值:返回文件指针的位置


    作用:
        1.移动文件指针到文件头
        lseek(fd, 0, SEEK_SET);

        2.获取当前文件指针的位置
        lseek(fd, 0, SEEK_CUR);

        3.获取文件长度
        lseek(fd, 0, SEEK_END);

        4.拓展文件的长度,当前文件10b, 110b, 增加了100个字节
        lseek(fd, 100, SEEK_END)
        注意:需要写一次数据

*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {

    int fd = open("hello.txt", O_RDWR);

    if(fd == -1) {
        perror("open");
        return -1;
    }

    // 扩展文件的长度
    int ret = lseek(fd, 100, SEEK_END);
    if(ret == -1) {
        perror("lseek");
        return -1;
    }

    // 写入一个空数据
    write(fd, " ", 1);

    // 关闭文件
    close(fd);

    return 0;
}

下载一个5G的东西,先用空的占了这5G,最后在替换成我们下载的东西,而不是说下载着下载着不够了

stat、lstat

stat 和 lstat的区别:

stat是获取文件的相关信息

lstat是获取软链接本身的信息,而不是软链接所指向的文件的信息

/*
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>

    int stat(const char *pathname, struct stat *statbuf);
        作用:获取一个文件相关的一些信息
        参数:
            - pathname:操作的文件的路径
            - statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
        返回值:
            成功:返回0
            失败:返回-1 设置errno

    int lstat(const char *pathname, struct stat *statbuf);
        参数:
            - pathname:操作的文件的路径
            - statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
        返回值:
            成功:返回0
            失败:返回-1 设置errno

*/

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>

int main() {

    struct stat statbuf;

    int ret = stat("a.txt", &statbuf);

    if(ret == -1) {
        perror("stat");
        return -1;
    }

    printf("size: %ld\n", statbuf.st_size);


    return 0;
}

在这里插入图片描述

在这里插入图片描述

模拟实现ls -l

stat

关于 int argc, char * argv[]

举例:D:\tc2>test.exe myarg1 myarg2
的话,argc的值是3。也就是 命令名 加上两个参数,一共三个参数

我们这个仿照ls-l至少2个参数,一个调用的名字,一个文件名称 文件名称也就被放在argv[1]

//模拟实现ls-l
//-rw-r--r-- 1 root root  107 Sep 12 21:10 hello.txt
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <string.h>

// 模拟实现 ls -l 指令
// -rw-rw-r-- 1 nowcoder nowcoder 12 12月  3 15:48 a.txt
int main(int argc, char * argv[]) {

    // 判断输入的参数是否正确
    if(argc < 2) {
        printf("%s filename\n", argv[0]);
        return -1;
    }

    //通过stat获取用户传入的文件的信息
    struct stat st;
    //int stat(const char *path, struct stat *buf)
    //一个指向他的指针,把它给填满了(其实和c++引用是一个意思)
    int ret = stat(argv[1], &st);
    if(ret == -1)
    {
        perror("stat");
        return -1;
    }

    //获取文件类型和文件权限
    char perms[11] = {0};   //用于保存文件类型和文件权限
    switch(st.st_mode & S_IFMT)
    {
        case S_IFLNK:
            perms[0] = 'l';
            break;
        case S_IFDIR:
            perms[0] = 'd';
            break;
        case S_IFREG:
            perms[0] = '-';
            break; 
        case S_IFBLK:
            perms[0] = 'b';
            break; 
        case S_IFCHR:
            perms[0] = 'c';
            break; 
        case S_IFSOCK:
            perms[0] = 's';
            break;
        case S_IFIFO:
            perms[0] = 'p';
            break;
        default:
            perms[0] = '?';
            break;
    }

    //判断文件的访问权限

    // 文件所有者
    perms[1] = (st.st_mode & S_IRUSR) ? 'r' : '-';
    perms[2] = (st.st_mode & S_IWUSR) ? 'w' : '-';
    perms[3] = (st.st_mode & S_IXUSR) ? 'x' : '-';

    // 文件所在组
    perms[4] = (st.st_mode & S_IRGRP) ? 'r' : '-';
    perms[5] = (st.st_mode & S_IWGRP) ? 'w' : '-';
    perms[6] = (st.st_mode & S_IXGRP) ? 'x' : '-';

    // 其他人
    perms[7] = (st.st_mode & S_IROTH) ? 'r' : '-';
    perms[8] = (st.st_mode & S_IWOTH) ? 'w' : '-';
    perms[9] = (st.st_mode & S_IXOTH) ? 'x' : '-';

    // 硬连接数
    int linkNum = st.st_nlink;

    // 文件所有者
    // st_uid只是获取文件所有者的ID,但是我们是要名称
    // 所以用getpwduid这个函数,这个函数返回一个结构体的指针,它里面有pw_name
    char * fileUser = getpwuid(st.st_uid)->pw_name;

    // 文件所在组
    char * fileGrp = getgrgid(st.st_gid)->gr_name;

    // 文件大小
    long int fileSize = st.st_size;

    // 获取修改的时间
    char * time = ctime(&st.st_mtime);

    char mtime[512] = {0};
    strncpy(mtime, time, strlen(time) - 1);

    char buf[1024];
    sprintf(buf, "%s %d %s %s %ld %s %s", perms, linkNum, fileUser, fileGrp, fileSize, mtime, argv[1]);

    printf("%s\n", buf);
    return 0;
}

在这里插入图片描述

文件属性操作函数

access -> 判断当前调用这个函数所在的进程对这个文件是否有这个权限

/*
    #include <unistd.h>
    int access(const char *pathname, int mode);
        作用:判断某个文件是否有某个权限,或者判断文件是否存在
        参数:
            - pathname: 判断的文件路径
            - mode:
                R_OK: 判断是否有读权限
                W_OK: 判断是否有写权限
                X_OK: 判断是否有执行权限
                F_OK: 判断文件是否存在
        返回值:成功返回0, 失败返回-1
*/

#include <unistd.h>
#include <stdio.h>

int main() {

    int ret = access("a.txt", F_OK);
    if(ret == -1) {
        perror("access");
    }

    printf("文件存在!!!\n");

    return 0;
}

chmod 修改文件的权限

/*
    #include <sys/stat.h>
    int chmod(const char *pathname, mode_t mode);
        修改文件的权限
        参数:
            - pathname: 需要修改的文件的路径
            - mode:需要修改的权限值,八进制的数
        返回值:成功返回0,失败返回-1

*/
#include <sys/stat.h>
#include <stdio.h>
int main() {

    int ret = chmod("a.txt", 0777);

    if(ret == -1) {
        perror("chmod");
        return -1;
    }

    return 0;
}

chown 修改文件的 所有者 和 组别

truncate:缩减或者扩展文件的尺寸至指定的大小

/*
    #include <unistd.h>
    #include <sys/types.h>
    int truncate(const char *path, off_t length);
        作用:缩减或者扩展文件的尺寸至指定的大小
        参数:
            - path: 需要修改的文件的路径
            - length: 需要最终文件变成的大小
        返回值:
            成功返回0, 失败返回-1
*/

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>

int main() {

    int ret = truncate("b.txt", 5);

    if(ret == -1) {
        perror("truncate");
        return -1;
    }

    return 0;
}

如果变大,那就给他填充了null、如果变小,就给他砍了

(hello,world) -> (hello)

目录操作函数

/*
    #include <sys/stat.h>
    #include <sys/types.h>
    int mkdir(const char *pathname, mode_t mode);
        作用:创建一个目录
        参数:
            pathname: 创建的目录的路径
            mode: 权限,八进制的数
        返回值:
            成功返回0, 失败返回-1
*/

#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>

int main() {

    //必须要加上前面的0, 否则它认为这是十进制的数
    //注意这里的权限最终是(mode & ~umask)
    int ret = mkdir("aaa", 0777);

    if(ret == -1) {
        perror("mkdir");
        return -1;
    }

    return 0;
}
/*
    #include <stdio.h>
    int rename(const char *oldpath, const char *newpath);

*/
#include <stdio.h>

int main() {

    int ret = rename("aaa", "bbb");

    if(ret == -1) {
        perror("rename");
        return -1;
    }

    return 0;
}

chdir:修改进程的工作目录

/*

    #include <unistd.h>
    int chdir(const char *path);
        作用:修改进程的工作目录
            比如在/home/nowcoder 启动了一个可执行程序a.out, 进程的工作目录 /home/nowcoder
        参数:
            path : 需要修改的工作目录

    #include <unistd.h>
    char *getcwd(char *buf, size_t size);
        作用:获取当前工作目录
        参数:
            - buf : 存储的路径,指向的是一个数组(传出参数)
            - size: 数组的大小
        返回值:
            返回的指向的一块内存,这个数据就是第一个参数

*/
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main() {

    // 获取当前的工作目录
    char buf[128];
    getcwd(buf, sizeof(buf));
    printf("当前的工作目录是:%s\n", buf);

    // 修改工作目录
    int ret = chdir("/root/gjcc/lesson13");
    if(ret == -1) {
        perror("chdir");
        return -1;
    } 

    // 创建一个新的文件
    int fd = open("chdir.txt", O_CREAT | O_RDWR, 0664);
    if(fd == -1) {
        perror("open");
        return -1;
    }

    close(fd);

    // 获取当前的工作目录
    char buf1[128];
    getcwd(buf1, sizeof(buf1));
    printf("当前的工作目录是:%s\n", buf1);
    
    return 0;
}

在这里插入图片描述

目录遍历函数

注意这里getFileNum涉及到递归,如果判断读取到的这个是目录,还要再次调用程序

(同时读取子目录时注意拼接地址)

/*
    // 打开一个目录
    #include <sys/types.h>
    #include <dirent.h>
    DIR *opendir(const char *name);
        参数:
            - name: 需要打开的目录的名称
        返回值:
            DIR * 类型,理解为目录流
            错误返回NULL


    // 读取目录中的数据
    #include <dirent.h>
    struct dirent *readdir(DIR *dirp);
        - 参数:dirp是opendir返回的结果
        - 返回值:
            struct dirent,代表读取到的文件的信息
            读取到了末尾或者失败了,返回NULL

    // 关闭目录
    #include <sys/types.h>
    #include <dirent.h>
    int closedir(DIR *dirp);

*/
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int getFileNum(const char * path);

// 读取某个目录下所有的普通文件的个数
int main(int argc, char * argv[]) {

    if(argc < 2) {
        printf("%s path\n", argv[0]);
        return -1;
    }

    int num = getFileNum(argv[1]);

    printf("普通文件的个数为:%d\n", num);

    return 0;
}

// 用于获取目录下所有普通文件的个数
// 注意这里涉及到递归,如果判断读取到的这个是目录,还要再次调用程序
int getFileNum(const char * path) {

    // 1.打开目录
    DIR * dir = opendir(path);

    if(dir == NULL) {
        perror("opendir");
        exit(0);
    }

    struct dirent *ptr;

    // 记录普通文件的个数
    int total = 0;
	
   	// 只要读出来的ptr 不为空,就循环继续读
    while((ptr = readdir(dir)) != NULL) {

        // 获取名称
        char * dname = ptr->d_name;

        // 忽略掉. 和..
        // 注意还有这两个特殊的文件
        if(strcmp(dname, ".") == 0 || strcmp(dname, "..") == 0) {
            continue;
        }

        // 判断是否是普通文件还是目录
        if(ptr->d_type == DT_DIR) {
            // 目录,需要继续读取这个目录
            char newpath[256];
            // 注意 把地址衔接起来
            sprintf(newpath, "%s/%s", path, dname);
            total += getFileNum(newpath);
        }

        if(ptr->d_type == DT_REG) {
            // 普通文件
            total++;
        }


    }

    // 关闭目录
    closedir(dir);

    return total;
}

readdir 返回的dirent 结构体

在这里插入图片描述

dup、dup2函数

dup 复制文件描述符

dup2 重定向文件描述符

dup:

​ 从空闲的文件描述符表中找一个最小的,作为新的拷贝的文件描述符

​ 注意dup得到的文件描述符,最终操作完之后也需要把它自己close掉

/*
    #include <unistd.h>
    int dup(int oldfd);
        作用:复制一个新的文件描述符
        fd=3, int fd1 = dup(fd),
        fd指向的是a.txt, fd1也是指向a.txt
        从空闲的文件描述符表中找一个最小的,作为新的拷贝的文件描述符


*/

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>

int main() {

    int fd = open("a.txt", O_RDWR | O_CREAT, 0664);

    int fd1 = dup(fd);

    if(fd1 == -1) {
        perror("dup");
        return -1;
    }

    printf("fd : %d , fd1 : %d\n", fd, fd1);

    close(fd);

    char * str = "hello,world";
    int ret = write(fd1, str, strlen(str));
    if(ret == -1) {
        perror("write");
        return -1;
    }

    close(fd1);

    return 0;
}
/*
    #include <unistd.h>
    把第二个参数文件描述符指向第一个参数文件描述符对应的文件(默认关了自己原来的 )
    int dup2(int oldfd, int newfd);
        作用:重定向文件描述符
        oldfd 指向 a.txt, newfd 指向 b.txt
        调用函数成功后:newfd 和 b.txt 做close, newfd 指向了 a.txt
        oldfd 必须是一个有效的文件描述符
        oldfd和newfd值相同,相当于什么都没有做
*/
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main() {

    int fd = open("1.txt", O_RDWR | O_CREAT, 0664);
    if(fd == -1) {
        perror("open");
        return -1;
    }

    int fd1 = open("2.txt", O_RDWR | O_CREAT, 0664);
    if(fd1 == -1) {
        perror("open");
        return -1;
    }

    printf("fd : %d, fd1 : %d\n", fd, fd1);

    int fd2 = dup2(fd, fd1);
    if(fd2 == -1) {
        perror("dup2");
        return -1;
    }

    // 通过fd1去写数据,实际操作的是1.txt,而不是2.txt
    char * str = "hello, dup2";
    int len = write(fd1, str, strlen(str));

    if(len == -1) {
        perror("write");
        return -1;
    }

    printf("fd : %d, fd1 : %d, fd2 : %d\n", fd, fd1, fd2);

    close(fd);
    close(fd1);

    return 0;
}

fcntl 函数

(1)复制文件描述符

(2)设置/获取文件的状态标志

/*

    #include <unistd.h>
    #include <fcntl.h>

    int fcntl(int fd, int cmd, ...);
    参数:
        fd : 表示需要操作的文件描述符
        cmd: 表示对文件描述符进行如何操作
            - F_DUPFD : 复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符(返回值)
                int ret = fcntl(fd, F_DUPFD);

            - F_GETFL : 获取指定的文件描述符文件状态flag
              获取的flag和我们通过open函数传递的flag是一个东西。

            - F_SETFL : 设置文件描述符文件状态flag
              必选项:O_RDONLY, O_WRONLY, O_RDWR 不可以被修改
              可选性:O_APPEND, O)NONBLOCK
                O_APPEND 表示追加数据
                NONBLOK 设置成非阻塞
        
        阻塞和非阻塞:描述的是函数调用的行为。
*/

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

int main() {

    // 1.复制文件描述符
    // 如果只是RD_ONLY 是无法往后面追加的
    // int fd = open("1.txt", O_RDONLY);
    // int ret = fcntl(fd, F_DUPFD);

    // 2.修改或者获取文件状态flag
    int fd = open("1.txt", O_RDWR);
    if(fd == -1) {
        perror("open");
        return -1;
    }

    // 获取文件描述符状态flag
    // 需要先获取到原先的flag,再按位或 O_APPEND
    // 最后再把新的flag 设置到fd上面
    int flag = fcntl(fd, F_GETFL);
    if(flag == -1) {
        perror("fcntl");
        return -1;
    }
    flag |= O_APPEND;   // flag = flag | O_APPEND

    // 修改文件描述符状态的flag,给flag加入O_APPEND这个标记
    int ret = fcntl(fd, F_SETFL, flag);
    if(ret == -1) {
        perror("fcntl");
        return -1;
    }

    char * str = "nihao";
    write(fd, str, strlen(str));

    close(fd);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值