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 & ~umaskumask 是 0002 取反是 0775传入是0777 可是最后变成了0775umask的作用是抹去某些权限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 设置errnoint 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_namechar * 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.txtoldfd 必须是一个有效的文件描述符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.txtchar * 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)NONBLOCKO_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.修改或者获取文件状态flagint 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;
}

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

  1. Linux 高并发服务器实战 - 2 Linux多进程开发

    Linux 高并发服务器实战 - 2 Linux多进程开发 进程概述 概念1: 概念2: 微观而言,单CPU任意时刻只能运行一个程序 并发:两个队列交替使用一台咖啡机 并行:两个队列同时使用两台咖啡机 ...

  2. [Linux 高并发服务器]GDB调试

    [Linux 高并发服务器]GDB调试 [Linux 高并发服务器]GDB调试 [Linux 高并发服务器]GDB调试 GDB是什么 预先准备 基本命令 例子 进入和退出gdb 获取帮助 查看文件代码 ...

  3. Linux高并发服务器开发---笔记1(环境搭建、系统编程、多进程)

    0613 第4章 项目制作与技能提升 4.0 视频课链接 4.1 项目介绍与环境搭建 4.1.1 项目介绍 4.1.2 开发环境搭建 ①安装Linux系统.XSHELL.XFTP.Visual Stu ...

  4. Linux 高并发服务器开发

    该文章是通过观看牛客网的视频整理所得,以及在实践过程中遇到的问题及解决方案的整理总结. Linux 高并发服务器开发 linux 系统编程 linux 环境的搭建 环境搭建需要的软件 虚拟机中安装 u ...

  5. Linux高并发服务器开发---笔记2(多进程)

    0630 第4章 项目制作与技能提升 4.0 视频课链接 4.1 项目介绍与环境搭建 4.2 Linux系统编程1.4.3 Linux系统编程2 4.4 多进程 1-9 10.进程间通信☆☆☆ 进程间 ...

  6. Linux高并发服务器开发---笔记4(网络编程)

    0705 第4章 项目制作与技能提升 4.0 视频课链接 4.1 项目介绍与环境搭建 4.2 Linux系统编程1.4.3 Linux系统编程2 4.4 多进程 1-9 10.进程间通信☆☆☆ 4.5 ...

  7. Linux高并发服务器解决方案

    Linux高并发服务器案例演示 在网络通信中,我们常常的服务器经常会受到成千上万的请求提示,而电脑会根据请求建立相对应的socket链接,但是接触过Linux网络编程的人都知道,Linux链接和客户端 ...

  8. [Linux 高并发服务器]网络基础

    [Linux 高并发服务器]网络基础 文章概述 该博客为牛客网C++项目课:Linux高并发服务器 个人笔记 作者信息 NEFU 2020级 zsl ID:fishingrod/鱼竿钓鱼干 Email ...

  9. linux 高并发文件实时同步,Linux海量数据高并发实时同步架构方案杂谈

    不论是Redhat还是CentOS系统,除去从CDN缓存或者数据库优化.动静分离等方面来说,在架构层面上,实 现海量数据高并发实时同步访问概括起来大概可以从以下几个方面去入手,当然NFS的存储也可以是 ...

最新文章

  1. 人脸识别数据集精粹(下)
  2. SharePoint 2007 系列(4) -Site Settings
  3. Java 中的 BigDecimal,你真的会用吗?
  4. web.xml文件位于web项目的目录结构中的_看完这篇,别人的开源项目结构应该能看懂了...
  5. 第二十二章 鲁曹沫柯地之盟强索地 齐桓公因势利导著信义
  6. mybatis针对mysql自增主键怎样配置mapper
  7. Ansible系列之roles使用说明
  8. Windows 10 中 VMware 要求禁用 Device Guard 问题
  9. 机器学习实战(6):SVM-SMO-核函数 手写识别
  10. 计算机考试交互,2017计算机等级考试HTTP的基本概念与交互模型练习题
  11. HTM皮质学习算法资料
  12. 用JavaScript实现更复杂的交互
  13. 如何修改sql server 表中自增长ID列,因删除而不连续。可以使用临时表
  14. Atitit java项目常用类库表 目录 1. Ati总的常用库 1 1.1. 表达式,语言解析类库 1 1.2. 字符串模板解析库velocity freemark 1 1.3. rest库
  15. 让Eclipse起飞——这些插件不可不知
  16. 计算机表格布局,修改Word2007的表格布局
  17. ipv6 华为交换机 路由配置_H3C Huawei 交换机 IPv6环境配置
  18. 【目标检测】(10) Mosaic 数据增强方法,附Python完整代码
  19. 2010年美国计算机图书市场Part1
  20. swiper滑到最后一页交互

热门文章

  1. 阿里巴巴服务化架构演进
  2. 伤心........
  3. Vue.js绑定class属性
  4. 硅谷的由来—(计算机基础课七)
  5. 印刷和喷绘过程中高精度油墨流量和压力的串级控制解决方案
  6. C/C++编程笔记:详解三种指针(悬空指针、空指针和野指针)
  7. 易语言自己动手修改配置 添加UPX加壳
  8. CMake 配置问题记录-missing and no known rule to make it
  9. 2827: 千山鸟飞绝 splay打标记
  10. 小红书“时尚C计划”,00后美妆博主军团崛起