当前位置:   article > 正文

【Linux】进程间通讯的五种方式(管道、信号量、共享内存、消息队列、套接字)_linux使用的进程间通信方式有:管道和命名管道、______________、__________

linux使用的进程间通信方式有:管道和命名管道、______________、______________、

信号:通知一个进程发生了什么事件

进程间的通讯方式有:

管道、信号量、共享内存、消息队列、套接字。

一、管道

特点:

     1、它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。

     2、它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。

     3、它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

管道分为有名管道和无名管道。

无论有名管道还是无名管,在同一时刻,只能是一段读一段写

1、有名管道(命名管道):在磁盘上会存在一个管道文件标识,但管道文件并不占用磁盘block空间。

应用:可以应用于同一台的主机上的所有权限访问的任意几个进程间通讯

函数:int mkfifo(const char *pathname,int mode);

特点:

     1、FIFO可以在无关的进程之间交换数据,与无名管道不同。

     2、FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

例:

  1. #include<stdio.h>
  2. #include<stdlib.h> // exit
  3. #include<fcntl.h> // O_WRONLY
  4. #include<sys/stat.h>
  5. #include<time.h> // time
  6. int main()
  7. {
  8. int fd;
  9. int n, i;
  10. char buf[1024];
  11. time_t tp;
  12. printf("I am %d process.\n", getpid()); // 说明进程ID
  13. if((fd = open("fifo1", O_WRONLY)) < 0) // 以写打开一个FIFO
  14. {
  15. perror("Open FIFO Failed");
  16. exit(1);
  17. }
  18. for(i=0; i<10; ++i)
  19. {
  20. time(&tp); // 取系统当前时间
  21. n=sprintf(buf,"Process %d's time is %s",getpid(),ctime(&tp));
  22. printf("Send message: %s", buf); // 打印
  23. if(write(fd, buf, n+1) < 0) // 写入到FIFO中
  24. {
  25. perror("Write FIFO Failed");
  26. close(fd);
  27. exit(1);
  28. }
  29. sleep(1); // 休眠1秒
  30. }
  31. close(fd); // 关闭FIFO文件
  32. return 0;
  33. }

2、无名管道:不会存在管道文件标识

限制:只能用于父子进程之间。

原理:借助父子进程之间共享fork之前打开的文件描述符。

若要数据流从父进程流向子进程,则关闭父进程的读端(fd[0])与子进程的写端(fd[1]);反之,则可以使数据流从子进程流向父进程。

特点:

1、如果所有读端关闭,则写端也退出,反之亦然;

2、读写次数没有直接联系   →    字节流服务;

3、如果写端保持,但是并没有写数据,则读端阻塞;如果读端保持,但是并没有获取数据,写端在将管道内存写满时,阻塞。

例:

  1. #include<stdio.h>
  2. #include<unistd.h>
  3. int main()
  4. {
  5. int fd[2]; // 两个文件描述符
  6. pid_t pid;
  7. char buff[20];
  8. if(pipe(fd) < 0) // 创建管道
  9. printf("Create Pipe Error!\n");
  10. if((pid = fork()) < 0) // 创建子进程
  11. printf("Fork Error!\n");
  12. else if(pid > 0) // 父进程
  13. {
  14. close(fd[0]); // 关闭读端
  15. write(fd[1], "hello world\n", 12);
  16. }
  17. else
  18. {
  19. close(fd[1]); // 关闭写端
  20. read(fd[0], buff, 20);
  21. printf("%s", buff);
  22. }
  23. return 0;
  24. }

二、信号量

1、概念

        信号量的本质是数据操作锁,本身不具有数据交换的功能,而是通过控制其他的通信资源(文件、外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。

        用来同步进程的特殊变量:一个特殊的计数器,大于0时记录资源的数量,小于0时,记录等待资源的进程的数量。当信号量的值大于0时,进程总是可以获取到资源并使用,小于0时,进程必须阻塞等待其他进程释放资源。

2、特点

  1. 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。

  2. 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。

  3. 每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。

  4. 支持信号量组。

3、作用

完成进程同步控制,用于多进程访问同一临界资源。

信号量类似计数器

①> 0  为临界资源的个数;

② == 0 没有临界资源可用,申请资源进行执行P操作,则进程会被阻塞。

进程同步:进程协同工作。

进程异步:进程独立运行,互不干扰,需要内核机制来通知信号。

临界资源:同一时刻只能被一个进程访问使用的资源(临界资源可有多份)。

临界区:访问临界资源的代码区域。

原子操作:不会被线程调度机制打断的操作。不能被终止,不能被暂停的程序段。一旦开始操作,必须等待他结束的操作。

               P操作:(获取资源)-1

                              a、将信号量S的值减1,既S=S-1;

                              b、如果S>=0,则该进程继续执行,否则该进程置为等待状态,排入等待序列。

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

                              a、将信号量S的值加1,既S=S+1;

                              b、如果S > 0,则该进程继续执行,否则释放队列中等一个等待信号量的进程。

1、创建/获取信号量

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

nsems:创建时,指定信号量集的个数。

2、初始化信号量值

int semctl(int semid,int semnum,int cmd,/*union semun arg*/);

union semun

{

    int val;

    //struct semid_ds*buf

};

返回值:semid

3、P操作、V操作

int  semop(int semid,struct sembuf[ ],int size);

struct sembuf

{

    short sem_num;//操作第几个

    short sem_op;// -1 P,  +1 V

    short sem_flg;//控制模式,IPC_NOWAIT     SEM_UNDO

}

其中 sem_op 是一次操作中的信号量的改变量:

  • sem_op > 0,表示进程释放相应的资源数,将 sem_op 的值加到信号量的值上。如果有进程正在休眠等待此信号量,则换行它们。

  • sem_op < 0,请求 sem_op 的绝对值的资源。

    • 如果相应的资源数可以满足请求,则将该信号量的值减去sem_op的绝对值,函数成功返回。
    • 当相应的资源数不能满足请求时,这个操作与sem_flg有关。
      • sem_flg 指定IPC_NOWAIT,则semop函数出错返回EAGAIN
      • sem_flg 没有指定IPC_NOWAIT,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:
        1. 当相应的资源数可以满足请求,此信号量的semncnt值减1,该信号量的值减去sem_op的绝对值。成功返回;
        2. 此信号量被删除,函数smeop出错返回EIDRM;
        3. 进程捕捉到信号,并从信号处理函数返回,此情况下将此信号量的semncnt值减1,函数semop出错返回EINTR
  • sem_op == 0,进程阻塞直到信号量的相应值为0:

    • 当信号量已经为0,函数立即返回。
    • 如果信号量的值不为0,则依据sem_flg决定函数动作:
      • sem_flg指定IPC_NOWAIT,则出错返回EAGAIN
      • sem_flg没有指定IPC_NOWAIT,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:
        1. 信号量值为0,将信号量的semzcnt的值减1,函数semop成功返回;
        2. 此信号量被删除,函数smeop出错返回EIDRM;
        3. 进程捕捉到信号,并从信号处理函数返回,在此情况将此信号量的semncnt值减1,函数semop出错返回EINTR 

例:

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<sys/sem.h>
  4. // 联合体,用于semctl初始化
  5. union semun
  6. {
  7. int val; /*for SETVAL*/
  8. struct semid_ds *buf;
  9. unsigned short *array;
  10. };
  11. // 初始化信号量
  12. int init_sem(int sem_id, int value)
  13. {
  14. union semun tmp;
  15. tmp.val = value;
  16. if(semctl(sem_id, 0, SETVAL, tmp) == -1)
  17. {
  18. perror("Init Semaphore Error");
  19. return -1;
  20. }
  21. return 0;
  22. }
  23. // P操作:
  24. // 若信号量值为1,获取资源并将信号量值-1
  25. // 若信号量值为0,进程挂起等待
  26. int sem_p(int sem_id)
  27. {
  28. struct sembuf sbuf;
  29. sbuf.sem_num = 0; /*序号*/
  30. sbuf.sem_op = -1; /*P操作*/
  31. sbuf.sem_flg = SEM_UNDO;
  32. if(semop(sem_id, &sbuf, 1) == -1)
  33. {
  34. perror("P operation Error");
  35. return -1;
  36. }
  37. return 0;
  38. }
  39. // V操作:
  40. // 释放资源并将信号量值+1
  41. // 如果有进程正在挂起等待,则唤醒它们
  42. int sem_v(int sem_id)
  43. {
  44. struct sembuf sbuf;
  45. sbuf.sem_num = 0; /*序号*/
  46. sbuf.sem_op = 1; /*V操作*/
  47. sbuf.sem_flg = SEM_UNDO;
  48. if(semop(sem_id, &sbuf, 1) == -1)
  49. {
  50. perror("V operation Error");
  51. return -1;
  52. }
  53. return 0;
  54. }
  55. // 删除信号量集
  56. int del_sem(int sem_id)
  57. {
  58. union semun tmp;
  59. if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
  60. {
  61. perror("Delete Semaphore Error");
  62. return -1;
  63. }
  64. return 0;
  65. }
  66. int main()
  67. {
  68. int sem_id; // 信号量集ID
  69. key_t key;
  70. pid_t pid;
  71. // 获取key值
  72. if((key = ftok(".", 'z')) < 0)
  73. {
  74. perror("ftok error");
  75. exit(1);
  76. }
  77. // 创建信号量集,其中只有一个信号量
  78. if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
  79. {
  80. perror("semget error");
  81. exit(1);
  82. }
  83. // 初始化:初值设为0资源被占用
  84. init_sem(sem_id, 0);
  85. if((pid = fork()) == -1)
  86. perror("Fork Error");
  87. else if(pid == 0) /*子进程*/
  88. {
  89. sleep(2);
  90. printf("Process child: pid=%d\n", getpid());
  91. sem_v(sem_id); /*释放资源*/
  92. }
  93. else /*父进程*/
  94. {
  95. sem_p(sem_id); /*等待资源*/
  96. printf("Process father: pid=%d\n", getpid());
  97. sem_v(sem_id); /*释放资源*/
  98. del_sem(sem_id); /*删除信号量集*/
  99. }
  100. return 0;
  101. }

三、共享内存        最快的一种IPC

定义:指两个或者多个进程共享一个给定的区域(物理内存地址)。

特点:

         1、共享内存是最快的一种IPC,因为进程是直接对内存进行读取;

         2、因为多个进程可以同时操作,所以需要进行同步。

         3、信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

共享内存一旦使得进程映射到此共享内存区域,后续操作时,不需要用户态切换内核态。

共享内存相比较于其他通讯方式,会少两次数据的拷贝

共享内存的使用:
1、创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
2、连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
3、断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr);
4、删除内核对象。控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

例:实现服务器进程与客户进程间的通信。

  1. //server.c
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<sys/shm.h> // shared memory
  5. #include<sys/sem.h> // semaphore
  6. #include<sys/msg.h> // message queue
  7. #include<string.h> // memcpy
  8. // 消息队列结构
  9. struct msg_form {
  10. long mtype;
  11. char mtext;
  12. };
  13. // 联合体,用于semctl初始化
  14. union semun
  15. {
  16. int val; /*for SETVAL*/
  17. struct semid_ds *buf;
  18. unsigned short *array;
  19. };
  20. // 初始化信号量
  21. int init_sem(int sem_id, int value)
  22. {
  23. union semun tmp;
  24. tmp.val = value;
  25. if(semctl(sem_id, 0, SETVAL, tmp) == -1)
  26. {
  27. perror("Init Semaphore Error");
  28. return -1;
  29. }
  30. return 0;
  31. }
  32. // P操作:
  33. // 若信号量值为1,获取资源并将信号量值-1
  34. // 若信号量值为0,进程挂起等待
  35. int sem_p(int sem_id)
  36. {
  37. struct sembuf sbuf;
  38. sbuf.sem_num = 0; /*序号*/
  39. sbuf.sem_op = -1; /*P操作*/
  40. sbuf.sem_flg = SEM_UNDO;
  41. if(semop(sem_id, &sbuf, 1) == -1)
  42. {
  43. perror("P operation Error");
  44. return -1;
  45. }
  46. return 0;
  47. }
  48. // V操作:
  49. // 释放资源并将信号量值+1
  50. // 如果有进程正在挂起等待,则唤醒它们
  51. int sem_v(int sem_id)
  52. {
  53. struct sembuf sbuf;
  54. sbuf.sem_num = 0; /*序号*/
  55. sbuf.sem_op = 1; /*V操作*/
  56. sbuf.sem_flg = SEM_UNDO;
  57. if(semop(sem_id, &sbuf, 1) == -1)
  58. {
  59. perror("V operation Error");
  60. return -1;
  61. }
  62. return 0;
  63. }
  64. // 删除信号量集
  65. int del_sem(int sem_id)
  66. {
  67. union semun tmp;
  68. if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
  69. {
  70. perror("Delete Semaphore Error");
  71. return -1;
  72. }
  73. return 0;
  74. }
  75. // 创建一个信号量集
  76. int creat_sem(key_t key)
  77. {
  78. int sem_id;
  79. if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
  80. {
  81. perror("semget error");
  82. exit(-1);
  83. }
  84. init_sem(sem_id, 1); /*初值设为1资源未占用*/
  85. return sem_id;
  86. }
  87. int main()
  88. {
  89. key_t key;
  90. int shmid, semid, msqid;
  91. char *shm;
  92. char data[] = "this is server";
  93. struct shmid_ds buf1; /*用于删除共享内存*/
  94. struct msqid_ds buf2; /*用于删除消息队列*/
  95. struct msg_form msg; /*消息队列用于通知对方更新了共享内存*/
  96. // 获取key值
  97. if((key = ftok(".", 'z')) < 0)
  98. {
  99. perror("ftok error");
  100. exit(1);
  101. }
  102. // 创建共享内存
  103. if((shmid = shmget(key, 1024, IPC_CREAT|0666)) == -1)
  104. {
  105. perror("Create Shared Memory Error");
  106. exit(1);
  107. }
  108. // 连接共享内存
  109. shm = (char*)shmat(shmid, 0, 0);
  110. if((int)shm == -1)
  111. {
  112. perror("Attach Shared Memory Error");
  113. exit(1);
  114. }
  115. // 创建消息队列
  116. if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
  117. {
  118. perror("msgget error");
  119. exit(1);
  120. }
  121. // 创建信号量
  122. semid = creat_sem(key);
  123. // 读数据
  124. while(1)
  125. {
  126. msgrcv(msqid, &msg, 1, 888, 0); /*读取类型为888的消息*/
  127. if(msg.mtext == 'q') /*quit - 跳出循环*/
  128. break;
  129. if(msg.mtext == 'r') /*read - 读共享内存*/
  130. {
  131. sem_p(semid);
  132. printf("%s\n",shm);
  133. sem_v(semid);
  134. }
  135. }
  136. // 断开连接
  137. shmdt(shm);
  138. /*删除共享内存、消息队列、信号量*/
  139. shmctl(shmid, IPC_RMID, &buf1);
  140. msgctl(msqid, IPC_RMID, &buf2);
  141. del_sem(semid);
  142. return 0;
  143. }

client.c

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<sys/shm.h> // shared memory
  4. #include<sys/sem.h> // semaphore
  5. #include<sys/msg.h> // message queue
  6. #include<string.h> // memcpy
  7. // 消息队列结构
  8. struct msg_form {
  9. long mtype;
  10. char mtext;
  11. };
  12. // 联合体,用于semctl初始化
  13. union semun
  14. {
  15. int val; /*for SETVAL*/
  16. struct semid_ds *buf;
  17. unsigned short *array;
  18. };
  19. // P操作:
  20. // 若信号量值为1,获取资源并将信号量值-1
  21. // 若信号量值为0,进程挂起等待
  22. int sem_p(int sem_id)
  23. {
  24. struct sembuf sbuf;
  25. sbuf.sem_num = 0; /*序号*/
  26. sbuf.sem_op = -1; /*P操作*/
  27. sbuf.sem_flg = SEM_UNDO;
  28. if(semop(sem_id, &sbuf, 1) == -1)
  29. {
  30. perror("P operation Error");
  31. return -1;
  32. }
  33. return 0;
  34. }
  35. // V操作:
  36. // 释放资源并将信号量值+1
  37. // 如果有进程正在挂起等待,则唤醒它们
  38. int sem_v(int sem_id)
  39. {
  40. struct sembuf sbuf;
  41. sbuf.sem_num = 0; /*序号*/
  42. sbuf.sem_op = 1; /*V操作*/
  43. sbuf.sem_flg = SEM_UNDO;
  44. if(semop(sem_id, &sbuf, 1) == -1)
  45. {
  46. perror("V operation Error");
  47. return -1;
  48. }
  49. return 0;
  50. }
  51. int main()
  52. {
  53. key_t key;
  54. int shmid, semid, msqid;
  55. char *shm;
  56. struct msg_form msg;
  57. int flag = 1; /*while循环条件*/
  58. // 获取key值
  59. if((key = ftok(".", 'z')) < 0)
  60. {
  61. perror("ftok error");
  62. exit(1);
  63. }
  64. // 获取共享内存
  65. if((shmid = shmget(key, 1024, 0)) == -1)
  66. {
  67. perror("shmget error");
  68. exit(1);
  69. }
  70. // 连接共享内存
  71. shm = (char*)shmat(shmid, 0, 0);
  72. if((int)shm == -1)
  73. {
  74. perror("Attach Shared Memory Error");
  75. exit(1);
  76. }
  77. // 创建消息队列
  78. if ((msqid = msgget(key, 0)) == -1)
  79. {
  80. perror("msgget error");
  81. exit(1);
  82. }
  83. // 获取信号量
  84. if((semid = semget(key, 0, 0)) == -1)
  85. {
  86. perror("semget error");
  87. exit(1);
  88. }
  89. // 写数据
  90. printf("***************************************\n");
  91. printf("* IPC *\n");
  92. printf("* Input r to send data to server. *\n");
  93. printf("* Input q to quit. *\n");
  94. printf("***************************************\n");
  95. while(flag)
  96. {
  97. char c;
  98. printf("Please input command: ");
  99. scanf("%c", &c);
  100. switch(c)
  101. {
  102. case 'r':
  103. printf("Data to send: ");
  104. sem_p(semid); /*访问资源*/
  105. scanf("%s", shm);
  106. sem_v(semid); /*释放资源*/
  107. /*清空标准输入缓冲区*/
  108. while((c=getchar())!='\n' && c!=EOF);
  109. msg.mtype = 888;
  110. msg.mtext = 'r'; /*发送消息通知服务器读数据*/
  111. msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
  112. break;
  113. case 'q':
  114. msg.mtype = 888;
  115. msg.mtext = 'q';
  116. msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
  117. flag = 0;
  118. break;
  119. default:
  120. printf("Wrong input!\n");
  121. /*清空标准输入缓冲区*/
  122. while((c=getchar())!='\n' && c!=EOF);
  123. }
  124. }
  125. // 断开连接
  126. shmdt(shm);
  127. return 0;
  128. }

四、消息队列

发送带有类型的数据,可以真正实现多进程间通讯。

消息:类型+数据

消息队列:先进先出(队列)

特点:

  1. 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。

  2. 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。

  3. 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

消息队列的使用:

1、创建/获取消息队列

int msgget((key_t)key,int mode/flag);

mode/flag:权限//IPC_CREAT|0664(所有者可读可写)

返回内核对象的标识符

2、发送消息(类型+数据)

int msgsnd(int msgid,void *ptr,int datalen,int flg);

ptr:指向的数据类型包含一个long的类型字段,一个数据字段;

datalen:数据长度(数据部分的有效数据长度);

flag:标记

struct data

{

    long type;

    char text[128];

};

3、接收消息

int msgrcv(int msgid,void *ptr,int size,long type,int flag);

ptr:类型、数据

4、删除内核对象 IPC结构

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

ctl:设置函数;

and:IPC_RMID删除

函数msgrcv在读取消息队列时,type参数有下面几种情况:

  • type == 0,返回队列中的第一个消息;
  • type > 0,返回队列中消息类型为 type 的第一个消息;
  • type < 0,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。

五、套接字  socket

概念:

      是一种通讯机制,凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以跨网络进行。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/256305
推荐阅读
相关标签
  

闽ICP备14008679号