进程间通讯

简介:

进程间通讯方式有:

1.内存映射(mmap):

使用mmap函数将磁盘空间映射到内存

2.管道

3.信号

4.套接字(socket)

5.信号机制

通过进程中kill函数,去给另一个函数发送信号,另一个函数捕捉到该信号之后,重新定义该信号的操作,实现进程间通讯

6.system V IPC:

简介:

IPC对象包括:共享内存,消息队列,信号灯集

1.每个IPC对象都有唯一的ID与Key关联(通过访问key就知道访问的是哪一块IPC对象)

2.IPC对象创建后一直存在,直到被显式删除

3.属于内核中的数据结构

共享内存:

很老,但仍有应用

消息队列:

过时

信号灯集:

过时

方式1、内存映射

简介:

 使用系统调用函数mmap,将一个磁盘文件与内存中的一个缓冲区相映射,进程可以像访问普通内存一样对文件进行访问,不需要再调用read,write。

(进程在内存中都有自己的内存空间,不能相互访问,即使内存地址相同也不能相互访问)

(同一个磁盘文件是可以被多个进程相互访问的,因此将磁盘文件映射到内存中的缓冲区,相当于直接在内存中对磁盘文件进行读写,实现用户空间和内核空间高校交互)

原本的进程间交互方式:

使用mmap系统调用函数后:

函数实现:

mmap函数:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

功能:创建共享内存映射

函数返回值:成功返回创建的映射区首地址,失败返回宏:MAP_FAILED( ((void *) -1) )(也就是-1),设置errno值

参数说明:

addr:指定要映射的内存地址,一般设置为 NULL 让操作系统自动选择合适的内存地址。

length:必须>0。映射地址空间的字节数,它从被映射文件开头 offset 个字节开始算起。

prot:指定共享内存的访问权限。可取如下几个值的可选:PROT_READ(可读), PROT_WRITE(可写), PROT_EXEC(可执行), PROT_NONE(不可访问)。

flags:由以下几个常值指定:

MAP_SHARED(共享的)(进程间通讯)

MAP_PRIVATE(私有的)(单个进程)(方便对文件快速读写)

MAP_FIXED(表示必须使用 start 参数作为开始地址,如果失败不进行修正)

其中,MAP_SHARED , MAP_PRIVATE必选其一,而MAP_FIXED 则不推荐使用。MAP_ANONYMOUS(匿名映射,用于血缘关系进程间通信)(类似与无名管道)

fd:表示要映射的文件描述符。如果匿名映射写-1(不需要文件)。

offset:表示映射文件的偏移量,一般设置为 0 表示从文件头部开始映射。

注意事项:

(1) 创建映射区的过程中,隐含着一次对映射文件的读操作,将文件内容读取到映射区。

(2) 当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护),如果不满足报非法参数(Invalid argument)错误。

当MAP_PRIVATE时候,mmap中的权限是对内存的限制,只需要文件有读权限即可,操作只在内存有效,不会写到物理磁盘,且不能在进程间共享。

(3) 映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭,文件关闭后,使用修改映射内存中的数据,磁盘文件本身也会被修改。

(4) 用于映射的文件大小必须>0,当映射文件大小为0时

指定非0大小创建映射区,访问映射地址会报总线错误

指定0大小创建映射区,报非法参数错误(Invalid argument)

(5) 文件偏移量必须为0或者4K的整数倍(否则 会报非法参数Invalid argument错误).

(因为内存是按页分配的,一页为4k字节)

(6)可访问内存空间根据实际文件大小进行定义;和mmap函数参数映射的内存大小无关(只要mmap参数length不为0即可),但是大于文件实际大小的内存位置不会被写入文件

例:文件实际字节小于一页(也就是4k,4096字节),可访问内存就为一页(mmap参数length可以取不为0的任意数)

(7)文件大小代表映射的内存能写入的数据大小,文件中原有数据也会被映射到内存中去

munmap函数

int munmap(void *addr, size_t length);

返回值:成功返回0,失败返回-1,并设置errno值。

addr:调用mmap函数成功返回的映射区首地址

length:映射区大小(即:mmap函数的第二个参数)

注意事项:

相关函数:

lseek函数:

off_t lseek(int fd,off_t offset,int whence);

功能:查看文件长度,是一个在 C 语言中的系统调用,用于在文件中移动文件指针。

成功时,返回新的文件偏移量(从文件开头开始的字节数)。

失败时,返回 -1,并设置 errno 以指示错误。

fd:被查看文件的文件描述符

offset:在文件内的偏移量(也就是起始地址)

whence:一个整型常量,用于指定偏移量的引用点。

可取的值有:

SEEK_SET: 文件开头,offset 是相对于文件开头的字节偏移。

SEEK_CUR: 当前文件位置,offset 是相对于当前文件位置的字节偏移。

SEEK_END: 文件末尾,offset 是相对于文件末尾的字节偏移。

memcpy函数:

void *memcpy(void *dest, const void *src, size_t n);

功能:是 C 和 C++ 中的一个标准库函数,用于从一个内存位置复制数据到另一个内存位置。它定义在 <string.h> 头文件中,常用于处理内存块的拷贝。

无返回值

参数:

dest:指向要复制到的目标内存块的指针。

src:指向要复制的源内存块的指针。

n:要复制的字节数。

相关命令:

使用方式:

写:

#include<stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
#include<string.h>
int main() {void *addr;int fd;/** 1.打开文件*/fd=open("text",O_RDWR);if(fd<0) {perror("open");return 0;}/** 2.映射文件到内存*/addr=mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);if(addr<0) {perror("mmap");return 0;}/** 3.关闭文件*/close(fd);/** 4.读写数据-写*/int i=0;while(i<4096) {memcpy(addr+i,"b",1);i++;sleep(1);}return 0;
}

读:

#include<stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
#include<string.h>
int main(){void *addr;int fd;
/** 1.打开文件*/fd=open("text",O_RDWR);if(fd<0){perror("open");return 0;}
/** 2.映射文件到内存*/addr=mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);if(addr<0){perror("mmap");return 0;}
/** 3.关闭文件*/close(fd);
/** 4.读写数据-读*/while(1){printf("%s\n",(char *)(addr));sleep(1);}return 0;
}

注意事项:

方式2、system V IPC共享内存

简介:

1.共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
2.共享内存在内核空间创建,可被进程映射到用户空间访问,使用灵活。
3.由于多个进程可同时访问共享内存,因此需要同步和互斥机制配合使用。

函数实现:

1.创建key

key_t  f tok(const char *path,  int id);

 #include  <sys/types.h>

 #include <sys/ipc.h>  

key_t  ftok(const char *path,  int proj_id);  

成功时返回合法的key值,失败时返回EOF  

path  存在且可访问的文件的路径  

proj_id  用于生成key的数字,范围1-255。

2.创建/打开共享内存

#include <sys/ipc.h>  

#include <sys/shm.h>  

int shmget(key_t key, int size, int shmflg);  

成功时返回共享内存的id,失败时返回EOF  

key  和共享内存关联的key,IPC_PRIVATE 或 ftok生成  

size 创建共享内存的大小(字节为单位)

shmflg 共享内存标志位,用于指定共享内存的创建方式和访问权限。

常用的标志包括:

IPC_CREAT: 如果指定的共享内存段不存在,则创建一个新的共享内存段。

IPC_EXCL: 如果指定的共享内存段已经存在,则调用失败。

权限标志位,如 0666,可以指定访问权限。

3.映射共享内存

即把指定的共享内存映射到进程的地址空间用于访问

#include <sys/ipc.h>  

#include <sys/shm.h>  

void  *shmat(int shmid, const void *shmaddr, int shmflg);  

成功时返回映射后的地址,失败时返回(void *)-1  

shmid   要映射的共享内存id  

shmaddr   映射后的地址, NULL表示由系统自动映射  

shmflg   标志位  0表示可读写;SHM_RDONLY表示只读

4.读写共享内存

5.撤销共享内存映射

#include <sys/ipc.h>  

#include <sys/shm.h>  

int  shmdt(void *shmaddr);  

成功时返回0,失败时返回EOF  

6.删除共享内存对象

#include <sys/ipc.h>  

#include <sys/shm.h>  

int  shmctl(int shmid, int cmd, struct shmid_ds *buf);  

成功时返回0,失败时返回EOF  

shmid   要操作的共享内存的id  

  • cmd: 操作的命令,决定了函数的行为。常用的命令包括:
    • IPC_RMID: 删除共享内存段。
    • IPC_STAT: 获取共享内存段的状态信息(存储在 struct shmid_ds 中)。
    • IPC_SET: 设置共享内存段的属性。
  • buf: 指向 struct shmid_ds 的指针,用于存储或提供状态信息。如果 cmd 是 IPC_STATbuf 应指向一个有效的 struct shmid_ds 结构体。如果 cmd 是 IPC_SETbuf 应指向一个包含要设置的新值的 struct shmid_ds 结构体;如果 cmd 是 IPC_RMID,则可以设置为 NULL。

相关函数:

strcpy函数

char *strcpy(char *dest, const char *src)

返回目标字符串 dest 的指针。

功能:函数是 C 语言中用于字符串复制的标准库函数,定义在 <string.h> 头文件中。它的基本功能是将一个字符串的内容复制到另一个字符串中。

参数:

dest:目标字符串,复制后的内容将存放在这里。这个字符串必须有足够的空间来存放源字符串及其结束符(\0)。

src:源字符串,内容将被复制的对象。

相关命令:

ipcs命令:

可以在终端查看共享内存、消息队列、信号量(都属于内核中数据结构)

touch命令:创建文件

使用方式:

读:

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<string.h>
int main() {key_t key;int shmid;void *addr;int dt;int ctl;/** 1.生成key*/key=ftok("keytest",34);if(key<0) {perror("ftok");return 0;}printf("key=%d\n",key);/** 2.创建共享内存*/shmid=shmget(key,512,IPC_CREAT|0666);if(shmid<0) {perror("shmget");return 0;}printf("ID=%d\n",shmid);/** 3.映射共享内存*/addr=shmat(shmid,NULL,0);if(addr<0) {perror("shmat");return 0;}printf("addr=%p\n",addr);/** 4.读写内存-读*/printf("%s\n",(char *)addr);/** 5.撤销共享内存*/dt=shmdt(addr);if(dt<0) {perror("shmdt");return 0;}/** 6.删除共享内存*/ctl=shmctl(shmid,IPC_RMID,NULL);if(ctl<0) {perror("shmctl");}return 0;
}

写:

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<string.h>
int main() {key_t key;int shmid;void *addr;/** 1.生成key*/key=ftok("keytest",34);if(key<0) {perror("ftok");return 0;}printf("key=%d\n",key);/** 2.创建共享内存*/shmid=shmget(key,512,IPC_CREAT|0666);if(shmid<0) {perror("shmget");return 0;}printf("ID=%d\n",shmid);/** 3.映射共享内存*/addr=shmat(shmid,NULL,0);if(addr<0) {perror("shmat");return 0;}printf("addr=%p\n",addr);/** 4.读写内存-写*/strcpy(addr,"hello");}

注意事项:

1.写内存使如果创建了共享内存,读内存时就不需要再创建

2.共享内存创建后不会随着程序结束而消失,如果共享内存不被删除则一直存在,共享内存中数据一直也存在

3.使用shmdt撤销后,此处共享内存将不能被访问,但是该共享内存还存在,如果不在使用该共享内存,需要使用shmctl将共享内存删除

方式3、system V IPC消息队列

简介:

队列:先进先出(链表实现)(内核中的一种数据结构)

消息队列由消息队列 ID 来唯一标识
消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等
消息队列可以按照类型来发送/接收消息

函数实现:

1.打开/创建消息队列

 #include <sys/ipc.h>

 #include <sys/msg.h>

 int msgget(key_t key, int msgflg);

  成功时返回消息队列的id,失败时返回EOF

key: 消息队列的唯一标识符的键值。这个值可以通过ftok函数生成,它通常是一个整数,用于标识不同的消息队列。

msgflg: 控制消息队列创建和访问的标志,是一个位标志,可以使用以下几种选项组合:

IPC_CREAT: 如果消息队列不存在,则创建一个新的消息队列。

IPC_EXCL: 与IPC_CREAT一起使用,确保如果消息队列已存在,msgget将失败,而不是返回现有队列的ID。

其他标志,如权限位,可以控制访问权限(例如:0666 表示所有用户可读写)。

2.发送消息

#include <sys/ipc.h>

 #include <sys/msg.h>

 int msgsnd(int msgid, const void *msgp, size_t size, int msgflg);

  成功时返回0,失败时返回-1

 参数1:msgid   消息队列id
 参数2:msgp    消息缓冲区地址(msgT结构体地址)
参数:msgp:

消息格式:

typedef struct{long msg_type;//必须是long型,表示消息的类型char buf[128];}msgT;

注意:

1.消息结构必须有long类型的msg_type字段,表示消息的类型。(与接收方关联)

2.消息长度不包括结构体中"消息类型long msg_type"的长度(也就是参数size=结构体长度-消息类型长度=buf消息长度)

3.发送端消息类型取值必须大于0

参数3:size    消息正文长度
参数4:msgflg   标志位 0 或 IPC_NOWAIT
参数:msgflg:

0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列

IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回

3.接收消息:

#include <sys/ipc.h>

 #include <sys/msg.h>

 int msgrcv(int msgid, void *msgp, size_t size, long msgtype,int msgflg);

  成功时返回收到的消息长度,失败时返回-1

参数1:msgid   消息队列id
参数2:msgp   消息缓冲区地址
参数3:size   指定接收的消息长度
参数4:msgtype   指定接收的消息类型   
参数msgtype:

msgtype=0:收到的第一条消息,任意类型都接收。

msgtype>0:收到的第一条 msg_type类型的消息。

msgtype<0:接收类型等于或者小于msgtype绝对值的第一个消息。

例子:如果msgtype=-4,只接受类型是1、2、3、4的消息

参数5:msgflg   标志位  
参数:msgflg:

0:阻塞式接收消息(如果没有消息,将会一直等待)

IPC_NOWAIT:没有消息也返回,返回错误码,此时错误码为ENOMSG

MSG_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息

4.控制消息队列:

#include <sys/ipc.h>

 #include <sys/msg.h>

 int msgctl(int msgid, int cmd, struct msqid_ds *buf);

  成功时返回0,失败时返回-1

  msgid    消息队列id

  cmd    要执行的操作  IPC_STAT / IPC_SET / IPC_RMID(删除)

  buf   存放消息队列属性的地址(删除操作时给空)

相关函数:

创建key

key_t  f tok(const char *path,  int id);

 #include  <sys/types.h>

 #include <sys/ipc.h>  

key_t  ftok(const char *path,  int proj_id);  

成功时返回合法的key值,失败时返回EOF  

path  存在且可访问的文件的路径  

proj_id  用于生成key的数字,范围1-255。

strcpy函数

char *strcpy(char *dest, const char *src)

返回目标字符串 dest 的指针。

功能:函数是 C 语言中用于字符串复制的标准库函数,定义在 <string.h> 头文件中。它的基本功能是将一个字符串的内容复制到另一个字符串中。

参数:

dest:目标字符串,复制后的内容将存放在这里。这个字符串必须有足够的空间来存放源字符串及其结束符(\0)。

src:源字符串,内容将被复制的对象。

相关命令:

ipcs命令:

可以在终端查看共享内存、消息队列、信号量(都属于内核中数据结构)

使用方式:

发送端:

1 申请Key

2打开/创建消息队列   msgget

3向消息队列发送消息   msgsnd

#include <sys/types.h>
#include <sys/ipc.h>
#include<stdio.h>
#include<string.h>
#include <sys/msg.h>#define MSGLEN (sizeof(msgT)-sizeof(long))
typedef struct {long msg_type;char buf[128];
} msgT;
int main() {key_t key;int msgid;int ret;msgT msg;/** 1.创建key*/key=ftok(".",100);if(key<0) {perror("ftok");return 0;}/** 2.创建消息队列*/msgid=msgget(key,IPC_CREAT|0666);if(msgid<0) {perror("msgget");return 0;}/** 3.发送消息*/msg.msg_type=1;strcpy(msg.buf,"this msg type 1");ret=msgsnd(msgid,&msg,MSGLEN,0);if(ret<0) {perror("msgsnd");return 0;}
}

接收端:

1打开/创建消息队列   msgget

2从消息队列接收消息   msgrcv

3 控制(删除)消息队列   msgctl

#include <sys/types.h>
#include <sys/ipc.h>
#include<stdio.h>
#include<string.h>
#include <sys/msg.h>#define MSGLEN (sizeof(msgT)-sizeof(long))
typedef struct{long msg_type;char buf[128];
}msgT;
int main(){key_t key;int msgid;int ret;msgT msg;
/** 1.创建key*/key=ftok(".",100);if(key<0){perror("ftok");return 0;}
/** 2.创建消息队列*/msgid=msgget(key,IPC_CREAT|0666);if(msgid<0){perror("msgget");return 0;}
/** 3.接收消息*/ret=msgrcv(msgid,&msg,MSGLEN,0,0);if(ret<0){perror("msgrcv");return 0;}printf("%d %s\n",(int)msg.msg_type,msg.buf);
}

循环接收:

 /** 3.接收消息*/while(1) {ret=msgrcv(msgid,&msg,MSGLEN,0,0);if(ret<0) {perror("msgrcv");return 0;}printf("%d %s\n",(int)msg.msg_type,msg.buf);}

删除消息队列:

ret=msgctl(msgid,IPC_RMID,NULL);if(ret<0){perror("msgctl");return 0;}

注意事项:

1.执行一次msgsnd就会将消息发送一次

2.执行一次msgrcv就会将消息接收一次

方式4、管道

4.1、无名管道

特点:

1.只能用于具有亲缘关系的进程之间的通讯(父子进程)

2.单工的通信模式,具有固定的读端和写端(一个无名管道只能一端写,一端读。如果要实现双工通讯(两端都可以同时作为读端和写端)就需要建立两条无名管道)

3.无名管道创建时会返回两个文件描述符,分别用于读写管道

创建无名管道


#include <unistd.h>
int pipe(int pfd[2]);
成功时返回 0,失败时返回 EOF
pfd 包含两个元素的整形数组,用来保存文件描述符
pfd[0] 为读描述符
pfd[1] 为写描述符

代码实现:

父子进程:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include <sys/types.h>
#include <unistd.h>int main() {int pfd[2];int ret;char buf[20]= {0};pid_t pid;ret=pipe(pfd);if(ret<0) {perror("pipe");return 0;}pid=fork();if(pid<0) {perror("fork");return 0;}/** 子进程写*/else if(pid==0) {close(pfd[0]);while(1) {strcpy(buf,"hhhahahah");write(pfd[1],buf,strlen(buf));sleep(1);}}/** 父进程读*/else {close(pfd[1]);while(1) {ret=read(pfd[0],buf,20);if(ret>0) {printf("%s\n",buf);}}}return 0;
}
多进程:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include <sys/types.h>
#include <unistd.h>int main() {int pfd[2];int ret,i;char buf[40]= {0};char endbuf[40]= {0};pid_t pid;ret=pipe(pfd);if(ret<0) {perror("pipe");return 0;}for(i=0; i<2; i++) {pid=fork();if(pid<0) {perror("fork");return 0;}/** 子进程*/else if(pid==0) {break;//跳出循环,子进程不再执行fork}/** 父进程*/else {}}/** 父进程代码*/if(i==2) {close(pfd[1]);while(1) {ret=read(pfd[0],endbuf,40);if(ret>0) {printf("%s\n",endbuf);}}return 0;}/** 第二个子进程代码*/if(i==1) {close(pfd[0]);while(1) {strcpy(buf,"This is second");write(pfd[1],buf,strlen(buf));sleep(1);}return 0;}/** 第一个子进程代码*/if(i==0) {close(pfd[0]);while(1) {strcpy(buf,"This is one");write(pfd[1],buf,strlen(buf));usleep(10000);}return 0;}return 0;
}


流程:

文件描述符0,1,2分别被标准输出、标准输入、标准错误占用,只能从文件描述符3开始

创建子进程,由于子进程继承了父进程打开的文件描述符,所以父子进程就可以通过创建的管道进行通信。

如果父进程使用写端,就将父进程中的读端关闭,子进程的写端关闭,反之如此

相关函数:

fork函数
write函数
read函数
strcpy函数
sleep函数
usleep函数

注意事项:

1.在同一个进程中是可以又读又写的,只不过无名管道用于亲缘关系进程通讯,因此一般不用于同一个进程又读又写

2.管道可以用于大于两个进程共享

3.创建的管道固定为64k字节

4.管道中的数据,只要读出来了,就不存在于管道中了

无名管道的读写特性:


① 读管道:
1. 管道中有数据,read 返回实际读到的字节数。
2. 管道中无数据:
(1) 管道写端被全部关闭,read 返回 0 (好像读到文件结尾)
(2) 写端没有全部被关闭,read 阻塞等待(不久的将来可能有数据递达,此时会让出 cpu)

② 写管道:

1. 管道读端全部被关闭, 进程异常终止(也可使用捕捉 SIGPIPE 信号,使进程不终止)
2. 管道读端没有全部关闭:
(1) 管道已满,write 阻塞。(管道大小 64K)
(2) 管道未满,write 将数据写入,并返回实际写入的字节数。

问题记录:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include <sys/types.h>
#include <unistd.h>int main() {int pfd[2];int ret;char buf[20]= {0};char endbuf[20]= {0};pid_t pid;ret=pipe(pfd);if(ret<0) {perror("pipe");return 0;}pid=fork();if(pid<0) {perror("fork");return 0;}/** 子进程*/else if(pid==0) {//	close(pfd[0]);while(0) {strcpy(buf,"hhhahahah");write(pfd[1],buf,strlen(buf));ret=read(pfd[0],endbuf,20);if(ret>0) {printf("%s\n",endbuf);}sleep(1);}}/** 父进程*/else {//	close(pfd[1]);while(1) {strcpy(buf,"hhhahahah");write(pfd[1],buf,strlen(buf));ret=read(pfd[0],endbuf,20);if(ret>0) {printf("%s\n",endbuf);}sleep(1);}}while(1);return 0;
}

思路:父进程读写管道,将buf中的字符串写入管道,再从管道中读取字符串到endbuf,如果读取成功,将ednbuf打印出来。

子进程同样操作,都是可以单进程通过无名管道读写的。

4.2有名管道:

特点:


1 有名管道可以使非亲缘的两个进程互相通信
2 通过路径名来操作,在文件系统中可见,但内容存放在内存中
3 文件 IO 来操作有名管道
4 遵循先进先出规则
5 不支持 leek 操作(虽然是文件)
6 单工读写

函数实现:

创建管道


#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename, mode_t mode);

成功时返回 0 ,失败时返回 EOF
path 创建的管道文件路径
mode 管道文件的权限,如 0666

注意:

不能将管道建立在共享目录下,因为windows不支持管道

#include <sys/types.h>
#include <sys/stat.h>
#include<stdio.h>
int main(){int ret;ret=mkfifo("/myfifo",666);if(ret<0){perror("mkfifo");return 0;}return 0;
}
打开管道

open(const char *path, O_RDONLY);//1
open(const char *path, O_RDONLY | O_NONBLOCK);//2
open(const char *path, O_WRONLY);//3
open(const char *path, O_WRONLY | O_NONBLOCK);//4

四种打开模式,默认为阻塞状态,与上O_NONBLOCK为非阻塞状态

注意:

只能使用文件IO,不能使用标准IO

读进程:
#include <sys/types.h>
#include <sys/stat.h>
#include<stdio.h>
#include<string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
int main() {int ret,fd;char buf[20]= {0};ret=mkfifo("/myfifo",666);if(ret<0) {perror("mkfifo");//return 0;}fd=open("/myfifo",O_RDONLY);if(fd<0) {perror("open");return 0;}while(1) {ret=read(fd,buf,20);if(ret>0) {printf("%s\n",buf);}}return 0;
}
写进程:
#include <sys/types.h>
#include <sys/stat.h>
#include<stdio.h>
#include<string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
int main() {int ret,fd;char buf[20]= {0};ret=mkfifo("/myfifo",777);if(ret<0) {perror("mkfifo");//return 0;}fd=open("/myfifo",O_WRONLY);if(fd<0) {perror("open");return 0;}while(1) {strcpy(buf,"ahhhhh");write(fd,buf,strlen(buf));}return 0;
}

注意事项:

1 程序也是可以用 O_RDWR(读写)模式打开 FIFO 文件进行读写操作,但是其行为也未明确定义,
且,有名管道一般用于进程间通讯,不使用同一个进程实现读写的方式(同有名管道)

2 第二个参数中的选项 O_NONBLOCK,选项 O_NONBLOCK 表示非阻塞,加上这个选项后,表示
open 调用是非阻塞的,如果没有这个选项,则表示 open 调用是阻塞的


3.对于以只读方式(O_RDONLY)打开的 FIFO 文件,如果 open 调用是阻塞的(即第二个参
数为 O_RDONLY),除非有一个进程以写方式打开同一个 FIFO,否则它不会返回;如果 open
调用是非阻塞的的(即第二个参数为 O_RDONLY | O_NONBLOCK),则即使没有其他进程以写
方式打开同一个 FIFO 文件,open 调用将成功并立即返回。


对于以只写方式(O_WRONLY)打开的 FIFO 文件,如果 open 调用是阻塞的(即第二个参数为
O_WRONLY),open 调用将被阻塞,直到有一个进程以只读方式打开同一个 FIFO 文件为止;
如果 open 调用是非阻塞的(即第二个参数为 O_WRONLY | O_NONBLOCK),open 总会立即返回,但如果没有其他进程以只读方式打开同一个 FIFO 文件,open 调用将返回-1,并且 FIFO 也
不会被打开。


4.数据完整性,如果有多个进程写同一个管道,使用 O_WRONLY 方式打开管道,如果写入
的数据长度小于等于 PIPE_BUF(4K),那么或者写入全部字节,或者一个字节都不写入,
系统就可以确保数据决不会交错在一起。

方式5、信号机制

简介:

1.信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式

2.linux内核通过信号通知用户进程,不同的信号类型代表不同的事件

3.进程对信号有不同的相应方式:

(1)缺省信号(根据信号指示执行)

(2)忽略信号(忽略信号指示)

(3)捕捉信号(捕捉信号,改变信号行为)

信号的产生:

1.按键产生

2.系统调用函数产生(例如raise、kill)

3.硬件异常产生

4.命令行产生(例:kill)

5.软件条件(例:被0除,非法访问内存)

常用信号:

命令实现:

kill +选项 +进程pid

通过终端命令给进程发送信号

函数实现:

kill函数:

int kill(pid_t pid, int signum)
功能:发送信号
参数:
pid:

> 0:发送信号给指定进程
= 0:发送信号给跟调用 kill 函数的那个进程处于同一进程组的进程。
< -1: 取绝对值,发送信号给该绝对值所对应的进程组的所有组员。
= -1:发送信号给,有权限发送的所有进程。(轻易不要使用)
signum:待发送的信号

实现案例:

1.nx.c生成可执行文件nx,执行nx

#include<stdio.h>
int main(){while(1);return 0;
}

2.ps -ef|grep nx查看nx进程pid

3.cs.c文件执行kill函数

#include<stdio.h>
#include <sys/types.h>
#include <signal.h>
int main(){kill(2664,11);return 0;
}

4.结果

raise函数:

int raise(int sig);

功能:给自己发送信号,等价于 kill(getpid(), signo);

sig:要发送的信号

实现案例:

1.cs.c文件

#include<stdio.h>
#include <sys/types.h>
#include <signal.h>
int main(){raise(11);return 0;
}

2.结果:

定时器函数:

int alarm(unsigned int seconds);

成功时返回上个定时器的剩余时间,失败时返回 EOF
seconds 定时器的时间


注意:

一个进程中只能设定一个定时器,时间到时产生 SIGALRM

实现案例:

1.三秒后产生SIGALRM信号

#include<stdio.h>
#include<unistd.h>
int main(){alarm(3);while(1);return 0;
}

2.结果

int pause(void);

可以用于,阻止程序继续往下执行,收到信号后再继续执行,实现信号驱动程序


功能:进程一直阻塞,直到被信号中断
被信号中断后返回 -1 , errno 为 EINTR

函数行为:

1如果信号的默认处理动作是终止进程,则进程终止,pause函数么有机会返回。

2如果信号的默认处理动作是忽略,进程继续处于挂起状态,pause函数不返回

3 如果信号的处理动作是捕捉,则调用完信号处理函数之后,pause返回-1。

4 pause收到的信号如果被屏蔽,那么pause就不能被唤醒 

实现案例1:

1.cs.c(代替while,直到被信号中断)

#include<stdio.h>
#include<unistd.h>
int main(){alarm(3);pause();return 0;
}

2.结果

实现案例2:
解释:

在程序执行中给pause函数,有信号过来再将信号阻塞,往下执行cs函数,cs函数执行完毕,解除信号阻塞,在阻塞期间来的信号会被延迟,阻塞解除之后将信号释放,pause接收到释放的信号,就会进入下一个循环。

#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
#include <signal.h>
void handle(int sig){printf("sig=%d\n",sig);
}
void cs(){printf("111\n");sleep(3);printf("222\n");
}
int main(){struct sigaction act;act.sa_handler=handle;act.sa_flags=0;sigemptyset(&act.sa_mask);sigaction(SIGINT,&act,NULL);pause();printf("after pause\n");sigset_t set;sigemptyset(&set);sigaddset(&set,SIGINT);while(1){sigprocmask(SIG_BLOCK,&set,NULL);cs();sigprocmask(SIG_UNBLOCK,&set,NULL);pause();}
}
问题:

信号好像发生在解除对SIGINT的阻塞和pause之间。如果发生了这种情况,或者如果在解除阻塞时刻和pause之间确实发生了信号,那么就产生了问题。因为我们可能不会再见到该信号,所以从这种意义上而言,在此时间窗口(解除阻塞和pause之间)中发生的信号丢失了,这样就使pause永远阻塞。

为了纠正此问题,需要在一个原子操作中先恢复信号屏蔽字,然后使进程休眠,这种功能是由sigsuspend函数提供的。

int sigsuspend(const sigset_t *sigmask);

函数功能相当于解除sigprocmaskg阻塞,并且把信号给pause

功能:将进程的屏蔽字替换为由参数sigmask给出的信号集,然后使进程休眠

参数:

sigmask:希望屏蔽的信号

修改案例2如下:
#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
#include <signal.h>
void handle(int sig){printf("sig=%d\n",sig);
}
void cs(){printf("111\n");sleep(3);printf("222\n");
}
int main(){struct sigaction act;act.sa_handler=handle;act.sa_flags=0;sigemptyset(&act.sa_mask);sigaction(SIGINT,&act,NULL);pause();printf("after pause\n");sigset_t set,set2;sigemptyset(&set);sigaddset(&set,SIGINT);while(1){sigprocmask(SIG_BLOCK,&set,NULL);cs();//sigprocmask(SIG_UNBLOCK,&set,NULL);//pause();sigsuspend(&set2)}
}

循环定时器函数:

ualarm 

useconds_t ualarm(useconds_t usecs, useconds_t interval);
以 useconds 为单位,第一个参数为第一次产生时间,第二个参数为间隔产生

例:ualarm(3,4),执行之后,3秒后产生信号,间隔4秒再产生信号(以后为4秒循环)

setitimer

int setitimer(int which,,const struct itimerval *new_value,struct itimerval *old_value);
功能:定时的发送 alarm 信号
参数:

which:
ITIMER_REAL:以逝去时间递减。发送 SIGALRM 信号
ITIMER_VIRTUAL: 计算进程(用户模式)执行的时间。 发送 SIGVTALRM 信号
ITIMER_PROF: 进程在用户模式(即程序执行时)和核心模式(即进程调度用时)均计算时
间。 发送 SIGPROF 信号


new_value: 负责设定 timout 时间


old_value: 存放旧的 timeout 值,一般指定为 NULL
struct itimerval {
struct timeval it_interval; // 闹钟触发周期(例如触发后,每隔 1 秒触发)
struct timeval it_value; // 闹钟触发时间(例如 5 秒后触发)
};
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};

#include<stdio.h>
#include<unistd.h>
#include <signal.h>
#include<unistd.h>
#include <sys/time.h>void handle(int sig) {printf("cath the SIGALRM \n");
}
int main() {struct sigaction act;struct itimerval new_value;act.sa_handler=handle;act.sa_flags=0;sigemptyset(&(act.sa_mask));new_value.it_interval.tv_sec=3;new_value.it_interval.tv_usec=0;new_value.it_value.tv_sec=5;new_value.it_value.tv_usec=0;setitimer(ITIMER_REAL,&new_value,NULL);sigaction(SIGALRM,&act,NULL);while(1) {sleep(1);}
}

信号的捕捉:

解释:1.程序遇到信号,2.会进入内核,如果信号的处理动作是非自定义,内核将信号处进入5,再返回1;如果信号处理东西是自定义,如上图;

信号捕捉过程:

1.定义新的信号的执行函数handle。

2.使用signal/sigaction 函数,把自定义的handle和指定的信号相关联。

函数实现:
signal函数:

typedef void (*sighandler_t)(int);(只能写成这种形式)

typedef void (*sighander_t)(int)

含义:将一个返回值为void类型,参数为int类型的函数指针定义成sighander_t,函数参数就是传进来的信号

sighandler_t  signal(int signum, sighandler_t handler);

功能:捕捉信号执行自定义函数

返回值:成功时返回原先的信号处理函数,失败时返回SIG_ERR

参数:

 Signum 要捕捉的信号类型

 handler 指定的信号处理函数:

SIG_DFL代表缺省方式; SIG_IGN 代表忽略信号;  

系统建议使用sigaction函数,因为signal在不同类unix系统的行为不完全一样。

实现1.

#include<stdio.h>
#include<unistd.h>
#include <signal.h>
#include<unistd.h>
void handle(int sig){printf("cath the SIGINT \n");
}
int main(){signal(SIGINT,handle);while(1){sleep(1);}
}

实现2.

#include<stdio.h>
#include<unistd.h>
#include <signal.h>
#include<unistd.h>
typedef void (*sighandler_t)(int);
sighandler_t old;
void handle(int sig){printf("cath the SIGINT \n");signal(SIGINT,old);
}
int main(){old=signal(SIGINT,handle);while(1){sleep(1);}
}
sigaction函数:

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

struct sigaction {

    void (*sa_handler)(int);

    void (*sa_sigaction)(int, siginfo_t *, void *);

    sigset_t sa_mask;

    int sa_flags;

    void (*sa_restorer)(void);

}

参数:

signum:处理的信号

act,oldact::处理信号的新行为和旧的行为,是一个sigaction结构体。

sigaction结构体成员定义如下:

sa_handler: 是一个函数指针,其含义与 signal 函数中的信号处理函数类似

sa_sigaction: 另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。

sa_flags参考值如下:(不使用以下标志位可写0)

SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数

SA_RESTART:使被信号打断的系统调用自动重新发起。

SA_RESETHAND:信号处理之后重新设置为默认的处理方式。

SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。

re_restorer:是一个已经废弃的数据域

实现1.

#include<stdio.h>
#include<unistd.h>
#include <signal.h>
#include<unistd.h>
void handle(int sig){printf("cath the SIGINT \n");
}
int main(){struct sigaction act;act.sa_handler=handle;act.sa_flags=SA_RESETHAND;sigemptyset(&(act.sa_mask));sigaction(SIGINT,&act,NULL);while(1){sleep(1);}
}

信号的阻塞:

信号来的时候会打断程序进行,如果希望信号不打断程序进行,就需要将信号阻塞

信号的阻塞概念

信号的”阻塞“是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生(程序是可以收到信号的)。

信号的状态:

信号递达(Delivery ):实际信号执行的处理过程(3种状态:忽略,执行默认动作,捕获)

信号未决(Pending):从产生到递达之间的状态(程序不中断,程序接收不到这个信号)

将一个信号写进信号屏蔽字,如果信号来了,就将信号写入未决信号集里(挂起),未决信号集和信号屏蔽字每一位都代表一个信号。

例:2 SIGINT信号,未决信号集和信号屏蔽字中第二个比特位就表示SIGINT信号

        3 SIGOUT信号,未决信号集和信号屏蔽字中第三个比特位就表示SIGOUT信号

使用方式:可以将某个信号的未决信号集位,置1,当该信号到来,就将该信号的信号屏蔽字中的表示位,置1,该信号就被挂起,不会打断程序进行。

函数实现:

sigset_t set;  自定义信号集。  是一个32bit  64bit  128bit的数组每一个bit位表示一个信号。

sigemptyset(sigset_t *set); 清空信号集

sigfillset(sigset_t *set); 将信号集全部置1

sigaddset(sigset_t *set, int signum); 将一个信号添加到集合中

sigdelset(sigset_t *set, int signum); 将一个信号从集合中移除

sigismember(const sigset_t *set,int signum); 判断一个信号是否在集合中。

(此时还未真正的将信号屏蔽,以下函数才将信号真正屏蔽)

#include <signal.h>

int sigprocmask( int how, const sigset_t *restrict set, sigset_t *restrict oset );

返回值:若成功则返回0,若出错则返回-1

首先,若oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。

其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。

how可选用的值:(注意,不能阻塞SIGKILL和SIGSTOP信号)

SIG_BLOCK :   把参数set中的信号添加到信号屏蔽字中

SIG_UNBLOCK: 从信号屏蔽字中删除参数set中的信号

SIG_SETMASK: 把信号屏蔽字设置为参数set中的信号

使用流程:

1.创建信号集

2.清空信号集

3.将信号add进信号集

4.将set中的信号添加到信号屏蔽字中

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
void handle(int sig){printf("get sig=%d",sig);
}
int main(){struct sigaction act;act.sa_handler=handle;act.sa_flags=0;sigemptyset(&act.sa_mask);sigaction(SIGINT,&act,NULL);sigset_t set;sigemptyset(&set);sigaddset(&set,SIGINT);sigprocmask(SIG_BLOCK,&set,NULL);while(1){sleep(1);}
}

相关命令:

kill -l:查看当前系统支持哪些信号

ps -ef |grep +进程名:查看此进程

相关函数:

sigemptyset 

是一个 C 语言的标准库函数,属于信号处理部分,主要用来初始化一个信号集合。在使用信号相关功能时,通常需要创建并管理信号集,比如在信号屏蔽或处理程序中。

### 函数原型
```c
#include <signal.h>

int sigemptyset(sigset_t *set);
```

### 参数
- `set`: 指向需要初始化的信号集合的指针。

### 返回值
- 成功时返回 `0`,失败时返回 `-1`,并设置 `errno`。

### 功能
`sigemptyset` 将给定的信号集合 `set` 清空,确保集合中不包含任何信号。这通常在需要创建一个新的信号集合时使用,特别是在设置信号屏蔽或者与信号处理程序交互的情况下。

### 示例
以下是一个使用 `sigemptyset` 的示例代码:

```c
#include <stdio.h>
#include <signal.h>

int main() {
    sigset_t set;

    // 初始化信号集
    if (sigemptyset(&set) != 0) {
        perror("sigemptyset");
        return 1;
    }

    // 检查集合是否为空
    if (sigismember(&set, SIGINT)) {
        printf("集合中包含 SIGINT\n");
    } else {
        printf("集合中不包含 SIGINT\n");
    }

    return 0;
}
```

### 注意事项
- 使用 `sigemptyset` 后,信号集是一个空集合,您可以使用其他函数如 `sigaddset` 向其中添加特定的信号。
- 该函数通常用于在多线程和进程间的信号处理时,确保信号的正确管理。

相关信号:

SIGCHLD信号实现回收子进程

SIGCHLD的产生条件

1子进程终止时

2子进程接收到SIGSTOP信号停止时

3子进程处在停止态,接受到SIGCONT后唤醒时

#include<stdio.h>
#include<unistd.h>
#include <signal.h>
#include<unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include<stdlib.h>
void handle(int sig) {wait(NULL);printf("cath the sig=%d \n",sig);
}
int main() {int pid;struct sigaction act;act.sa_handler=handle;act.sa_flags=0;sigemptyset(&(act.sa_mask));pid=fork();if(pid>0){sigaction(SIGCHLD,&act,NULL);while(1){printf("this is father\n");sleep(1);}}else{sleep(5);exit(0);}}

含义:父进程执行,在父进程内捕捉SIGCHLD信号,子进程exit(0)结束后,产生SIGCHLD信号,父进程捕捉到该信号,跳转到相关函数,将子进程回收

方式7、信号灯

进程和线程间通讯的一种方式,并且经常与共享内存一块使用,管理进程或线程间同步操作,只作为一种行为。

信号量和信号灯是同一种东西。

信号量代表某一类资源,其值表示系统中该资源的数量。

信号量是一个受保护的变量,只能通过三种操作来访问。

初始化

P操作(申请资源)(资源-1)

V操作(释放资源)(资源+1)

posix信号灯的使用:

初始化:sem_open()、sem_init()

wait是P操作,post是V操作

与共享内存使用:

初始共享内存为空;此时写信号量初始应为1,可写;读信号量初始为0,不可读

写入共享内存数据后;写信号量减1,为0,不可再写,读信号量加1,为1,可读;

读出共享内存数据后;读信号量减1,为0,不可再读,写信号量加1,为1,可写;

如此循环;

注意:

sem_open创建好信号量文件之后,如果没有将文件删除:关闭可执行文件,再打开可执行文件,就不可再次使用;因此需要在退出时将信号量文件删掉;

posix有名信号灯:

使用pthread库实现,编译时需要链接上pthread库

有名信号灯打开:

sem_t *sem_open(const char *name, int oflag);

sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);

参数:

name:name是给信号灯起的名字

oflag:打开方式,常用O_CREAT

mode:文件权限。常用0666

value:信号量值。二元信号灯值为1,普通表示资源数目(有10个资源,就可以初始化写为10)

信号灯文件位置:/dev/shm

使用open打开信号灯后,文件会自动放在该目录下边

有名信号灯关闭:

int sem_close(sem_t *sem);参数为open函数的返回值

作用:关闭信号灯,不可使用(文件依旧存在)

有名信号灯的删除:

int sem_unlink(const char* name);

作用:删除/dev/shm下创建的信号灯文件

实现:

一般创建两个信号量,一个读,一个写;

向共享内存中写入数据,需要sem_wait检查写信号量是否有资源,有资源就继续往下执行,写入数据,再sem_post读信号量。

从共享内存读数据,需要sem_wait检查读信号量是否有组员,有资源就继续往下执行,读出数据,再sem_post写信号量。

写端:

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <signal.h>
#include<stdlib.h>
void unlink(int arg){sem_unlink("sem_w");exit(0);
}
int main(){sem_t *sem_w;sem_t *sem_r;void *addr;pthread_t shmid;key_t key;struct sigaction act;act.sa_handler=unlink;act.sa_flags=0;sigemptyset(&act.sa_mask);sigaction(SIGINT,&act,NULL);key=ftok("/tmp",22);if(key<0){perror("ftok");return 0;}shmid=shmget(key,512,IPC_CREAT|0666);if(shmid<0){perror("shmget");return 0;}addr=shmat(shmid,NULL,0);if(addr<0){perror("shmat");return 0;}
/** 初始化信号量*/sem_r=sem_open("/sem_r",O_CREAT,0666,0);sem_w=sem_open("/sem_w",O_CREAT,0666,1);while(1){sem_wait(sem_w); 	//判断信号量,有资源才会继续往下执行,同时信号量会减1printf(">");fgets(addr,512,stdin);sem_post(sem_r); 	//将信号量加1}	}

读端:

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <signal.h>
#include<stdlib.h>
void unlink(int arg){sem_unlink("sem_r");exit(0);
}
int main(){sem_t *sem_w;sem_t *sem_r;char *addr;pthread_t shmid;key_t key;struct sigaction act;act.sa_handler=unlink;act.sa_flags=0;sigemptyset(&act.sa_mask);sigaction(SIGINT,&act,NULL);key=ftok("/tmp",22);if(key<0){perror("ftok");return 0;}shmid=shmget(key,512,IPC_CREAT|0666);if(shmid<0){perror("shmget");return 0;}addr=shmat(shmid,NULL,0);if(addr<0){perror("shmat");return 0;}
/** 初始化信号量*/sem_r=sem_open("/sem_r",O_CREAT,0666,0);sem_w=sem_open("/sem_w",O_CREAT,0666,1);while(1){sem_wait(sem_r); 	//判断信号量,有资源才会继续往下执行,同时信号量会减1printf("%s\n",addr);sem_post(sem_w); 	//将信号量加1}	}

posix无名信号灯:

(linux中只支持线程同步)

使用pthread库实现,编译时需要链接上pthread库

无名信号灯初始化/打开

int sem_init(sem_t *sem, int shared, unsigned int value);

参数: 

sem:需要初始化的信号灯变量

shared: shared指定为0,表示信号量只能由初始化这个信号量的进程使用,不能在进程间使用,linux 不支持进程间同步。

Value:信号量的值(初始值)

无名信号灯销毁:

int sem_destroy(sem_t* sem);

参数:信号灯变量

System信号灯:

创建/打开信号灯:

int semget(key_t key, int nsems, int semflg);

功能:创建/打开信号灯

参数:key:ftok产生的key值(和信号灯关联的key值)

  nsems:信号灯集中包含的信号灯数目

  semflg:信号灯集的访问权限,通常为IPC_CREAT |0666

返回值:成功:信号灯集ID ; 失败:-1

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

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

相关文章

毕业项目推荐:基于yolov8/yolov5的行人检测识别系统(python+卷积神经网络)

文章目录 概要一、整体资源介绍技术要点功能展示&#xff1a;功能1 支持单张图片识别功能2 支持遍历文件夹识别功能3 支持识别视频文件功能4 支持摄像头识别功能5 支持结果文件导出&#xff08;xls格式&#xff09;功能6 支持切换检测到的目标查看 二、数据集三、算法介绍1. YO…

[桌面运维]windows自动设置浅深色主题

设置自动浅色/深色主题 我看很多up主的教程过于繁琐&#xff0c;需要添加四个功能&#xff0c;并且有些还不能生效&#xff01; 大多数都是教程&#xff1a; 自动任务栏浅色 add HKCUSOFTWAREMicrosoftWindowsCurrentVersionThemesPersonalize/v SystemUsesLightTheme /t …

LQ quarter 5th

目录 B. 开赛主题曲 C. BlueAI E. 精准难度 B. 开赛主题曲 &#xff08;1&#xff09;两层循环枚举所有子串。第一层子串长度&#xff0c;第二层子串起点 &#xff08;2&#xff09;判子串是否合法还要一个 for&#xff0c;26 * 26 * 2e5 快要超时&#xff0c;因此计算每个字母…

Directx12 chapter4

官方的初始化需要的组件 Initialize 初始化涉及到首次设置全局变量和类&#xff0c;initialize 函数必须准备管道和资产。 初始化管道。 启用调试层。创建设备。创建命令队列。创建交换链。创建渲染器目标视图 (RTV) 描述符堆。 备注 可将描述符堆视为描述符的数组。 其中…

STM32 软件I2C读写

单片机学习&#xff01; 目录 前言 一、软件I2C读写代码框架 二、I2C初始化 三、六个时序基本单元 3.1 引脚操作的封装和改名 3.2 起始条件执行逻辑 3.3 终止条件执行逻辑 3.4 发送一个字节 3.5 接收一个字节 3.5 发送应答&接收应答 3.5.1 发送应答 3.5.2 接…

计算机网络--UDP和TCP课后习题

【5-05】 试举例说明有些应用程序愿意采用不可靠的UDP, 而不愿意采用可靠的TCP。 解答&#xff1a; 这可能有以下几种情况。 首先&#xff0c;在互联网上传输实时数据的分组时&#xff0c;有可能会出现差错甚至丢失。如果利用 TCP 协议对这些出错或丢失的分组进行重传&…

【C++】B2099 矩阵交换行

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;题目描述题目描述输入格式输出格式输入输出样例输入 #1输出 #1 &#x1f4af;题目分析&#x1f4af;不同解法分析我的做法实现步骤&#xff1a;优点&#xff1a;不足&#…

[微服务]redis主从集群搭建与优化

搭建主从集群 单节点Redis的并发能力是有上限的&#xff0c;要进一步提高Redis的并发能力&#xff0c;就需要搭建主从集群&#xff0c;实现读写分离。 1. 主从集群结构 下图就是一个简单的Redis主从集群结构&#xff1a; 如图所示&#xff0c;集群中有一个master节点、两个s…

使用WebSocket 获取实时数据

回车发送数据&#xff0c;模拟服务器发送数据 效果图&#xff1a; 源码&#xff1a; <template><div><h1>WebSocket 实时数据</h1><input type"text" v-model"ipt" keyup.enter"sendMessage(ipt)"><div v-if…

Element-UI:如何实现表格组件el-table多选场景下根据数据对某一行进行禁止被选中?

如何实现表格组件el-table多选场景下根据数据对某一行进行禁止被选中&#xff1f; 在使用 Element UI 的 Table 组件时&#xff0c;如果你想要禁用某一行的选中&#xff08;特别是在多选模式下&#xff09;&#xff0c;可以通过自定义行的 selectable 属性来实现。selectable …

移动端自动化测试Appium-java

一、Appium的简介 移动端的自动化测试框架 模拟人的操作进行功能自动化常用于功能测试、兼容性测试 跨平台的自动化测试 二、Appium的原理 核心是web服务器&#xff0c;接受客户端的连接&#xff0c;接收客户端的命令&#xff0c;在手机设备上执行命令&#xff0c;收集命令…

Geoserver修行记-后端调用WMS/WMTS服务无找不到图层Could not find layer

项目场景 调用geoserver地图服务WMS,找不到图层 我在进行地图服务调用的时候&#xff0c;总是提示我找不多图层 Could not find layer&#xff0c;重点是这个图层我明明是定义了&#xff0c;发布了&#xff0c;且还能够正常查看图层的wms的样式&#xff0c;但是在调用后端调用…

深入探讨 Android 中的 AlarmManager:定时任务调度及优化实践

引言 在 Android 开发中&#xff0c;AlarmManager 是一个非常重要的系统服务&#xff0c;用于设置定时任务或者周期性任务。无论是设置一个闹钟&#xff0c;还是定时进行数据同步&#xff0c;AlarmManager 都是不可或缺的工具之一。然而&#xff0c;随着 Android 系统的不断演…

玉米识别数据集,4880张图,正确识别率可达98.6%,支持yolo,coco json,pasical voc xml格式的标注,可识别玉米

玉米识别数据集&#xff0c;4880张图&#xff0c;正确识别率可达98.6%&#xff0c;支持yolo&#xff0c;coco json,pasical voc xml格式的标注&#xff0c;可识别玉米 数据集下载地址&#xff1a; yolo v11:https://download.csdn.net/download/pbymw8iwm/90230969 yolo v9:…

【UI自动化测试】selenium八种定位方式

&#x1f3e1;个人主页&#xff1a;謬熙&#xff0c;欢迎各位大佬到访❤️❤️❤️~ &#x1f472;个人简介&#xff1a;本人编程小白&#xff0c;正在学习互联网求职知识…… 如果您觉得本文对您有帮助的话&#xff0c;记得点赞&#x1f44d;、收藏⭐️、评论&#x1f4ac;&am…

【前端系列01】优化axios响应拦截器

文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、axios响应拦截器&#xff1a;☀️☀️☀️2.1 为什么前端需要响应拦截器element ui的消息组件 一、前言&#x1f680;&#x1f680;&#x1f680; ☀️ 回报不在行动之后&#xff0c;回报在行动之中。 这个系列可…

【C语言程序设计——选择结构程序设计】求阶跃函数的值(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 相关知识 1. 选择结构基本概念 2. 主要语句类型​&#xff08;if、if-else、switch&#xff09; 3. 跃迁函数中变量的取值范围 4. 计算阶跃函数的值 编程要求 测试说明 通关代码 测试结果 任务描述 本关任务&#xff1a;输入x的值&#x…

利用 NineData 实现 PostgreSQL 到 Kafka 的高效数据同步

记录一次 PostgreSQL 到 Kafka 的数据迁移实践。前段时间&#xff0c;NineData 的某个客户在一个项目中需要将 PostgreSQL 的数据实时同步到 Kafka。需求明确且普遍&#xff1a; PostgreSQL 中的交易数据&#xff0c;需要实时推送到 Kafka&#xff0c;供下游多个系统消费&#…

【C++面向对象——类的多态性与虚函数】编写教学游戏:认识动物(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 详细说明&#xff08;类的设计&#xff09; 基类&#xff1a; Animal 派生类: 应用程序说明&#xff1a; 相关知识 1. 虚函数与多态 一、多态的概念与意义 二、虚函数实现多态的原理 三、虚函数的语法细节 2. 纯虚函数与抽象类 一、纯虚…

我的nvim的init.lua配置

nvim的配置文件路径在&#xff5e;/.config/nvim路径下&#xff1a; 一、目录如下&#xff1a; coc-settings.json文件是配置代码片段路径的文件init.lua配置文件的启动脚本lua/config.lua 全局配置文件lua/keymaps.lua 快捷键映射键文件lua/plugins.lua 插件的安装和配置文件…