linux进程间通信学习
管道通信
匿名管道
匿名管道称为半双工管道,特点如下:
-
数据只能由一个进程流向另一个进程,如果要进行双工通信,则需要建立两个管道。
-
管道只能用于父子进程或者兄弟进程间通信。
管道创建函数
#include <unistd.h>
int pipe(int fd[2]);
//fd[0] 读管道 fd[1] 写管道,一般的文件I/O函数都可以用于管道,如close、read、write
父子进程通过管道通信测试例子
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define INPUT 0
#define OUTPUT 1
int main()
{
int fd[2];//定义管道的两端,fd[0]->读,fd[1]->写
int read_count;
pid_t pid;
char buf[256]={'\0'};
pipe(fd);//创建管道
pid=fork(); //fork子进程
if(pid<0)
{
printf("error in fork\n");
exit(1);
}
else if(pid==0) //子进程
{
close(fd[INPUT]);
while(1)
{
memset(buf,'\0',sizeof(buf));
fgets(buf,sizeof(buf),stdin);
write(fd[OUTPUT],buf,strlen(buf));
if(strncmp("exit",buf,4)==0)
{
break;
}
}
exit(0);
}
else //父进程
{
close(fd[OUTPUT]);
while (1)
{
read_count=read(fd[INPUT],buf,sizeof(buf));
buf[read_count]='\0';
printf("%d bytes of data received from child process: %s\n",read_count,buf);
if(strncmp("exit",buf,4)==0)
{
break;
}
}
}
return 0;
}
命名管道
命名管道提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。其具有以下特点:
- 使互不相关的两个进程间实现彼此通信
- 命名管道通过路径名来指出,并且在文件系统中是可见的,可当作普通文件进行读写操作
- FIFO严格遵循先进先出规则,对管道及FIFO的读操作总是从开始处返回数据,对它们的写操作则是把数据添加到末尾。
命名管道创建
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname,mode_t mode);
//pathname 普通的路径名
//与open()函数中的mode相同
//创建成功后,一般文件的I/O函数都可以作用于FIFO,如close、read、write等。
命名管道通信测试例子
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define P_FIFO "/tmp/p_fifo" //命名管道的名字
//命名管道读测试
int read_fifo()
{
char cache[100]={'\0'};
int fd;
memset(cache,0,sizeof(cache));
if(access(P_FIFO,F_OK)==0) //判断管道是否存在,若存在就删除
{
execlp("rm","-f",P_FIFO,NULL);
printf("access.\n");
}
if(mkfifo(P_FIFO,0777)<0) //创建命名管道
{
printf("create named pipe failed.\n");
}
fd=open(P_FIFO,O_RDONLY|O_NONBLOCK);//打开命名管道,以读权限以及非阻塞模式打开
while(1)
{
memset(cache,0,sizeof(cache));
if((read(fd,cache,100))==0) //读取命名管道里面的数据
{
printf("nodata:\n");
}
else
{
printf("getdata:%s\n",cache);
if(strncmp("exit",cache,4)==0)
{
break;
}
}
sleep(1);
}
close(fd);
return 0;
}
//命名管道写测试
int write_fifo()
{
int fd;
char cache[100]={'\0'};
fd=open(P_FIFO,O_WRONLY|O_NONBLOCK);//打开命名管道,以写权限以及非阻塞模式打开命名管道
while(1)
{
fgets(cache,sizeof(cache),stdin);
write(fd,cache,sizeof(cache)); //往命名管道写入数据
if(strncmp("exit",cache,4)==0)
{
break;
}
}
close(fd);
return 0;
}
消息队列
用于同一台机器上的进程间通信,与管道相似,是一个在系统内核中用来保存消息的队列。在系统内核中是以消息链表的形式出现。
相关函数说明
创建新消息队列或取得已存在的消息队列
int msgget(key_t,int msgflg);
//key 可认为是一个端口号,可由ftok函数生成
//msgflg ,若为IPC_CREAT,则如果没有该队列,就创建一个并返回新标识符,如果已经存在就返回原标识符
//msgflg,若为IPC_EXCL,如果没有该队列,则返回-1,若已存在,则返回0
读写消息队列
//从消息队列读取信息
ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg);
//向消息队列发送信息
int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg);
//msqid 消息队列标识码
//msgp 指向消息缓冲区的指针,用于暂时存储发送和接收的消息,是一个用户可自定义的通用数据结构,一般定义为结构体
//msgsz 消息的大小
//msgtyp 指从消息队列内读取的消息形态,如果值为零,则表示消息队列中的所有消息都会被读取。
//msgflg 指明核心程序在队列没有数据的情况下所应采取的行动,如果msgflg和常数IPC_NOWAIT合用,则在msgsnd()执行时若是消息队列已满,则msgsnd()将不会阻塞,而立即返回-1,如果执行的是msgrcv(),则在消息队列为空时,不做等待马上返回-1,并设定错误码为ENOMSG,当msgflg为0时,msgsnd()及msgrcv()在队列为满或为空的情形时,采取阻塞等待的处理模式。
设置消息队列属性
int msgctl(int msgqid,int cmd,struct msqid_ds *buf);
//对msgqid标识的消息队列执行cmd操作,系统定义了3种cmd操作:IPC_STAT、IPC_SET、IPC_RMID。
//IPC_STAT 用来获取消息队列对应的msqid_ds数据结构,并将其保存到buf指定的地址空间。
//IPC_SET 用来设置消息队列的属性,要设置的属性存储在buf种
//IPC_RMID 用来从内核种删除msqid标识的消息队列
消息队列通信测试例子
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/msg.h>
struct msg_st{
long int msg_type;
char text[512];
};
int msg_receive()
{
int running = 1;
int msgid = -1;
struct msg_st data;
memset(&data,0,sizeof(data));
long int msgtype = 0;
//建立消息队列
msgid = msgget((key_t)1234,0666|IPC_CREAT);
if(msgid==-1)
{
fprintf(stderr,"msgget failed with error:%d\n",errno);
exit(EXIT_FAILURE);
}
while(running)
{
//从消息队列种获取信息,遇到exit结束
if(msgrcv(msgid,(void*)&data,sizeof(data),msgtype,0)==-1)
{
fprintf(stderr,"msgrcv failed with error:%d\n",errno);
exit(EXIT_FAILURE);
}
printf("You write: %s\n",data.text);
if(strncmp("exit",data.text,4)==0)
{
running=0;
}
}
//删除消息队列
if(msgctl(msgid,IPC_RMID,0)==-1)
{
fprintf(stderr,"msgctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
int msg_send()
{
int running=1;
struct msg_st data;
char buffer[512];
int msgid=-1;
//建立消息队列
msgid=msgget((key_t)1234,0666|IPC_CREAT);
if(msgid==-1)
{
fprintf(stderr,"msgget failed with error:%d\n",errno);
exit(EXIT_FAILURE);
}
while(running)
{
printf("Enter some msg: \n");
fgets(buffer,512,stdin);
data.msg_type=1;
strcpy(data.text,buffer);
//向消息队列发送数据
if(msgsnd(msgid,(void*)&data,512,0)==-1)
{
fprintf(stderr,"msgsnd() failed\n");
exit(EXIT_FAILURE);
}
if(strncmp("exit",buffer,4)==0)
{
running=0;
}
sleep(1);
}
exit(EXIT_SUCCESS);
}
消息队列与命名管道区别
- 消息队列可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难
- 可以同时通过发送消息以避免命名管道的同步和阻塞问题,而不需要由进程自己来提供同步方法
- 接收程序可以通过消息类型有选择地接收数据,而不像命名管道,只能默认接收
共享内存
允许两个不相关的进程访问同一个逻辑内存,在两个运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排在同一段的物理内存中,进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址。
共享内存未提供同步机制,通常需要其他的机制来同步对共享内存的访问。
相关函数说明
创建共享内存
#include <sys/shm.h>
int shmget(key_t key,int size,int flag);
//key 非0整数,有效地为共享内存段命名
//size 以字节为单位指定需要共享的内存容量
//flag 权限标志,作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在的条件下创建它的话,可以与IPC_CREAT做或操作。
//shmget函数运行成功时返回一个与key相关的共享内存标志符(非负整数)用于后续的共享内存函数,调用失败时返回-1。
将共享内存连接到自身的地址空间中
void *shmat(int shmid,void *addr,int flag);
//shmid 为shmget函数返回的共享存储标识符
//addr与flag参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址,其他进程可以对此进程进行读写操作。
将共享内存与当前进程分离
int shmdt(const void *shmaddr);
//shmaddr是shmat函数返回的地址指针,调用成功返回0,失败返回-1
共享内存通信测试例子
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#define TEXT_SZ 2048
struct shared_use_st {
int written;
char text[TEXT_SZ];
};
int consumer()
{
int shmid;
srand((unsigned int)getpid());//设置随机种子
//创建共享内存
shmid=shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);
if(shmid==-1)
{
fprintf(stderr,"shmget failed\n");
exit(EXIT_FAILURE);
}
void *shared_memory=(void*)0;
//将共享内存连接到自身的地址空间内
shared_memory=shmat(shmid,(void*)0,0);
if(shared_memory==(void*)-1)
{
fprintf(stderr,"shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attached at %X\n",(long)shared_memory);
struct shared_use_st *shared_stuff;
shared_stuff=(struct shared_use_st*)shared_memory;
shared_stuff->written=0;
int running=1;
while(running)
{
if(shared_stuff->written)//判断是否有数据写入
{
printf("You wrote: %s\n",shared_stuff->text);
sleep(rand()%4);
shared_stuff->written=0;
if(strncmp("exit",shared_stuff->text,4)==0)
{
running=0;
}
}
}
//将共享内存与当前进程分离
if(shmdt(shared_memory)==-1)
{
fprintf(stderr,"shmdt failed\n");
exit(EXIT_FAILURE);
}
//删除共享内存
if(shmctl(shmid,IPC_RMID,0)==-1)
{
fprintf(stderr,"shmctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
int producer()
{
int shmid;
//获取共享内存
shmid=shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);
if(shmid==-1)
{
fprintf(stderr,"shmget failed\n");
exit(EXIT_FAILURE);
}
void *shared_memory=(void*)0;
//将共享内存连接到当前进程的地址空间中
shared_memory=shmat(shmid,(void*)0,0);
if(shared_memory==(void*)-1)
{
fprintf(stderr,"shmat failed\n");
exit(EXIT_FAILURE);
}
printf("memory attached at %X\n",(long)shared_memory);
struct shared_use_st *shared_stuff;
shared_stuff=(struct shared_use_st*)shared_memory;
int running=1;
char buffer[TEXT_SZ];
while(running)
{
while(shared_stuff->written==1)
{
sleep(1);
printf("waiting for client...\n");
}
printf("Enter some text: ");
fgets(buffer,TEXT_SZ,stdin);
strncpy(shared_stuff->text,buffer,TEXT_SZ); //往共享内存写入数据
shared_stuff->written=1;
if(strncmp("exit",buffer,4)==0)
{
running=0;
}
}
//将共享内存与当前进程分离
if(shmdt(shared_memory)==-1)
{
fprintf(stderr,"shmdt failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
信号量
用于用户态进程通信的SYSTEM V信号量,信号量用来协调进程对共享资源的访问,
信号量只能进行等待和发送信号。即P和V操作。
- P(sv)操作,如果sv大于0,就减1,如果sv为0,就挂起该进程的执行
- V(sv)操作,如果有其他进程因等待而被挂起,就让它恢复运行,如果没有进程因等待而挂起,就给它加1
相关函数说明
创建和打开信号量
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key,int nsems,int semflg);
//成功返回信号量标识符,失败返回-1
//key,通过ftok函数得到的键值
//nsems 代表创建信号量的个数,如果只是访问而不创建可以指定该参数为0,但一旦创建了信号量,就不能更改其信号量个数
//semflg 指定该信号量的读写权限
改变信号量的值
int semop(int semid,struct sembuf *sops,unsigned nsops);
//semid 由semget返回的信号量标识符
//sembuf结构体
/*
struct sembuf{
short sem_num;//除非使用一组信号量,否则为0
short sem_op;//信号量在一次操作中需改变的数据,通常是两个数:-1 P操作,1 V操作
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,在进程没有释放该信号量而终止时,操作系统释放信号量
}
*/
直接控制信号量信息
int semctl(int semid,int semnum,int cmd, ...);
//成功返回一个整数,失败返回-1
/*
如果有第四个参数,通常是一个union semum结构,定义如下:
union semun {
int val;
struct semid_ds *buf;
unsigned short *arry;
struct seminfo *__buf;
};
cmd:
IPC_SET:对此集合去semid_ds结构,并存放在由arg.buf指向的结构中
SETVAL:设置信号量集中的一个单独的信号量的值,此时需要传入第四个参数
IPC_RMID:从系统中删除该信号量
*/
信号量通信测试例子
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <errno.h>
#define SEM_KEY 4001
#define SHM_KEY 5678
union semun {
int val;
};
int sem_read()
{
int semid,shmid;
//创建共享内存
shmid=shmget(SHM_KEY,sizeof(int),IPC_CREAT|0666);
if(shmid<0)
{
printf("create shm error\n");
return -1;
}
void *shmptr;
//将共享内存连接到当前进程地址空间内
shmptr=shmat(shmid,NULL,0);
if(shmptr==(void*)-1)
{
printf("shmat error:%s\n",strerror(errno));
return -1;
}
int *data=(int*)shmptr;
//创建信号量,个数为2
semid=semget(SEM_KEY,2,IPC_CREAT|0666);
union semun semun1;
semun1.val=0;
//设置信号量值
semctl(semid,0,SETVAL,semun1);
semun1.val=1;
//设置信号量值
semctl(semid,1,SETVAL,semun1);
struct sembuf sembuf1;
while(1)
{
sembuf1.sem_num=0;
sembuf1.sem_op=-1;//等待操作
sembuf1.sem_flg=SEM_UNDO; //使操作系统跟踪信号
semop(semid,&sembuf1,1);
printf("the NUM:%d\n",*data);
sembuf1.sem_num=1;
sembuf1.sem_op=1; //发送操作
sembuf1.sem_flg=SEM_UNDO;
semop(semid,&sembuf1,1);
if((*data)==-1)
{
break;
}
}
//将共享内存与当前进程分离
if(shmdt(shmptr)==-1)
{
fprintf(stderr,"shmdt failed\n");
exit(EXIT_FAILURE);
}
//删除共享内存
if(shmctl(shmid,IPC_RMID,0)==-1)
{
fprintf(stderr,"shmctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
return 0;
}
int sem_write()
{
int semid,shmid;
//获取共享内存
shmid=shmget(SHM_KEY,sizeof(int),IPC_CREAT|0666);
if(shmid<0)
{
printf("create shm error\n");
return -1;
}
void *shmptr;
//将共享内存连接到当前进程地址空间内
shmptr=shmat(shmid,NULL,0);
if(shmptr==(void*)-1)
{
printf("shmat error:%s\n",strerror(errno));
return -1;
}
int *data=(int*)shmptr;
//获取信号量,个数为2
semid=semget(SEM_KEY,2,0666);
struct sembuf sembuf1;
union semun semun1;
while(1)
{
sembuf1.sem_num=1;
sembuf1.sem_op=-1;//等待操作
sembuf1.sem_flg=SEM_UNDO;
semop(semid,&sembuf1,1);
scanf("%d",data);
sembuf1.sem_num=0;
sembuf1.sem_op=1;//发送操作
sembuf1.sem_flg=SEM_UNDO;
semop(semid,&sembuf1,1);
if((*data)==-1)
{
break;
}
}
return 0;
}
套接字
UNIX本地套接字,是在socket的框架上发展出的一种IPC机制,即UNIX Domain Socket。用于同一台计算机上运行的进程之间的通信。它的执行效率更高,不需要进行协议处理,仅仅是复制数据,将应用数据从一个进程拷贝到另一个进程。
UNIX本地套接字也提供面向流和面向消息的两种API接口,类似于TCP和UDP,但是面向消息的UNIX本地套接字也是可靠的,即消息不会丢失也不会顺序错乱。
相关函数说明
创建套接字
int socket(int domain,int type,int protocol);
//domain 协议族,对于本地套接字,为AF_LOCAL 或 AF_UNIX
//type:确定套接字类型 SOCKET_STREAM(面向流) SOCKET_SEQPACKET(面向消息)
//protocol:默认传0 默认协议即可
本地套接字地址结构
struct sockaddr_un {
__kernel_sa_family_t sun_family;//地址结构类型
char sun_path[UNIX_PATH_MAX]; //socket文件名(含路径) 绝对路径
}
绑定套接字
int unlink(char *pathname);
int remove(char *pathname);
int bind(int socket,const struct sockaddr *address,size_t address_len);
//struct sockaddr: 本地套接字地址结构,使用struct sockaddr_un
监听
int listen(int socket,int backlog);
int accept(int socket,struct sockaddr *address,size_t *address_len);
//struct sockaddr: 本地套接字地址结构,使用struct sockaddr_un
连接
int connect(int socket,const struct sockaddr *address,size_t address_len);
//struct sockaddr: 本地套接字地址结构,使用struct sockaddr_un
读写
ssize_t read(int fd,void *buf,size_t count);
sszie_t write(int fd,const void *buf,size_t count);
通信流程
服务端:
- 创建本地套接字
- 绑定本地套接字
- 监听
- 消息收发
客户端:
- 创建本地套接字
- 绑定本地套接字
- 链接
- 消息收发
套接字通信测试例子
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <errno.h>
#define UNIX_STREAM_PATH "/tmp/unix_socket.socket"
#define BUFFER_SIZE 4096
int unix_socket_server()
{
int listen_fd,con_fd;
int index=0,read_len=0;
socklen_t len;
struct sockaddr_un servaddr,cliaddr;
char buf[BUFFER_SIZE];
if((listen_fd=socket(AF_LOCAL,SOCK_STREAM,0))==-1)
{
fprintf(stderr,"socket(AF_LOCAL) error:%s\n",strerror(errno));
exit(EXIT_FAILURE);
}
unlink(UNIX_STREAM_PATH);
bzero(&servaddr,sizeof(servaddr));
servaddr.sun_family=AF_LOCAL;
strcpy(servaddr.sun_path,UNIX_STREAM_PATH);
if(bind(listen_fd,(struct sockaddr*)&servaddr,sizeof(servaddr))==-1)
{
fprintf(stderr,"bind(AF_LOCAL) error:%s\n",strerror(errno));
exit(EXIT_FAILURE);
}
listen(listen_fd,20);
printf("begin Accept ...\n");
len=sizeof(cliaddr);
if((con_fd=accept(listen_fd,(struct sockaddr*)&cliaddr,&len))==-1)
{
fprintf(stderr,"accept(AF_LOCAL) error:%s\n",strerror(errno));
exit(EXIT_FAILURE);
}
while(1)
{
bzero(buf,sizeof(buf));
if((read_len=read(con_fd,buf,sizeof(buf)))==0)
{
break;
}
printf("recv:%s\n",buf);
for(index=0;index<read_len;index++)
{
buf[index]=toupper(buf[index]);
}
write(con_fd,buf,read_len);
if(strncmp("exit",buf,4)==0)
{
break;
}
}
close(con_fd);
close(listen_fd);
return 0;
}
int unix_socket_client()
{
int con_fd;
int read_len=0;
struct sockaddr_un servaddr;
char buf[BUFFER_SIZE];
con_fd=socket(AF_LOCAL,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sun_family=AF_LOCAL;
strcpy(servaddr.sun_path,UNIX_STREAM_PATH);
connect(con_fd,(struct sockaddr*)&servaddr,sizeof(servaddr));
while(1)
{
bzero(buf,sizeof(buf));
printf("Enter text: \n");
if(fgets(buf,sizeof(buf),stdin)==NULL)
{
break;
}
write(con_fd,buf,strlen(buf));
read_len=read(con_fd,buf,sizeof(buf));
printf("recv:%s\n",buf);
if(strncmp("EXIT",buf,4)==0)
{
break;
}
}
close(con_fd);
return 0;
}