基于共享内存、信号、命名管道和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环境高级编程> ...
随机推荐
- SRS源码—— Thread笔记
SRS源码中的Thread是一层套一层,最终的Thread类是在 srs_app_thread.cpp 的 SrsThread 类 这里我们暂且先放下协程的概念,把它当线程来看,其逻辑如下: 1. 在 ...
- h5 穿透滚动
引子 h5 页面有弹窗浮层时,浮层之下若产生了滚动,滑动浮层时会让其产生滚动.这是示例页面,移动端访问如下: Origin My GitHub 原因 找到的信息里面有两种说法: 使用了 -webkit ...
- 五、centos7安装mysql:安装mysqlser
一.下载通用安装二进制包 先下载mysql安装包:打开 http://dev.mysql.com/downloads/mysql/ 选择 linux - Generic并在其下选择 Linux - G ...
- 字符串NSString与NSMutableString常用方法
NSString 1.初始化 NSString *str1 = @"a OC Program"; 2.初始化 NSString *str2 = [[NSString alloc] ...
- 题解 P3950 【部落冲突】
树链剖分吼啊 一看就看出是LCT模板题啦 前记 见这么多人写LCT,却很少人写树链剖分,于是我就来一发树链剖分(其实是因为自己不会LCT) 本蒟蒻的写法和诸位写树链剖分的大神有点不同 思路 树链剖分, ...
- KMP(模板)
kmp算法是解决单模匹配问题的算法,难点在于求next[]数组 求next[]数组:对于模板串的所有前缀子串的最长公共前后缀的长度,就是next[]数组的值 eg:主串为cbbbaababac 子串 ...
- JDBC--DAO设计模式
1.DAO(Data Access Object):访问数据信息的类,包含对数据的CRUD(Create.Read.Update.Delete),而不包含任何业务相关的信息. --DAO能够实现功能的 ...
- BOM--window对象
BOM 的核心对象是window,它表示浏览器的一个实例.在浏览器中,window 对象有双重角色,它既是通过JavaScript 访问浏览器窗口的一个接口,又是ECMAScript 规定的Globa ...
- IOS UIKIT_EXTERN, __attribute__((visibility ("default"))) 是啥玩意?
问题提出 在学习IOS时候,碰到一个函数NSStringFromCGPoint (UIGeometry.h) 其原型是 UIKIT_EXTERN NSString *NSStringFromCGPoi ...
- 软件环境常识 --dev sit uat
DEV环境:DEV顾名思义就是develop,即代码开发的环境. SIT环境:System Integration Test系统集成测试,开发人员自己测试流程是否走通. UAT环境:User Acce ...