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的关系
调用和被调用的关系
磁盘和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的值
返回值:返回文件指针的位置
作用:
-
移动文件指针到文件头(不用关了再重新打开了)
lseek(fd, 0, SEEK_SET);
-
获取当前文件指针的位置
lseek(fd, 0, SEEK_CUR);
-
获取文件长度
lseek(fd, 0, SEEK_END);
-
扩展文件的长度,当前文件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;
}