【Linux系统编程】

Linux系统编程

  • 一.文件编程
    • 1.常用API
      • 1.1 打开:open
      • 1.2 读写:write/read
      • 1.3 光标定位: lseek
      • 1.4 创建:creat
      • 1.4 关闭:close
    • 2.文件的打开及创建
    • 3.文件的写入
    • 4.文件的读取
    • 5.文件描述符
    • 5.代码实现cp指令
    • 6.编程实现修改文件配置
    • 7.写一个整数/结构体到文件
    • 8.写一个数组/链表
    • 9.标准的c语言库
      • 1.1 open与fopen的区别
      • 1.2 open与fopen的区别
  • 二.进程
    • 1.介绍
    • 2.进程的创建
      • 2.1 fork()
        • 2.1.1 fork()
        • 2.1.2 验证
        • 2.1.3 fork创建子进程的目的
      • 2.2 vfork()
    • 3.进程退出
      • 3.1 等待子进程退出
        • 3.1.1 不等待
        • 3.1.2 等待
      • 3.2 得到子进程退出的状态
        • 3.2.1 wait()
        • 3.2.2 waitpid()
      • 3.3 孤儿进程
    • 4.exec族函数
      • 4.1 execl
      • 4.2 调用其他api获取当前时间
      • 4.3 execlp
      • 4.4 exec配合fork
    • 5.system函数(封装后的exec)
    • 6.popen
  • 三.进程间的通信(IPC)
    • 1.管道通信
      • 1.1 无名管道
      • 2.1 命名管道(FIFO)
    • 2.消息队列
      • 2.1 创建
      • 2.2 键值生成及消息队列移除
    • 3.共享内存
    • 4.信号(软中断)(qt事件处理机制)
      • 4.1 信号处理handler
      • 4.2 系统命令
      • 4.3 信号忽略
      • 4.4 信号携带消息
        • 4.4.1 获取信号携带的消息
        • 4.4.2 发送消息携带信号
    • 5.信号量
      • 5.1 概述
      • 5.2 编程实现
  • 四.线程
    • 1. 线程的创建等待退出
        • 1.1.1 线程创建
        • 1.1.2 线程等待
        • 1.1.3 线程退出
        • 1.1.4 线程id获取
        • 1.1.5 编程实现
    • 2. 线程共享内存空间
    • 3. 线程同步
      • 3.1 互斥量加锁解锁
        • 3.1.1 创建及销毁互斥锁
        • 3.1.1 创建及销毁互斥锁
      • 3.2 互斥锁(互斥量)限制共享资源的访问
      • 3.3 死锁
      • 3.4 线程条件控制实现线程同步
      • 3.5 宏
  • 五.网络编程
    • 1.概述
      • 1.1 TCP/UDP对比
      • 1.2 端口号及其作用
      • 1.3 字节序
      • 1.4 字节序转换api
    • 2. socket编程步骤
      • 2.1 开发流程
      • 2.2 Linux提供的API接口
        • 2.2.1 连接协议--创建套接字
        • 2.2.2 地址+端口号
        • 2.2.3 地址转换API
        • 2.2.4 监听
        • 2.2.6 连接
        • 2.2.5 数据收发
        • 2.2.5 数据收发第二套API
        • 2.2.5 监听
    • 3. socket服务端代码实现
    • 4. socket客户端代码实现
    • 5. 实现多方聊天
      • 5.1 服务端
      • 5.1 客户端

一.文件编程

常用API打开:		open读写:		write/read光标定位:		lseek关闭: 		close

1.常用API

1.1 打开:open

在这里插入图片描述
在这里插入图片描述

1.2 读写:write/read

在这里插入图片描述
在这里插入图片描述

1.3 光标定位: lseek

在这里插入图片描述

1.4 创建:creat

在这里插入图片描述

1.4 关闭:close

在这里插入图片描述

2.文件的打开及创建

open("./file1", O_RDWR | O_EXCL, 0600);	//文件已存在则出错
open("./file1", O_RDWR | O_CREAT, 0600);	//不存在则创建打开
open("./file1", O_RDWR | O_APPEND, 0600); //追加写入,每次在文件的末端即光标所在的位置写入,即新行写入
open("./file1", O_RDWR | O_TRUNC, 0600); //将原本的文件内容删掉再写入int fd = creat("/home/file1",S_IRWXU) //可读可写可执行的方式在/home下创建file1文件#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{int fd;fd = open("./file1",O_RDWR); //返回文件描述符if(fd == -1){printf("open file1 failed\n");fd =  open("./file1", O_RDWR | O_CREAT, 0600); //创建打开,加0x600权限	r:4,  w:2,  x:1     r+w = 6//0600给文件所有者可读可写权限if(fd > 0){printf("open file1 success\n");}}return 0;
}

3.文件的写入

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{int fd;char *buf = "qzh Handsome";fd = open("./file1",O_RDWR); //返回文件描述符if(fd == -1){printf("open file1 failed\n");fd =  open("./file1", O_RDWR | O_CREAT, 0600); //创建打开,加0600权限if(fd > 0){printf("open file1 success\n");}}//ssize_t write(int fd, const void *buf, size_t count);int writeNum =  write(fd,buf,strlen(buf));//返回写入文件的字符数printf("writeNum = %d\n",writeNum);close(fd);return 0;
}

4.文件的读取

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{int fd;char *buf = "qzh Handsome";fd = open("./file1",O_RDWR); //返回文件描述符if(fd == -1){printf("open file1 failed\n");fd =  open("./file1", O_RDWR | O_CREAT, 0600); //创建打开,加0600权限if(fd > 0){printf("open file1 success\n");}}//ssize_t write(int fd, const void *buf, size_t count);int n_write =  write(fd,buf,strlen(buf));//返回写入文件的字符数if(n_write != -1){printf("write %d byte to file\n",n_write);}//写操作完成后会将光标移动至尾部,//要想读取写入的数据,就需要将光标移动到头部,所以lseek移动光标lseek(fd,0,SEEK_SET);   //将光标移动至开头读取char *readBuf = (char *)malloc(sizeof(char ) * n_write + 1);int n_read = read(fd,readBuf,n_write);  //返回成功读取的字符数printf("readBuff = %s, readNum = %d\n",readBuf,n_read);free(readBuf);close(fd);return 0;
}

5.文件描述符

在这里插入图片描述
在这里插入图片描述

Linux系统默认文件描述符
0:标准输入	read(0,buf,5); //把键盘上敲5下的字符放在buf中
1:标准输出	write(1,buf,5); //将buf中的五个字符显示在shell上
2:标准错误

5.代码实现cp指令


参数: 1	 		 2	 			 3cp 			scr.c 			des.cargv[0]		argv[1]			argv[2]
int main(int argc, char **argv)
{}
https://blog.csdn.net/qq_46626969/article/details/109920812?spm=1001.2014.3001.5501

6.编程实现修改文件配置

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char **argv)
{int fdSrc;char *readBuf=NULL;if(argc != 2){printf("pararm error\n");exit(-1);}fdSrc = open(argv[1],O_RDWR);int size = lseek(fdSrc,0,SEEK_END);lseek(fdSrc,0,SEEK_SET);readBuf=(char *)malloc(sizeof(char)*size + 8);int n_read = read(fdSrc, readBuf, size);char *p = strstr(readBuf,"LENG=");if(p==NULL){printf("not found\n");exit(-1);}p = p+strlen("LENG=");*p = '5';//光标移动到头开始写//或者O_TRUNC的方式打开lseek(fdSrc,0,SEEK_SET);int n_write = write(fdSrc,readBuf,strlen(readBuf));close(fdSrc);return 0;
}

7.写一个整数/结构体到文件

ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);buf是一个指针,因此将整数,结构体对的地址给buf再进行读写即可//写入字符
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>int main()
{int fd;int data = 100;int data2 = 0;fd = open("./file1",O_RDWR);int n_write = write(fd,&data,sizeof(int));lseek(fd,0,SEEK_SET);int n_read = read(fd, &data2, sizeof(int));printf("read %d \n",data2);close(fd);return 0;
}//写入结构体
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>struct Test
{int a;char c;};int main()
{int fd;struct Test data = {100,'a'};struct Test data2;fd = open("./file1",O_RDWR);int n_write = write(fd,&data,sizeof(struct Test));lseek(fd,0,SEEK_SET);int n_read = read(fd, &data2, sizeof(struct Test));printf("read %d,%c \n",data2.a,data2.c);close(fd);return 0;
}

8.写一个数组/链表

由于是按块读取数组的地址连续,直接取数组的首地址即可链表的地址不连续,因此需要遍历获取首地址

9.标准的c语言库

1.1 open与fopen的区别

https://www.cnblogs.com/NickyYe/p/5497659.html

1.2 open与fopen的区别

#include <stdio.h>
#include <string.h>
int main()
{//FILE *fopen(const char *pathname, const char *mode);FILE *fp;char *str = "qzh handsome";char readBuf[128] = {0};fp = fopen("./qin.txt","w+");//size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);fwrite(str,sizeof(char),strlen(str),fp);fseek(fp,0,SEEK_SET);//size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);fread(readBuf,sizeof(char),strlen(str),fp);printf("read data : %s\n",readBuf);return 0;}

二.进程

1.介绍

1.什么是进程,什么是程序,区别程序是静态的概念 gcc xx.c -o pro磁盘中生成的pro文件时程序进程是动态的概念,是程序一次的运行活动2.如何查看进程过滤查看进程:ps -aux|grep inittop 查看进程(类似于任务管理器)3.什么是进程标识符每个进程都有一个非负整数表示的唯一ID--pidPid = 0; 成为交换进程(swapper)作用--进程调度 即当前某一时刻谁来运行Pid = 1; init进程作用--系统初始化getpid() //获取自身的进程标识符getppid() //获取父进程的进程标识符4.什么是父进程,什么是子进程进程A创建了进程B那么A叫父进程B叫子进程

在这里插入图片描述
在这里插入图片描述

2.进程的创建

2.1 fork()

fork()
调用成功返回两次
返回值为0, 代表当前进程是子进程
返回值为非负, 代表当前进程是父进程-其此时返回值刚好是子进程的pid
调用失败返回-1在 fork创建以后会有两个进程,一个是本身,一个是由这个本身创建的进程
即:xxx.c文件为父进程在xxx.c文件中fork的进程为子进程在以下的代码中,父进程会打印pidfork在printf之前,子进程创建以后也会执行printf的代码显示的其父进程的pid
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{pid_t pid;pid = getpid();fork();printf("my pid = %d ,currrent pid = %d\nn",pid, getpid());while(1);return 0;
}

在这里插入图片描述

从结果可以看出,打印了两次
第一次是本身的进程打印的
第二次是fork之后的子进程打印的
子进程中仍然保留了父进程中的数据
情况1.不更新pid	结果第一个图
情况2.更新pid	结果第二个图
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{pid_t pid;pid_t pid2;pid = getpid();printf("befor fork pid = %d\n",pid);fork();//   pid = getpid(); //更新pidprintf("after fork pid = %d\n",pid);if(pid == pid2){printf("this is father printf\n");}else{printf("this is chile print, pid = %d\n",getpid());}while(1);return 0;
}

在这里插入图片描述
在这里插入图片描述

2.1.1 fork()

在这里插入图片描述

在这里插入图片描述

fork创建的子进程,会写实拷贝父进程的全部内容,未修改的数据进行共享,修改的东西进行拷贝(即选择性的拷贝)
2.1.2 验证
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{pid_t pid;int a = 10;printf("father: id=%d\n",getpid());pid = fork();if(pid > 0){printf("this is father print, pid = %d\n",getpid());}else if(pid == 0){printf("this is child print,child pid = %d\n",getpid());}printf("data = %d\n",a);return 0;
}

在这里插入图片描述

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{pid_t pid;int a = 10;printf("father: id=%d\n",getpid());pid = fork();if(pid > 0){printf("this is father print, pid = %d\n",getpid());}else if(pid == 0){printf("this is child print,child pid = %d\n",getpid());a += 100;}printf("data = %d\n",a);return 0;
}

在这里插入图片描述

2.1.3 fork创建子进程的目的

在这里插入图片描述

场景模拟1:网络服务
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{pid_t pid;int data = 10;while(1){printf("please input a data\n");scanf("%d",&data);	if(data == 1){pid = fork();if(pid > 0){}else if(pid == 0){while(1){printf("do net request,pid=%d\n",getpid());sleep(3);}}}else{printf("wait ,do nothing\n");}}return 0;
}

2.2 vfork()

区别:vfork直接使用父进程存存储空间,不拷贝vfork保证子进程先运行,当子进程程序调用exit退出后,父进程才执行
#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>int main()
{pid_t pid;int cnt = 0;pid = vfork();if(pid > 0){while(1){printf("cnt=%d\n",cnt);printf("this is father print, pid = %d\n",getpid());sleep(1);}	}else if(pid == 0){while(1){printf("this is chilid print, pid = %d\n",getpid());sleep(1);cnt++;if(cnt == 3){exit(0);break;}}	}return 0;
}

在这里插入图片描述

可以看到在vfork子进程修改cnt的值父进程的cnt的值也会改变
而fork只改变该子进程的值,而不会对父进程有影响

3.进程退出

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.1 等待子进程退出

在这里插入图片描述

1.为什么要等待子进程退出?创建子进程的目的是为了让子进程干活,所以我们需要知道子进程干的怎么样,是干完了退出还是怎么样退出所父进程要等待子进程的退出并手机子进程的退出状态exit(状态码)
wait()返回状态码
通过宏解析状态码

在这里插入图片描述

子进程退出状态不被收集会变成僵死进程(僵尸进程)
zombie 
include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{pid_t pid;int cnt = 0;pid = fork();if(pid > 0){//情况1不等待 wait(NULL);wait(NULL);//情况2等待while(1){printf("cnt=%d\n",cnt);printf("this is father print, pid = %d\n",getpid());sleep(1);}}else if(pid == 0){while(1){printf("this is chilid print, pid = %d\n",getpid());sleep(1);cnt++;if(cnt == 5){exit(0);}}}return 0;
}                                  
3.1.1 不等待

在这里插入图片描述

	父进程不会等待子进程都会运行
3.1.2 等待

在这里插入图片描述

	父进程会等待子进程结束

3.2 得到子进程退出的状态

在这里插入图片描述

3.2.1 wait()

在这里插入图片描述

include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{pid_t pid;int cnt = 0;int status = 10;pid = fork();if(pid > 0){wait(&status);printf("child quit , child status = %d\n",WEXITSTATUS(status));while(1){printf("cnt=%d\n",cnt);printf("this is father print, pid = %d\n",getpid());sleep(1);}}else if(pid == 0){while(1){printf("this is chilid print, pid = %d\n",getpid());sleep(1);cnt++;if(cnt == 5){exit(3);}}}return 0;
}

在这里插入图片描述

3.2.2 waitpid()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{pid_t pid;int cnt = 0;int status = 10;pid = fork();if(pid > 0){waitpid(pid,&status,WNOHANG);//不挂起的方式printf("child quit , child status = %d\n",WEXITSTATUS(status));while(1){printf("cnt=%d\n",cnt);printf("this is father print, pid = %d\n",getpid());sleep(1);}}else if(pid == 0){while(1){printf("this is chilid print, pid = %d\n",getpid());sleep(1);cnt++;if(cnt == 5){exit(3);}}}return 0;
}

在这里插入图片描述
在这里插入图片描述

	用waitpid非阻塞的状态虽然有收集子进程退出的状态,但是子进程任然会变成僵尸进程z+(zombie)

3.3 孤儿进程

	父进程如果不等待子进程的退出,在子进程结束之前就结束了自己的声明,此时的进程叫孤儿进程linux为避免孤儿进程的过多init进程收留孤儿进程,变成孤儿进程的父进程init进程pid系统默认号为1但是在最新的Ubuntu系统中放弃了一直沿用的init包而是采用upstar来初始化系统所以现在的孤儿进程是由upstart收养https://www.cnblogs.com/Cccarl/p/6601765.html
#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{pid_t pid;int cnt = 0;int status = 10;	pid = fork();if(pid > 0){printf("this is father print, pid = %d\n",getpid());}else if(pid == 0){while(1){printf("this is chilid print, pid = %d, my father pid = %d\n",getpid(),getppid());sleep(1);cnt++;if(cnt == 5){exit(3);}}}return 0;
}

在这里插入图片描述

4.exec族函数

4.1 execl

https://blog.csdn.net/u014530704/article/details/73848573
1.为什么要用exec族函数,有什么作用

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//path:可执行文件的路径名字
//arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
//file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。
//返回值:exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。
int main(void)
{printf("before execl****\n");if(execl("./echoarg","echoarg","abc",NULL) == -1)   //真正的参数abc,必须以NULL结尾{printf("execl failed!\n");perror("why");  //exec调用失败会返回-1并会设置一个错误码,错误码通过perror解析}printf("after execl*****\n");return 0;
}错误情况,会按照当前的断点继续往下运行
正确情况,main进程会被替代执行echoarg中的内容
错误情况

在这里插入图片描述

正确情况

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//path:可执行文件的路径名字
//arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
//file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。
//返回值:exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。
int main(void)
{printf("before execl****\n");if(execl("/usr/bin/ls","ls","-l",NULL) == -1)       //"-l"给ls传参数{printf("execl failed!\n");perror("why");  //exec调用失败会返回-1并会设置一个错误码,错误码通过perror解析}   printf("after execl*****\n");return 0;
}

在这里插入图片描述

4.2 调用其他api获取当前时间

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//path:可执行文件的路径名字
//arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
//file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。
//返回值:exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。
int main(void)
{printf("this pro get system date\n");if(execl("/usr/bin/date","date",NULL,NULL) == -1)   //"-l"给ls传参数{printf("execl failed!\n");perror("why");  //exec调用失败会返回-1并会设置一个错误码,错误码通过perror解析}printf("after execl*****\n");return 0;
} 

在这里插入图片描述

4.3 execlp

将以上代码的绝对路径删掉后execl就找不到date,但是用execlp就可以找到date不需要date的绝对路径,
execlp能通过环境变量path找到可执行文件date//环境变量 date $PATH
//pwd 获取当前路径
//echo $PATH 获取环境变量
//export PATH=$PATH:/home/qzh/LinuxPidCode 将当前路径加入环境变量int main(void)
{printf("this pro get system date\n");if(execl("/usr/bin/date","date",NULL,NULL) == -1)   //"-l"给ls传参数{printf("execl failed!\n");perror("why");  //exec调用失败会返回-1并会设置一个错误码,错误码通过perror解析}printf("after execl*****\n");return 0;
} 

4.4 exec配合fork

实现功能,当父进程检测到输入为1的时候,创建子进程把配置文件的字段值修改掉
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{pid_t pid;int data = 10;while(1){printf("please input a data\n");scanf("%d",&data);if(data == 1){pid = fork();if(pid > 0){wait(NULL);}if(pid == 0){execl("./modify","modify","config.txt",NULL);}}else{printf("wait ,do nothing\n");}}return 0;
}
//modify.c
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char **argv)
{int fdSrc;char *readBuf=NULL;if(argc != 2){printf("pararm error\n");exit(-1);}fdSrc = open(argv[1],O_RDWR);int size = lseek(fdSrc,0,SEEK_END);lseek(fdSrc,0,SEEK_SET);readBuf=(char *)malloc(sizeof(char)*size + 8);int n_read = read(fdSrc, readBuf, size);char *p = strstr(readBuf,"LENG=");if(p==NULL){printf("not found\n");exit(-1);}p = p+strlen("LENG=");*p = '5';lseek(fdSrc,0,SEEK_SET);int n_write = write(fdSrc,readBuf,strlen(readBuf));close(fdSrc);return 0;
}

5.system函数(封装后的exec)

https://www.cnblogs.com/leijiangtao/p/4051387.html
system()函数的返回值如下:成功,则返回进程的状态值;当sh不能执行时,返回127;失败返回-1
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{pid_t pid;int data = 10;while(1){printf("please input a data\n");scanf("%d",&data);if(data == 1){pid = fork();if(pid > 0){wait(NULL);}if(pid == 0){//execl("./modify","modify","config.txt",NULL);system("./modify config.txt);}}else{printf("wait ,do nothing\n");}}return 0;
}
system会返回到原进程中
exec不会返回到原进程中,除非失败

6.popen

在这里插入图片描述

https://blog.csdn.net/libinbin_1014/article/details/51490568比system在应用中的好处:可以获取运行的输出结果
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{char ret[1024]={0};FILE *fp;fp = popen("ps","r");	//输出的结果以流的方式写入管道//size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);int nread = fread(ret,1,1024,fp);printf("read ret %d byte,ret = %s\n",nread,ret);return 0;
}

在这里插入图片描述

popen将ps的执行结果全部捕获在ret中

三.进程间的通信(IPC)

1.管道通信

1.1 无名管道

1、特点:
它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
fd[0]	//读端
fd[1]	//写端
https://blog.csdn.net/wh_sjc/article/details/70283843

在这里插入图片描述

//半双工通信,同一时间内只能进行一种单向通信
//父写子读,父读子写
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{int fd[2];int pid;char *buf = "qzh handsome";//int pipe(int pipefd[2]);if(pipe(fd) == -1){printf("create pipe failed\n");}pid = fork();if(pid < 0){printf("create child failed\n");}else if(pid > 0){      //写则关闭读通道printf("this is father\n");close(fd[0]);   //关闭读通道write(fd[1],buf,strlen(buf));wait(NULL);}else if(pid == 0){     //读则关闭写通道printf("this is child\n");close(fd[1]);   //关闭写通道char *readBuf;readBuf = (char *)malloc(sizeof(char) * strlen(buf));read(fd[0],readBuf,strlen(buf));printf("child read = %s\n",readBuf);exit(0);}return 0;
}

在这里插入图片描述

2.1 命名管道(FIFO)

FIFO,也称为命名管道,它是一种文件类型。
1、特点
FIFO可以在无关的进程之间交换数据,与无名管道不同。FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>//int mkfifo(const char *pathname, mode_t mode);
int main()
{if(mkfifo("./file",0600)== -1 && errno != EEXIST){//文件存在printf("mkfifo failure\n");perror("why");}int fd = open("./file",O_RDONLY);printf("open success");return 0;
}
//此时只运行该代码,不会执行打开以后的操作,会阻塞在这里,只有当其他文件为了 读 而打开他时才会往下运行
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>//int mkfifo(const char *pathname, mode_t mode);
int main()
{int fd = open("./file",O_WRONLY);printf("write open success\n");return 0;
}
//此代码运行后上一段代码会停止阻塞          
写和读取则通过一般的文件操作进行
//read.c
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>//int mkfifo(const char *pathname, mode_t mode);
int main()
{char buf[30] = {0};if(mkfifo("./file",0600)== -1 && errno != EEXIST){//文件存在printf("mkfifo failure\n");perror("why");}int fd = open("./file",O_RDONLY);printf("open success\n");int nread = read(fd,buf,30);printf("read %d byte from fifo.context : %s\n",nread,buf);close(fd);return 0;
}
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{char *str = "qzh handsome";int fd = open("./file",O_WRONLY);printf("write open success\n");write(fd,str,strlen(str));close(fd);return 0;
}

2.消息队列

2.1 创建

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。1、特点消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。(管道的内容会被删除)消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。共享队列可以同时发送接收
1 #include <sys/msg.h>
2 // 创建或打开消息队列:成功返回队列ID,失败返回-1
3 int msgget(key_t key, int flag);
4 // 添加消息:成功返回0,失败返回-1
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
6 // 读取消息:成功返回消息数据的长度,失败返回-1
7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
8 // 控制消息队列:成功返回0,失败返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);在以下两种情况下,msgget将创建一个新的消息队列:如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。key参数为IPC_PRIVATE。
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//int msgget(key_t key, int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);struct msgbuf {long mtype;       /* message type, must be > 0 */char mtext[128];    /* message data */
};
int main()
{//1.获取struct msgbuf sendBuf = {888,"qzh handsome"};struct msgbuf readBuf;int msgId =  msgget(0x1234,IPC_CREAT|0777); //有则直接获取,无则创建if(msgId == -1){printf("get que failure\n");}msgsnd(msgId, &sendBuf,strlen(sendBuf.mtext),0);msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);printf("read from que 988: %s\n",readBuf.mtext);return 0;
}
//get.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//int msgget(key_t key, int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);struct msgbuf {long mtype;       /* message type, must be > 0 */char mtext[128];    /* message data */
};
int main()
{//1.获取struct msgbuf readBuf;struct msgbuf sendBuf = {988, "this is 988 msg"};int msgId =  msgget(0x1234,IPC_CREAT|0777); //有则直接获取,无则创建if(msgId == -1){printf("get que failure\n");}msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);//默认的方式接受消息,读不到888的类型的消息会一直阻塞msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);printf("read from que 888: %s\n",readBuf.mtext);return 0;
}

2.2 键值生成及消息队列移除

//get.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
//        int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
//                      int msgflg);
struct msgbuf {long mtype;       /* message type, must be > 0 */char mtext[256];    /* message data */
};int main()
{//1.huoqustruct msgbuf readBuf;	key_t key;key = ftok(".",'m');printf("key=%x\n",key);int msgId = msgget(key, IPC_CREAT|0777);if(msgId == -1 ){printf("get que failuer\n");}memset(&readBuf,0,sizeof(struct msgbuf));msgrcv(msgId, &readBuf,sizeof(readBuf.mtext),888,0);	printf("read from que:%s\n",readBuf.mtext);struct msgbuf sendBuf = {988,"thank you for reach"};msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);msgctl(msgId,IPC_RMID,NULL);return 0;
}
//send.c#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
//        int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
//                      int msgflg);
struct msgbuf {long mtype;       /* message type, must be > 0 */char mtext[256];    /* message data */
};int main()
{//1.huoqustruct msgbuf sendBuf = {888,"this is message from quen"};	struct msgbuf readBuf;memset(&readBuf,0,sizeof(struct msgbuf));key_t key;key = ftok(".",'m');printf("key=%x\n",key);int msgId = msgget(key, IPC_CREAT|0777);if(msgId == -1 ){printf("get que failuer\n");}msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);printf("send over\n");msgrcv(msgId, &readBuf,sizeof(readBuf.mtext),988,0);printf("reaturn from get:%s\n",readBuf.mtext);msgctl(msgId,IPC_RMID,NULL);return 0;
}

3.共享内存

1 #include <sys/shm.h>
2 // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
3 int shmget(key_t key, size_t size, int flag);
4 // 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
5 void *shmat(int shm_id, const void *addr, int flag);
6 // 断开与共享内存的连接:成功返回0,失败返回-1
7 int shmdt(void *addr); 
8 // 控制共享内存的相关信息:成功返回0,失败返回-1
9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);printf打印
strcpy写入
ipcs -m 查看共享内存
ipcrm -m shmid //删除共享内存
/**/
//写
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
//int shmget(key_t key, size_t size, int shmflg);
int main()
{int shmid;char *shmaddr;key_t key;key = ftok(".",1);shmid = shmget(key,1024*4,IPC_CREAT|0600);//size至少1024if(shmid == -1){printf("shmget noOK\n");exit(-1);}//void *shmat(int shmid, const void *shmaddr, int shmflg);shmaddr = shmat(shmid,0,0);//默认的方式映射共享内存的地址printf("shmat ok\n");strcpy(shmaddr,"qzh handsome");sleep(5);shmdt(shmaddr);//卸载共享内存shmctl(shmid,IPC_RMID,0);printf("quit\n");return 0;}
//读
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
//int shmget(key_t key, size_t size, int shmflg);
int main()
{int shmid;char *shmaddr;key_t key;key = ftok(".",1);shmid = shmget(key,1024*4,0);//size至少1024if(shmid == -1){printf("shmget noOK\n");exit(-1);}//void *shmat(int shmid, const void *shmaddr, int shmflg);shmaddr = shmat(shmid,0,0);//默认的方式映射共享内存的地址printf("shmat ok\n");printf("data = %s\n",shmaddr);shmdt(shmaddr);//卸载共享内存printf("quit\n");return 0;}

4.信号(软中断)(qt事件处理机制)

4.1 信号处理handler

kill -l 查看所有的系统中断
#include <signal.h>
#include <stdio.h>
//       typedef void (*sighandler_t)(int);//       sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum)
{printf("get signum=%d\n",signum);switch(signum){case 2:printf("SIGINT\n");break;case 9:printf("SIGKILL\n");break;case 10:printf("SIGUSR1\n");break;}printf("never quit\n");}
int main()
{signal(SIGINT,handler);//信号注册signal(SIGKILL,handler);signal(SIGUSR1,handler);while(1);return 0;
}

4.2 系统命令

#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
//       typedef void (*sighandler_t)(int);//       sighandler_t signal(int signum, sighandler_t handler);
int main(int argc, char **argv)
{int signum;int pid;char cmd[128] = {0};signum = atoi(argv[1]);pid = atoi(argv[2]);printf("num=%d,pid=%d\n",signum,pid);//kill(pid,signum);sprintf(cmd,"kill -%d %d",signum,pid);  //整合字符指令system(cmd);printf("send signal ok");return 0;
}

4.3 信号忽略

signal(SIGINT,SIG_IGN);

4.4 信号携带消息

发信号1.用什么发	-int sigqueue(pid_t pid, int sig, const union sigval value);2.怎么放入消息
收信号1.用什么绑定函数 - int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);2.如何读出消息https://www.jianshu.com/p/f445bfeea40a

在这里插入图片描述

配置以上 24  
要想获取消息4必须配置siginfo_t {int      si_signo;    /* Signal number */int      si_errno;    /* An errno value */int      si_code;     /* Signal code */int      si_trapno;   /* Trap number that causedhardware-generated signal(unused on most architectures) */pid_t    si_pid;      /* Sending process ID */uid_t    si_uid;      /* Real user ID of sending process */int      si_status;   /* Exit value or signal */clock_t  si_utime;    /* User time consumed */clock_t  si_stime;    /* System time consumed */sigval_t si_value;    /* Signal value */int      si_int;      /* POSIX.1b signal */void    *si_ptr;      /* POSIX.1b signal */int      si_overrun;  /* Timer overrun count; POSIX.1b timers */int      si_timerid;  /* Timer ID; POSIX.1b timers */void    *si_addr;     /* Memory location which caused fault */int      si_band;     /* Band event */int      si_fd;       /* File descriptor */
}
4.4.1 获取信号携带的消息
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>//int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);void handler(int signum, siginfo_t *info, void *context)
{printf("get signum %d\n",signum);if(context != NULL){ //context 用来判断是否有内容printf("get data =  %d\n",info->si_int); //真正的数据siginfo中printf("get data =  %d\n",info->si_value.sival_int);printf("from pid:%d\n",info->si_pid);}
}
int main()
{struct sigaction act;printf("pid = %d\n",getpid());act.sa_sigaction = handler;act.sa_flags = SA_SIGINFO ;     //能够获取消息-必须配置sigaction(SIGUSR1,&act,NULL);//sigaction注册信号,等待信号SIGUSR1while(1);return 0;
}
4.4.2 发送消息携带信号
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>//int sigqueue(pid_t pid, int sig, const union sigval value);
int main(int arcv,char **argv)
{int signum;int pid;printf("send pid = %d\n",getpid());signum = atoi(argv[1]);pid = atoi(argv[2]);union sigval value;value.sival_int = 100;sigqueue(pid,signum,value);printf("done\n");return 0;}

5.信号量

5.1 概述

在一个密闭的房间内(临界资源),只有拿到钥匙(信号量)的人才能进去,(多进程访问,只有拿到信号量的进程才能访问)Linux中:信号量集
P操作:拿锁的过程
V操作:放回锁的过程
	信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。1、特点信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。支持信号量组。2、原型最简单的信号量是只能取 01 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。

5.2 编程实现

编程实现:子进程先运行,父进程后运行
思路:首先设置信号量为0父进程获取信号量,由于没有信号量所以父进程一直会阻塞在那子进程直接输出,输出完后放回信号量,此时信号量变为1父进程由于阻塞在那一直获取信号量,当子进程执行完放入信号量的时候,父进程获取到了信号量,往下执行,执行完后再放回
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
//int semget(key_t key, int nsems, int semflg);
//int semctl(int semid, int semnum, int cmd, ...);//初始化
//int semop(int semid, struct sembuf *sops, size_t nsops);union semun {int              val;    /* Value for SETVAL */unsigned short  *array;  /* Array for GETALL, SETALL */struct seminfo  *__buf;  /* Buffer for IPC_INFO(Linux-specific) */
};
void pGetKey(int id)
{struct sembuf set;set.sem_num = 0;set.sem_op = -1; //钥匙-1 拿钥匙set.sem_flg = SEM_UNDO;semop(id,&set,1);printf("get key \n");
}void vPutBackKey(int id)
{struct sembuf set;set.sem_num = 0;set.sem_op = 1; //钥匙-1 拿钥匙set.sem_flg = SEM_UNDO;semop(id,&set,1);printf("Put back key \n");
}int main(int argc, char **argv)
{key_t key;key = ftok(".",2);//信号量集合中有一个信号量int semid = semget(key,1,IPC_CREAT|0600);	//获取信号量/创建信号量union semun initsem;initsem.val = 0;	//设置0把锁//操作第0个信号量	semctl(semid,0, SETVAL,initsem);//初始化信号量//设置信号量的值int pid = fork();if(pid > 0){//去拿锁-拿到锁能执行下面一句pGetKey(semid);//拿钥匙printf("this is father\n");vPutBackKey(semid);//放回semctl(semid,0,IPC_RMID);//销毁锁}else if(pid == 0){printf("this is child\n");vPutBackKey(semid);//放回}else{printf("fork failed\n");}return 0;	
}

四.线程

在这里插入图片描述

https://www.cnblogs.com/xiehongfeng100/p/4620852.html
进程是程序执行的实例,当担分配系统资源的基本单位
面向线程设计的系统中,线程本身不是基本的运行单位,是线程的容器。
程序本身是指令,数据及其组织形式的描述,进程才是程序的真正运行的实例

1. 线程的创建等待退出

1.1.1 线程创建
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
// 返回:若成功返回0,否则返回错误编号当pthread_create成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于定制各种不同的线程属性,暂可以把它设置为NULL,以创建默认属性的线程。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。
1.1.2 线程等待
#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
// 返回:若成功返回0,否则返回错误编号调用这个函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。如果例程只是从它的启动例程返回i,rval_ptr将包含返回码。如果线程被取消,由rval_ptr指定的内存单元就置为PTHREAD_CANCELED。可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL。如果对线程的返回值不感兴趣,可以把rval_ptr置为NULL。在这种情况下,调用pthread_join函数将等待指定的线程终止,但并不获得线程的终止状态。
1.1.3 线程退出
单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:1)线程只是从启动例程中返回,返回值是线程的退出码。2)线程可以被同一进程中的其他线程取消。3)线程调用pthread_exit:#include <pthread.h>
int pthread_exit(void *rval_ptr);rval_ptr是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。
1.1.4 线程id获取
(void**)&p 是空类型
void * 是无类型指针
void ** 是无类型指针的指针
&p 是p变量取地址运算符
(类型) 为强转类型运算
(void **)&p 变量,就是把变量p地址强制转化无类型指针的指针
#include <pthread.h>
pthread_t pthread_self(void);
// 返回:调用线程的ID#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);
// 返回:若相等则返回非0值,否则返回0
1.1.5 编程实现
#include <pthread.h>
#include <stdio.h>
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
//                          void *(*start_routine) (void *), void *arg);
void *func1(void *arg)
{//static int ret = 10; //必须写staticstatic char *p = "t1 is run out";printf("t1 : %ld thread is  create\n",(unsigned long)pthread_self());printf("param is %d\n",*((int *)arg));//pthread_exit((void *)&ret);pthread_exit((void *)p);
}
int main()
{int ret;int param = 100;pthread_t t1;//int *pret = NULL;char *pret = NULL;ret = pthread_create(&t1,NULL,func1,(void*)&param);if(ret == 0){printf("create t1 success\n");}printf("main %ld\n",(unsigned long)pthread_self());pthread_join(t1,(void **)&pret);//二级指针      //可以回收线程退出的状态//printf("main: t1 quit: %d\n",*pret);printf("main: t1 quit: %s\n",pret);//while(1);return 0;
}

2. 线程共享内存空间

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
//                          void *(*start_routine) (void *), void *arg);
int g_data = 0;
void *func1(void *arg)
{printf("t1 : %ld thread is  create\n",(unsigned long)pthread_self());printf("t1 param is %d\n",*((int *)arg));while(1){printf("t1: %d\n",g_data++);		sleep(1);}
}
void *func2(void *arg)
{printf("t2 : %ld thread is  create\n",(unsigned long)pthread_self());printf("t2 param is %d\n",*((int *)arg));while(1){printf("t2: %d\n",g_data++);		sleep(1);}
}
int main()
{int ret;int param = 100;pthread_t t1;pthread_t t2;//int *pret = NULL;char *pret = NULL;ret = pthread_create(&t1,NULL,func1,(void*)&param);if(ret == 0){printf("create t1 success\n");}ret = pthread_create(&t2,NULL,func2,(void*)&param);if(ret == 0){printf("create t2 success\n");}printf("main %ld\n",(unsigned long)pthread_self());while(1){printf("main: %d\n",g_data++);sleep(1);		}pthread_join(t1,(void **)&pret);//二级指针	//可以回收线程退出的状态pthread_join(t2,(void **)&pret);//二级指针	//可以回收线程退出的状态return 0;
}

在这里插入图片描述

3. 线程同步

3.1 互斥量加锁解锁

3.1.1 创建及销毁互斥锁
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t mutex);
// 返回:若成功返回0,否则返回错误编号
3.1.1 创建及销毁互斥锁
互斥量就是一把锁
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t mutex);
int pthread_mutex_trylock(pthread_mutex_t mutex);
int pthread_mutex_unlock(pthread_mutex_t mutex);
// 返回:若成功返回0,否则返回错误编号如果线程不希望被阻塞,它可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0,否则pthread_mutex_trylock就会失败,不能锁住互斥量,而返回EBUSY。
例:有多个线程,都对线程的信息进行加锁,当某个线程抢先运行后会将锁内的内容执行完(解锁),解锁完之后多个线程抢锁,先抢到的先运行,每次只有一个线程可以运行
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
//                          void *(*start_routine) (void *), void *arg);
int g_data = 0;
pthread_mutex_t mutex;
void *func1(void *arg)
{//只有锁内的东西完成以后才会进行下一个线程pthread_mutex_lock(&mutex);//上锁printf("t1 : %ld thread is  create\n",(unsigned long)pthread_self());printf("t1 param is %d\n",*((int *)arg));pthread_mutex_unlock(&mutex);//解锁}
void *func2(void *arg)
{pthread_mutex_lock(&mutex);//上锁printf("t2 : %ld thread is  create\n",(unsigned long)pthread_self());printf("t2 param is %d\n",*((int *)arg));pthread_mutex_unlock(&mutex);//解锁}
int main()
{int ret;int param = 100;pthread_t t1;pthread_t t2;//int *pret = NULL;char *pret = NULL;//int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);pthread_mutex_init(&mutex,NULL);//加锁ret = pthread_create(&t1,NULL,func1,(void*)&param);if(ret == 0){printf("create t1 success\n");}ret = pthread_create(&t2,NULL,func2,(void*)&param);if(ret == 0){printf("create t2 success\n");}printf("main %ld\n",(unsigned long)pthread_self());pthread_join(t1,(void **)&pret);//二级指针	//可以回收线程退出的状态pthread_join(t2,(void **)&pret);//二级指针	//可以回收线程退出的状态pthread_mutex_destroy(&mutex); //销毁锁return 0;
}

3.2 互斥锁(互斥量)限制共享资源的访问

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
//                          void *(*start_routine) (void *), void *arg);
int g_data = 0;
pthread_mutex_t mutex;
void *func1(void *arg)
{//只有锁内的东西完成以后才会进行下一个线程printf("t1 : %ld thread is  create\n",(unsigned long)pthread_self());printf("t1 param is %d\n",*((int *)arg));pthread_mutex_lock(&mutex);//上锁while(1){printf("t1 : %d\n",g_data++);sleep(1);if(g_data == 3){pthread_mutex_unlock(&mutex);//解锁printf("quit============================\n");//pthread_exit(NULL);exit(0);}}}
void *func2(void *arg)
{printf("t2 : %ld thread is  create\n",(unsigned long)pthread_self());printf("t2 param is %d\n",*((int *)arg));while(1){printf("t2 : %d\n",g_data);pthread_mutex_lock(&mutex);//上锁g_data++;pthread_mutex_unlock(&mutex);//上锁sleep(1);}}
int main()
{int ret;int param = 100;pthread_t t1;pthread_t t2;//int *pret = NULL;//int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);pthread_mutex_init(&mutex,NULL);//加锁ret = pthread_create(&t1,NULL,func1,(void*)&param);if(ret == 0){printf("create t1 success\n");}ret = pthread_create(&t2,NULL,func2,(void*)&param);if(ret == 0){printf("create t2 success\n");}printf("main %ld\n",(unsigned long)pthread_self());while(1){printf("main : %d\n",g_data);sleep(1);}pthread_join(t1,NULL);	//可以回收线程退出的状态pthread_join(t2,NULL);	//可以回收线程退出的状态pthread_mutex_destroy(&mutex); //销毁锁return 0;
}

3.3 死锁

假设有两把锁
线程1获得到了锁1,但没有解锁,此时线程1仍然需要获得锁2
线程2获得到了锁2,但没有解锁,此时线程2仍然需要获得锁1此时锁死
即线程1得到锁1未释放,仍需获得锁2
线程2得到锁2未释放,仍然需要获得锁1,
双方都阻塞在取锁的阶段

3.4 线程条件控制实现线程同步

在这里插入图片描述

要求:在线程1中输出3,其他在线程2中输出#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
//                          void *(*start_routine) (void *), void *arg);
int g_data = 0;
pthread_mutex_t mutex;
pthread_cond_t cond;	void *func1(void *arg)
{//只有锁内的东西完成以后才会进行下一个线程printf("t1 : %ld thread is  create\n",(unsigned long)pthread_self());printf("t1 param is %d\n",*((int *)arg));static int cnt = 0;	while(1){pthread_cond_wait(&cond,&mutex);printf("t1 run==========================\n");printf("t1 : %d\n",g_data++);g_data = 0;sleep(1);if(cnt++ == 10){exit(1);}}}
void *func2(void *arg)
{printf("t2 : %ld thread is  create\n",(unsigned long)pthread_self());printf("t2 param is %d\n",*((int *)arg));while(1){printf("t2 : %d\n",g_data);pthread_mutex_lock(&mutex);//上锁g_data++;if(g_data == 3){pthread_cond_signal(&cond); //func1在等到,其他线程将g_data加到3时,唤醒func1}pthread_mutex_unlock(&mutex);//解锁sleep(1);}}
int main()
{int ret;int param = 100;pthread_t t1;pthread_t t2;//int *pret = NULL;//int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);pthread_mutex_init(&mutex,NULL);//加锁pthread_cond_init(&cond,NULL);ret = pthread_create(&t1,NULL,func1,(void*)&param);if(ret == 0){//printf("create t1 success\n");}ret = pthread_create(&t2,NULL,func2,(void*)&param);if(ret == 0){//printf("create t2 success\n");}printf("main %ld\n",(unsigned long)pthread_self());pthread_join(t1,NULL);	//可以回收线程退出的状态pthread_join(t2,NULL);	//可以回收线程退出的状态pthread_mutex_destroy(&mutex); //销毁锁pthread_cond_destroy(&cond);return 0;
}

在这里插入图片描述

调试程序
#include <stdlib.h>
int main(int argc,char **argv)
{int time = atoi(argv[1]);int i = 0;for(i=0;i<time;i++){system("./lptest");}return 0;
}
生成调试结果文件
./a.out 10 >>test.ret.txt &	//追加的 方式

3.5 宏

在这里插入图片描述

五.网络编程

1.概述

1.1 TCP/UDP对比

socket套字节
tcp:面向连接
udp:面向报文1. TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接 
2. TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付 
3. TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的 UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等) 
4. 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信 
5. TCP首部开销20字节;UDP的首部开销小,只有8个字节 
6. TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

1.2 端口号及其作用

	一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等 这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。  实际上是通过“IP地址+端口号”来区 分不同的服务的。 端口提供了一种访问通道 服务器一般都是通过知名端口号来识别的。例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69

1.3 字节序

	字节序(Byte Order)是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。在计算机中是以字节为单位,每个地址对应一个字节,一个字节8bit。在计算机系统中,当物理单位的长度大于1个字节时,就要区分字节顺序。常见的字节顺序有两种:12大端(Big-endian):将高序字节存储在起始地址,即数据的高位字节存放在内存的低地址中,而数据的低位字节存放在内存的高地址中。小端(Little-endian):将低序字节存储在起始地址,即数据的高位字节存放在内存的高地址中,而数据的低位字节存放在内存的低地址中。此外,还有网络字节顺序,是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,网络字节顺序是大端字节序,从而可以保证数据在不同主机之间传输时能够被正确解释。

在这里插入图片描述

即:小端字节序,将低位字节存储在起始位置大端字节序,将高位字节存储在起始位置

1.4 字节序转换api

在这里插入图片描述

2. socket编程步骤

2.1 开发流程

在这里插入图片描述

1.创建套接字——socket()	
2.为套件字添加信息(ip+端口号)——bind()
3.监听网络——listen()
4.监听到有客户端接入,接收一个连接
5.数据交互
6.关闭套接字,断开连接

2.2 Linux提供的API接口

2.2.1 连接协议–创建套接字
//连接协议--创建套接字
1.int socket(int domain,int type,int protocol);

在这里插入图片描述

2.2.2 地址+端口号
2.地址+端口号
查找 sockaddr_in 头文件
cd /user/include/
grep "struct sockaddr_in {" * nir
vi linux/in.h +261 (查找sockaddr_in)

在这里插入图片描述

2.2.3 地址转换API
把字符串形式的“192.168.1.123”转为网络能识别的格式
int inet_aton(const char* straddr,struct in_addr *addrp); 把网络格式的ip地址转为字符串形式
char* inet_ntoa(struct in_addr inaddr);  
2.2.4 监听

在这里插入图片描述

2.2.6 连接

在这里插入图片描述

2.2.5 数据收发

在这里插入图片描述

2.2.5 数据收发第二套API
2.2.5 监听

3. socket服务端代码实现

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{int s_fd;char readBuf[128];struct sockaddr_in s_addr;      //serverstruct sockaddr_in c_addr;      //clientmemset(&s_addr,0,sizeof(struct sockaddr_in));memset(&c_addr,0,sizeof(struct sockaddr_in));//1. sockets_fd = socket(AF_INET, SOCK_STREAM,0);if(s_fd == -1){perror("socket");exit(-1);}s_addr.sin_family = AF_INET;s_addr.sin_port = htons(8989);inet_aton("127.0.0.1",&s_addr.sin_addr);//2. bindbind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//3. listenlisten(s_fd,10);//4. acceptint clen = sizeof(struct sockaddr_in);int c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen); //返回客户端fdif(c_fd == -1){perror("client");}printf("get connnect: %s\n",inet_ntoa(c_addr.sin_addr));//5. readint nread = read(c_fd,readBuf,128);if(nread == -1){perror("read");}else{printf("get message:%d, %s\n",nread,readBuf);}//6. writewrite(c_fd,"I get your message\n",strlen("I get your message"));printf("connect \n");return 0;
}

4. socket客户端代码实现

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{int c_fd;char readBuf[128];char *msg = "msg from client";struct sockaddr_in c_addr;      //clientmemset(&c_addr,0,sizeof(struct sockaddr_in));//1. socketc_fd = socket(AF_INET, SOCK_STREAM,0);if(c_fd == -1){perror("socket");exit(-1);}c_addr.sin_family = AF_INET;c_addr.sin_port = htons(8989);inet_aton("127.0.0.1",&c_addr.sin_addr);//2. connectif(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr))==-1){perror("connect");exit(-1);}printf("get connnect: %s\n",inet_ntoa(c_addr.sin_addr));//3. sendwrite(c_fd,msg,strlen(msg));//4. readint nread = read(c_fd,readBuf,128);if(nread == -1){perror("read");}else{printf("get message:%d, %s\n",nread,readBuf);}return 0;
}

5. 实现多方聊天

思路:
服务器:父进程用于等待新客户端接入请求子进程用于处理客户端数据
客户端:父进程用于读取子进程发送数据

5.1 服务端

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char **argv)
{int s_fd;int c_fd;int nread;
//      char *return_msg = "I get your message\n";char msg[128] = {0};int mark = 0;char readBuf[128];struct sockaddr_in s_addr;      //serverstruct sockaddr_in c_addr;      //clientmemset(&s_addr,0,sizeof(struct sockaddr_in));memset(&c_addr,0,sizeof(struct sockaddr_in));//1. sockets_fd = socket(AF_INET, SOCK_STREAM,0);if(s_fd == -1){perror("socket");exit(-1);}s_addr.sin_family = AF_INET;s_addr.sin_port = htons(atoi(argv[2]));inet_aton(argv[1],&s_addr.sin_addr);//2. bindbind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//3. listenlisten(s_fd,10);//4. acceptint clen = sizeof(struct sockaddr_in);while(1){c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen); //返回客户端fdif(c_fd == -1){perror("accept");}mark++;printf("get connnect: %s\n",inet_ntoa(c_addr.sin_addr));if(fork() == 0){        //返回值为0表示当前为子进程,子进程进行数据处理,父进程等待下一个请求if(fork() == 0){while(1){sprint(msg,"welcome No.%d client",mark);write(c_fd,msg,strlen(msg));sleep(3);}}while(1){//5. readmemset(readBuf,0,sizeof(readBuf));nread = read(c_fd,readBuf,128);if(nread == -1){perror("read\n");}else if(nread > 0){printf("get message num = %d, msg =  %s\n",nread,readBuf);}else{printf("client quit\n");break;}}break;}}return 0;
}

5.1 客户端

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char **argv)
{int c_fd;int nread;char readBuf[128];
//      char *msg = "msg from client";char msg[128] = {0};struct sockaddr_in c_addr;      //clientmemset(&c_addr,0,sizeof(struct sockaddr_in));//1. socketc_fd = socket(AF_INET, SOCK_STREAM,0);if(c_fd == -1){perror("socket");exit(-1);}c_addr.sin_family = AF_INET;c_addr.sin_port = htons(atoi(argv[2]));inet_aton(argv[1],&c_addr.sin_addr);//2. connectif(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr))==-1){perror("connect");exit(-1);}while(1){if(fork() == 0){        //子进程发送,父进程读取while(1){//3. sendmemset(msg,0,sizeof(msg));printf("input:\n");gets(msg);write(c_fd,msg,strlen(msg));}}//4. readwhile(1){memset(readBuf,0,sizeof(readBuf));nread = read(c_fd,readBuf,128);if(nread == -1){perror("read");}else{printf("get message:%d, %s\n",nread,readBuf);}}}return 0;
}

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

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

相关文章

实现Node.js安装与配置。

一 、Node.js简介 Node.js是一个基于Chrome V8引擎的JavaScript运行时环境&#xff0c;用于构建高性能、可扩展的网络应用程序。它发布于2009年5月&#xff0c;由Ryan Dahl开发&#xff0c;允许使用JavaScript进行服务器端编程&#xff0c;使开发者能够在前后端都使用同一种编程…

Unity 新版输入系统(Input System)

前言 官方教程 注意 新的输入系统需要 Unity 2019.4 和 .NET 4 运行时。它不适用于 .NET 3.5 的项目。 教程版本&#xff1a;Unity 2021.3.26 1. 安装 1.1 打开 Package Manager 导航栏 -> Window -> Package Manager 1.2 安装 Input System 选择 Unity Registry 在列…

RabbitMQ, DelayQueue, Redis的介绍以及IDEA的实现

RabbitMQ RabbitMQ是一个开源的消息队列中间件&#xff0c;它实现了高效、可靠的消息传递机制。它支持多种消息传递模式&#xff0c;如发布/订阅、点对点、请求/回应等。RabbitMQ以其可靠性、灵活性和易用性受到广泛的关注和应用。 RabbitMQ基于AMQP&#xff08;Advanced Mess…

孙中亮:北斗三十周年,看北斗芯片高质量发展历程和方向

1994年1月10日&#xff0c;北斗一号建设正式启动&#xff0c;党中央决策建设独立自主的北斗卫星导航系统。2020年7月31日&#xff0c;北斗三号全球卫星导航系统正式开通&#xff0c;标志着北斗系统进入全球化发展新阶段。随着2024年的到来&#xff0c;北斗系统建设已走过栉风沐…

汇智知了堂走进宜宾学院,共话国产化信创未来!

在春意盎然的四月&#xff0c;汇智知了堂以其深厚的品牌底蕴和卓越的教育品质&#xff0c;再次展现了其在教育领域的领先地位。4月18日&#xff0c;汇智知了堂走进宜宾学院&#xff0c;为广大学子带来了一场关于国产化信创时代的技术变革与专业学习建议的讲座。 汇智知了堂作…

2024深圳杯(东三省)数学建模挑战赛D题:音板的振动模态分析与参数识别思路代码成品论文分析

​ 更新完整代码和成品完整论文 《2024深圳杯&东三省数学建模思路代码成品论文》↓↓↓ https://www.yuque.com/u42168770/qv6z0d/zx70edxvbv7rheu7?singleDoc# 问题重述 深圳杯&#xff08;东三省&#xff09;数学建模挑战赛2024D题&#xff1a;音板的振动模态分析与…

YoloV9改进策略:注意力改进、Neck层改进_自研全新的Mamba注意力_即插即用,简单易懂_附结构图_检测、分割、关键点均适用(独家原创,全世界首发)

摘要 无Mamba不狂欢,本文打造基于Mamba的注意力机制。全世界首发基于Mamba的注意力啊!对Mamba感兴趣的朋友一定不要错过啊! 基于Mamba的高效注意力代码和结构图 import torch import torch.nn as nn # 导入自定义的Mamba模块 from mamba_ssm import Mamba class Eff…

vue做导入导出excel文档

系统中经常会遇到要实现批量导入/导出数据的功能&#xff0c;导入就需要先下载一个模板&#xff0c;然后在模板文件中填写内容&#xff0c;最后导入模板&#xff0c;导出就可能是下载一个excel文件。 1、导出 新建一个export.js文件如下&#xff1a; import {MessageBox,Mes…

赋能数据检索:构建用于www.sohu.com的新闻下载器

引言 在信息爆炸的时代&#xff0c;随着新闻数据的数量不断增长&#xff0c;获取和分析这些数据变得尤为关键。本文将介绍如何构建一个高效的新闻下载器&#xff0c;专门用于从搜狐网&#xff08;www.sohu.com&#xff09;检索和下载新闻内容。 背景介绍 搜狐网作为中国领先…

40. 【Android教程】AsyncTask:异步任务

在前面的章节有提到过&#xff0c;Android 系统默认会在主线程&#xff08;UI 线程&#xff09;执行任务&#xff0c;但是如果有耗时程序就会阻塞 UI 线程&#xff0c;导致页面卡顿。这时候我们通常会将耗时任务放在独立的线程&#xff0c;然后通过 Handler 等线程间通信机制完…

外贸干货|客户迟迟不付款,怎么催?

(一) Gentle reminder 温馨提醒 "Hello Mary, l hope this message finds you well. l wanted to kindly remind you about the payment for our agreed-upon order. We appreciate your business and would like to proceed with the next steps as soon as possible.…

DS32K查看内置寄存器数值

需要在debug的时候进行查看&#xff0c;先暂停&#xff0c;再打开EmbSys Registers窗口。 需要先将导出的内容选中并双击&#xff0c;不然复制出来会变成问号。右上角有个复制按钮&#xff0c;复制到剪贴板就行。譬如我这里选择了MCR寄存器&#xff0c;复制出来的就是这个寄存器…

下载nvm来配置node版本

背景提示&#xff1a;入职的公司项目久远&#xff0c;一直运行不起来&#xff0c;原来是我node版本太高&#xff0c;需要降级才行。然后找到这个nvm配置一下 准备工作 如果电脑有配置node的&#xff0c;需要先卸载掉才能配置nvm&#xff01;&#xff01;&#xff01;这是重点嗷…

大模型解决方案:具体业务场景下的智能表单填充(附代码)

大模型相关目录 大模型,包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容 从0起步,扬帆起航。 大模型应用向开发路径:AI代理工作流大模型应用开发实用开源项目汇总大模型问答项目问答性能评估方法大模型…

JS -正则表达式

正则表达式 关于正则表达式&#xff0c;其实我写过几篇了&#xff0c;但是真正的正则表达式其实主要用于定义一些字符串的规则&#xff0c;计算机根据给出的正则表达式&#xff0c;来检查一个字符串是否符合规则。 我们来看一下&#xff0c;在JS中如何创建正则表达式对象。 语…

第67天:APP攻防-Frida反证书抓包移动安全系统资产提取评估扫描

思维导图 案例一&#xff1a;内在-资产提取-AppinfoScanne AppinfoScanner 一款适用于以 HW 行动/红队/渗透测试团队为场景的移动端(Android、iOS、WEB、H5、静态网站)信息收集扫描工具&#xff0c;可以帮助渗透测试工程师、攻击队成员、红队成员快速收集到移动端或者静态 WEB …

【禅道客户案例】小反馈,大杠杆!银丰新融「反馈管理」优秀实践

企业介绍 北京银丰新融科技开发有限公司&#xff08;简称&#xff1a;银丰新融&#xff09;成立于2000 年&#xff0c;自创立以来一贯专注于金融监管、风险管控等领域的信息系统建设&#xff0c;拥有目前国内金融风险领域规模庞大的信息技术服务团队。 银丰新融业务范围覆盖了…

VUE3 ref,props,生命周期

1.--ref属性 1.1代码 1.1.1子表 <template><div class"person"><h1>中国</h1><h2 ref"title2">北京</h2><h3>尚硅谷</h3><button click"showLog">点我输出h2这个元素</button>&l…

JavaScript注释:单行注释和多行注释详解

为了提高代码的可读性&#xff0c;JS与CSS一样&#xff0c;也提供了注释功能。JS中的注释主要有两种&#xff0c;分别是单行注释和多行注释。 在编程的世界里&#xff0c;注释是那些默默无闻的英雄&#xff0c;它们静静地站在代码的背后&#xff0c;为后来的维护者、为未来的自…

到底什么是爬虫

1. 引言 在数据驱动的世界里&#xff0c;网络爬虫&#xff08;Web Crawling&#xff09;技术扮演着获取和处理网上数据的关键角色。无论是为了数据分析、机器学习项目的数据集构建还是简单地监测网页变化&#xff0c;学习如何创建一个基本的网页爬虫可以大大提升你的工作效率和…