进程间通讯
简介:
进程间通讯方式有:
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_STAT
,buf
应指向一个有效的struct shmid_ds
结构体。如果cmd
是IPC_SET
,buf
应指向一个包含要设置的新值的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
相关文章:

进程间通讯
简介: 进程间通讯方式有: 1.内存映射(mmap): 使用mmap函数将磁盘空间映射到内存 2.管道 3.信号 4.套接字(socket) 5.信号机制 通过进程中kill函数,去给另一个函数发送信号&a…...

STM32-笔记33-OLED实验
实验目的 驱动 OLED 屏幕,显示点、线、字符、字符串、汉字、图片等内容。 项目实现-OLED通讯协议 复制项目文件19-串口打印功能 重命名为47-OLED实验 打开项目文件 加载文件 代码书写顺序: oled.c #include "oled.h"//初始化oled的gpio …...

低空管控技术-无人机云监视技术详解!
一、无人机监听技术的原理 无人机监听技术主要依赖于射频(RF)探测、光学和红外传感器等技术手段。这些技术通过被动监听和监测无人机与飞行员(或控制器)之间的通信链路传输,以确定无人机的位置,甚至在某些…...

RedisTemplate执行lua脚本及Lua 脚本语言详解
使用RedisTemplate执行lua脚本 在开发中,我们经常需要与Redis数据库进行交互,而Redis是一个基于内存的高性能键值存储数据库,它支持多种数据结构,并提供了丰富的命令接口。在某些情况下,我们可能需要执行一些复杂的逻…...

基于springboot的网上商城购物系统
作者:学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等 文末获取“源码数据库万字文档PPT”,支持远程部署调试、运行安装。 目录 项目包含: 开发说明: 系统功能: 项目截图…...

服务器攻击方式有哪几种?
随着互联网的快速发展,网络攻击事件频发,已泛滥成互联网行业的重病,受到了各个行业的关注与重视,因为它对网络安全乃至国家安全都形成了严重的威胁。面对复杂多样的网络攻击,想要有效防御就必须了解网络攻击的相关内容…...

【Unity3D】AB包加密(AssetBundle加密)
加密前: 加密后,直接无法加载ab,所以无法正常看到ab内容。 using UnityEngine; using UnityEditor; using System.IO; public static class AssetBundleDemoTest {[MenuItem("Tools/打包!")]public static void Build(){//注意:St…...

【FTP 协议】FTP主动模式
一、测试工具 服务器:FileZilla_Server-cn-0_9_60_2.exe 中文版本 客户端:FileZilla_3.66.5_win64 客户端IP: 192.168.9.186 服务端 IP: 192.168.9.161 在客户端请求PORT之前,抓包测试的结果跟被动模式流程相同。 二、客户端主动模式命令…...

十五、Vue 响应接口
文章目录 一、响应式系统基础什么是响应式系统响应式数据的声明与使用二、响应式原理深入Object.defineProperty () 方法的应用(Vue2)Proxy 对象的应用(Vue3)三、响应式接口之 ref 和 reactive(Vue3)ref 函数的使用reactive 函数的使用四、计算属性(computed)作为响应式…...

至强6搭配美光CZ122,证明CXL可以提高生成式AI的性能表现
最近发现了英特尔官网公布的一项最新测试报告,报告显示,将美光的CZ122 CXL内存模块放到英特尔至强6平台上,显著提升了HPC和AI工作负载的内存带宽,特别是在采用基于软件的交错配置(interleave configuration)…...

一文理解ssh,ssl协议以及应用
在使用基于密钥的认证方式的时候,私钥的位置一定要符合远程服务器规定的位置,否则找不到私钥的位置会导致建立ssh连接失败 SSH 全称是 “Secure Shell”,即安全外壳协议。 它是一种网络协议,用于在不安全的网络中安全地进行远程登…...

电子应用设计方案87:智能AI收纳箱系统设计
智能 AI 收纳箱系统设计 一、引言 智能 AI 收纳箱系统旨在为用户提供更高效、便捷和智能的物品收纳与管理解决方案,通过融合人工智能技术和创新设计,提升用户的生活品质和物品整理效率。 二、系统概述 1. 系统目标 - 实现物品的自动分类和整理…...

BloombergGPT: A Large Language Model for Finance——面向金融领域的大语言模型
这篇文章介绍了BloombergGPT,一个专门为金融领域设计的大语言模型(LLM)。以下是文章的主要内容总结: 背景与动机: 大语言模型(如GPT-3)在多个任务上表现出色,但尚未有针对金融领域的…...

LeetCode - #180 Swift 实现连续数字查询
文章目录 摘要描述SQL 解法Swift 题解代码Swift 题解代码分析核心逻辑关键函数 示例测试及结果测试 1测试 2 时间复杂度空间复杂度总结 摘要 本文将解决如何从日志数据中找出连续出现至少三次的数字。通过 SQL 查询语句结合 Swift 数据库操作,我们将完成这一任务。…...

为什么ip属地一会河南一会江苏
在使用互联网的过程中,许多用户可能会遇到这样一个问题:自己的IP属地一会儿显示为河南,一会儿又变成了江苏。这种现象可能会让人感到困惑,甚至产生疑虑,担心自己的网络活动是否受到了某种影响。为了解答这一疑问&#…...
使用最广泛的FastAPI项目结构推荐,命名规范等
使用最广泛的FastAPI项目结构推荐,命名规范等 一、FastAPI项目结构如下:二、组件管理:使用依赖注入三、命名约定四、建议分层架构的设计五、文档和测试六、版本控制和持续集成七、环境和配置管理工具八、性能优化与权限安全 一、FastAPI项目结…...

[大模型开源]SecGPT 网络安全大模型
模型介绍 SecGPT的愿景是将人工智能技术引入网络安全领域,以提高网络防御的效率和效果。其使命是推动网络安全智能化,为社会提供更安全的数字生活环境。 ① SecGPT开源地址:https://github.com/Clouditera/secgpt② 模型地址:htt…...
android 启动页倒计时页面编写
一、需求和技术 1、实现5,4,3,2,1启动页倒计时 2、倒计时实现使用CountDownTimer 二、activity代码 public class OpenActivity extends AppCompatActivity {private Button in;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanc…...
nuxt3路由及路由拦截
配置 nuxt3没有专门的路由配置文件,是由文件目录自动生成路由 ssr nuxt3会默认所有页面都是服务端渲染,如果需要设置某个页面不走服务端渲染,可以在nuxt.config.js中单独配置 routeRules: {/home: { ssr: false },/spa/**: { ssr: false …...

git版本管理
安装 打开 使用codeup 连接本地仓库和云仓库 找到本地存放代码的位置 单击右键打开git bash 初始化本地库 设置用户签名 显示隐藏文件夹之后才能看到,git文件夹 把这个复制下来 粘贴到bash中 拉取远程仓库的master分支: git pull origin master master分支提交和 dev 分支开…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...

从物理机到云原生:全面解析计算虚拟化技术的演进与应用
前言:我的虚拟化技术探索之旅 我最早接触"虚拟机"的概念是从Java开始的——JVM(Java Virtual Machine)让"一次编写,到处运行"成为可能。这个软件层面的虚拟化让我着迷,但直到后来接触VMware和Doc…...
微服务通信安全:深入解析mTLS的原理与实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、引言:微服务时代的通信安全挑战 随着云原生和微服务架构的普及,服务间的通信安全成为系统设计的核心议题。传统的单体架构中&…...

C++--string的模拟实现
一,引言 string的模拟实现是只对string对象中给的主要功能经行模拟实现,其目的是加强对string的底层了解,以便于在以后的学习或者工作中更加熟练的使用string。本文中的代码仅供参考并不唯一。 二,默认成员函数 string主要有三个成员变量,…...