基于共享内存、信号、命名管道和Select模型实现聊天窗口
问题模型
- A、B两个进程通过管道通信,A 进程每次接收到的数据通过共享内存传递给A1进程显示,同理,B进程每次接收到的数据通过共享内存传递给B1进程显示;
- 对于A、B 进程,采用ctrl+c(实际为SIGINT信号)方式退出,A、B进程通过捕捉SIGINT信号注册信号处理函数进行资源清理,A1、B1进程手动关闭即可。

特别注意
- A、B通过管道通信,如果首先通过ctrl+c退出A进程,那么B进程的fifo1管道的写端会收到SIGPIPE信号而终止B进程,因此必须在B进程终止前清理掉被B占用的共享内存2,将共享内存2的引用计数减一,否则,当B1进程退出并清理共享内存2后,共享内存2的引用计数不为0,会导致共享内存2得不到释放;
- 为了解决前一个问题,A、B进程在启动后立即将各自的进程id通过管道发送给对方,并在各自的进程退出时向对方进程id发送SIGINT信号,触发对方进程进入信号处理接口执行资源回收工作;
- A和A1通过共享内存1通信,会从A进程和A1进程的虚拟地址空间分配一段连续的页映射到同一块连续的物理内存页上,这样A、A1两个进程都可以间接访问物理内存页,从而达到通信的目的,一般共享内存需要进行保护,读写不能同时进行,也不能同时进行写操作,共享内存省去了从内核缓冲区到用户缓冲区的拷贝,因此效率高。
编码与效果图
func.h:
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <strings.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <netinet/in.h>
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
processA.cpp:
#include "func.h"
int shmid;
int pidB; // 存放对端进程B的进程id号
char *p; // 共享内存指针
// 回收共享内存资源前先杀死对端进程,否则回收失败
void handle(int num)
{
kill(pidB, SIGINT);
shmdt(p);
int ret;
if(-1 == (ret=shmctl(shmid, IPC_RMID, NULL)))
{
perror("shmctl");
return (void)-1;
}
exit(0);
}
int main(int argc, char **argv)
{
signal(SIGINT, handle);
if(-1 == (shmid=shmget(1234, 4096, IPC_CREAT|0666)))
{
perror("shmget");
return -1;
}
if((char*)-1 == (p=(char*)shmat(shmid, NULL, 0)))
{
perror("shmat");
return -1;
}
// 管道文件为单工通信方式,因此需要建立两条管道
// A进程通过管道文件fifo1的读端fdr读取B进程发送的数据
// A进程通过管道文件fifo2的写端fdw向B进程发送数据
int fdr, fdw;
if(-1 == (fdr=open("fifo1", O_RDONLY)) || -1 == (fdw=open("fifo2", O_WRONLY)))
{
perror("open fifo1 or open fifo2");
return -1;
}
// 通信之前先通过管道互相告知对方自己的进程id
char s1[10] = {0};
char s2[10] = {0};
sprintf(s1, "%d\n", getpid());
write(fdw, s1, strlen(s1) - 1);
read(fdr, s2, strlen(s1) - 1);
pidB = atoi(s2);
printf("pipe connect success, A to A1 shmid:[%d], pidA:[%d], pidB:[%d]\n", shmid, getpid(), pidB);
char buf[1024] = {0};
int ret;
fd_set rdset;
while(true)
{
FD_ZERO(&rdset);
FD_SET(0, &rdset);
FD_SET(fdr, &rdset);
if((ret=select(fdr+1, &rdset, NULL, NULL, NULL) > 0))
{
// fdr可读,则接收数据之后通过共享内存传给A1
if(FD_ISSET(fdr, &rdset))
{
bzero(buf, sizeof(buf));
if(read(fdr, buf, sizeof(buf)) > 0)
{
strncpy(p, buf, sizeof(buf));
}
else
{
break;
}
}
// 标准输入可读,读出来传递给B进程
if(FD_ISSET(0, &rdset))
{
bzero(buf, sizeof(buf));
if(read(STDIN_FILENO, buf, sizeof(buf)) > 0)
{
write(fdw, buf, strlen(buf) - 1);
}
else
{
break;
}
}
}
}
close(fdr);
close(fdw);
return 0;
}
processB.cpp:
#include "func.h"
int shmid;
int pidA; // 存放对端进程id
char *p; // 共享内存指针
// 回收共享内存资源前先杀死对端进程,否则回收失败
void handle(int num)
{
kill(pidA, SIGINT);
shmdt(p);
int ret;
if(-1 == (ret=shmctl(shmid, IPC_RMID, NULL)))
{
perror("shmctl");
return (void)-1;
}
exit(0);
}
int main(int argc, char **argv)
{
signal(SIGINT, handle);
if(-1 == (shmid=shmget(1235, 4096, IPC_CREAT|0666)))
{
perror("shmget");
return -1;
}
if((char*)-1 == (p=(char*)shmat(shmid, NULL, 0)))
{
perror("shmat");
return -1;
}
// 管道文件为单工通信方式
// B进程通过管道文件fifo1的写端fdw向A进程发送数据
// B进程通过管道文件fifo2的读端fdr接收A进程的数据
int fdr, fdw;
if(-1 == (fdw=open("fifo1", O_WRONLY)) || -1 == (fdr=open("fifo2", O_RDONLY)))
{
perror("open fifo1 or open fifo2");
return -1;
}
// 通信之前先通过管道互相告知对方自己的进程id
char s1[10] = {0};
char s2[10] = {0};
sprintf(s1, "%d\n", getpid());
write(fdw, s1, strlen(s1) - 1);
read(fdr, s2, strlen(s1) - 1);
pidA = atoi(s2);
printf("pipe connect success, B to B1 shmid:[%d], pidA:[%d], pidB:[%d]\n", shmid, pidA, getpid());
char buf[1024] = {0};
int ret;
fd_set rdset;
while(true)
{
FD_ZERO(&rdset);
FD_SET(0, &rdset);
FD_SET(fdr, &rdset);
if((ret=select(fdr+1, &rdset, NULL, NULL, NULL) > 0))
{
// fdr可读,则接收数据之后通过共享内存传给B1
if(FD_ISSET(fdr, &rdset))
{
bzero(buf, sizeof(buf));
if(read(fdr, buf, sizeof(buf)) > 0)
{
strncpy(p, buf, sizeof(buf));
}
else
{
break;
}
}
// 标注输入可读,读出来传递给A进程
if(FD_ISSET(0, &rdset))
{
bzero(buf, sizeof(buf));
if(read(STDIN_FILENO, buf, sizeof(buf)) > 0)
{
write(fdw, buf, strlen(buf) - 1);
}
else
{
break;
}
}
}
}
close(fdr);
close(fdw);
return 0;
}
processA1.cpp:
#include "func.h"
int main(void)
{
char buf[1024] = {0};
int shmid;
if(-1 == (shmid=shmget(1234, 4096, IPC_CREAT|0666)))
{
perror("shmget");
return -1;
}
char *p;
if((char*)-1 == (p=(char*)shmat(shmid, NULL, 0)))
{
perror("shmat");
return -1;
}
while(true)
{
if(!(strcmp(buf, p)))
{
continue;
}
else
{
// 共享内存有数据可读
bzero(buf, sizeof(buf));
strcpy(buf, p);
printf("I am A1, recv from A:[%s]\n", buf);
}
}
if(-1 ==(shmctl(shmid, IPC_RMID, 0)))
{
perror("shmctl");
return -1;
}
return 0;
}
processB1.cpp:
#include "func.h"
int main(void)
{
char buf[1024] = {0};
int shmid;
if(-1 == (shmid=shmget(1235, 4096, IPC_CREAT|0666)))
{
perror("shmget");
return -1;
}
char *p;
if((char*)-1 == (p=(char*)shmat(shmid, NULL, 0)))
{
perror("shmat");
return -1;
}
while(true)
{
if(!(strcmp(buf, p)))
{
continue;
}
else
{
// 共享内存有数据可读
bzero(buf, sizeof(buf));
strcpy(buf, p);
printf("I am B1, recv from B:[%s]\n", buf);
}
}
if(-1 ==(shmctl(shmid, IPC_RMID, 0)))
{
perror("shmctl");
return -1;
}
return 0;
}
回收资源
- 这里首先通过ctrl+c退出A进程,然后B进程收到SIGPIPE信号退出,A、B进程同时调用各自的信号处理函数回收资源,通过ipcs命令发现拥有者为root的共享内存资源的nattch都为1,分别被A1和B1占有。
- 然后手动关闭A1、B1进程,再次执行ipcs命令,发现拥有者为root的共享内存资源不存在,已经释放成功。
$ ipcs # 查看共性内存资源数量

源码获取
本文所有源码链接
基于共享内存、信号、命名管道和Select模型实现聊天窗口的更多相关文章
- Mysql服务器相互作用的通讯协议包括TCP/IP,Socket,共享内存,命名管道
MySQL实现了四种通信协议 TCP/IP协议,通常我们通过来连接MySQL,各种主要编程语言都是根据这个协议实现了连接模块 Unix Socket协议,这个通常我们登入MySQL服务器中使用这个协议 ...
- ACE框架 基于共享内存的分配器 (算法设计)
继承上一篇<ACE框架 基于共享内存的分配器设计>,本篇分析算法部分的设计. ACE_Malloc_T模板定义了这样一个分配器组件 分配器组件聚合了三个功能组件:同步组件ACE_LOCK, ...
- (原创)[.Net] 进程间通信框架(基于共享内存)——SimpleMMF
一.前言 进程间通信技术的应用非常广泛,在Windows下常用的实现方式有:管道.Socket.消息.本地文件.共享内存等,每种方式都有各自适应的场景. 在进行大数据交换时,最优的方式便是共享内存. ...
- Unix IPC之基于共享内存的计数器
目的 本文主要实现一个基于共享内存的计数器,通过父子进程对其访问. 本文程序需基于<<Unix网络编程-卷2>>的环境才能运行.程序中大写开头的函数为其小写同名函数的包裹函数, ...
- ACE框架 基于共享内存的进程间通讯
ACE框架将基于共享内存的进程间通讯功能,如其它IO组件或IPC组件一样,设计成三个组件.流操作组件ACE_MEM_Stream,连接器组件ACE_MEM_Connector,以及接收连接组件ACE_ ...
- ACE框架 基于共享内存的分配器
ACE框架提供了一个内存分配器模板,并且提供了(仅且)一个模板实例,基于共存内存的内存分配器.这个共存内存分配器模板实例在ACE框架应用于,基于内存映射的进程通讯,以及进程间同步等. ACE内存分配器 ...
- 撸代码--linux进程通信(基于共享内存)
1.实现亲缘关系进程的通信,父写子读 思路分析:1)首先我们须要创建一个共享内存. 2)父子进程的创建要用到fork函数.fork函数创建后,两个进程分别独立的执行. 3)父进程完毕写的内容.同一时候 ...
- Linux进程间通信方式--信号,管道,消息队列,信号量,共享内存
1.概述 通信方法 无法介于内核态与用户态的原因 管道(不包括命名管道) 局限于父子进程间的通信. 消息队列 在硬.软中断中无法无阻塞地接收数据. 信号量 无法介于内核态和用户态使用. 内存共享 需要 ...
- Linux进程间通信--进程,信号,管道,消息队列,信号量,共享内存
Linux进程间通信--进程,信号,管道,消息队列,信号量,共享内存 参考:<linux编程从入门到精通>,<Linux C程序设计大全>,<unix环境高级编程> ...
随机推荐
- 事件类型-UI事件、焦点事件
DOM3级事件包括以下几类事件: UI事件:当用户与页面上的元素交互时触发 焦点事件:当元素获得或失去焦点时触发 鼠标事件:当用户通过鼠标在页面上执行操作时触发 滚轮事件:当使用鼠标滚轮时触发 文本事 ...
- RadioButton 用法
@Html.RadioButton("rdoNotice", "1ST", true, new { id = "rdoFirstNotice" ...
- Numpy中 arange() 的用法
1. 概述Numpy 中 arange() 主要是用于生成数组,具体用法如下: 2. arange()2.1 语法numpy.arange(start, stop, step, dtype = Non ...
- pytorc人工神经网络Logistic regression与全连接层
//2019.10.08神经网络与全连接层1.logistics regression逻辑回归的思想是将数据利用激活函数sigmoid函数转换为0-1的概率,然后定义一定的阈值0.5,大于阈值则为一类 ...
- Linux设备驱动的软件架构思想
驱动相关:硬件之上的软件层,负责底层硬件与用户程序的交互 设备相关:底层设备的硬件操作 总线:匹配设备和驱动 设备驱动分层的思想:为同一类设备设计一个框架,而框架中的核心层则实现了该设备的一些 ...
- Python学习第九课——匿名函数
匿名函数 # 匿名函数 func = lambda x: x + 1 # x表示参数 x+1表示处理逻辑 print(func(10)) # 输出结果为11 # 例:如何将name="han ...
- 二、js中基础知识
该篇文章主要是强化一下自己javaScript的基础,让写代码变得更轻松些.基础好的请忽略. JavaScript一种直译式脚本语言,是一种动态类型.弱类型.基于原型的语言,内置支持类型.它的解 ...
- 富文本编辑器summernote的基本使用
summernote比较突出的优点就是能保持复制过来的东西的原有样式,并且比较流畅. 官方文档地址:https://summernote.org/getting-started 我是用到cdn引入 & ...
- 「POI2015」KIN
传送门 Luogu 解题思路 想要做这道题,只要会维护区间最大子段和就好了. 而这个可以用线段树维护模板点这里 对于重复的情况,我们可以对每一个位置记一个前驱表示和当前位置种类相同的前一个位置. 然后 ...
- Github版本控制系统
Git是什么? Git是目前世界上最先进的分布式版本控制系统(没有之一). 特别推荐简单易懂的廖雪锋大神制作的学习教程: https://www.liaoxuefeng.com/wiki/896043 ...