linux进程间通信学习

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);
通信流程
服务端:
  1. 创建本地套接字
  2. 绑定本地套接字
  3. 监听
  4. 消息收发
客户端:
  1. 创建本地套接字
  2. 绑定本地套接字
  3. 链接
  4. 消息收发

套接字通信测试例子

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值