IPC进程间通信

linux进程间通信方式总结

ipcs命令:查看系统使用的IPC资源,报告进程间通信设施状态。
ipcs -m 查看系统使用的IPC共享内存资源
ipcs -q 查看系统使用的IPC队列资源
ipcs -s 查看系统使用的IPC信号量资源

一、无名管道(Pipe)
    (1)只能用于有亲缘关系的进程间通信
    (2)半双工的通信模式,具有固定的读写端
    (3)pipe看做特殊的文件来操作(又符合linux一切皆是文件的思想)
    #include <unistd.h>
    int pipe(int pipefd[2]);
pipefd[0] refers tothe read end of the pipe.  pipefd[1] refers to  the  write  end  of  the pipe.

当读一个写端被关闭的管道时,在所有数据被读取后,read返回0,表示文件结束;
当写一个读端被关闭的管道时,则产生信号SIGPIPE,如果忽略该信号或者捕捉该信号并从处理程序返回,则write返回-1

Sample_pipe
    1、建立一个管道
    2、父进程写入一些内容 fork()
    3、子进程读取一些内容,并且打印出来
pipe()建立管道
fork()生成新进程
判断,如果是父,则写入管道内容fd[1]
判断,如果是子,则读出管道内容fd[0]
关闭进程

             

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main()
{
        int pfd[2] ;
        int pid ;

        char w_count[] = "hello wyj !" ;
        char r_count[255] ;
        //creat pipe
        if(pipe(pfd)<0)
        {
                //falure
                perror("creat pipe failed\n") ;
                return -1 ;
        }
        else
        {
                if( (pid=fork()) < 0)
                {
                        perror("creat process failed!\n") ;
                }else if(pid > 0)
                {
                //parent process
                close(pfd[0]) ;
                write(pfd[1],w_count,strlen(w_count)) ;
                close(pfd[1]) ;
                wait() ;

                }else
                {
                        //child process
                        close(pfd[1]) ;
                        sleep(2) ;
                        read(pfd[0],r_count,255) ;
                        printf("child process read:%s\n",r_count) ;
                }

        }

        return 0;
}
               


二、有名管道(Fifo)
  实现一个命名管道实际上就是实现一个FIFO文件。命名管道一旦建立,之后它的读写以及关闭操作
都与普通管道完全相同。虽然FIFO文件的inode节点在磁盘上,但是仅是一个节点而已,文件的数据还是
存在于内存缓存页面中,和普通管道相同。
    int  mkfifo(const char *pathname,mode_t mode) ;
    
    //建立有名管道文件
    让两个进程去操作这个文件,open,write,read .
Sample_fifo
    1.建立一个fifo_r用来读取有名管道  

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#define FIFO_PATH "myfifofile"


//read fifo
int main(int argc, char **argv)
{
        int fd ;
        char cont_r[255] ;

        //creat fifo file 
        if(mkfifo(FIFO_PATH,0666) < 0 && errno != EEXIST)
        {
                perror("create fifo failed") ;
                return -1 ;
        }
        else
        {
                printf("create fifo success\n") ;

                //open fifo file
                fd = open(FIFO_PATH, O_CREAT|O_RDONLY,0666)  ;
                if(fd > 0)
                {
                        //read content
                        while(1)
                        {
                                read(fd, cont_r, 255) ;
                                printf("read:%s\n", cont_r) ;
                        }
                        close(fd) ;
                }
                else
                {
                        perror("open failed") ;
                }
        }






        return 0 ;
/*              end             */
}

    2.建立一个fifo_w用来写入一个有名管道

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#define FIFO_PATH "myfifofile"


//write fifo
int main(int argc, char **argv)
{
        int fd ;
        char cont_w[] = "hello world!" ;

        //creat fifo file 
        if(mkfifo(FIFO_PATH,0666) < 0 && errno != EEXIST)
        {
                perror("create fifo failed") ;
                return -1 ;
        }
        else
        {
                printf("create fifo success\n") ;

                //open fifo file
                fd = open(FIFO_PATH, O_CREAT|O_WRONLY,0666)  ;
                if(fd > 0)
                {
                        //read content
                        while(1)
                        {
                                write(fd, cont_w, strlen(cont_w)) ;
                                printf("write success\n") ;
                                sleep(2) ;
                        }
                        close(fd) ;
                }
                else
                {
                        perror("open failed") ;
                }
        }


        return 0 ;
/*              end             */


    3.两个进程通过有名管道通信
    ... ...

使用命名管道实现了一个进程间聊天的程序:

#include <stdio.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>

#define	FIFO_PATH0	"fifo_file0"
#define	FIFO_PATH1	"fifo_file1"
#define	BUF_SIZE	256


int g_stop = 0;

void sig_pipe(int signum)
{
	if(SIGPIPE == signum)
	{
		printf("get pipe broken signal and let programe exit\n");
		g_stop = 1;
	}
}

void print_usage(char *argv)
{
	printf("Usage:  %s [0/1]\n", argv) ;
    printf("        One process \"%s 0\"\n", argv) ;
    printf("        Other process \"%s 1\"\n", argv) ;
    printf("        process %s 0 can chat with %s 1\n", argv, argv) ;
	return ;
}


int main(int argc, char **argv)
{
	int	fifo_fdr, fifo_fdw ;
	char	*path_name = NULL ;
	int	mode ;
	fd_set	rset;
	char	rbuf[BUF_SIZE] ;
	int	rv ;
	time_t	ticks ;

	path_name = basename(argv[0]) ;
    if(argv[1] == NULL) 
    {
        print_usage(path_name);
        return 0 ;
    }
	mode = atoi(argv[1]) ;
    printf("running in here\n");
	if( argc != 2 )
	{
		print_usage(path_name) ;
		return 0 ;
	}
	if(mode != 0 && mode != 1)
	{
		print_usage(path_name) ;
		return 0 ;
	}


	if( access(FIFO_PATH0, F_OK) != 0)//F_OK tests for the existence of the file
	{
		if( mkfifo( FIFO_PATH0, 0666) < 0)
		{
			printf("mkfifo() failure: %s\n", strerror(errno)) ;
			return -1 ;
		}
		printf("Create %s sucessful!\n", FIFO_PATH0) ;
	}

	if( access(FIFO_PATH1, F_OK) != 0)//F_OK tests for the existence of the file
	{	
		if( mkfifo( FIFO_PATH1,0666) < 0)
		{
			printf("mkfifo() failure: %s\n", strerror(errno)) ;
			return -1 ;
		}
		printf("Create %s sucessful!\n", FIFO_PATH1) ;
	}
	signal(SIGPIPE, sig_pipe);


	if(mode == 0)//mode[0] open FIFO_PATH0 for read, FIFO_PATH1 for write
	{
		printf("Mode[%d] is running...\n", mode) ;	
		fifo_fdr = open(FIFO_PATH0, O_RDONLY) ;
		fifo_fdw = open(FIFO_PATH1, O_WRONLY) ;
		if(fifo_fdr < 0 || fifo_fdw < 0)
		{
			printf("Model[%d] process open %s or %s failure: %s\n",mode ,FIFO_PATH0, FIFO_PATH1, strerror(errno)) ;
			goto cleanup ;
		}
		printf("Model[%d] open fifo sucessful!\n", mode) ;
	
	}

	else //mode[1] open FIFO_PATH0 for write, FIFO_PATH1 for read
	{	
		printf("Mode[%d] is running...\n", mode) ;	
		fifo_fdw = open(FIFO_PATH0, O_WRONLY) ;
		fifo_fdr = open(FIFO_PATH1, O_RDONLY) ;
		if(fifo_fdr < 0 || fifo_fdw < 0)
		{
			printf("Model[%d] open %s or %s failure: %s\n",mode ,FIFO_PATH0, FIFO_PATH1, strerror(errno)) ;
			goto cleanup ;
		}
		printf("Model[%d] open fifo sucessful!\n", mode) ;
	
	}

	printf("start chating with another program now, please input message now: \n");
	while(!g_stop)
	{
		FD_ZERO(&rset) ;
		FD_SET(STDIN_FILENO, &rset) ;//select每次返回,没有发生事件的fd会被清空,所以每次都要再FD_SET
		FD_SET(fifo_fdr, &rset) ;
                /* select多路复用监听标准输入和作为输入的命名管道读端 */
		rv = select(fifo_fdr+1, &rset, NULL, NULL, NULL) ;
		if(rv < 0)
		{
			printf("select() failed: %s\n", strerror(errno)) ;
			goto cleanup ;
		}
		if(rv == 0)
		{
			printf("select() time out!\n") ;
			goto cleanup ;
		}

		ticks = time(NULL) ;

		if( FD_ISSET(STDIN_FILENO, &rset) )
		{
			memset(rbuf, 0, sizeof(rbuf)) ;
			fgets(rbuf, sizeof(rbuf), stdin) ;
			if( write(fifo_fdw, rbuf, strlen(rbuf)) < 0)
			{
				printf("write to other side failure :%s\n", strerror(errno)) ;
				break ;
			}

		}

		if( FD_ISSET(fifo_fdr, &rset) )
		{
			memset(rbuf, 0, sizeof(rbuf)) ;
			rv = read(fifo_fdr, rbuf, sizeof(rbuf)) ;
			if(rv < 0)
			{
				printf("read() error: %s\n", strerror(errno)) ;
				break ;
			}
			if(rv ==0 )
			{
				printf("FIFO side of write close, process will exit...\n") ;
				break ;
			}
			
			printf("%.24s\n",ctime(&ticks)) ;
			printf("	<-- %s", rbuf) ;
		}

	}


cleanup:
	close(fifo_fdr) ;
	close(fifo_fdw) ;

	return 0 ;
}

三、信号(Signal)
    安装信号
     #include <signal.h>

       typedef void (*sighandler_t)(int);

       sighandler_t signal(int signum, sighandler_t handler);

    向进程本身发送信号
    int raise(int SIGNO) ;
    
    向指定进程发送信号
    int kill(pid_t pid,int SIGNO) ;
    
    特殊发送信号函数,比如:
    unsigned int alarm(unsigned int seconds) ;

    等待信号
    pause()

信号控制子进程运行然后父进程运行的程序:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>

int    g_child_stop = 1 ;
int    g_parent_stop = 1 ;
int    g_chil_exit = 0 ;

void signal_worker1(int signum) 
{
    if(signum == SIGUSR1)
    {
        g_parent_stop = 0 ;
    }
    if(signum == SIGUSR2)
    {
        g_child_stop = 0 ;
        g_parent_stop = 1;
    }
    if(signum == SIGCHLD)
    {
        printf("Child process is exit!\n");
        g_chil_exit = 1 ;
    }
}

int main(int argc, char **argv)
{
    int     pid ;
    int     wstatus ;

    signal(SIGUSR1,signal_worker1 ) ;
    signal(SIGUSR2,signal_worker1 ) ;
    signal(SIGCHLD,signal_worker1 ) ;

    pid = fork() ;
    if(pid < 0)
    {
        printf("fork() failed: %s\n", strerror(errno));
        return -1 ;
    }
    if(pid == 0) // child process
    {
        printf("child process is running , pid = %d\n", getpid());
        sleep(2) ;
        /* child process can do somting in here ... */
        printf("Child process do someting over, and send sinal to parent process[%d]\n", getppid());
        kill(getppid() ,SIGUSR1) ;
        while(g_child_stop ) 
        {
            ;
        }
        printf("Child process will exit...\n") ;
        sleep(1) ;
        exit(2) ; 
    }
    /* parent process */
    while( g_parent_stop )
    {
        ;
    }
    printf("Parent process is running, pid = %d\n", getpid()) ;
    /* parent process can do someting in here */
    sleep(3) ;
    printf("Parent process do someting over, and send sinal to Child process[%d]\n", pid);
    kill(pid, SIGUSR2) ;
    wait(&wstatus) ;//parent process will block in here until child process exit
    if( g_chil_exit )
    {
        printf("Parent receive child exit signal\n");    
    }


    return 0 ;
}

四、共享内存(share memory)
***共享内存是最快的进程间通信!!!***

ftok()函数获取IPC关键字
    key_t ftok( const char * fname, int id )
    fname就是你指定的文件名
    id是子序号。虽然是int类型,但是只使用8bits(1-255)
    在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。
    如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。
    查询文件索引节点号的方法是: ls -i

int shamget(key_t key,size_t size, int shmflg) ;    (开辟)
    Return:成功返回 shmid, 失败返回 -1 on error
    第一个参数 key为ftok返回的
    第二个参数 size为需要内存的大小,以内存为单位。
    第三个参数 shmflg
        shmflg是权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。
        共享内存的权限标志与文件的读写权限一样,如0644,允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。
        IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;
        如果存在这样的共享内存,返回此共享内存的标识符
        IPC_CREAT|IPC_EXCL:如果内核中不存在键值 与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存则报错
        


void *shmat(int shmid,const void *shamaddr,int shmflg); (映射)
    Return:成功返回地址,失败返回(void *)-1 on error
    第一个参数:shmid是shamget()返回的共享内存标识符
    第二个参数:shmaddr指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置
    第三个参数:如果在flag中指定了SHM_RDONLY以只读模式访问内存,否则为读写模式    

    

void *memcpy(void *dest, const void *src, size_t n);       (操作)
    可以用memcpy操作这段共享内存,也可用指针指向shmat返回的地址操作共享内存

int shmdt(const void *shmaddr) ;    (关闭映射)
    Return: 成功0, 失败-1
    参数shmaddr是连接的共享内存的起始地址
    与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存
    并不删除所指定的共享内存区,而只是将先前用shmat函数连接(attach)好的共享内存脱离(detach)目前的进程


int shmctl(int shmid,int cmd, struct shmid_ds *buf);(共享内存管理)
    Return:失败-1 
    第一个参数shmid为shmget返回的共享内存标识符
    第二个参数cmd是要采取的操作,它可以取下面的三个值:
        IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中
        IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内
        IPC_RMID:删除这片共享内存
    第三个参数buf是一个结构指针,指向共享内存管理结构体
    The buf argument is a pointer to a shmid_ds structure, defined in <sys/shm.h> as follows:

           struct shmid_ds {
               struct ipc_perm shm_perm;    /* Ownership and permissions */
               size_t          shm_segsz;   /* Size of segment (bytes) */
               time_t          shm_atime;   /* Last attach time */
               time_t          shm_dtime;   /* Last detach time */
               time_t          shm_ctime;   /* Last change time */
               pid_t           shm_cpid;    /* PID of creator */
               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
               shmatt_t        shm_nattch;  /* No. of current attaches */
               ...
           };
     
    The ipc_perm structure is defined as follows (the highlighted fields are settable using IPC_SET):

           struct ipc_perm {
               key_t          __key;    /* Key supplied to shmget(2) */
               uid_t          uid;      /* Effective UID of owner */
               gid_t          gid;      /* Effective GID of owner */
               uid_t          cuid;     /* Effective UID of creator */
               gid_t          cgid;     /* Effective GID of creator */
               unsigned short mode;     /* Permissions + SHM_DEST and
                                           SHM_LOCKED flags */
               unsigned short __seq;    /* Sequence number */
           };

写共享内存:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define     BUF_SIZE 128


typedef struct _st_student_info
{
    char    name[BUF_SIZE] ;
    long int     ID ;
}student ;


int main(int argc, char **argv)
{
    key_t       key ;
    student     *stu = NULL;
    int         shmid ;

    key = ftok(".",0x98) ;
    if(key == -1)
    {
        printf("ftok() failed: %s\n", strerror(errno)) ;
        return -1 ;
    }
    printf("ftok() sucessful,key = %d\n", key) ;
    shmid =  shmget(key, sizeof(student), IPC_CREAT|0644);
    if( shmid == -1 )
    {
        printf("shmget() failed: %s\n", strerror(errno)) ;
        return -2 ;
    }
    printf("shmid = %d\n", shmid);
    stu = shmat(shmid, NULL, 0);
    if(stu == (void *) -1 )
    {
        printf("shmat failed: %s\n", strerror(errno)) ;
        shmctl(shmid, IPC_RMID, NULL) ;
        return -3 ;
    }
    snprintf(stu->name, sizeof(stu->name), "吴宇骏" ) ;
    stu->ID = 201621112125 ;
    printf("shmat sucessful, student name:%s ID: %ld\n", stu->name, stu->ID) ;
    shmdt(stu) ;            //关闭映射
    printf("Finish write to memory...\n") ;
    

    

    return 0 ;
}

读共享内存:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define     BUF_SIZE 128


typedef struct _st_student_info
{
    char    name[BUF_SIZE] ;
    long int     ID ;
}student ;


int main(int argc, char **argv)
{
    key_t       key ;
    student     *stu = NULL ;
    int         shmid ;

    key = ftok(".",0x98) ;
    if(key == -1)
    {
        printf("ftok() failed: %s\n", strerror(errno)) ;
        return -1 ;
    }
    printf("ftok() sucessful,key = %d\n", key) ;
    shmid =  shmget(key, sizeof(student), IPC_CREAT|0644);
    if( shmid == -1 )
    {
        printf("shmget() failed: %s\n", strerror(errno)) ;
        return -2 ;
    }
    printf("shmid = %d\n", shmid);
    stu = shmat(shmid, NULL,SHM_RDONLY);
    if(stu == (void *) -1 )
    {
        printf("shmat failed: %s\n", strerror(errno)) ;
        goto cleanup ;
    }
    printf("shmat sucessful, student name:%s ID: %ld\n", stu->name, stu->ID) ;
    shmdt(stu) ;            //关闭映射
    printf("Finish read from memory And close shared memory!\n") ;
    

    
cleanup:
        shmctl(shmid, IPC_RMID, NULL) ;   //读取完关闭共享内存

    return 0 ;
}

五、信息队列(message queue)

ftok()函数获取IPC关键字
    key_t ftok( const char * fname, int id )
    fname就是你指定的文件名
    id是子序号。虽然是int类型,但是只使用8bits(1-255)
    在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。
    如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。
    查询文件索引节点号的方法是: ls -i

(用于创建一个新的或打开一个已经存在的消息队列)
int msgget(key_t key, int msgflg) ;

RETURN VALUE
       If successful, the return value will be the message queue identifier (a nonnegative integer), otherwise -1 with  errno  indi‐
       cating the error.
    成功返回一个消息队列的标识符,失败返回-1
    第一个参数:key是函数ftok返回的
    第二个参数:msgflg消息队列的建立标志和存取权限:
        IPC_CREAT如果内核中没有此队列,则创建它。
        IPC_EXCL当和IPC_CREAT一起使用时,如果队列已经存在,则失败。

(发送消息)
int msgsnd(int msqid, const void *msgp,size_t msgsz,int msgflg);

第一个参数msqid是msgget()返回的消息队列的标识符.
第二个参数msgp是一个指针,它指向要发送的消息结构体类型的变量。
可以是任何类型的结构体,但第一个字段必须为long类型,
即表明此发送消息的类型,msgrcv根据此接收消息。msgp定义的参照格式如下:


struct s_msg{ /*msgp定义的参照格式*/

    long type; /* 必须大于0,消息类型 */
    char mtext[512]; /*消息正文,可以是其他任何类型*/

 } 
第三个参数msgsz是发送消息的大小

如果队列中有足够的可用空间,则msgsnd()立即成功。
(队列容量由

消息队列的相关数据结构中的msg_qbytes字段。
在队列创建期间,此字段初始化为

MSGMNB字节,但是这个限制可以使用msgctl(2)修改。


msgsnd()的默认行为是阻塞直到空间变得可用。
如果在msgflg中指定IPC_NOWAIT,msgsnd()不阻塞并返回错误EAGAIN失败。
第四个参数msgflg这个参数依然是是控制函数行为的标志:当前消息队列满或到达系统上限时将要发生的事情,设置为IPC_NOWAIT表示队列满不等待,返回EAGAIN
错误。


(接收消息)
ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg);
第一个参数msqid是msgget()返回的消息队列的标识符.
第二个参数msgp指针,指向接收消息的结构体
第三个参数msgsz是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型
第四个参数msgtyp是接收消息类型:
    0:接收第一个消息
    >0:接收类型等于msgtyp的第一个消息
    <0:接收类型等于或者小于msgtyp绝对值的最小的一个消息。
第五个参数msgflg控制函数行为的标志: 
    IPC_NOWAIT如果队列中没有请求类型的消息,立即返回。系统调用失败,errno设置为ENOMSG
    
    MSG_EXCEPT
              使用大于0的msgtyp,读取队列中与msgtyp不同的消息类型的第一条消息。
    
    MSG_NOERROR
        如果超过msgsz字节,则截断消息文本。
RETURN VALUE
       On  failure  both  functions return -1 with errno indicating the error, otherwise msgsnd() returns 0 and msgrcv() returns the
       number of bytes actually copied into the mtext array.
返回值:失败时,两个函数返回-1,错误设置errno,否则msgsnd()返回0,msgrcv()返回

实际复制到mtext数组中的字节数。

(消息队列控制操作)
int msgctl(int msgid,int cmd,struct msqid_ds *buf);
    返回值:成功返回0,失败返回-1.
    第一个参数msqid是msgget()返回的消息队列的标识符.
    第二个参数cmd是要采取的操作,它可以取下面值:
        IPC_STAT:把msqid_ds结构中的数据设置为消息队列的当前关联值;
        IPC_SET: 如果进程有足够的权限,就把消息队列的当前关联值设置为msqid_ds结构中给出的值;
        IPC_RMID:删除消息队列;
        IPC_INFO(Linux专用的)

返回关于系统范围的消息队列限制和buf所指向的结构中的参数的信息。
    第三个参数,buf是一个结构指针,它指向存储消息队列的相关信息的 buf;
    The msqid_ds data structure is defined in <sys/msg.h> as follows:

           struct msqid_ds {
               struct ipc_perm msg_perm;     /* Ownership and permissions */
               time_t          msg_stime;    /* Time of last msgsnd(2) */
               time_t          msg_rtime;    /* Time of last msgrcv(2) */
               time_t          msg_ctime;    /* Time of last change */
               unsigned long   __msg_cbytes; /* Current number of bytes in
                                                queue (nonstandard) */
               msgqnum_t       msg_qnum;     /* Current number of messages
                                                in queue */
               msglen_t        msg_qbytes;   /* Maximum number of bytes
                                                allowed in queue */
               pid_t           msg_lspid;    /* PID of last msgsnd(2) */
               pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
           };

       The ipc_perm structure is defined as follows (the highlighted fields are settable using IPC_SET):

           struct ipc_perm {
               key_t          __key;       /* Key supplied to msgget(2) */
               uid_t          uid;         /* Effective UID of owner */
               gid_t          gid;         /* Effective GID of owner */
               uid_t          cuid;        /* Effective UID of creator */
               gid_t          cgid;        /* Effective GID of creator */
               unsigned short mode;        /* Permissions */
               unsigned short __seq;       /* Sequence number */
           };

消息队列发送程序:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define mtext_size 512

struct s_msg{ /* msgp定义的参照格式*/
        long type; /*  必须大于0,消息类型 */
        char mtext[mtext_size]; /* 消息正文,可以是其他任何类型*/           
} ;
int main(int argc ,char ** argv)
{
        key_t           key ;
        int             msgid ;
        struct s_msg    msgbuf ;

        key = ftok(".", 0x98) ; // 获取IPC关键字
        if(key == -1) 
        {
            printf("ftok() failed: %s\n", strerror(errno));
            return -1 ;
        }
        printf("ftok sucessful, and create key = %d\n", key) ;
        msgid = msgget(key, IPC_CREAT|0644) ;//创建一个新的或打开一个已经存在的消息队列
        if(msgid == -1)
        {
            printf("msgget() failed: %s\n", strerror(errno));
            return -2 ;
        }
        printf("msgget() sucessful, msgid = %d\n", msgid);
        memset(&msgbuf,0, sizeof(msgbuf) ) ;
        msgbuf.type = msgid ;
        snprintf( msgbuf.mtext, sizeof(msgbuf.mtext), "Hello msgqueue recver!")  ;
                                                                     
        if( msgsnd(msgid, &msgbuf, strlen(msgbuf.mtext), IPC_NOWAIT) == -1 ) //发送消息
        {                    
            printf("msgsnd() failed: %s\n", strerror(errno));
            msgctl(msgid, IPC_RMID, NULL) ;
            return -3 ;      
        }                    
        printf("msgqueue send '%s' to msgqueue sucessful!\n", msgbuf.mtext) ;                                                                              
        
        return  0 ;
                            

}

消息队列接收程序:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define mtext_size 512

struct s_msg{ /* msgp定义的参照格式*/
        long type; /*  必须大于0,消息类型 */
        char mtext[mtext_size]; /* 消息正文,可以是其他任何类型*/           
} ;
int main(int argc ,char ** argv)
{
        key_t           key ;
        int             msgid ;
        struct s_msg    msgbuf ;
        int             rv = -1;
        int             msgtype ;

        key = ftok(".", 0x98) ; // 获取IPC关键字
        if(key == -1) 
        {
            printf("ftok() failed: %s\n", strerror(errno));
            return -1 ;
        }
        printf("ftok sucessful, and create key = %d\n", key) ;
        msgid = msgget(key, IPC_CREAT|0644) ; //创建一个新的或打开一个已经存在的消息队列
        if(msgid == -1)
        {
            printf("msgget() failed: %s\n", strerror(errno));
            return -2 ;
        }
        printf("msgget() sucessful, msgid = %d\n", msgid);


        memset(&msgbuf,0, sizeof(msgbuf) ) ;
        msgtype = msgid ;
        rv  = msgrcv(msgid, &msgbuf, sizeof(msgbuf.mtext), msgtype, IPC_NOWAIT) ; //接收消息
        if( rv == -1 )
        {                    
            printf("msgrcv() failed: %s\n", strerror(errno));
            goto cleanup ;
        }                    

        printf("msgqueue receive %d byte from message:%s\n", rv, msgbuf.mtext) ;                                                                              

cleanup:        
            msgctl(msgid, IPC_RMID, NULL) ; //关闭消息队列

        return  0 ;
                            

}

六、信息量(semaphore)
信号量是用来解决进程间的同步与互斥问题的一种进程间通信机制。
对于信号量,可以认为是一个仓库,有两个概念,容量和当前的货物个数。
P操作从仓库拿货,如果仓库中没有货,线程一直等待,直到V操作,往仓库里添加了货物,为了避免P操作一直等待下去,会有一个超时时间。
V操作往仓库送货,如果仓库满了,线程等待,直到有P操作,从仓库中拿走货物,有空的位置。
创建信号量,设置容量,先有V操作,才能P操作。
P操作:货物个数减1,减过之后,货物个数大于等于0,说明已经拿到货物,线程继续。否者线程阻塞。
V操作:货物个数加1,加过之后,货物个数小于等于容量,说明添加成功,线程继续。否者线程阻塞。
信号量:0≤ 信号量≤容量 ,取值 表示当前可以使用的货物;

      信号量<0 ,  取值 表示当前等待使用货物的线程;

            信号量>容量 ,  信号量-容量 表示当前等待添加货物的线程。
通常,信号量的容量设置很大,可以一直V操作,不会阻塞,但是P操作的时候,很可能阻塞。

当容量为1,也就是互斥,执行流程必定是V操作,P操作,V操作,P操作...
参考:https://www.cnblogs.com/nzbbody/p/4219957.html

ftok()函数获取IPC关键字
key_t ftok( const char * fname, int id )
fname就是你指定的文件名
id是子序号。虽然是int类型,但是只使用8bits(1-255)
在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。
如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。
查询文件索引节点号的方法是: ls -i

semget(),是获取与某个键关联的信号量集标识。
int semget(key_t key,int nsems,int semflg);
如果成功,则返回信号量集的IPC标识符。
如果失败,则返回-1
第一个参数key是ftok返回的
第二个参数num_sems指定需要的信号量数目,它的值通常为1。
第三个参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。
设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。

semctl()用来直接控制信号量信息
 int semctl(int semid, int semnum, int cmd, ...);
第一个参数semid是semget()返回的标识符
第二个参数semnum是对第几个信号进行操作,第一个信号是0
第三个参数cmd是命令,表示要进行的操作,通常是下面两个:
SETVAL:用来把信号量初始化为一个已知的值。这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。
需要注意的是,对于semun联合体,最好自己定义
This function has three or four arguments, depending on cmd.  When there are four, the fourth has the type union semun.   The
       calling program must define this union as follows:

           union semun {
               int              val;    /* Value for SETVAL */
               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short  *array;  /* Array for GETALL, SETALL */
               struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
           };

联合中的成员信息如下:
       The semid_ds data structure is defined in <sys/sem.h> as follows:

           struct semid_ds {
               struct ipc_perm sem_perm;  /* Ownership and permissions */
               time_t          sem_otime; /* Last semop time */
               time_t          sem_ctime; /* Last change time */
               unsigned long   sem_nsems; /* No. of semaphores in set */
           };

       The ipc_perm structure is defined as follows (the highlighted fields are settable using IPC_SET):

           struct ipc_perm {
               key_t          __key; /* Key supplied to semget(2) */
               uid_t          uid;   /* Effective UID of owner */
               gid_t          gid;   /* Effective GID of owner */
               uid_t          cuid;  /* Effective UID of creator */
               gid_t          cgid;  /* Effective GID of creator */
               unsigned short mode;  /* Permissions */
               unsigned short __seq; /* Sequence number */
           };


semop()完成对信号量的P操作或V操作
int semop(int semid, struct sembuf *sops, size_t nsops);
第一个参数semid是semget返回的标识符
第二个参数sops:指向存储信号操作结构的数组指针,信号操作结构的原型如下
struct sembuf
{
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
};
这三个字段的意义分别为:
sem_num:操作信号在信号集中的编号,第一个信号的编号是0。
sem_op:如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权(V操作);
如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权(P操作);
如果sem_op的值为0,如果没有设置IPC_NOWAIT,则调用该操作的进程或者线程将暂时睡眠,直到信号量的值为0;否则,进程或者线程不会睡眠,函数返回错误EAGAIN。
sem_flg:信号操作标志,可能的选择有两种
IPC_NOWAIT //对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
SEM_UNDO //程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。
第三个参数nsops:进行操作信号量的个数;在semget函数函数int semget(key_t key, int nsems, int semflg);
其中nsems指定创建的信号量的数目。也就是可以一次性创建多个信号量,而且可以一次性对多个信号量进行操作。
对多个信号量的操作的控制在在semop中就由nsops参数控制。
最常见设置此值等于1,只完成对一个信号量的操作

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <stdlib.h>

union semun{    int              val;    /* Value for SETVAL */
                struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
                unsigned short  *array;  /* Array for GETALL, SETALL */
                struct seminfo  *__buf;  /* Buffer for IPC_INFO(Linux-specific) */
};

int main(int argc, char **argv)
{
        key_t           key ;
        int             semid ;
        union semun     sem_union ;
        int             pid ;
        struct sembuf   sem_buf ;

        key = ftok(".",128) ;
        if( key == -1 )
        {
                printf("ftok() failed: %s\n", strerror(errno)) ;
                return -1 ;
        }
        printf("ftok sucessful,and key = %d\n", key) ;

        semid = semget(key, 1, IPC_CREAT|0666) ;
        if(semid < 0)
        {
                printf("semget() failed: %s\n", strerror(errno)) ;
                return -2 ;
        }
        printf("semget sucessful, and semid = %d\n", semid) ;


        sem_union.val = 0 ;
        if( semctl(semid, 0, SETVAL, sem_union) < 0 ) //对第一个信号初始化
        {
                printf("semctl init failed: %s\n", strerror(errno)) ;
                return -3 ;
        }

        pid = fork() ;
        if(pid < 0)
        {
                printf("fork failed: %s\n", strerror(errno)) ;
                return -4 ;
        }

        if(pid == 0) //Child process
        {
                printf("Child process[%d] is running,and can do someting in here...\n", getpid()) ;
                sleep(3) ;
                printf("Child process do something over...\n");
                sem_buf.sem_num = 0 ;                  //V操作
                sem_buf.sem_op = 1 ;
                sem_buf.sem_flg = SEM_UNDO ;
                if( semop(semid, &sem_buf, 1) < 0)
                {
                        printf("semop V operator failed: %s\n", strerror(errno)) ;
                        goto cleanup ;
                }
                printf("Child process will exit!\n") ;
                sleep(1) ;
                exit(0) ;

        }
        if(pid > 0) //Parent process
        {
                printf("Parent process[%d] is running...\n", getpid()) ;
                printf("Parent can't continue until child finish V operator!\n") ;
                sem_buf.sem_num = 0 ;                  //P操作
                sem_buf.sem_op = -1 ;
                sem_buf.sem_flg = SEM_UNDO ;
                if( semop(semid, &sem_buf, 1) < 0)
                {
                        printf("semop P operator failed: %s\n", strerror(errno)) ;
                        goto cleanup ;
                }
                printf("Parent process finish P operator\n") ;
                sleep(1) ;
                printf("Perent process destroy semaphore and exit\n") ;
                semctl(semid, 0, IPC_RMID) ;

        }


cleanup:
        semctl(semid, 0, IPC_RMID) ;


        return 0 ;
}


七、基于套接字(Socket)的进程间通信

int socket(int domain, int type, int protocol);

参数domain指定协议族:对于命名socket/Unix域socket,其值须被置为 AF_UNIX或AF_LOCAL;如果是网络socket则其值应该
为 AF_INET;
参数type指定套接字类型,它可以被设置为 SOCK_STREAM(流式套接字)或 SOCK_DGRAM(数据报式套接字);
参数protocol应被设置为 0;

#include <sys/un.h>

struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* 路径名 */
};
网络通讯的ip地址和端口变成path路径,因为仅在同一主机不同进程间通信

套接字进程间通信的服务器:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <sys/un.h>


#define UNIX_PATH       "socket.domain"
#define BACK_LOG        13
#define BUF_SIZE        125
#define STR_MSG         "Hello, Unix Domain Socket Client!"

int main(int argc, char **argv)
{
        int                     listen_fd, conn_fd;
        struct sockaddr_un      serv_addr ;
        struct sockaddr_un conn_addr ;
        socklen_t               conn_len ;
        char                    buf[BUF_SIZE] ;
        int                     rv = -1 ;


        listen_fd = socket(AF_UNIX, SOCK_STREAM, 0) ;
        if(listen_fd < 0)
        {
                printf("socket() failure: %s\n", strerror(errno)) ;
        }
        printf("Create socket[%d] sucessful!\n", listen_fd) ;

        memset(&serv_addr, 0, sizeof(serv_addr) - 1) ;
        serv_addr.sun_family = AF_UNIX ;
        strncpy(serv_addr.sun_path, UNIX_PATH, sizeof(serv_addr.sun_path)) ;


        if( bind(listen_fd,(struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
        {
                printf("bind() error: %s\n", strerror(errno)) ;
                goto cleanup ;
        }
        printf("Bind Unix domain on \"%s\" sucessful!\n", UNIX_PATH) ;

        listen(listen_fd, BACK_LOG) ;

        while(1)
        {
                printf("Start waiting client connect...\n") ;
                conn_fd = accept(listen_fd, (struct sockaddr *)&conn_addr, &conn_len );
                if(conn_fd < 0)
                {
                        printf("Accept failed: %s\n", strerror(errno)) ;
                        continue ;
                }
                printf("Accept client[%d] sucessful!", conn_fd) ;
                memset(buf, 0, sizeof(buf)) ;

                rv = read(conn_fd, buf, sizeof(buf)) ;
                if(rv <= 0)
                {
                        printf("Read() error or client disconnect, will close client[%d]!\n", conn_fd) ;
                        close(conn_fd) ;
                        continue ;
                }

                printf("Read %d byte from client[%d]: \"%s\"\n", rv, conn_fd, buf) ;

                if( write(conn_fd, STR_MSG, strlen(STR_MSG)) <0  )
                {
                        printf("write to client[%d] failed: %s\n", conn_fd, strerror(errno)) ;
                        close(conn_fd) ;
                        continue ;
                }

                sleep(1) ;
                close(conn_fd) ;


        }








cleanup:
        close(listen_fd) ;

        return 0 ;
}

套接字进程间通信的客户端:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#define UNIX_PATH       "socket.domain"
#define STR_MSG         "Hello, Unix Domain Socket Server!"
#define BUF_SIZE        128

int main(int argc, char **argv)
{
        int                     conn_fd ;
        struct sockaddr_un      serv_addr ;
        char                    buf[BUF_SIZE] ;
        int                     rv = -1 ;


        conn_fd = socket(AF_UNIX, SOCK_STREAM, 0) ;
        if(conn_fd < 0)
        {
                printf("Socket() failure: %s\n", strerror(errno)) ;
        }
        printf("Create socket sucessful, conn_fd[%d]\n", conn_fd) ;

        memset(&serv_addr, 0, sizeof(serv_addr)) ;
        serv_addr.sun_family = AF_UNIX ;
        strncpy(serv_addr.sun_path, UNIX_PATH, sizeof(serv_addr.sun_path) - 1) ;

        if( connect(conn_fd , (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
        {
                printf("Connect() failed: %s\n", strerror(errno)) ;
                goto cleanup ;
        }
        printf("Connect Unix domain '%s' sucessful!\n", UNIX_PATH) ;

        if( write(conn_fd, STR_MSG, strlen(STR_MSG)) < 0)
        {
                printf("write() error: %s\n", strerror(errno)) ;
                goto cleanup ;
        }

        memset(buf, 0, sizeof(buf)) ;
        rv = read(conn_fd, buf, sizeof(buf)) ;
        if(rv <= 0)
        {
                printf("Read() error or disconnect!\n") ;
                goto cleanup ;
        }

        printf("Read %d byte from server: \"%s\"\n",rv,buf) ;





cleanup:
        close(conn_fd) ;

        return 0 ;
}


 

猜你喜欢

转载自blog.csdn.net/caijiwyj/article/details/85255082