课程来源:
牛客网c++高薪求职项目
牛客职导校招冲刺集训营-C++
感谢牛客网免费提供的项目课程~
本篇博客记录学习过程中的笔记~
代码详细注释 提取码:bakr
未包含注释的代码,github项目
1.Linux基础知识
1.1 环境搭建
环境搭建我没有选择装虚拟机,购买的腾讯云服务器,150r3年学生机,使用vscode链接服务器进行coding~
1.2 gcc
只要掌握了gcc与g++即可,编译过程如下
gcc main.c -o app
1.2.1 gcc参数
1.3 静态库的制作
注意:由于改文件夹下.o文件正好是制作静态库的所需文件,所以使用正则表达式是可以的。
hope@VM-24-17-ubuntu:~/Linux_cpp/lession04/calc$ gcc -c add.c div.c mult.c sub.c
hope@VM-24-17-ubuntu:~/Linux_cpp/lession04/calc$ ls
add.c add.o div.c div.o head.h main.c mult.c mult.o sub.c sub.o
hope@VM-24-17-ubuntu:~/Linux_cpp/lession04/calc$ ar rcs libcalc.a *.o
hope@VM-24-17-ubuntu:~/Linux_cpp/lession04/calc$ ls
add.c add.o div.c div.o head.h libcalc.a main.c mult.c mult.o sub.c sub.o
1.4 静态库的使用
首先生产静态库,然后使用参数:
-l calc
指定使用的库名称,注意不是libcalc.a
这时候还会报错没有找到库文件,原因是没有指定库文件的路径
-L ./lib
指定库文件路径
-I 指定包含的头文件路径
gcc main.c -o app -I ./include -L ./lib -l calc
1.5 动态库的制作
1.6 动态库加载失败的原因
使用静态库加载的方式是错误的
使用ldd命令可以发现libcalc.so库没有找到,需要把它放到目录下,实际上就是环境变量。
hope@VM-24-17-ubuntu:~/Linux_cpp/lession06/library$ ldd main
linux-vdso.so.1 (0x00007ffd3f03d000)
libcalc.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f09cb3f0000)
/lib64/ld-linux-x86-64.so.2 (0x00007f09cb5f5000)
1.7 解决动态无法加载问题
hope@VM-24-17-ubuntu:~/Linux_cpp/lession06/library/lib$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/hope/Linux_cpp/lession06/library/lib
hope@VM-24-17-ubuntu:~/Linux_cpp/lession06/library/lib$ ls
libcalc.so
hope@VM-24-17-ubuntu:~/Linux_cpp/lession06/library/lib$ cd ..
hope@VM-24-17-ubuntu:~/Linux_cpp/lession06/library$ ls
include lib main main.c src
hope@VM-24-17-ubuntu:~/Linux_cpp/lession06/library$ ldd main
linux-vdso.so.1 (0x00007ffd7391b000)
libcalc.so => /home/hope/Linux_cpp/lession06/library/lib/libcalc.so (0x00007f8056133000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8055f35000)
/lib64/ld-linux-x86-64.so.2 (0x00007f805613f000)
hope@VM-24-17-ubuntu:~/Linux_cpp/lession06/library$ ./main
a = 20, b = 12
a + b = 32
a - b = 8
a * b = 240
a / b = 1.666667
hope@VM-24-17-ubuntu:~/Linux_cpp/lession06/library$
这种是改变动态加载库的问题,但是当重开开一个bash的时候还是不可以解决问题,所以需要在环境变量中修改,然后持久化一下。
在家目录下修改.bashrc文件,最后一行加上
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库目录
1.8 makefile
自动编译,按照规则自己设定编译的条件。
- 命名只能是Makefile 或者makefile
- 目标文件:依赖项
TAB空格 编译方式 - 根据文件的修改时间去选择是否更新
1.9 GDB
GDB是由GNU软件系统社区提供的调试工具,同GCC配套组成了一套完整的开发环境,GDB是Linux和许多Unix系统中的标准开发环境。
一般来说,GDB主要帮助你完成下面四个方面的功能
- 启动程序,可以按照自定义的要求随心所欲的运行程序
- 可让被调试的程序在所指定的调置断点处停住(断点可以是条件表达式)
- 当程序被停住时,可以检查此时程序中所发生的事
- 可以改变程序,将一个BUG产生的影响修正从而测试其他BUG
准备工作:
- 通常,在为调试而编译时,我们会()关掉编译器的优化选项('-o'),并打开调试按钮('-g')。另外,'-Wall'在尽量不影响程序行为的情况下选项打开所有warning,也可以发现许多问题,避免一些不必要的BUG。
- gcc -g -Wall program.c -o program
- '-g'选择的作用是在可执行文件中加入源代码信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所在在调试时必须保证GDB能够找到源文件。
-g 加入调试信息,所以生成的test1比test占用内存大
hope@VM-24-17-ubuntu:~/Linux_cpp/lession07$ gcc test.c -o test
hope@VM-24-17-ubuntu:~/Linux_cpp/lession07$ gcc test.c -o test1 -g
hope@VM-24-17-ubuntu:~/Linux_cpp/lession07$ ll -h test
-rwxrwxr-x 1 hope hope 17K May 22 09:53 test*
hope@VM-24-17-ubuntu:~/Linux_cpp/lession07$ ll -h test1
-rwxrwxr-x 1 hope hope 20K May 22 09:53 test1*
hope@VM-24-17-ubuntu:~/Linux_cpp/lession07$
设置args参数
获取设置的参数
(gdb) set args 10 20
(gdb) show args
Argument list to give program being debugged when it is started is "10 20".
1.9.1 GDB使用帮助
(gdb) help
1.9.2 GDB显示代码文件,编译的时候需要加上参数-g
gcc test.c -o test -g
默认显示10行
(gdb) l
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int test(int a);
5
6 int main(int argc, char* argv[]) {
7 int a, b;
8 printf("argc = %d\n", argc);
9
10 if(argc < 3) {
也可以指定代码文件
(gdb) list main
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int test(int a);
5
6 int main(int argc, char* argv[]) {
7 int a, b;
8 printf("argc = %d\n", argc);
9
10 if(argc < 3) {
(gdb)
1.9.3 设置端点,查看断点,设置断点
设置端点
gdb break main.cpp:6 // 第6行打断点
查看断点
gdb i b
例:
(gdb) b main.cpp:6
Breakpoint 1 at 0x11e9: file main.cpp, line 6.
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000000011e9 in main() at main.cpp:6
(gdb)
也可以指定源文件里面的函数开始处设置断点
gdb b bulle.cpp:bubbleSort
(gdb) b bubble.cpp:bubbleSort
Breakpoint 2 at 0x13eb: file bubble.cpp, line 6.
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000000011e9 in main() at main.cpp:6
2 breakpoint keep y 0x00000000000013eb in bubbleSort(int*, int) at bubble.cpp:6
(gdb)
删除断点
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000000011e9 in main() at main.cpp:6
2 breakpoint keep y 0x00000000000013eb in bubbleSort(int*, int) at bubble.cpp:6
(gdb) d 1
(gdb) i b
Num Type Disp Enb Address What
2 breakpoint keep y 0x00000000000013eb in bubbleSort(int*, int) at bubble.cpp:6
(gdb)
设置断点无效
可以看到keep处为n
2 breakpoint keep y 0x00000000000013eb in bubbleSort(int*, int) at bubble.cpp:6
(gdb) dis 2
(gdb) i b
Num Type Disp Enb Address What
2 breakpoint keep n 0x00000000000013eb in bubbleSort(int*, int) at bubble.cpp:6
(gdb)
设置断点生效
(gdb) ena 2
(gdb) i b
Num Type Disp Enb Address What
2 breakpoint keep y 0x00000000000013eb in bubbleSort(int*, int) at bubble.cpp:6
(gdb)
设置条件断点
11 bubbleSort(array, len);
12
13 // 遍历
14 cout << "冒泡排序之后的数组: ";
15 for(int i = 0; i < len; i++) {
16 cout << array[i] << " ";
17 }
18 cout << endl;
19 cout << "===================================" << endl;
20
(gdb) break 16 if i==3
Breakpoint 3 at 0x1261: file main.cpp, line 16.
(gdb) i b
Num Type Disp Enb Address What
2 breakpoint keep y 0x00000000000013eb in bubbleSort(int*, int) at bubble.cpp:6
3 breakpoint keep y 0x0000000000001261 in main() at main.cpp:16
stop only if i==3
(gdb)
1.9.4 调试命令
1.10 IO函数库
1.10.1 标准C库IO函数
1.10.2 虚地址空间
1.10.3 文件描述符
1.10.4 Linux系统IO函数
open函数示例
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>//perror 打印错误信息 宏定义errno
#include <unistd.h>//close close - close a file descriptor
/*
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname,int flags,mode_t mode){
参数:
- pathname :要创建的文件的路径
- flags :对文件的操作权限和其他设置
- 必选项: O_RDONLY, O_WRONLY,or O_RDWR.
- 可选项:O_CREAT 文件不存在,创建新文件
- mode: 八进制的数,表示创建出的新的文件的操作权限,比如:0775
最终的权限是:mode & ~umask
~0002 = 0775
0777 0777
0775 ^ 0002 -
111 111 101 0775 111 111 101
umask 0002
umask: 抹去权限
}
*/
int main(){
//flag O_RDONLY, O_WRONLY,or O_RDWR.
//int fd = open("a.txt",O_RDONLY);
int fd = open("creat.txt",O_RDWR | O_CREAT,0777);
if(fd == -1){
perror("open");
}
close(fd);
return 0;
}
write read函数示例
/*
#include<unistd.h>
ssize_t read(int fd,void *buf,size_t count);
参数:
-fd : 文件描述符,open得到的,通过这个文件描述符操作某个文件
-buf : 需要读取数据存放的地方 数组的地址 (传出参数)
- count :指定的数组的大小
返回值:
成功:
> 0 : 返回实际的读取到的字节数
= 0 : 文件已经读取完了
失败:
-1 :设置error
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数:
-fd : 文件描述符,open得到的,通过这个文件描述符操作某个文件
-buf : 往磁盘写入的数据
- count :要写的数据实际的大小
返回值:
成功:实际写入的字节数
失败:返回 -1 设置error
*/
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(){
//open 打开English.txt文件
int srcfd = open("english.txt",O_RDONLY);
if(-1 == srcfd){
perror("open");
return -1;
}
//创建一个新的文件(拷贝文件)
int opfd = open("cpy.txt",O_WRONLY | O_CREAT,0664);
if(-1 == opfd){
perror("open");
return -1;
}
//频繁的读写操作
char buff[1024] = {0};
int len = 0;
while((len = read(srcfd,buff,sizeof buff)) > 0){
write(opfd,buff,len);//第三个参数是实际的大小
}
if(-1 == len){
perror("read");
return -1;
}
//关闭文件
close(opfd);
close(srcfd);
return 0;
}
lseek函数示例
/*
//标准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得到的 用来操作某个文件
-off_t: 偏移量
-whence
SEEK_SET
The file offset is set to offset bytes.
设置文件指针的偏移量
SEEK_CUR
The file offset is set to its current location plus offset bytes.
设置偏移量:当前位置 + 第二个参数offset的值
SEEK_END
The file offset is set to the size of the file plus offset bytes.
设置偏移量:文件的大小 + 第二个参数offset的值
返回值: 返回文件指针的位置
作用:
1.移动文件指针到文件头
lseek(fd,0,SEEK_SET);
2.获取当前文件指针的位置
lseek(fd,0,SEEK_CUR);
3.可以获取文件长度
lseek(fd,0,SEEK_END);
4.拓展文件的长度 当前文件10b 110b 增加了100个字节
lssek(fd,100,SEEK_END);
*/
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>//perror
#include<unistd.h>//close
int main(){
int fd = open("hello.txt",O_RDONLY | O_WRONLY);
if(-1 == fd){
perror("open");
return -1;
}
//扩展文件长度
int re = lseek(fd,100,SEEK_END);
if(re == -1) {
perror("lseek");
return -1;
}
//写入数据 不写的话不会改变大小
write(fd," ",1);
//关闭文件
close(fd);
return 0;
}
stat函数示例
//模拟实现ls -l 功能
//-rw-rw-r-- 1 hope hope 0 May 31 09:40 a.txt
#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
#include<pwd.h>
#include<grp.h>
#include <time.h>
#include<string.h>
int main(int argc,char * argv[]){
//判断输入的参数是否正确
if(argc < 2){
printf("%s filename:",argv[0]);
return -1;
}
struct stat stabuff;
int ret = stat(argv[1],&stabuff);
if(ret == -1){
perror("stat");
return -1;
}
//获取文件类型和文件权限
char perms[11] = {0};
switch (stabuff.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] = (stabuff.st_mode & S_IRUSR) ? 'r' : '-';
perms[2] = (stabuff.st_mode & S_IRUSR) ? 'w' : '-';
perms[3] = (stabuff.st_mode & S_IRUSR) ? 'x' : '-';
//文件所在组
perms[4] = (stabuff.st_mode & S_IRGRP) ? 'r' : '-';
perms[5] = (stabuff.st_mode & S_IRGRP) ? 'w' : '-';
perms[6] = (stabuff.st_mode & S_IRGRP) ? 'x' : '-';
//文件其他人
perms[7] = (stabuff.st_mode & S_IROTH) ? 'r' : '-';
perms[8] = (stabuff.st_mode & S_IROTH) ? 'w' : '-';
perms[9] = (stabuff.st_mode & S_IROTH) ? 'x' : '-';
//硬链接数
int linkNum = stabuff.st_nlink;
//文件所有者
char* fileUser = getpwuid(stabuff.st_uid)->pw_name;
//文件所在组
char* fileGroup = getgrgid(stabuff.st_gid)->gr_name;
//文件的大小
long int sizefile = stabuff.st_size;
//获取修改时间
char* time = ctime(&stabuff.st_mtime);
char mtime[512] = {0};
strncpy(mtime,time,strlen(time) - 1);
char buff[1024];
sprintf(buff,"%s %d %s %s %ld %s %s",perms,linkNum,fileUser,fileGroup,sizefile,mtime,argv[1]);
printf("%s\n",buff);
return 0;
}
/*
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
作用: 获取一个文件相关的信息
参数:
-pathname:操作的文件的路径
-statbuf:结构体变量,传出参数 用于保存获取到的文件信息
返回值:
成功:返回0
失败:返回-1设置error
int lstat(const char *pathname, struct stat *statbuf);
*/
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
int main(){
struct stat stabuff;
int ret = stat("a.txt",&stabuff);
if(ret == -1){
perror("stat");
return -1;
}
printf("size: %ld\n",stabuff.st_size);
return 0;
}
1.10.5 stat结构体
1.10.6 文件属性操作函数
accsss函数示例
/*
#include <unistd.h>
int access(const char *pathname, int mode);
作用: 判断某个文件是否有某个权限,或者判断文件是否存在
参数:
-pathname:判断的文件路径
-mode:F_OK tests for the existence of the file. R_OK, W_OK, and X_OK test whether the file exists
and grants read, write, and execute permissions, respectively.
返回值:
成功返回 0
失败返回 -1
*/
#include<unistd.h>
#include<stdio.h>
int main(){
int res = access("a.txt",F_OK);
if(-1 == res){
perror("access");
return -1;
}
printf("文件存在\n");
return 0;
}
chmod函数示例
/*
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
作用:修改文件的权限
参数:
-pathname:需要修改的文件的路径
-mode_t: 需要修改的权限值
返回值:
成功返回 0
失败返回 -1
*/
#include <sys/stat.h>
#include<stdio.h>
int main(){
int res = chmod("a.txt",0777);
if(res == -1){
perror("chmode");
}
return 0;
}
truncate函数示例
/*
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
作用:缩减或者扩展文件的尺寸至指定的大小
参数:
-path :需要修改的文件的路径
-off_t 需耀修改的最终大小
*/
#include <unistd.h>
#include <sys/types.h>
#include<stdio.h>
int main(){
int res = truncate("b.txt",20);
if(res == -1){
perror("truncate");
return -1;
}
return 0;
}
1.10.7 目录操作函数
chdir函数示例
/*
#include <unistd.h>
int chdir(const char *path);
作用:修改进程工作目录
比如在/home/nowchder 启动了一个可执行程序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[1024];
getcwd(buf,sizeof buf);
printf("当前的工作目录是:%s\n",buf);
//修改工作目录
//写到 13里面
int res = chdir("/home/hope/Linux_cpp/lession13");
if(-1 == res){
perror("chdir");
return -1;
}
//创建一个新文件
int fd = open("chdir.txt",O_CREAT | O_RDONLY,0664);
if(fd == -1){
perror("open");
return -1;
}
close(fd);
//获取当前的工作目录
char buff[1024];
getcwd(buff,sizeof buff);
printf("当前的工作目录是:%s\n",buff);
return 0;
}
mkdir函数示例
/*
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
作用:创建一个目录
参数:
-pathname:创建的的目录的名称 路径
-mode:文件权限 8进制的数字或者宏
返回值:
成功返回0
失败返回-1
*/
#include<sys/stat.h>
#include<sys/types.h>
#include<stdio.h>
int main(){
int res = mkdir("aaa",0777);
if(-1 == res){
perror("mkdir");
return -1;
}
return 0;
}
rename函数示例
/*
#include <stdio.h>
int rename(const char *oldpath, const char *newpath);
*/
#include<stdio.h>
int main(){
int res = rename("a.txt","b.txt");
if(-1 == res){
perror("rename");
return -1;
}
return 0;
}
1.10.8 目录遍历函数
opendir函数示例
/*
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
作用:打开一个目录
参数
-name:需要打开的目录
返回值:
目录流
错误返回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);
//作用:关闭目录
参数:opendir的返回值
*/
//读取某个out文件夹中文件的个数
#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 ans = 0;
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 newpathName[256];
sprintf(newpathName,"%s/%s",path,dname);
ans += getFileNum(newpathName);
}
if(ptr->d_type == DT_REG){
//是普通文件
ans++;
}
}
//关闭目录
closedir(dir);
return ans;
}
1.10.9 dirent结构体和d_type
1.10.10 dup函数
dup函数示例
/*
#include <unistd.h>
int dup(int oldfd);
作用: The dup() system call creates a copy of the file descriptor oldfd,
using the lowest-num‐bered unused file descriptor for the new descriptor.
dup() 系统调用创建文件描述符 oldfd 的副本,使用编号最小的未使用文件描述符作为新描述符。
例: fd = 3, int fd1 = dup(fd);
fd指向的是a.txt fd1也是指向a.txt
*/
#include<unistd.h>
#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<string.h>
int main(){
//O_RDONLY 只读打开。 O_RDWR 读、写打开。
//O_WRONLY 只写打开。 O_APPEND 每次写时都加到文件的尾端。
int fd = open("a.txt",O_CREAT | O_RDWR,0664);
if(fd == -1){
perror("open");
return -1;
}
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 res = write(fd1,str,strlen(str));
if(res == -1){
perror("write");
return -1;
}
close(fd1);
return 0;
}
dup2函数示例
/*
#include <unistd.h>
int dup2(int oldfd, int newfd);
作用:重定向文件描述符
oldfd 指向a.txt newfd指向b.txt
调用函数成功后,newfd和b.txt做close newfd指向a.txt
oldfd 必须是一个有效的文件描述符
如果old 和 new 相同 相当于什么都没做
*/
#include<unistd.h>
#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<string.h>
int main(){
int fd1 = open("a.txt",O_CREAT | O_RDWR,0664);
if(fd1 == -1){
perror("open");
return -1;
}
int fd2 = open("b.txt",O_CREAT | O_RDWR,0664);
if(fd2 == -1){
perror("open");
return -1;
}
printf("fd1 : %d fd2: %d\n",fd1,fd2);
int fd3 = dup2(fd1,fd2);
if(fd3 == -1){
perror("dup2");
return -1;
}
//通过fd2去写数据 实际操作的是a.txt 而不是b.txt
char* str = "hello world";
int len = write(fd2,str,strlen(str));
if(len == -1){
perror("write");
return -1;
}
//打印三个文件描述符
printf("fd1 : %d fd2: %d fd3:%d\n",fd1,fd2,fd3);
close(fd1);
close(fd2);
return 0;
}
1.10.11 fcntl函数
fcntl函数示例
/*
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ...)
https://baike.baidu.com/item/fcntl.h/332738
作用:
复制文件描述符
用fcntl 修改或者获取文件的状态flag
参数:
-fd:表示需要操作的文件描述符
-cmd:表示对文件描述符进行如何操作
-F_DUPFD:复制文件描述符 得到一个新的文件描述符
int newfd = fcntl(fd,F_DUPFD);
-F_GETFD: 获取指定的文件描述符状态flag
获取的flag与通过open函数传递的flag是一个东西
-F_SETFL:设置文件描述符文件状态flag
必选项:O_RDONLY O_WRONLY O_REWD 不可以被修改
可选性:O_APPEND O_NONBLOCK
O_APPEND:表示追加数据 O_APPEND 当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。
O_NONBLOCK:设置成非阻塞
阻塞和非阻塞:描述的是函数调用的行为。
*/
#include <unistd.h>
#include <fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdio.h>
#include<string.h>
int main(){
//1.复制文件描述符
//int fd = open("1.txt",O_RDONLY);
//int ret = fcntl(fd,F_DUPFD)
//用fcntl 修改或者获取文件的状态flag
int fd = open("1.txt",O_RDWR);
if(fd == -1){
perror("open");
return -1;
}
//获取文件原有的状态描述符状态flag
int flag = fcntl(fd,F_GETFL);
if(flag == -1){
perror("fcntl");
return -1;
}
//追加数据
//修改文件描述符 然后再赋值给原文件
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;
}
2 进程
2.1 程序和进程
2.2 单道,多道程序设计
2.3 时间片
2.4 并发和并行
2.5 进程控制块
2.6 进程的状态
2.7 进程相关的命令
2.7.1 查看进程
2.7.2 STAT参数的意义
2.7.3 TOP实时显示进程信息
2.7.4 杀死进程
2.7.5 进程号相关函数 getpid
2.8 进程创建
fork函数示例
/*
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ...)
https://baike.baidu.com/item/fcntl.h/332738
作用:
复制文件描述符
用fcntl 修改或者获取文件的状态flag
参数:
-fd:表示需要操作的文件描述符
-cmd:表示对文件描述符进行如何操作
-F_DUPFD:复制文件描述符 得到一个新的文件描述符
int newfd = fcntl(fd,F_DUPFD);
-F_GETFD: 获取指定的文件描述符状态flag
获取的flag与通过open函数传递的flag是一个东西
-F_SETFL:设置文件描述符文件状态flag
必选项:O_RDONLY O_WRONLY O_REWD 不可以被修改
可选性:O_APPEND O_NONBLOCK
O_APPEND:表示追加数据 O_APPEND 当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。
O_NONBLOCK:设置成非阻塞
阻塞和非阻塞:描述的是函数调用的行为。
*/
#include <unistd.h>
#include <fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdio.h>
#include<string.h>
int main(){
//1.复制文件描述符
//int fd = open("1.txt",O_RDONLY);
//int ret = fcntl(fd,F_DUPFD)
//用fcntl 修改或者获取文件的状态flag
int fd = open("1.txt",O_RDWR);
if(fd == -1){
perror("open");
return -1;
}
//获取文件原有的状态描述符状态flag
int flag = fcntl(fd,F_GETFL);
if(flag == -1){
perror("fcntl");
return -1;
}
//追加数据
//修改文件描述符 然后再赋值给原文件
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;
}
2.9 父子进程虚拟地址空间
2.10 多进程GDB调试
2.11 exec函数族
execl函数示例
/*
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ...)
https://baike.baidu.com/item/fcntl.h/332738
作用:
复制文件描述符
用fcntl 修改或者获取文件的状态flag
参数:
-fd:表示需要操作的文件描述符
-cmd:表示对文件描述符进行如何操作
-F_DUPFD:复制文件描述符 得到一个新的文件描述符
int newfd = fcntl(fd,F_DUPFD);
-F_GETFD: 获取指定的文件描述符状态flag
获取的flag与通过open函数传递的flag是一个东西
-F_SETFL:设置文件描述符文件状态flag
必选项:O_RDONLY O_WRONLY O_REWD 不可以被修改
可选性:O_APPEND O_NONBLOCK
O_APPEND:表示追加数据 O_APPEND 当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。
O_NONBLOCK:设置成非阻塞
阻塞和非阻塞:描述的是函数调用的行为。
*/
#include <unistd.h>
#include <fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdio.h>
#include<string.h>
int main(){
//1.复制文件描述符
//int fd = open("1.txt",O_RDONLY);
//int ret = fcntl(fd,F_DUPFD)
//用fcntl 修改或者获取文件的状态flag
int fd = open("1.txt",O_RDWR);
if(fd == -1){
perror("open");
return -1;
}
//获取文件原有的状态描述符状态flag
int flag = fcntl(fd,F_GETFL);
if(flag == -1){
perror("fcntl");
return -1;
}
//追加数据
//修改文件描述符 然后再赋值给原文件
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;
}
execlp函数示例
/*
#include <unistd.h>
extern char **environ;
int execlp(const char *file, const char *arg, ...);
参数:
-会到环境变量中查找可执行文件,如果找到了就去执行,如果找不到就返回-1
-file:需要指定的执行的文件名称
a.out /home/hope/a.out
ps
-arg:是执行可执行文件的所需要的参数列表
第一个参数一般没什么作用,为了方便,一般写的是可行的程序的名称
从第二个参数开始往后就是程序执行所需要的参数列表
参数最后需要以null结束,
返回值:
只有当出错的时候才会有返回值 -1
如果调用成功没有返回值
*/
#include<unistd.h>
#include<stdio.h>
int main(){
//创建一个子进程,在子进程中执行exec函数族中的函数
pid_t pid = fork();
if(pid > 0){
//父进程ls
printf("I am parent process,pid : %d\n",getpid());
sleep(1);
}
else if(!pid){
//子进程
//execl("/home/hope/Linux_cpp/lession19/hello","hello",NULL);
execlp("ps","ps","aux",NULL);
printf("I am child process, pid : %d\n",getpid());
}
for(int i = 0;i < 3; i++){
printf("i = %d pid = %d\n",i,getpid());
}
return 0;
}
2.12 孤儿进程,僵尸进程,进程退出
2.12.1 进程退出
exit函数示例
/*
#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);
参数:
-status:是进程退出时的一个状态信息,父进程回收子进程资源的时候可以获取到。
*/
#include<stdlib.h>
#include<unistd.h>
#include<stdio.h>
int main(){
printf("hello\n");
printf("world");
//exit(0);//可以刷新缓存区 会打印出world
//由于每刷新缓冲区 所以只打印hello
_exit(0);
return 0;
}
2.12.3 孤儿进程
孤儿进程示例
#include<sys/stat.h>
#include<unistd.h>
#include<stdio.h>
//解释输出
/*
当父进程结束的时候会切换到前台,但是这个时候还有子进程没有结束,所以在终端的那里会输出子进程
而且内核区是共享的,所以文件描述符表也是共享的,0 1 2 输入输出错误描述符也是共享的,所以输出的结果在一个终端里面
*/
int main(){
//创建子进程
pid_t pid = fork();
//判断是父进程还是子进程
if(pid > 0){
//如果大于0 返回的是创建的子进程的进程号 当前是父进程
printf("I am parent process pid: %d,ppid : %d\n",getpid(),getppid());
}
else if(pid == 0){
sleep(1);
//当前是子进程
printf("I am child process pid: %d,ppid : %d\n",getpid(),getppid());
}
//for 循环
for(int i = 0;i < 5; i++){
printf("i: %d pid : %d\n",i,getpid());
}
return 0;
}
2.12.4 僵尸进程
僵尸进示例
#include<sys/stat.h>
#include<unistd.h>
#include<stdio.h>
//解释输出
/*
当父进程结束的时候会切换到前台,但是这个时候还有子进程没有结束,所以在终端的那里会输出子进程
而且内核区是共享的,所以文件描述符表也是共享的,0 1 2 输入输出错误描述符也是共享的,所以输出的结果在一个终端里面
*/
int main(){
//创建子进程
pid_t pid = fork();
//判断是父进程还是子进程
if(pid > 0){
//如果大于0 返回的是创建的子进程的进程号 当前是父进程
while (1)
{
sleep(1);
printf("I am parent process pid: %d,ppid : %d\n",getpid(),getppid());
}
}
else if(pid == 0){
//当前是子进程
printf("I am child process pid: %d,ppid : %d\n",getpid(),getppid());
}
//for 循环
for(int i = 0;i < 5; i++){
printf("i: %d pid : %d\n",i,getpid());
}
return 0;
}
输出
父进程有个while循环会一直卡住,子进程结束的时候父进程没有对它进行回收,所以变成了僵尸进程。
解决僵尸进程就是杀死它的父进程
2.12.5 进程回收
wait函数示例
/*
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
作用:等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收子进程的资源
参数:进程退出时的状态信息,传入的是一个int类型的地址,传出参数
返回值:
-成功:返回被收回的子进程的id
-失败:失败返回的是-1(所有的子进程都结束了,调用函数失败)
调用wait函数的进程会被挂起(阻塞),直到它的一个子进程退出
或者收到一个不能被忽略的信号时才被唤醒(相当于继续往下执行)
如果没有子进程了,函数立刻返回,返回-1;如果子进程都已经结束了,也会立刻返回,返回-1
pid_t waitpid(pid_t pid, int *wstatus, int options);
*/
#include<sys/wait.h>
#include<sys/types.h>
#include<stdio.h>
#include<unistd.h>
int main(){
// 有个父进程,创建5个子进程
pid_t pid;
for(int i = 0; i < 5; i++){
pid = fork();
if(!pid) break;
}
if(pid > 0){
while(1){
printf("I am parent pid : %d\n",getpid());
int ret = wait(NULL);
if(ret == -1){
break;
}
printf("child die,pid : %d\n",ret);
sleep(1);
}
}
else if(!pid){
while(1){
printf("I am child process pid : %d\n",getpid());
sleep(1);
}
}
return 0;
}
waitpid函数示例
waitpid可以回收所有的子进程
/*
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
作用:回收指定进程号的子进程,可以设置是否阻塞
参数:
-pid:
pid > 0:指定进程的pid
pid = 0:回收当前进程组的所有子进程
pid = -1:表示我要回收所有的子进程
pid < -1: 某个进程组的组id的绝对值,回收指定进程组中的子进程
-wstatus:进程退出时的状态信息,传入的是一个int类型的地址,传出参数
-options:
0:阻塞
WNOHANG:非阻塞
返回值:
>0: 返回子进程的id
=0:(options=WNOHANG),表示还有子进程活着
=-1:错误,或者没有子进程了
调用wait函数的进程会被挂起(阻塞),直到它的一个子进程退出
或者收到一个不能被忽略的信号时才被唤醒(相当于继续往下执行)
如果没有子进程了,函数立刻返回,返回-1;如果子进程都已经结束了,也会立刻返回,返回-1
*/
#include<sys/wait.h>
#include<sys/types.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
// 有个父进程,创建5个子进程
pid_t pid;
for(int i = 0; i < 5; i++){
pid = fork();
if(!pid) break;
}
if(pid > 0){
while(1){
sleep(1);
printf("I am parent pid : %d\n",getpid());
int st;
//int ret = waitpid(-1,&st,0);
int ret = waitpid(-1,&st,WNOHANG);
if(ret == -1){
break;
}
else if(!ret) continue;
else if(ret > 0){
if(WIFEXITED(st)){
//是不是正常退出
printf("退出的状态码:%d\n",WEXITSTATUS(st));
}
if(WIFSIGNALED(st)){
//是不是被干掉了
printf("被哪个信号干掉了: %d\n",WTERMSIG(st));
}
printf("child die ,pid : %d\n",ret);
}
}
}
else if(!pid){
while(1){
printf("I am child process pid : %d\n",getpid());
sleep(1);
}
exit(0);
}
return 0;
}
2.12.6 退出信息相关宏函数
2.13 进程通信
2.13.1 进程间通信方式
2.13.2 匿名管道
2.13.3 管道的特点
管道的读写特点
使用管道时,需要注意以下几点特殊的情况 (假设都是阻塞I/O操作)
1.所有的指向管道写端的文件描述符都关闭了 (管道的写端引用计数为0),有进程从管道的
读端读数据,那么管道中剩余的数据被读取以后,再次去read会返回0.就像读到文件末尾一样。
2.如果有指向管道写端的文件描述符没有关闭(管道的写端引用计数是大于0的),而持有管道写端的进程
也没有往管道中写数据,这个时候有进程从管道中读取数据,那么管道的剩余的数据被读取后,再次调用read的
时候会阻塞,直到管道中有数据可以读了,才读取到数据并返回。
3.如果所有指向管道读端的文件描述符都关闭了(管道的读端引用计数为0),这个时候有进程向管道中写数据,
该进程会收到一个信号SIGPIPE,通常会导致进程异常终止。
4.如果有指向管道写端的文件描述符没有关闭(管道的写端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道中
写数据,那么在管道被写满的时候再次调用write会阻塞,直到管道中有空位置才能再次写入数据并返回。
总结:
读管道:
管道中有数据,read返回实际读到的字节数。
管道中无数据:
写端被全部关闭,read返回0(相当于读到文件的末尾)
写端没有完全关闭,read阻塞等待
写管道:
管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号,默认终止进程)
管道没有全部被关闭:
管道已经满了,write阻塞
管道没有满,write将数据写入,并返回实际实际写入的字节数
管道阻塞非阻塞是去修改文件阻塞非阻塞,使用fcntl函数看lesson17
write和read看lessen10
2.13.4 为什么可以使用管道进行进程间的通信
2.13.5 管道的数据结构
2.13.6 匿名管道的使用
pipe函数示例
/*
#include <unistd.h>
int pipe(int pipefd[2]);
功能: 创建一个匿名管道,用来进程间通信。
参数: int pipedf[2] 这个数组是一个传出参数
pipefd[0] 对应的管道的读端
pipefd[1] 对应的管道的写端
返回值:
成功:0
失败:-1
管道默认是阻塞的:如果管道中没有数据,read阻塞,如果管道满了,write阻塞。
注意:匿名管道只能用于具有关系的进程之间的通信(父子进程,兄弟进程)
*/
// 子进程发送数据给父进程 父进程读取到数据 输出
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(){
//在fork之前创建管道
int pipefd[2];
int ret = pipe(pipefd);
if(ret == -1){
perror("pipe");
exit(-1);
}
//创建子进程
pid_t pid = fork();
if(pid > 0){
//父进程
//从管道的读取端读取数据
char buf[1024] = {0};
while(1){
int len = read(pipefd[0],buf,sizeof(buf));
if(len == -1){
perror("read");
return -1;
}
printf("parent recv : %s,pid : %d\n",buf,getpid());
//不断的向管道中写数据
char *str = "hello I am parent";
int ret = write(pipefd[1],str,strlen(str));
if(ret == -1){
perror("write");
return -1;
}
sleep(1);
}
}
//子进程与父进程的读写一定要有一个先写一个先读,不然读写会阻塞
else if(!pid){
//子进程
printf("I am child process ,pid : %d\n",getpid());
char buf[1024] = {0};
while(1){
//不断的向管道中写数据
char *str = "hello I am child";
int ret = write(pipefd[1],str,strlen(str));
if(ret == -1){
perror("write");
return -1;
}
sleep(1);
int len = read(pipefd[0],buf,sizeof(buf));
if(len == -1){
perror("read");
return -1;
}
printf("child recv : %s,pid : %d\n",buf,getpid());
}
}
return 0;
}
fpathconf函数示例
查看管道的大小
/*
#include <unistd.h>
long fpathconf(int fd, int name);
作用: 获取管道的大小
*/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(){
int pipefd[2];
int ret = pipe(pipefd);
long Size = fpathconf(pipefd[0], _PC_PIPE_BUF);
printf("pipe size : %ld\n",Size);
return 0;
}
2.13.7 有名管道
匿名管道只能作用于具有亲缘关系的进程之间,所以为了克服这个缺点引出了有名管道。
2.13.8 有名管道的使用
mkfifo函数示例
/*
创建fifo文件
1. 通过命令: mkfifo 名字
2. 通过函数:int mkfifo(const char* pathname,mode_t mode);
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数:
-pathname: 管道名称的路径
-mode:文件的权限 和open的mode 的权限是一样的 是一个八进制的数
返回值:
如果成功了返回0
失败了返回-1 并设置错误号
*/
#include <sys/types.h>
#include <sys/stat.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
//判断文件的权限
int aret = access("fifo1",F_OK);
if(aret == -1){
//文件不存在
printf("管道不存在 创建管道\n");
int ret = mkfifo("fifo1",0664);
if(ret == -1){
perror("mkfifo");
exit(0);
}
}
return 0;
}
从管道中读数据
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
//从管道中读数据
int main(){
int fd = open("test",O_RDONLY);
if(fd == -1){
perror("open");
exit(0);
}
//读数据
while(1){
char buf[1024] = {0};
int len = read(fd,buf,sizeof buf);
if(!len){
printf("写端断开连接了....\n");
break;
}
printf("read recv buf : %s\n",buf);
}
close(fd);
return 0;
}
从管道中写数据
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
/*
有名管道注意事项:
1.一个为只读而打开一个管道的进程会阻塞,直到另外一个进程为只写打开管道
2.一个为只写而打开一个管道的进程会阻塞,直到另一个进程为只读打开管道
读管道:
管道中有数据,read返回实际读到的字节数
管道中无数据:
管道写端被全部关闭,read返回0(相当于读到文件末尾)
写端没有全部被关闭,read阻塞等待
写管道:
管道读端被全部关闭 进程异常终止 (收到一个SIGPIPE信号)
管道读端没有全部关闭:
管道已经满了 write会阻塞
管道没有满 write将数据写入 并返回实际写入的字节数
*/
//向管道中写数据ls
int main(){
//判断文件的权限
int aret = access("test",F_OK);
if(aret == -1){
//文件不存在
printf("管道不存在 创建管道\n");
int ret = mkfifo("test",0664);
if(ret == -1){
perror("mkfifo");
exit(0);
}
}
//以只写的方式打开管道
int fd = open("test",O_WRONLY);
if(fd == -1){
perror("open");
exit(0);
}
//写数据
for (int i = 0; i < 100; i++)
{
char buf[1024] = {0};
//把格式化的数据写入某个字符串缓冲区。
sprintf(buf,"hello,%d\n",i);
printf("write data : %s\n",buf);
write(fd,buf,strlen(buf));
sleep(1);
}
close(fd);
return 0;
}
2.13.9 案例:使用有名管道实现聊天
chata.c
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
int main(){
int ret = access("fifo1",F_OK);
if(ret == -1){
//文件不存在
printf("管道不存在 创建对应的有名管道\n");
ret = mkfifo("fifo1",0664);
if(ret == -1){
perror("mkfifo");
exit(0);
}
}
ret = access("fifo2",F_OK);
if(ret == -1){
//文件不存在
printf("管道不存在 创建对应的有名管道\n");
ret = mkfifo("fifo2",0664);
if(ret == -1){
perror("mkfifo");
exit(0);
}
}
//以只写的方式打开管道1
int fdw = open("fifo1",O_WRONLY);
if(fdw == -1){
perror("open");
exit(0);
}
printf("打开fifo1成功,等待写入数据..\n");
//以只读的方式打开管道2
int fdr = open("fifo2",O_RDONLY);
if(fdr == -1){
perror("open");
exit(0);
}
printf("打开fifo2成功,等待读取数据..\n");
char buf[128] = {0};
//循环的写读数据
while(1){
memset(buf,0,sizeof buf);
//获取标准输入的数据
fgets(buf,128,stdin);
//写数据
ret = write(fdw,buf,strlen(buf));
if(ret == -1){
perror("write");
break;
}
//读管道数据
memset(buf,0,sizeof buf);
ret = read(fdr,buf,sizeof buf);
if(ret <= 0){
perror("read");
break;
}
printf("buf: %s",buf);
}
close(fdw);
close(fdr);
return 0;
}
chatb.c
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
int main(){
int ret = access("fifo1",F_OK);
if(ret == -1){
//文件不存在
printf("管道不存在 创建对应的有名管道\n");
ret = mkfifo("fifo1",0664);
if(ret == -1){
perror("mkfifo");
exit(0);
}
}
ret = access("fifo2",F_OK);
if(ret == -1){
//文件不存在
printf("管道不存在 创建对应的有名管道\n");
ret = mkfifo("fifo2",0664);
if(ret == -1){
perror("mkfifo");
exit(0);
}
}
//以只读的方式打开管道1
int fdr = open("fifo1",O_RDONLY);
if(fdr == -1){
perror("open");
exit(0);
}
printf("打开fifo1成功,等待读取数据..\n");
//以只写的方式打开管道2
int fdw = open("fifo2",O_WRONLY);
if(fdw == -1){
perror("open");
exit(0);
}
printf("打开fifo2成功,等待写入数据..\n");
char buf[128] = {0};
//循环的读写数据
while(1){
//读管道数据
memset(buf,0,sizeof buf);
ret = read(fdr,buf,sizeof buf);
if(ret <= 0){
perror("read");
break;
}
printf("buf: %s",buf);
memset(buf,0,sizeof buf);
//获取标准输入的数据
fgets(buf,128,stdin);
//写数据
ret = write(fdw,buf,strlen(buf));
if(ret == -1){
perror("write");
break;
}
}
close(fdw);
close(fdr);
return 0;
}
但是这种实现方式会发生阻塞,当有读端和写端同时存在时,假设在读端,如果管道中没有数据,则读端会阻塞住,所以,如果写端一直不去写数据,那么就会一直阻塞。
所以为了解决这个问题可以使用fork()函数开辟子进程,将读写操作分离。
chata.c
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
int main(){
int ret = access("fifo1",F_OK);
if(ret == -1){
//文件不存在
printf("管道不存在 创建对应的有名管道\n");
ret = mkfifo("fifo1",0664);
if(ret == -1){
perror("mkfifo");
exit(0);
}
}
ret = access("fifo2",F_OK);
if(ret == -1){
//文件不存在
printf("管道不存在 创建对应的有名管道\n");
ret = mkfifo("fifo2",0664);
if(ret == -1){
perror("mkfifo");
exit(0);
}
}
pid_t pid = fork();
if(pid > 0){
//父进程
//以只写的方式打开管道1
int fdw = open("fifo1",O_WRONLY);
if(fdw == -1){
perror("open");
exit(0);
}
printf("打开fifo1成功,等待写入数据..\n");
//写消息
char buf[128] = {0};
//循环的写读数据
while(1){
memset(buf,0,sizeof buf);
//获取标准输入的数据
fgets(buf,128,stdin);
//写数据
ret = write(fdw,buf,strlen(buf));
if(ret == -1){
perror("write");
break;
}
}
close(fdw);
}
else if(!pid){
//子进程
//以只读的方式打开管道2
int fdr = open("fifo2",O_RDONLY);
if(fdr == -1){
perror("open");
exit(0);
}
printf("打开fifo2成功,等待读取数据..\n");
char buf[128] = {0};
//循环的写读数据
while(1){
//读管道数据
memset(buf,0,sizeof buf);
ret = read(fdr,buf,sizeof buf);
if(ret <= 0){
perror("read");
break;
}
printf("buf: %s",buf);
}
close(fdr);
}
return 0;
}
chatb.c
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
int main(){
int ret = access("fifo1",F_OK);
if(ret == -1){
//文件不存在
printf("管道不存在 创建对应的有名管道\n");
ret = mkfifo("fifo1",0664);
if(ret == -1){
perror("mkfifo");
exit(0);
}
}
ret = access("fifo2",F_OK);
if(ret == -1){
//文件不存在
printf("管道不存在 创建对应的有名管道\n");
ret = mkfifo("fifo2",0664);
if(ret == -1){
perror("mkfifo");
exit(0);
}
}
pid_t pid = fork();
if(pid > 0){
//父进程
//以只读的方式打开管道1
int fdr = open("fifo1",O_RDONLY);
if(fdr == -1){
perror("open");
exit(0);
}
printf("打开fifo1成功,等待读取数据..\n");
char buf[128] = {0};
//循环的读写数据
while(1){
//读管道数据
memset(buf,0,sizeof buf);
ret = read(fdr,buf,sizeof buf);
if(ret <= 0){
perror("read");
break;
}
printf("buf: %s",buf);
}
close(fdr);
}
else if(!pid){
//子进程
//以只写的方式打开管道2
int fdw = open("fifo2",O_WRONLY);
if(fdw == -1){
perror("open");
exit(0);
}
printf("打开fifo2成功,等待写入数据..\n");
char buf[128] = {0};
//循环的读写数据
while(1){
memset(buf,0,sizeof buf);
//获取标准输入的数据
fgets(buf,128,stdin);
//写数据
ret = write(fdw,buf,strlen(buf));
if(ret == -1){
perror("write");
break;
}
}
close(fdw);
}
return 0;
}
2.13.10 内存映射
内存映射是将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。
对两个进程来说,可以将一块磁盘存储区映射到两个进程,这样两个进程就可以通信了。
设置映射与解除映射
内存映射案例,使用内存映射实现文件拷贝
//使用内存映射实现文件拷贝的功能
/*
思路:
1.对原始文件进行映射
2.创建一个新文件(拓展该文件)
3.把新文件的数据映射到该内存中
4.通过内存拷贝将第一个文件的内存数据拷贝到新的文件内存中
5.释放内存
*/
#include<sys/mman.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main(){
//打开文件
int fd = open("english.txt",O_RDWR);
if(fd == -1){
perror("open");
exit(0);
}
//获取文件大小
int Size = lseek(fd,0,SEEK_END);
//对文件进行映射
void* ptr = mmap(NULL,Size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
if(ptr == MAP_FAILED){
perror("mmap");
exit(0);
}
//创建一个新文件
//mmap的权限必须小于等于open的权限 所以应该是这里报错了
int nfd = open("english1.txt",O_RDWR | O_CREAT,0664);
if(nfd == -1){
printf("nfd\n");
perror("open");
exit(0);
}
//拓展该文件
int re = lseek(nfd,Size,SEEK_END);
write(nfd," ",1);
//映射
void* ptr1 = mmap(NULL,Size,PROT_READ | PROT_WRITE,MAP_SHARED,nfd,0);
if(ptr1 == MAP_FAILED){
perror("mmap");
exit(0);
}
//内存拷贝
memcpy(ptr1,ptr,Size);
//释放资源
munmap(ptr1,Size);
munmap(ptr,Size);
close(nfd);
close(fd);
return 0;
}
2.13.11 案例:内存映射实现亲缘进程与非亲缘进程通信
亲缘进程
/*
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
功能:映射一个文件或者设备的数据映射到内存当中
参数:
-addr:NULL,由内核指定
-length:要映射的数据的长度 这个值不能为0 建议使用文件的长度(最少是分页的整数倍)
获取文件的长度:stat lseek
stat:通过文件名filename 获取文件信息,并保存在buf所指的结构体stat中
lseek:lseek是一个用于改变读写一个文件时读写指针位置的一个系统调用。
-prot:对申请的内存映射区的操作权限 prot 参数描述了映射所需的内存保护
(并且不能与文件的打开模式冲突)。
-PROT_EXEC Pages may be executed.
-PROT_READ Pages may be read.
-PROT_WRITE Pages may be written.
-PROT_NONE Pages may not be accessed.没有权限
-flags:flags 参数确定映射的更新是否对映射同一区域的其他进程可见,
以及是否将更新传递到底层文件。
-MAP_SHARED:映射区的数据会自动和磁盘文件同步,进程间通信,必须设置这个选项
-MAP_SHARD_VALIDATE:不同步,内存映射区的数据改变了,对原来的文件不会修改
-MAP_PRIVATE:创建一个私人写时复制映射。映射的更新对映射相同文件的其他进程不可见,而且并不是 通向底层文件。
底层会重新创建一个新的文件(copy on write )
-fd:需要映射的那个文件的文件描述符
-通过open得到,open的是一个磁盘文件
-注意:文件袋 大小不能为0,open指定的权限不能和prot参数有冲突。
prot:PROT_READ open:只读/读写
prot:PROT_READ | PROT_WRITE open:读写
prot的权限必须小于等于oepn的权限
-offset:偏移量,一般不用,必须指定的是4k的整数倍才能偏移成功,0表示不偏移
返回值:
失败返回MAP_FAILED (void *) -1
int munmap(void *addr, size_t length);
功能:释放内存映射
参数:
-addr:要释放的内存的首地址
-length:要释放的内存的大小 要和mmap函数中的length的值一样
*/
/*
使用内存映射实现进程间的通信:
1.有关系的进程(父子进程)
-还没有子进程的时候
-通过唯一的父进程,先创建内存映射区
-有了内存映射区以后,创建子进程
-父子进程可以共享创建的内存映射区
2.没有关系的进程
-首先准备一个大小不是0的磁盘文件
-进程1 通过磁盘文件创建内存映射区
-得到一个操作这块内存的指针
-进程2 通过磁盘文件创建一个内存映射区
-得到一个操作这块内存的指针
-使用内存映射区通信,
注意:内存映射区通信 是非阻塞。
*/
#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<string.h>
#include<wait.h>
int main(){
//打开一个文件
int fd = open("test.txt",O_RDWR);
int size = lseek(fd,0,SEEK_END);//获取文件的大小
void* ptr = mmap(NULL,size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
//返回MAP_FAILED失败
if(ptr == MAP_FAILED){
perror("mmap");
exit(0);
}
//创建子进程
pid_t pid = fork();
if(pid > 0){
wait(NULL);
//定义一个数组
char buf[64];
strcpy(buf,(char*)ptr);
printf("read data: %s\n",buf);
}
else if(!pid){
strcpy((char *)ptr, "nihao a, parent!!!");
}
//关闭内存映射区
munmap(ptr,size);
//close(fd);
return 0;
}
非亲缘进程
pro1.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<string.h>
#include<wait.h>
int main(){
//打开一个文件
int fd = open("test.txt",O_RDWR);
if(fd == -1){
perror("open");
exit(0);
}
//获取文件的大小
int size = lseek(fd,0,SEEK_END);
//将文件映射到内存中
void* ptr = mmap(NULL,size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
if(ptr == MAP_FAILED){
perror("mmap");
exit(0);
}
//1进程写数据
char buf[1024];
fgets(buf,1024,stdin);
strcpy((char*)ptr,buf);
munmap(ptr,size);
close(fd);
}
proc2.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<string.h>
#include<wait.h>
int main(){
//打开一个文件
int fd = open("test.txt",O_RDWR);
if(fd == -1){
perror("open");
exit(0);
}
//获取文件的大小
int size = lseek(fd,0,SEEK_END);
//将文件映射到内存中
void* ptr = mmap(NULL,size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
if(ptr == MAP_FAILED){
perror("mmap");
exit(0);
}
//2进程读数据
char buf[1024];
strcpy(buf,(char *)ptr);
printf("1 s: %s\n",buf);
munmap(ptr,size);
close(fd);
}
2.13.12
内存映射相关问题
2.13.12.1.如果对mmap的返回值(ptr)做++操作(ptr++),munmap是否能够成功?
void* ptr = mmap(...);
ptr++; 可以对其进行++操作
munmap(ptr,length); // 错误
2.13.12.2.如果open时O_RDONLY ,mmap时prot参数指定 PROT_READ | PROT_WRITE 会怎么样?
会产生错误 会返回 MAP_FAILED
open()函数中的权限建议和prot参数的权限保持一致 prot的权限要小于等于open的权限
2.13.12.3.如果文件偏移量为1000会怎么样?
偏移量必须是4k的整数倍,返回MAP_FAILED
2.13.12.4.mmap什么情况下回调用失败
-第二个参数: length = 0
-第三个参数: prot的权限
-只指定了写权限
-prot指定的 PROT_READ | PROT_WRITE 但是第5个参数 fd
通过open打开时指定的是O_RDONLY 或者 O_WRONLY 就是mmap的权限小于open的权限
2.13.12.5.可以open的时候O_CREAT一个新文件来创建映射区吗?
-可以的,但是创建的文件的大小如果是0的话,肯定不行
可以对新的文件进行 lseek扩展到一定的大小 或者truncate()
2.13.12.6.mmap后关闭文件描述符,对mmap映射有没有影响?
int fd = open(...);
mmap(,,,fd,0);
close(fd);
映射区还存在,创建映射区的fd被关闭了,没有什么影响。
因为会对fd进行一个copy,所以关闭了也没事。
2.13.12.7 对ptr越界操作会怎么样?
void * ptr = mmap(NULL,100,...);
4k
越界操作 操作的是非法内存 -> 段错误
匿名映射只能用于亲缘进程,因为没有文件实体
//匿名映射
/*
匿名映射:不需要文件实体进行一个内存映射
flag = MAP_ANONTMOUS 指定匿名映射
*/
#include<sys/mman.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<wait.h>
int main(){
//创建匿名映射区
int len = 4096;
void* ptr = mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_ANONYMOUS | MAP_SHARED,-1,0);
if(ptr == MAP_FAILED){
perror("mmap");
exit(0);
}
//父子进程间通信
pid_t pid = fork();
if(pid > 0){
strcpy((char*)ptr,"hello world");
wait(NULL);
}
else if(!pid){
//内存映射非阻塞的 所以要先等待父进程写数据
sleep(1);
printf("%s\n",(char*)ptr);
}
int ret = munmap(ptr,len);
if(ret == -1){
perror("munmap");
exit(0);
}
return 0;
}
2.13.13 信号
2.13.13.1 信号对应的事件
KILL信号可以杀死任何正常的进程,僵尸进程不算正常的进程。
查看信号的详细信息: man 7 signal
2.13.13.2 信号的5种默认处理动作
编写一个错误的程序测试core文件
#include <stdio.h>
#include <string.h>
int main() {
char * buf;
strcpy(buf, "hello");
return 0;
}
tip:
有的乌班图不会生成core文件,可以看牛客网这节课下面我的留言。
使用gdb 执行a.out文件
(gdb) core-file core
[New LWP 1717669]
Core was generated by `./a.out'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x000055a8c541f135 in main () at cor.c:8
8 strcpy(buf, "hello");
(gdb)
可以看到 Program terminated with signal SIGSEGV, Segmentation fault.
2.13.13.3 alarm函数 定时器
/*
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
-功能:设置定时器,函数调用,开始倒计时,当倒计时为0的时候,
函数会给当前的进程发送一个信号:SIGALARM
-参数:
seconds:倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时)
取消一个定时器,通过alarm(0)
-返回值:
-之前有定时器,返回之前的定时器的倒计时剩余的时间
-之前没有定时器,返回0
SIGALARM:默认终止当前的进程,每一个进程都有且只有唯一的一个定时器。
alarm(10); -> 返回0
过了1秒
alarm(5); -> 返回9 不是4!!
alarm(100) -> 该函数是不阻塞的
*/
#include<unistd.h>
#include<stdio.h>
int main(){
int rt = alarm(5);
printf("%d\n",rt); //返回0
sleep(2);
rt = alarm(2); //打印3 非阻塞会继续下去
printf("%d\n",rt);
while(1){
}
return 0;
}
案例:
//1秒钟计算机能数多少个数
/*
实际运行的时间 = 内核时间 + 用户时间 + 消耗的时间
进行文件IO操作的时候比较浪费时间
定时器,与进程的状态无关(自然定时法)。无论进程处于什么状态,alarm都会进行计时
*/
#include<unistd.h>
#include<stdio.h>
int main(){
int rt = alarm(1);
int i = 0;
while(1){
printf("i = %d\n",i++);
}
return 0;
}
2.13.13.4 setitimer函数 周期定时器
/*
#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
功能:设置定时器。可以替代alarm函数。精度微秒us,可以实现周期性的定时 非阻塞
参数:
-which:定时器以什么时间计时
ITIMIER_REAL:真实时间,时间达到,发送SIGALARM信号 常用
ITIMER_VIATUAL:用户时间,时间达到,发送SIGVTALRM
ITIMER_PROF:以该进程在用户态和内核态下所消耗的时间来计算,时间达到发送SIGPROF
- new_value:设置定时器的属性
struct itimerval { //定时器的结构体
struct timeval it_interval; // Interval for periodic timer //间隔时间
struct timeval it_value; // Time until next expiration //延迟多长时间执行定时器
};
struct timeval { //时间的结构体
time_t tv_sec; // seconds //时间
suseconds_t tv_usec; // microseconds //微秒
};
-old_value:记录上一次的定时的时间参数 一般不使用,指定NULL
返回值:
0:成功
-1:失败 并设置错误号
*/
#include<sys/time.h>
#include<stdio.h>
#include<stdlib.h>
//过3s以后,每隔2秒钟定时一次
int main(){
struct itimerval new_value;
//设置值
//设置间隔的时间
new_value.it_interval.tv_sec = 2;
new_value.it_interval.tv_usec = 0;
//设置延迟的时间
new_value.it_value.tv_sec = 3;
new_value.it_value.tv_usec = 0;
int ret = setitimer(ITIMER_REAL,&new_value,NULL);
printf("定时器开始了\n");
if(ret == -1){
perror("setitimer");
exit(0);
}
getchar();
return 0;
}
2.13.13.5 kill,raise,abort函数
/*
#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid,int sig);
-功能:给任何的进程或者进程组pid,发送某个信号sig
-参数
-pid:需要发送给的进程的id
>0:将信号发送给指定的进程
=0:将信号发送给当前的进程组
= - 1:将信号发送给每一个有权限接受这个信号的进程
< -1:这个pid = 某个进程组的id取反
-sig:需要发送的信号的宏值或者编号,0表示不发送任何信号
kill(getppid(),9);
kill(getpid(),9);
int raise(int sig);
-功能:给当前进程发送信号
-参数:
-sig:要发送的信号
-返回值:
-成功:0
-失败:非0
kill(getpid(),sig);
void abort(void);
-功能:发送SIGABRT信号给当前的进程,杀死当前进程
kill(getpid(),SIGABRT);
*/
#include<sys/stat.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
int main(){
pid_t pid = fork();
if(pid > 0){
printf("parent process\n");
sleep(2);
printf("kill child process\n");
kill(pid,SIGINT);
}
else if(!pid){
for(int i = 0; i < 5; i++){
printf("child process\n");
sleep(1);
}
}
return 0;
}
####2.13.13.5 信号捕捉函数
signal函数示例
/*
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
-功能:设置某个信号的捕捉行为
-参数:
-signum:要捕捉的信号
-handler:捕捉到信号要如何处理
-SIG_IGN:忽略信号
-SIG_DFL:使用信号默认的行为
-回调函数:当发生信号时,去调用函数
-返回值:
成功:返回上一次注册的信号处理函数的地址。第一次调用返回NULL
失败:返回SIG_ERR,设置错误号
SIGKILL SIGSTOP 不能被捕捉和忽略
*/
#include<sys/time.h>
#include<stdio.h>
#include<stdlib.h>
#include <signal.h>
void myalarm(int num){
printf("捕捉到的信号的编号是:%d\n",num);
printf("xxxxxxx\n");
}
int main(){
//注册信号捕捉
//signal(SIGALRM,SIG_IGN);
//signal(SIGALRM,SIG_DFL);
//typedef void (*sighandler_t)(int); 函数指针 int 捕捉到的信号的值
signal(SIGALRM,myalarm);
struct itimerval new_value;
//设置值
//设置间隔的时间
new_value.it_interval.tv_sec = 2;
new_value.it_interval.tv_usec = 0;
//设置延迟的时间
new_value.it_value.tv_sec = 3;
new_value.it_value.tv_usec = 0;
int ret = setitimer(ITIMER_REAL,&new_value,NULL);
printf("定时器开始了\n");
if(ret == -1){
perror("setitimer");
exit(0);
}
getchar();
return 0;
}
sigaction函数
/*
#include<signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
-功能:检查或者改变信号的处理。信号捕捉
-参数:
-signum:需要捕捉的信号的编号或者宏值(信号的名称)
-act:捕捉到信号之后对应处理动作
-oldact:上一次对信号捕捉相关的设置,一般不使用 传递NULL
-返回值:
成功 0
失败 -1
struct sigaction {
void (*sa_handler)(int); //信号捕捉到之后的处理
void (*sa_sigaction)(int, siginfo_t *, void *); // 不常用
sigset_t sa_mask; //临时阻塞信号集 信号捕捉函数执行过程中,临时阻塞某些信号
int sa_flags; //使用哪一个信号处理对捕捉到的信号进行处理 如果是0表示调用sa_handler,其它调用第二个参数的函数
//这个值可以是0 也可以是sa_handler 也可以是SA_SIGINFO表示使用sa_sigaction
void (*sa_restorer)(void);//
};
*/
#include<sys/time.h>
#include<stdio.h>
#include<stdlib.h>
#include <signal.h>
void myalarm(int num){
printf("捕捉到的信号的编号是:%d\n",num);
printf("xxxxxxx\n");
}
int main(){
//注册信号捕捉
//signal(SIGALRM,SIG_IGN);
//signal(SIGALRM,SIG_DFL);
//typedef void (*sighandler_t)(int); 函数指针 int 捕捉到的信号的值
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = myalarm;
sigemptyset(&act.sa_mask); //清空临时阻塞信号集
sigaction(SIGALRM,&act,NULL);
struct itimerval new_value;
//设置值
//设置间隔的时间
new_value.it_interval.tv_sec = 2;
new_value.it_interval.tv_usec = 0;
//设置延迟的时间
new_value.it_value.tv_sec = 3;
new_value.it_value.tv_usec = 0;
int ret = setitimer(ITIMER_REAL,&new_value,NULL);
printf("定时器开始了\n");
if(ret == -1){
perror("setitimer");
exit(0);
}
//getchar();
while(1);
return 0;
}
2.13.13.6 信号集
2.13.13.7 阻塞信号集合未决信号集
2.13.13.8 信号集相关函数
/*
以下信号集相关的函数都是对自定义的信号集进行操作
#include <signal.h>
int sigemptyset(sigset_t *set)
-功能:清空信号集中的数据,将信号集中的所有的标志位置为0
-参数:set 传出参数,需要操作的信号集
-返回值:
0:成功
-1:失败
int sigfillset(sigset_t *set);
-功能:将信号集中的所有的标志位置为1
-参数:set 传出参数,需要操作的信号集
-返回值:
0:成功
-1:失败
int sigaddset(sigset_t *set, int signum);
-功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号
-参数:
-set 传出参数,需要操作的信号集
-signum:需要设置阻塞的那个信号
-返回值:
0:成功
-1:失败
int sigdelset(sigset_t *set, int signum);
-功能:设置信号集中的某一个信号对应的标志位为0,表示不阻塞这个信号
-参数:
-set 传出参数,需要操作的信号集
-signum:需要设置阻塞的那个信号
-返回值:
0:成功
-1:失败
int sigismember(const sigset_t *set, int signum);
-功能:判断某个信号是否阻塞
-参数:
-set 需要操作的信号集
-signum:需要判断的那个信号
-返回值:
0:未阻塞
1:阻塞
-1:失败
*/
#include<signal.h>
#include<stdio.h>
int main(){
//创建一个信号集
sigset_t set;
//清空信号集的内容
sigemptyset(&set);
//判断 SIGINT是否在信号里 set 里面
int ret = sigismember(&set,SIGINT);
if(!ret) printf("SIGINT 不阻塞\n");
else if(ret == 1) printf("SIGINT 阻塞\n");
//添加几个信号到信号集中
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
//判断SIGQUIT是否在信号集中
ret = sigismember(&set,SIGQUIT);
if(!ret) printf("SIGQUIT 不阻塞\n");
else if(ret == 1) printf("SIGQUIT 阻塞\n");
ret = sigismember(&set,SIGINT);
if(!ret) printf("SIGINT 不阻塞\n");
else if(ret == 1) printf("SIGINT 阻塞\n");
//删除信号
sigdelset(&set,SIGQUIT);
//判断SIGQUIT是否在信号集中
ret = sigismember(&set,SIGQUIT);
if(!ret) printf("SIGQUIT 不阻塞\n");
else if(ret == 1) printf("SIGQUIT 阻塞\n");
return 0;
}
sigpromask函数和sigpending函数
/*
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
-功能:将自定义信号集中的数据设置到内核中(设置阻塞,解除阻塞,替换)
-参数:
-how:如果对内核阻塞信号集进行处理
-SIG_BLOCK:将用户设置的阻塞信号添加到内核中,内核中原来的数据不变
假设内核中默认的阻塞信号集是信号集是mask, maks | set
-SIG_UNBLOCK: 根据用户设置的数据,对内核中的数据进行一个解除阻塞。
mask &= ~set
-SIG_SETMASK:覆盖内核中原来的值
-set:已经初始化好的用户自定义的信号集
-oldset:保存设置的之前内核中阻塞信号集的状态 NULL
-返回值:
成功:0
失败:-1
设置错误号,
-EFAULT:指向错误的地址
-EINVAL:指定的号是非法的
int sigpending(sigset_t* set)
-功能:获取内核中的未决信号集
-参数:set,传出参数,保存的是内核中的未决信号集中的信息
-返回值
成功:0
失败:-1
*/
//编写一个程序,把所有的常规信号(1 - 31) 未决状态打印到屏幕
//设置某些信号是阻塞的,通过键盘产生这些信号
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
//设置阻塞2、3号信号阻塞
sigset_t set;
sigemptyset(&set);
//将2、3号信号添加到信号集中
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
//修改内核中的阻塞信号集
sigprocmask(SIG_BLOCK,&set,NULL);
int num = 1;
while(1){
num++;
//获取当前的未决信号集的数据
sigset_t pendingset;
sigemptyset(&pendingset);
sigpending(&pendingset);
//遍历
for(int i = 1; i <= 31; i++){
//判断某个信号是否阻塞 sigismember
/*-返回值:
0:未阻塞
1:阻塞
-1:失败
*/
if(sigismember(&pendingset,i) == 1)
printf("1");
else if(sigismember(&pendingset,i) == 0)
printf("0");
else {
perror("sigismember");
exit(0);
}
}
printf("\n");
sleep(1);
if(num == 10){
//解除阻塞
sigprocmask(SIG_UNBLOCK,&set,NULL);
}
}
return 0;
}
2.13.13.9 内核实现信号捕捉的过程
2.13.13.10 SIGCHLD信号
- 子进程结束的时候,父进程会收到这个信号。
- 子进程接收到SIGSTOP信号停止时候
- 子进程处于停止态,接收到SIGCONT后唤醒
解决僵尸进程问题
/*
SIGCHLD信号产生的3个条件:
1.子进程结束
2.子进程暂停了
3.子进程继续运行
都会给父进程发送该信号,父进程默认忽略该信号。
使用SIGCHLD信号解决僵尸进程的问题。
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <sys/wait.h>
void myFun(int num) {
printf("捕捉到的信号 :%d\n", num);
// 回收子进程PCB的资源
// while(1) {
// wait(NULL);
// }
while(1) {
int ret = waitpid(-1, NULL, WNOHANG);
if(ret > 0) {
printf("child die , pid = %d\n", ret);
} else if(ret == 0) {
// 说明还有子进程或者
break;
} else if(ret == -1) {
// 没有子进程
break;
}
}
}
int main() {
// 提前设置好阻塞信号集,阻塞SIGCHLD,因为有可能子进程很快结束,父进程还没有注册完信号捕捉
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
sigprocmask(SIG_BLOCK, &set, NULL);
// 创建一些子进程
pid_t pid;
for(int i = 0; i < 20; i++) {
pid = fork();
if(pid == 0) {
break;
}
}
if(pid > 0) {
// 父进程
// 捕捉子进程死亡时发送的SIGCHLD信号
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = myFun;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD, &act, NULL);
// 注册完信号捕捉以后,解除阻塞
sigprocmask(SIG_UNBLOCK, &set, NULL);
while(1) {
printf("parent process pid : %d\n", getpid());
sleep(2);
}
} else if( pid == 0) {
// 子进程
printf("child process pid : %d\n", getpid());
}
return 0;
}
2.13.14 共享内存
2.13.14.1 共享内存使用步骤
2.13.14.2 共享内存操作函数
/*
共享内存相关函数
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key,size_t size,int shmflg);
-功能:创建一个新的共享内存段,或者获取一个既有的共享内存段的标识
新创建的内存段中的数据都会被初始化为0
-参数
-key:key_t类型是一个整型,通过这个找到或者创建一个共享内存
一般使用16进制标识非0值
-size:共享内存的大小
-shmflg:共享内存的属性
-访问权限
-附加属性:创建/判断是不是存在
-创建: IPC_CREAT
-判断共享内存是否存在:IPC_EXCL,需要和IPC_CREAT一起使用
IPC_CREAT | IPC_EXCL | 0664
-返回值:
-成功:大于0,返回的内存的引用ID,后面操作共享内存都是通过这个ID
-失败:-1并设置错误号
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid,const void* shmaddr,int shmflg)
-功能:和当前的进程关联
-参数:
-shmid:关系内存的标识(ID),由shmget返回值获取
-shmaddr:申请的关系内存的起始地址,指定为NULL,内核指定
-shmflg:对共享内存的操作
-读: SHM_RDONLY ,必须要有读权限
-读写:0
-返回值:
成功:返回共享内存的首(起始)地址,
失败:(void*) - 1
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void* shmaddr);
-功能:解除当前进程和共享内存的关联
-参数:
shmaddr:共享内存的首地址
-返回值:
-成功:0
-失败:-1
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
-功能:删除共享内存,共享内存要删除才会小时,创建共享内存的进程被销毁了对共享内存是没有影响的
-参数:
-shmid:共享内存的ID
-cmd:要做的操作
-IPC_STAT:获取共享内存的当前的状态
-IPC_SET:设置共享内存的状态
-IPC_RMID:标记共享内存需要被销毁
-buf:需要设置或者获取的共享内存的属性信息
-IPC_STAT:buf存储数据
-IPC_SET:buf中需要初始化数据,设置到内核中
-IPC_RMID:没有用, NULL
struct shmid_ds {
struct ipc_perm shm_perm; // Ownership and permissions
size_t shm_segsz; // Size of segment (bytes)
time_t shm_atime; // Last attach time
time_t shm_dtime; // Last detach time
time_t shm_ctime; // Last change time
pid_t shm_cpid; // PID of creator
pid_t shm_lpid; // PID of last shmat(2)/shmdt(2)
shmatt_t shm_nattch; // No. of current attaches
...
};
key_t ftok(const char* pathname,int proj_id);
-功能:根据指定的路径名,和int值,生产一个共享内存的key
-参数:
-pathname:指定一个存在的路径
/home/hope/Linux_cpp/a.txt
/
-proj_id:int类型的值,但是系统调用只会使用其中的1个字节
范围:0 - 255 一般指定一个字符'a'
*/
2.13.14.3 共享内存操作命令
2.13.14.4 共享内存的一些问题
问题1:操作系统如何知道一块内存被多少个程序共享
-共享内存维护一个结构体struct shmid_ds 这个结构体中有一个成员 shm_nattch
-shm_nattch 记录了关联的进程个数
问题2:可以不可以对共享内存进行多次删除 shmctl
-可以的
-以为shmctl标记删除共享内存,不是直接删除
当和共享内存关联的进程数为0的时候,就真正被删除
-当共享内存的key为0的时候,表示共享内存被标记删除了
如果一个进程和共享内存取消关联,那么这个进程就不能继续操作这个共享内存。也不能进行关联
问题3:共享内存和内存映射的区别
1.共享内存可以直接创建,内存映射需要磁盘文件(匿名映射除外)
2.共享内存效率更高
3.内存:
所有的进程操作的是同一块共享内存
内存映射,每个进程在自己的虚拟地址空间有一个独立的内存
问题4:数据安全问题
-进程突然退出
共享内存还存在
内存映射区消失
-运行进程的电脑突然死机了
共享内存 数据不在
内存映射区的数据还在(磁盘文件的数据还在,所以内存映射的数据还在)
问题5:声明周期
-内存映射区,进程退出,内存映射区销毁
-共享内存:进程退出,共享内存还在,标记删除(所有的关联的进程数为0),或者关机
如果一个进程退出,会自动和共享内存取消关联
2.13.15 守护进程
2.13.15.1 终端
2.13.15.2 进程组
2.13.15.3 会话
2.13.15.4 进程组、会话。控制终端的关系
2.13.15.5 进程组、会话操作函数
2.13.15.6 守护进程
2.13.15.7 守护进程的创建
子进程调用setsid是为了脱离控制终端,而且创建新的会话,新的组ID是子进程的ID,与父进程的组ID不同。
/*
写一个守护进程,每隔2s获取一下系统时间,将这个时间写入到磁盘文件中。
*/
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/time.h>
#include<signal.h>
#include<time.h>
#include<string.h>
void work(int num){
//捕捉到信号之后,获取系统时间,写入磁盘文件
time_t tm = time(NULL);
struct tm* loc = localtime(&tm);
// char buf[1024];
// sprintf(buf,"%d-%d-%d %d:%d:%d\n",loc->tm_yday,loc->tm_mon,loc->tm_mday,loc->tm_hour,
// loc->tm_min,loc->tm_sec);
// printf("%s\n",buf);
char* str = asctime(loc);
int fd = open("time.txt",O_RDWR | O_CREAT | O_APPEND,0664);
write(fd,str,strlen(str));
close(fd);
}
int main(){
//1.创建子进程,退出父进程
pid_t pid = fork();
if(pid > 0){
exit(0);
}
//2.将子进程重新创建一个会话
setsid();
//3.设置掩码 umask
umask(022);
//4.更改工作目录
chdir("/home/hope");
//5.关闭,重定向文件描述符 0 1 2 三个
int fd = open("/dev/null",O_RDWR);
dup2(fd,STDIN_FILENO);
dup2(fd,STDOUT_FILENO);
dup2(fd,STDERR_FILENO);
//6.每隔2s获取系统时间
//捕捉定时信号
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = work;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM,&act,NULL);
//创建定时器
struct itimerval val;
//延时时间
val.it_interval.tv_sec = 2;
val.it_interval.tv_usec = 0;
//间隔时间
val.it_value.tv_sec = 2;
val.it_value.tv_usec = 0;
setitimer(ITIMER_REAL,&val,NULL);
while(1){
sleep(10);
}
return 0;
}
3.线程
3.1 线程概述
3.2 线程和进程的区别
3.3 线程之间共享和非共享资源
3.4 线程操作
3.4.1 创建线程
pthread_create
/*
一般情况下,main函数所在的线程我们称之为主线程(main线程),其余创建的线程称之为子线程
程序中默认只有一个进程,fork()函数,会产生一个进程
程序中默认只有一个线程,pthread_create函数调用,程序中2个线程。
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
-功能:创建一个子线程
-参数:
-thread:传出参数,线程创建成功后的子线程ID
-attr:设置线程的属性,一般使用默认值,NULL
-start_routine:函数指针,这个函数是子线程需要处理的逻辑代码
-arg:给第三个参数使用,主要是传参
-返回值:
成功:0
失败:返回错误号,这个错误号和之前的errno不太一样
获取错误号信息:char* strerror(int errnum);
*/
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<unistd.h>
void* callback(void* arg){
printf("child thread ...\n");
printf("arg value : %d\n",*(int *)(arg));
return NULL;
}
int main(){
pthread_t tid;
int num = 10;
//创建子进程
int ret = pthread_create(&tid,NULL,callback,(void *)&num);
if(ret != 0){
char* errstr = strerror(ret);
printf("error : %s\n",errstr);
}
for(int i = 0; i < 5; i++){
printf("%d\n",i);
}
sleep(1);
return 0;
}
3.4.2 关闭线程,获取线程ID,判断相等
pthread_exit
pthread_equal
pthread_self
/*
#include <pthread.h>
void pthread_exit(void *retval);
-功能:终止一个线程,在那个线程中调用,就表示终止哪个线程
-参数:
-retval:需要传递一个指针,作为一个返回值,可以在pthread_join中获取到.
pthread_t pthread_self();
-功能:获取线程的ID
int pthread_equal(pthread_t t1, pthread_t t2);
-功能:比较两个线程ID是否相等
为什么不用 == :不同的操作系统,pthread_t 类型的实现不一样,有的是无符号的长整型,有的是结构体
*/
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<unistd.h>
void* callback(void* arg){
printf("child phread id : %ld\n",pthread_self());
return NULL;
}
int main(){
//创建一个子线程
pthread_t tid;
int ret = pthread_create(&tid,NULL,callback,NULL);
if(ret != 0){
char* str = strerror(ret);
printf("error num : %s\n",str);
}
//主线程
for(int i = 0; i < 5; i++){
printf("%d\n",i);
}
printf("tid : %ld,main thread id : %ld\n",tid,pthread_self());
//当主线程退出,不会影响其他正常运行的线程
pthread_exit(NULL);
return 0;
}
3.4.3 与其它线程连接
pthread_join
/*
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
-功能:和一个已经终止的线程进行连接
回收子线程的资源
这个函数是阻塞函数,调用一次只能回收一个子线程
一般在主线程中去使用
-参数:
-thread:要回收的子线程的ID
-retval:接收子线程退出时的返回值
-返回值:
成功:0
失败:错误号 非0
*/
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
int value = 10;
void* callback(void* arg){
printf("child phread id : %ld\n",pthread_self());
sleep(3);
pthread_exit((void *)&value);
}
int main(){
//创建一个子线程
pthread_t tid;
int ret = pthread_create(&tid,NULL,callback,NULL);
if(ret != 0){
char* str = strerror(ret);
printf("error num : %s\n",str);
}
//主线程
for(int i = 0; i < 5; i++){
printf("%d\n",i);
}
printf("tid : %ld,main thread id : %ld\n",tid,pthread_self());
//阻塞 直到子线程结束
//主线程调用pthread_join 回收子线程的资源
int *thread_return_value;
ret = pthread_join(tid,(void**)&thread_return_value);
if(ret != 0){
char * str = strerror(ret);
printf("error : %s\n",str);
}
printf("回收子线程资源之后成功!\n");
printf("子线程返回值为: %d\n",*(int *)thread_return_value);
//当主线程退出,不会影响其他正常运行的线程
pthread_exit(NULL);
return 0;
}
3.4.4 分离线程
pthread_detach
/*
#include <pthread.h>
int pthread_detach(pthread_t thread);
-功能:分离一个线程,被分离的线程在终止的时候,会自动释放资源返回给系统。
1.不能多次分离,会产生不可预料的行为。
2.不能去join 连接一个已经分离的线程,会报错。
-参数:
需要分离的线程的ID
-返回:
成功:0
失败:错误号
*/
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<string.h>
void * callback(void* arg){
printf("child tid : %ld\n",pthread_self());
pthread_exit(NULL);
}
int main(){
//创建一个子线程
pthread_t tid;
int ret = pthread_create(&tid,NULL,callback,NULL);
if(ret != 0){
char * str = strerror(ret);
printf("error1 : %s\n",str);
}
//输出主线程和子线程的ID
printf("tid : %ld, main thrad id : %ld\n",tid,pthread_self());
//设置子线程分离,子线程分离后,子线程结束时对应的资源就不需要主线程去释放
ret = pthread_detach(tid);
if(ret != 0){
char * str = strerror(ret);
printf("error2 : %s\n",str);
}
//设置分离后的,对分离的子线程进行连接
// ret = pthread_join(tid,NULL);
// if(ret != 0){
// char * str = strerror(ret);
// printf("error3 : %s\n",str);
// }
pthread_exit(NULL);
return 0;
}
3.4.5 线程取消
pthread_cancle
/*
#include <pthread.h>
int pthread_cancel(pthread_t thread);
-功能:取消线程(让线程终止)
取消某个线程,可以终止某个线程的运行,
但是并不是立马终止,而是当子线程执行到一个取消点,线程才会终止
取消点:系统规定好的一些系统调用,可以粗略的理解为从用户区到内核区的切换,这个位置称之为取消点。
*/
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<string.h>
void * callback(void* arg){
printf("child tid : %ld\n",pthread_self());
for(int i = 0; i < 5; i++){
printf("child : %d\n",i);
}
return NULL;
}
int main(){
//创建一个子线程
pthread_t tid;
int ret = pthread_create(&tid,NULL,callback,NULL);
if(ret != 0){
char * str = strerror(ret);
printf("error1 : %s\n",str);
}
//取消线程
pthread_cancel(tid);
for(int i = 0; i < 5; i++){
printf("%d\n",i);
}
//输出主线程和子线程的ID
printf("tid : %ld, main thrad id : %ld\n",tid,pthread_self());
pthread_exit(NULL);
return 0;
}
3.5 线程同步
3.6 互斥量
3.7 互斥量操作函数
/*
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t* restrict attr);
-功能:初始化互斥量
-参数:
-mutex:要初始化的互斥量变量,
-attr:互斥量相关的属性,NULL
-restrict:C语言的修饰符,被修饰的指针,不能由另外一个指向进行操作
pthread_mutexattr_t* restrict mutex = xxx;
pthread_mutexattr_t* mutex1 = mutex;//报错
int pthread_mutx_destroy(pthread_mutex_t *mutex);
-功能:释放互斥量的资源
int pthread_mutex_lock(pthread_mutex_t * mutex);
-功能:加锁,阻塞的 如果有其他的线程加锁了,这个线程需要阻塞
int pthread_mutex_trylock(pthread_mutex_t * mutex);
-功能:尝试加锁,如果加锁失败,会直接返回
int pthread_mutex_lunock(pthread_mutex_t * mutex);
-功能:解锁
*/
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
//创建一个互斥量
pthread_mutex_t mutex;
int ticket = 1000;
void* sellticket(void* arg){
//卖票
while(1){
//加锁
pthread_mutex_lock(&mutex);
if(ticket > 0){
printf("%ld 正在卖第 %d 张门票\n",pthread_self(),ticket);
ticket--;
//break;
}
else{
//解锁 防止资源没有了还在阻塞
pthread_mutex_unlock(&mutex);
break;
}
//解锁
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main(){
//初始化互斥量
pthread_mutex_init(&mutex,NULL);
//创建3个子线程
pthread_t tid1, tid2, tid3;
pthread_create(&tid1,NULL,sellticket,NULL);
pthread_create(&tid2,NULL,sellticket,NULL);
pthread_create(&tid3,NULL,sellticket,NULL);
//回收子线程的资源
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
//或者设置线程分离 其实没有意义 子线程的资源已经被主线程回收
// pthread_detach(tid1);
// pthread_detach(tid2);
// pthread_detach(tid3);
pthread_exit(NULL);//退出主线程
//释放互斥量
pthread_mutex_destroy(&mutex);
return 0;
}
3.8 死锁
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
//创建一个互斥量
pthread_mutex_t mutex;
int ticket = 1000;
void* sellticket(void* arg){
//卖票
while(1){
//加锁
//pthread_mutex_lock(&mutex);
pthread_mutex_lock(&mutex);
if(ticket > 0){
printf("%ld 正在卖第 %d 张门票\n",pthread_self(),ticket);
ticket--;
//break;
}
else{
//解锁 防止资源没有了还在阻塞
pthread_mutex_unlock(&mutex);
break;
}
//解锁
//pthread_mutex_unlock(&mutex);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main(){
//初始化互斥量
pthread_mutex_init(&mutex,NULL);
//创建3个子线程
pthread_t tid1, tid2, tid3;
pthread_create(&tid1,NULL,sellticket,NULL);
pthread_create(&tid2,NULL,sellticket,NULL);
pthread_create(&tid3,NULL,sellticket,NULL);
//回收子线程的资源
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
//或者设置线程分离 其实没有意义 子线程的资源已经被主线程回收
// pthread_detach(tid1);
// pthread_detach(tid2);
// pthread_detach(tid3);
pthread_exit(NULL);//退出主线程
//释放互斥量
pthread_mutex_destroy(&mutex);
return 0;
}
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<unistd.h>
pthread_mutex_t mutex1,mutex2;
void* work1(void* arg){
pthread_mutex_lock(&mutex1);
sleep(1);
pthread_mutex_lock(&mutex2);
printf("work A ...\n");
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
return NULL;
}
void* work2(void* arg){
pthread_mutex_lock(&mutex2);
sleep(1);
pthread_mutex_lock(&mutex1);
printf("work B ...\n");
pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex2);
return NULL;
}
int main(){
//初始化互斥量
pthread_mutex_init(&mutex1,NULL);
pthread_mutex_init(&mutex2,NULL);
//创建两个子线程
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,work1,NULL);
pthread_create(&tid2,NULL,work2,NULL);
//回收子线程资源
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
//释放
pthread_mutex_destroy(&mutex1);
pthread_mutex_destroy(&mutex2);
return 0;
}
3.9 读写锁
/*
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
*/
//案例:8个线程 操作同一个全局变量
//3个线程不定时的写一个全局变量 其余5个线程不定时的去读全局变量
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
pthread_rwlock_t mutex;
int num = 1;
void* writeNum(void* arg){
while(1){
pthread_rwlock_wrlock(&mutex);
num++;
printf("write ,tid %ld,num : = %d\n",pthread_self(),num);
pthread_rwlock_unlock(&mutex);
usleep(100);
}
return NULL;
}
void* readNum(void* arg){
while(1){
pthread_rwlock_rdlock(&mutex);
printf("read ,tid %ld,num : = %d\n",pthread_self(),num);
pthread_rwlock_unlock(&mutex);
usleep(100);
}
return NULL;
}
int main(){
//初始化读写锁
pthread_rwlock_init(&mutex,NULL);
//3个线程去写
//5个线程去读
pthread_t wtids[3],rtids[5];
for(int i = 0; i < 3; i++){
pthread_create(&wtids[i],NULL,writeNum,NULL);
}
for(int i = 0; i < 5; i++){
pthread_create(&rtids[i],NULL,readNum,NULL);
}
//线程分离
for(int i = 0; i < 3; i++){
pthread_detach(wtids[i]);
}
for(int i = 0; i < 5; i++){
pthread_detach(rtids[i]);
}
pthread_rwlock_destroy(&mutex);![image.png](/upload/2022/07/image-5035ddc64dd240a4a6a3a9d7f27e4fdc.png)
pthread_exit(0);
return 0;
}
3.10 生产者消费者模型
模型一共有三个要素:
- 生产者
- 消费者
- 容器
当容器装满时,生产者阻塞并通知消费者进行消费。
当容器空时,消费者通知生产者进行生产。
生产者和消费者可以有多个。
这个程序有问题,如果生产者供应不上消费者的速度就会出现段错误。
/*
生产者消费者模型(粗略版本)
*/
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
struct Node{
int num;
struct Node* next;
}*head;
void* product(void* arg){
//不断的创建新的结点 添加到链表中
while(1){
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->next = head;
head = newNode;
newNode->num = rand();
printf("add node ,num : %d,tid : %ld\n",newNode->num,pthread_self());
usleep(100);
}
return NULL;
}
void* customer(void* arg){
while(1){
struct Node* tmp = head;
head = head->next;
printf("del node , num : %d,tid : %ld\n",tmp->num,pthread_self());
usleep(100);
}
return NULL;
}
int main(){
//创建5个生产者 5个消费者
pthread_t ptids[5],ctids[5];
for(int i = 0; i < 5; i++){
pthread_create(&ptids[i],NULL,product,NULL);
pthread_create(&ctids[i],NULL,customer,NULL);
}
for(int i = 0; i < 5; i++){
pthread_detach(ptids[i]);
pthread_detach(ctids[i]);
}
while(1){
sleep(10);
}
pthread_exit(NULL);
return 0;
}
3.11 条件变量
**某个条件满足后阻塞,或者某个条件满足后解除阻塞。
完成线程进程的同步还是通过互斥量来完成。
**
/*
条件变量类型 pthread_cond_t
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t * restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t* restrict cond,pthread_mutex_t *restrict mutex);
-功能:阻塞函数,调用了该函数,线程会阻塞
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,struct timespec *restrict abstime);
-功能:等待多长时间,调用了这个函数,直到时间之前,线程会阻塞
int pthread_cond_signal(pthread_cond_t *cond);
-功能:唤醒一个或者多个等待的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
-功能:唤醒所有的等待的线程
*/
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
//创建一个互斥量
pthread_mutex_t mutex;
//创建一个条件变量
pthread_cond_t cond;
struct Node{
int num;
struct Node* next;
}*head;
void* product(void* arg){
//不断的创建新的结点 添加到链表中
while(1){
pthread_mutex_lock(&mutex);
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->next = head;
head = newNode;
newNode->num = rand();
printf("add node , num : %d,tid : %ld\n",newNode->num,pthread_self());
//只要生产一个就通知消费者消费
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
usleep(100);
}
return NULL;
}
void* customer(void* arg){
while(1){
pthread_mutex_lock(&mutex);
if(head != NULL){
struct Node* tmp = head;
head = head->next;
printf("del node , num : %d,tid : %ld\n",tmp->num,pthread_self());
pthread_mutex_unlock(&mutex);
usleep(100);
}
else{
//没有数据需要等待
//这个函数被阻塞了之后 会释放锁 ,被唤醒的时候会加锁
pthread_cond_wait(&cond,&mutex);
//不加这一条会卡住
/*
卡住的原因是 有A线程来持有锁,没有数据所以会阻塞,但是这个时候突然有B线程
把锁抢走了,但是也没有资源,B线程把锁持有了,A线程想去加锁,被阻塞。
*/
pthread_mutex_unlock(&mutex);
}
}
return NULL;
}
int main(){
//初始化互斥量
pthread_mutex_init(&mutex,NULL);
//初始化条件变量
pthread_cond_init(&cond,NULL);
//创建5个生产者 5个消费者
pthread_t ptids[5],ctids[5];
for(int i = 0; i < 5; i++){
pthread_create(&ptids[i],NULL,product,NULL);
pthread_create(&ctids[i],NULL,customer,NULL);
}
//使用join保证 子线程结束之前 mutex没有被释放
for(int i = 0; i < 5; i++){
pthread_join(ptids[i],NULL);
pthread_join(ctids[i],NULL);
}
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
pthread_exit(NULL);
return 0;
}
3.12 信号量
/*
#include<semaphore.h>
int sem_init(sem_t *sem,int pshared,unsigned int value);
-功能:初始化信号量
-参数:
-sem:信号量
-pshared:如果是0的话用在线程 非0用在进程
-value:sem的值
int sem_destroy(sem_t * sem);
-功能:释放信号量
int sem_wait(sem_t *sem);
-功能: -1 阻塞,判断sem值是不是0 如果是0阻塞 对信号量加锁
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem,const struct timespec *abs_timeout);
int sem_post(sem_t *sem);
-功能: + 1 对信号量解锁
int sem_getvalue(sem_t *sem,int *sval);
-功能:获取sem的值
sem_t psem,csem;
init(psem,0,8);
init(csem,0,0);
producer(){
sem_wait(&psem);
sem_post(&csem);
}
customer(){
sem_wait(&csem);
sem_post(&psem);
}
*/
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
//创建一个互斥量
pthread_mutex_t mutex;
//创建两个信号量
sem_t psem,csem;
struct Node{
int num;
struct Node* next;
}*head;
void* product(void* arg){
//不断的创建新的结点 添加到链表中
while(1){
sem_wait(&psem);
pthread_mutex_lock(&mutex);
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->next = head;
head = newNode;
newNode->num = rand();
printf("add node , num : %d,tid : %ld\n",newNode->num,pthread_self());
pthread_mutex_unlock(&mutex);
//生产了n个 消费者就可以消费n个
sem_post(&csem);
usleep(100);
}
return NULL;
}
void* customer(void* arg){
while(1){
sem_wait(&csem);
pthread_mutex_lock(&mutex);
struct Node* tmp = head;
head = head->next;
printf("del node , num : %d,tid : %ld\n",tmp->num,pthread_self());
pthread_mutex_unlock(&mutex);
//消费者处于空闲 通知生产者去生产
sem_post(&psem);
}
return NULL;
}
int main(){
//初始化互斥量
pthread_mutex_init(&mutex,NULL);
//初始化信号量
sem_init(&psem,0,5);
sem_init(&csem,0,0);
//创建5个生产者 5个消费者
pthread_t ptids[5],ctids[5];
for(int i = 0; i < 5; i++){
pthread_create(&ptids[i],NULL,product,NULL);
pthread_create(&ctids[i],NULL,customer,NULL);
}
//使用join保证 子线程结束之前 mutex没有被释放
for(int i = 0; i < 5; i++){
pthread_join(ptids[i],NULL);
pthread_join(ctids[i],NULL);
}
pthread_mutex_destroy(&mutex);
pthread_exit(NULL);
return 0;
}
4. Linux网络编程
4.1 网络结构模式
4.1.1 C/S结构
4.1.2 B/S结构
4.2 MAC地址
4.3 IP地址
4.3.1 简介
4.3.2 IP地址编址方式
4.3.2.1 A类IP地址
4.3.2.2 B类地址
4.3.2.3 C类地址
4.3.2.4 D类地址
4.3.2.5 特殊的网址
4.3.2.6 子网掩码
4.4 端口
4.4.1 简介
4.4.2 端口类型
4.5 网络模型
4.5.1 OSI七层参考模型
4.5.2 TCP/IP四层模型
4.6 协议
4.6.1 简介
4.6.2 常见协议
4.6.3 UDP
4.6.4 TCP
4.6.5 IP
4.6.6 以太网帧协议
4.6.7 ARP协议
4.6.8 封装
4.6.9 分用
4.7 socket
4.7.1 socket介绍
4.7.2 字节序
4.7.2.1 简介
4.7.2.2 字节序举例
4.7.2.3 字节序转换函数
h:主机
n:网络
4.7.3 TCP 通信流程
4.7.4 套接字函数
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h> //包含了这个头文件 上面两个就可以省略
int socket(int domain, int type, int protocal)
-功能:创建一个套接字
-参数:
-domain:
AF_INET: ipv4
AF_INET^6: ipv6
AF_UNIX, AF_LOCAL:本地套接字通信(进程间通信)
-type:通信过程中使用的协议类型
SOCK_STREAM:流式协议
SOCK_DGRAM:报式协议
-protocol:具体的一个协议,一般写0
SOCK_STREAM:默认使用TCP
SOCK_DGRAM:默认使用UDP
-返回值:
成功:0返回文件描述符,操作的是内核缓冲区
失败:-1 设置错误号
int bind(int sockfd, const struct sockaddr* addr, sicklen_t addrlen);
-功能:绑定,将fd和本地IP + 短裤进行绑定
-参数:
-sockfd:通过socket函数得到的文件描述符
-addr:需要绑定的ocket地址,这个地址封装了ip和端口号的信息
-addrlen:第二个参数的内存大小
-返回值:
成功:0
失败:-1 设置错误号
int listen(int sockfd, int backlog)
-功能:监听这个socket上的连接
-参数:
-sockfd:通过socket函数得到的文件描述符
-backlog:未连接和已连接的最大值
-返回值:
成功:0
是被:-1 设置错误号
int accept(int sockfd, struct sckaddr* addr, socklen_t* addrlen)
-功能:接收客户端连接,默认一个阻塞的函数,阻塞等待客户连接
-参数:
-sockfd:用于监听的文件描述符
-addr:传出参数 记录了连接成功后客户端的地址信息 socket地址
-addrlen:第二个参数对应的内存大小
-返回值:
成功:用于通信的文件描述符
失败:-1 设置错误号
int connect(int sockfd, const struct sockaddr* addr, socklen_t socklen)
-功能:客户端连接服务器
-参数:
-sockfd:用于通信的文件描述符
-addr:服务器的地址信息
-socklen:传入的addr大小
-返回值:
成功:0
失败:-1
ssize_t write(int fdm const void* buf, size_t count);
-功能:写数据
ssize_t read(intfd, void* buf, size_t count);
-功能:读数据
4.7.5 简单服务器客户端通信
server:
//TCP 通信的服务端
#include<arpa/inet.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
int main(){
//1.用于监听的socket
int dogsocket = socket(AF_INET, SOCK_STREAM,0);
if(dogsocket == -1){
perror("socket");
exit(-1);
}
//2.绑定
struct sockaddr_in sockaddr;
sockaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1",&sockaddr.sin_addr.s_addr);
//sockaddr.sin_addr,.addr = INADDR_ANY //0.0.0.0
sockaddr.sin_port = htons(9999);
int ret = bind(dogsocket, (struct sockaddr*)&sockaddr,sizeof(sockaddr));
if(ret == -1){
perror("bind");
exit(-1);
}
//3. 监听
ret = listen(dogsocket,8);
if(ret == -1){
perror("listen");
exit(-1);
}
//4. 接收
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int csockfd = accept(dogsocket,(struct sockaddr*)&clientaddr, &len);
if(csockfd == -1){
perror("accept");
exit(-1);
}
//输出客户端信息
char clientIP[16];
inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP));
unsigned short clientPort = ntohs(clientaddr.sin_port);
printf("client ip is %s, port is %d\n", clientIP, clientPort);
//5.
//获取客户端的数据
char recvBuf[1024] = {0};
int num = read(csockfd, recvBuf, sizeof(recvBuf));
if(num == -1){
perror("read");
exit(-1);
}
else if(num == 0){
//表示客户端断开连接
printf("client closed...\n");
}
else if(num > 0){
printf("recv client data %s:\n",recvBuf);
}
//给客户端发送数据
char* sendBuf = "hello";
len = write(csockfd,sendBuf,strlen(sendBuf));
if(len == -1){
}
close(dogsocket);
close(csockfd);
return 0;
}
client:
//TCP 通信的客户端
#include<arpa/inet.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
int main(){
//1. 创建套接字
int fd = socket(AF_INET,SOCK_STREAM,0);
//2.连接服务器端
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1",&serveraddr.sin_addr.s_addr);
serveraddr.sin_port = htons(9999);
int ret = connect(fd,(struct sockaddr*)&serveraddr, sizeof (serveraddr));
if(ret == -1){
perror("connect");
exit(-1);
}
//通信
//给服务器端发送数据
char *data = "hello";
write(fd,data,strlen(data));
char recvBuf[1024] = {0};
int len = read(fd,recvBuf,sizeof(recvBuf));
if(len == 1){
perror("read");
exit(-1);
}
else if(len > 0){
printf("recv server data: %s\n",recvBuf);
}
else if(len == 0){
//服务端断开连接
printf("server close\n");
}
close(fd);
return 0;
}
4.8 TCP三次握手
4.9 滑动窗口
4.10 四次挥手
4.11 TCP通信并发
要实现TCP通信服务器处理并发任务,使用多线程或者多进程来解决。
思路:
- 一个父进程,多个子进程
- 父进程负责等待并接受客户端的连接
- 子进程:完成通信,接受一个客户端连接,就创建一个子进程用于通信。
server_process
#include<arpa/inet.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<signal.h>
#include<wait.h>
void recyLeChild(int arg){
while(1){
int ret = waitpid(-1, NULL, WNOHANG);
if(ret == -1){
//所有的子进程都回收完了
break;
}
else if(ret == 0){
//还有子进程
break;
}
else if(ret > 0){
//被回收了
printf("子进程 %d 被回收了\n",ret);
}
}
}
int main(){
struct sigaction act;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = recyLeChild;
//注册信号捕捉
sigaction(SIGCHLD, &act, NULL);
//创建一个socket
int dogsocket = socket(AF_INET,SOCK_STREAM,0);
if(dogsocket == -1){
perror("socket");
exit(-1);
}
//调用bind绑定
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
inet_pton(AF_INET,"127.0.0.1",&saddr.sin_addr.s_addr);
saddr.sin_port = htons(9999);
int ret = bind(dogsocket, (struct sockaddr*)&saddr, sizeof(saddr));
if(ret == -1){
perror("bind");
exit(-1);
}
//监听
ret = listen(dogsocket,8);
if(ret == -1){
perror("listen");
exit(-1);
}
while(1){
//调用accept
struct sockaddr_in curaddr;
socklen_t size = sizeof(curaddr);
int cursock = accept(dogsocket, (struct sockaddr*)&curaddr, &size);
if(cursock == -1){
perror("accept");
exit(-1);
}
//每一个连接进来,创建一个子进程根客户单通信
pid_t pid = fork();
if(!pid){
//子进程
//获取客户端的信息
char clientIP[16];
inet_ntop(AF_INET, &curaddr.sin_addr.s_addr,clientIP, sizeof(clientIP));
unsigned short clientPort = ntohs(curaddr.sin_port);
printf("chlient ip is : %s,port is : %d\n",clientIP,clientPort);
//通信
//接收客户端的信息
char recvBuf[1024];
while (1)
{
int len = read(cursock, recvBuf, sizeof(recvBuf));
if(len == -1){
perror("read");
exit(-1);
}
else if(len > 0){
printf("recv client message : %s\n",recvBuf);
}
else if(!len){
printf("client close\n");
}
write(cursock, recvBuf,strlen(recvBuf) + 1);
}
close(cursock);
//退出当前子进程
exit(0);
}
}
close(dogsocket);
return 0;
}
通过线程实现
server_pthread
#include<arpa/inet.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<signal.h>
#include<wait.h>
#include<pthread.h>
struct sockinfo{
int fd;
pthread_t tid;
struct sockaddr_in addr;
}sockinfos[128];
void * work(void * arg) {
// 子线程和客户端通信 cfd 客户端的信息 线程号
// 获取客户端的信息
struct sockinfo * pinfo = (struct sockinfo *)arg;
char cliIp[16];
inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, cliIp, sizeof(cliIp));
unsigned short cliPort = ntohs(pinfo->addr.sin_port);
printf("client ip is : %s, prot is %d\n", cliIp, cliPort);
// 接收客户端发来的数据
char recvBuf[1024];
while(1) {
int len = read(pinfo->fd, &recvBuf, sizeof(recvBuf));
if(len == -1) {
perror("read");
exit(-1);
}else if(len > 0) {
printf("recv client : %s\n", recvBuf);
} else if(len == 0) {
printf("client closed....\n");
break;
}
write(pinfo->fd, recvBuf, strlen(recvBuf) + 1);
}
close(pinfo->fd);
return NULL;
}
int main(){
// 创建socket
int lfd = socket(PF_INET, SOCK_STREAM, 0);
if(lfd == -1){
perror("socket");
exit(-1);
}
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
saddr.sin_addr.s_addr = INADDR_ANY;
// 绑定
int ret = bind(lfd,(struct sockaddr *)&saddr, sizeof(saddr));
if(ret == -1) {
perror("bind");
exit(-1);
}
// 监听
ret = listen(lfd, 128);
if(ret == -1) {
perror("listen");
exit(-1);
}
// 初始化数据
int max = sizeof(sockinfos) / sizeof(sockinfos[0]);
for(int i = 0; i < max; i++) {
bzero(&sockinfos[i], sizeof(sockinfos[i]));
sockinfos[i].fd = -1;
sockinfos[i].tid = -1;
}
// 循环等待客户端连接,一旦一个客户端连接进来,就创建一个子线程进行通信
while(1) {
struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);
// 接受连接
int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);
struct sockinfo * pinfo;
for(int i = 0; i < max; i++) {
// 从这个数组中找到一个可以用的sockInfo元素
if(sockinfos[i].fd == -1) {
pinfo = &sockinfos[i];
break;
}
if(i == max - 1) {
sleep(1);
i--;
}
}
pinfo->fd = cfd;
memcpy(&pinfo->addr, &cliaddr, len);
// 创建子线程
pthread_create(&pinfo->tid, NULL, work, pinfo);
pthread_detach(pinfo->tid);
}
close(lfd);
return 0;
}
4.12 TCP状态转换
4.13 端口复用
4.14 I/O多路复用
I/O 多路复用使得程序能同时监听多个文件描述符,能够提高程序的性能,Linux 下实现 I/O 多路复用的系统调用主要有 select、poll 和 epoll。
4.14.1 slelect
用户将文件描述符设定到自定义的readfds集合中,然后拷贝到内核的集合里面今天监听,如果有IO操作设置为1,没有设置为0,然后再拷贝回用户定义的集合,这时候去遍历是不是1,来判断是否有IO操作。
例子:
/*
主治思想:
1.首先构造一个关于文件描述符的列表,将要监听的文件描述符添加到该列表中
2.调用一个系统函数,监听该列表中的文件描述符,直到这些描述符中的一个或者多个进行I/O操作,该函数才返回
a.这个函数是阻塞的
b.函数对文件描述符的检测是由内核完成的
3.在返回时,它会告诉进程有多少描述符要进行I/O操作。
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
-功能:I/O多路复用,检测有哪些文件描述符有I/O操作
-参数:
-nfds:委托内核检测的最大文件描述符的值 + 1
-readfds:读的文件集合
一般检测读操作
对应的是对方发送过来的数据,因为读是被动的接受数据,检测是读缓冲区
是一个传入传出参数
-writefds:写的文件集合
委托内核检测写缓冲区是不是还可以写数据
-exceptfds:错误的文件集合
-timeout:设置的超时时间
struct timeval {
long tv_sec; // seconds
long tv_usec; // microseconds
};
-NULL:永久阻塞,直到检测到了文件描述符有变化
-tv_sec = 0 tv_usec = 0 ,不阻塞
-tv_sec > 0 tv_usec > 0,阻塞对应的时间
-返回值:
失败:-1
成功:有n个文件描述符发生了变化
void FD_CLR(int fd, fd_set *set);
-功能:将参数文件描述符fd对应的标志位设置为0
int FD_ISSET(int fd, fd_set *set);
-功能:判断对应的标志位是0还是1,
-返回值:
fd对应的标志位
是 0 返回 0
是 1 返回 1
void FD_SET(int fd, fd_set *set);
-功能:将参数文件描述符fd对应的标志位设置为1
void FD_ZERO(fd_set *set);
-功能:清空 全部初始化为0
*/
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<sys/time.h>
#include<sys/select.h>
#include<stdio.h>
#include<sys/stat.h>
#include<string.h>
int main(){
//1.创建socket
int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr.s_addr);
addr.sin_port = htons(9999);
//绑定
int ret = bind(lfd, (struct sockaddr *)&addr, sizeof(addr));
if(ret == -1){
perror("bind");
exit(-1);
}
//监听
ret = listen(lfd,8);
if(ret == -1){
perror("listen");
exit(-1);
}
fd_set rdset, tmp;
FD_ZERO(&rdset);
FD_SET(lfd,&rdset);
int Max_fd = lfd;
while(1){
tmp = rdset;
// 调用select系统函数,让内核帮检测哪些文件描述符有数据
int ret = select(Max_fd + 1, &tmp, NULL, NULL, NULL);
if(ret == -1) {
perror("select");
exit(-1);
} else if(ret == 0) {
continue;
}
else if(ret > 0){
//有IO操作
if(FD_ISSET(lfd, &tmp)){
//表示有新的客户端连接进来了
//接受
struct sockaddr_in caddr;
socklen_t len = sizeof (caddr);
int cfd = accept(lfd, (struct sockaddr *)&(caddr), &len);
if(cfd == -1){
perror("accept");
exit(-1);
}
///检测新的文件描述符是否有IO操作
FD_SET(cfd, &rdset);
Max_fd = cfd > Max_fd ? cfd : Max_fd;
}
//这里如果不是从lfd + 1 开始会报错
for(int i = lfd + 1; i <= Max_fd; i++){
if(FD_ISSET(i, &tmp)){
//客户端有数据发送
char buf[1024] = {0};
int len = read(i, buf, sizeof buf);
if(len == -1){
perror("read");
exit(-1);
}
else if(len == 0){
printf("client close..\n");
close(i);
FD_CLR(i, &tmp);
}
else if(len > 0){
//说明有数据发送
printf("recv client message : %s\n",buf);
write(i, buf, strlen(buf));
}
}
}
}
}
close(lfd);
return 0;
}
4.14.2 poll
/*
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
-参数:
-fds:是一个struct polled结构体数组, 这是一个需要检测的文件描述符的集合
-nfds:这个是第一个参数数组中最后一个有效元素的下标 + 1
-timeout:阻塞时长
0:不阻塞
-1:阻塞,当检测到需要检测的文件描述符有变化就解除阻塞
>0:阻塞的时长
-返回值:
-1:失败
>0:表示成功 检测到的集合中有n个文件发生变化
struct pollfd {
int fd; // file descriptor
short events; // requested events 委托内核检测文件描述符的什么事件
short revents; // returned events 文件描述符实际发生的事件
};
#define _GNU_SOURCE // See feature_test_macros(7)
#include <signal.h>
#include <poll.h>
int ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *tmo_p, const sigset_t *sigmask);
*/
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<sys/time.h>
#include<poll.h>
#include<stdio.h>
#include<sys/stat.h>
#include<string.h>
int main(){
//1.创建socket
int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr.s_addr);
addr.sin_port = htons(9999);
//绑定
int ret = bind(lfd, (struct sockaddr *)&addr, sizeof(addr));
if(ret == -1){
perror("bind");
exit(-1);
}
//监听
ret = listen(lfd,8);
if(ret == -1){
perror("listen");
exit(-1);
}
//初始化poll
struct pollfd fds[1024];
for(int i = 0; i < 1024; i++){
fds[i].fd = -1;
fds[i].events = POLLIN;
}
fds[0].fd = lfd;
int nfds = 0;
while(1){
//调用poll系统函数,让内核检测哪些文件有IO操作
ret = poll(fds, nfds + 1, -1);
if(ret == -1) {
perror("poll");
exit(-1);
} else if(ret == 0) {
continue;
}
else if(ret > 0){
//有IO操作
if(fds[0].revents & POLLIN){
//表示有新的客户端连接进来了
//接受
struct sockaddr_in caddr;
socklen_t len = sizeof (caddr);
int cfd = accept(lfd, (struct sockaddr *)&(caddr), &len);
if(cfd == -1){
perror("accept");
exit(-1);
}
///检测新的文件描述符是否有IO操作
for(int i = 1; i < 1024; i++){
if(fds[i].fd == -1){
fds[i].fd = cfd;
fds[i].events = POLLIN;
break;
}
}
nfds = cfd > nfds ? cfd : nfds;
}
//这里如果不是从lfd + 1 开始会报错
for(int i = 1; i <= nfds; i++){
if(fds[i].revents & POLLIN){
//客户端有数据发送
char buf[1024] = {0};
int len = read(fds[i].fd, buf, sizeof buf);
if(len == -1){
perror("read");
exit(-1);
}
else if(len == 0){
printf("client close..\n");
close(fds[i].fd);
fds[i].fd = -1;
}
else if(len > 0){
//说明有数据发送
printf("recv client message : %s\n",buf);
write(fds[i].fd, buf, strlen(buf));
}
}
}
}
}
close(lfd);
return 0;
}
4.14.3 epoll
/*
创建一个新的epoll实例,在内核中创建一个数据,这个数据中有两个比较重要的数据,一个是
需要检测的文件描述符的信息(红黑树),还有一个就绪列表,存放检测到数据发生改变的文件
描述符集合,其实是一个双向链表
#include <sys/epoll.h>
int epoll_creat(int size)
-参数:
size:目前没有意义了,随便写一个数必须大于0
-返回值:
>0:文件描述符,操作epoll实例
-1:错误 设置错误号
typedef union epol_data{
void* ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
struct epoll_event{
uint322_t enents;
epoll_data_t data;
};
常见的epoll检测事件
-EPOLLIN
-EPOLLOUT
-EPOLLERR
-EPOLLET 边沿模式
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
-功能:对epoll实例进行管理,添加文件描述符信息,删除信息,修改信息
-参数:
-epfd:epoll实例对应的文件描述符
-op:要进行什么操作
EPOLL_CTL_ADD;添加
EPOLL_CTL_MDO;修改
EPOLL_CTL_DEL;删除
-fd:要检测的文件描述符
-event:检测文件描述符具体的事情
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
-功能:检测函数
-参数:
-epfd:epoll实例对应的文件描述符
-events:传出参数,保存了发生了变化的文件描述符的信息
-maxevents:第二个参数数组的大小
-timeout:阻塞时间
-0:不阻塞
-1:阻塞,直到检测到fd数据发生变化,解除阻塞
>0:阻塞时间(毫秒)、
-返回值:
成功,返回发生变化的文件描述符的个数
失败,-1
*/
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/epoll.h>
#include<unistd.h>
#include<arpa/inet.h>
int main(){
//1.创建socket
int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr.s_addr);
addr.sin_port = htons(9999);
//绑定
int ret = bind(lfd, (struct sockaddr *)&addr, sizeof(addr));
if(ret == -1){
perror("bind");
exit(-1);
}
//监听
ret = listen(lfd,8);
if(ret == -1){
perror("listen");
exit(-1);
}
//调用epoll_creat()创建一个实例
int epfd = epoll_create(100);
//将监听的文件描述符相关的检测信息添加到到epoll实例中
struct epoll_event epev;
epev.events = EPOLLIN;
epev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);
struct epoll_event epevs[1024];
while (1)
{
int ret = epoll_wait(epfd, epevs, 1024, -1);
if(ret == -1){
perror("epoll_wait");
exit(-1);
}
printf("ret = %d\n",ret);
for(int i = 0; i < ret; i++){
if(epevs[i].data.fd == lfd){
//说明有客户端要进行连接 监听的文件描述符有数据到达
struct sockaddr_in cliaddr;
int len = sizeof (cliaddr);
int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);
epev.events = EPOLLIN | EPOLLOUT;
epev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);
}
else{
if(epevs[i].events & EPOLLOUT) continue;
//有数据达到 需要通信
char buf[1024] = {0};
int len = read(epevs[i].data.fd, buf, sizeof buf);
if(len == -1){
perror("read");
exit(-1);
}
else if(len == 0){
printf("client close..\n");
epoll_ctl(epfd,EPOLL_CTL_DEL, epevs[i].data.fd, NULL);
close(epevs[i].data.fd);
}
else if(len > 0){
//说明有数据发送
printf("recv client message : %s\n",buf);
write(epevs[i].data.fd, buf, strlen(buf));
}
}
}
}
close(lfd);
close(epfd);
return 0;
}
4.14.4 epoll 两种工作模式
LT模式 水平触发
/*
创建一个新的epoll实例,在内核中创建一个数据,这个数据中有两个比较重要的数据,一个是
需要检测的文件描述符的信息(红黑树),还有一个就绪列表,存放检测到数据发生改变的文件
描述符集合,其实是一个双向链表
#include <sys/epoll.h>
int epoll_creat(int size)
-参数:
size:目前没有意义了,随便写一个数必须大于0
-返回值:
>0:文件描述符,操作epoll实例
-1:错误 设置错误号
typedef union epol_data{
void* ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
struct epoll_event{
uint322_t enents;
epoll_data_t data;
};
常见的epoll检测事件
-EPOLLIN
-EPOLLOUT
-EPOLLERR
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
-功能:对epoll实例进行管理,添加文件描述符信息,删除信息,修改信息
-参数:
-epfd:epoll实例对应的文件描述符
-op:要进行什么操作
EPOLL_CTL_ADD;添加
EPOLL_CTL_MDO;修改
EPOLL_CTL_DEL;删除
-fd:要检测的文件描述符
-event:检测文件描述符具体的事情
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
-功能:检测函数
-参数:
-epfd:epoll实例对应的文件描述符
-events:传出参数,保存了发生了变化的文件描述符的信息
-maxevents:第二个参数数组的大小
-timeout:阻塞时间
-0:不阻塞
-1:阻塞,直到检测到fd数据发生变化,解除阻塞
>0:阻塞时间(毫秒)、
-返回值:
成功,返回发生变化的文件描述符的个数
失败,-1
*/
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/epoll.h>
#include<unistd.h>
#include<arpa/inet.h>
int main(){
//1.创建socket
int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr.s_addr);
addr.sin_port = htons(9999);
//绑定
int ret = bind(lfd, (struct sockaddr *)&addr, sizeof(addr));
if(ret == -1){
perror("bind");
exit(-1);
}
//监听
ret = listen(lfd,5);
if(ret == -1){
perror("listen");
exit(-1);
}
//调用epoll_creat()创建一个实例
int epfd = epoll_create(100);
//将监听的文件描述符相关的检测信息添加到到epoll实例中
struct epoll_event epev;
epev.events = EPOLLIN;
epev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);
struct epoll_event epevs[1024];
while (1)
{
int ret = epoll_wait(epfd, epevs, 1024, -1);
if(ret == -1){
perror("epoll_wait");
exit(-1);
}
printf("ret = %d\n",ret);
for(int i = 0; i < ret; i++){
if(epevs[i].data.fd == lfd){
//说明有客户端要进行连接 监听的文件描述符有数据到达
struct sockaddr_in cliaddr;
int len = sizeof (cliaddr);
int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);
epev.events = EPOLLIN | EPOLLOUT;
epev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);
}
else{
if(epevs[i].events & EPOLLOUT) continue;
//有数据达到 需要通信
char buf[1024] = {0};
int len = read(epevs[i].data.fd, buf, sizeof buf);
if(len == -1){
perror("read");
exit(-1);
}
else if(len == 0){
printf("client close..\n");
epoll_ctl(epfd,EPOLL_CTL_DEL, epevs[i].data.fd, NULL);
close(epevs[i].data.fd);
}
else if(len > 0){
//说明有数据发送
printf("recv client message : %s\n",buf);
write(epevs[i].data.fd, buf, strlen(buf));
}
}
}
}
close(lfd);
close(epfd);
return 0;
}
ET模式 边沿触发
/*
创建一个新的epoll实例,在内核中创建一个数据,这个数据中有两个比较重要的数据,一个是
需要检测的文件描述符的信息(红黑树),还有一个就绪列表,存放检测到数据发生改变的文件
描述符集合,其实是一个双向链表
#include <sys/epoll.h>
int epoll_creat(int size)
-参数:
size:目前没有意义了,随便写一个数必须大于0
-返回值:
>0:文件描述符,操作epoll实例
-1:错误 设置错误号
typedef union epol_data{
void* ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
struct epoll_event{
uint322_t enents;
epoll_data_t data;
};
常见的epoll检测事件
-EPOLLIN
-EPOLLOUT
-EPOLLERR
-EPOLLET 边沿模式
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
-功能:对epoll实例进行管理,添加文件描述符信息,删除信息,修改信息
-参数:
-epfd:epoll实例对应的文件描述符
-op:要进行什么操作
EPOLL_CTL_ADD;添加
EPOLL_CTL_MDO;修改
EPOLL_CTL_DEL;删除
-fd:要检测的文件描述符
-event:检测文件描述符具体的事情
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
-功能:检测函数
-参数:
-epfd:epoll实例对应的文件描述符
-events:传出参数,保存了发生了变化的文件描述符的信息
-maxevents:第二个参数数组的大小
-timeout:阻塞时间
-0:不阻塞
-1:阻塞,直到检测到fd数据发生变化,解除阻塞
>0:阻塞时间(毫秒)、
-返回值:
成功,返回发生变化的文件描述符的个数
失败,-1
*/
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include<fcntl.h>
#include<errno.h>
int main() {
// 创建socket
int lfd = socket(PF_INET, SOCK_STREAM, 0);
struct sockaddr_in saddr;
saddr.sin_port = htons(9999);
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
// 绑定
bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
// 监听
listen(lfd, 8);
// 调用epoll_create()创建一个epoll实例
int epfd = epoll_create(100);
// 将监听的文件描述符相关的检测信息添加到epoll实例中
struct epoll_event epev;
epev.events = EPOLLIN;
epev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);
struct epoll_event epevs[1024];
while(1) {
int ret = epoll_wait(epfd, epevs, 1024, -1);
if(ret == -1) {
perror("epoll_wait");
exit(-1);
}
printf("ret = %d\n", ret);
for(int i = 0; i < ret; i++) {
int curfd = epevs[i].data.fd;
if(curfd == lfd) {
// 监听的文件描述符有数据达到,有客户端连接
struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
//设置cfd非阻塞
int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flag);
epev.events = EPOLLIN | EPOLLET;
epev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);
} else {
if(epevs[i].events & EPOLLOUT) {
continue;
}
// // 有数据到达,需要通信
// char buf[5] = {0};
// int len = read(curfd, buf, sizeof(buf));
// if(len == -1) {
// perror("read");
// exit(-1);
// } else if(len == 0) {
// printf("client closed...\n");
// epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
// close(curfd);
// } else if(len > 0) {
// printf("read buf = %s\n", buf);
// write(curfd, buf, strlen(buf) + 1);
// }
//循环读取出所有的数据
char buf[5];
int len = 0;
while(len = read(epevs[i].data.fd, buf, sizeof(buf)) > 0){
//打印数据
//printf("recv data : %s\n", buf);
write(STDOUT_FILENO, buf, len);
write(epevs[i].data.fd, buf, strlen(buf));
}
if(len == 0){
printf("client close\n");
}
else if(len == -1){
if(errno == EAGAIN){
printf("data over...\n");
}
else{
perror("read");
exit(-1);
}
}
}
}
}
close(lfd);
close(epfd);
return 0;
}
5. 项目
5.1 阻塞/非阻塞 同步/异步
5.2 Linux上的五种 I/O模型
5.2.1 阻塞 blocking
IO阻塞是文件描述符的属性
可以通过fcntl函数设置非阻塞
5.2.2 非阻塞 non-blocking
5.2.3 I/O 复用(I/O multiplexing)
5.2.4 信号驱动(signal-driven)
5.2.5 异步(asynchronous)
5.3 HTTP协议
5.4 服务器编程基本框架
5.5 两种高效的事故处理模式
服务器程序通常需要处理三类事件,I/O事件、信号及定时事件。有两种高效的事件处理模式:Reactor和Proactor,同步I/O模型通常用于实现Reactor模式,异步I/O模型通常用于实现Proactor模式。
5.5.1 Reactor模式
5.5.2 Proactor模式
5.5.3 模拟Proactor模式
5.6 线程池
评论区