1.命名管道实现
comm.hpp文件
1.定义宏
通过宏来简便代码中,判断错误用宏就可以少写代码。
#define ERR_EXIT(m) \do \{ \perror(m); \exit(EXIT_FAILURE); \} while (0)
在宏定义中使用 `do { ... } while (0)` 和不使用它的区别主要体现在**语句块的组合**和**控制流的安全性**上。以下是两者的详细对比,通过具体的例子来说明它们的区别。
---
### **1. 不使用 `do { ... } while (0)`**
假设我们定义一个宏,用于打印一条消息并退出程序:
```c
#define ERR_EXIT(msg) perror(msg); exit(EXIT_FAILURE);
```#### **问题 1:控制流问题**
如果在 `if` 语句中使用这个宏:
```c
if (condition)
ERR_EXIT("Error occurred");
else
printf("Everything is fine.\n");
```**展开后:**
```c
if (condition)
perror("Error occurred"); exit(EXIT_FAILURE);
else
printf("Everything is fine.\n");
```**问题:**
- `else` 无法与 `if` 匹配,因为 `perror("Error occurred");` 和 `exit(EXIT_FAILURE);` 是两条独立的语句。
- 编译器会报错,因为 `else` 没有对应的 `if`。#### **问题 2:链式调用问题**
如果尝试链式调用:
```c
ERR_EXIT("Error 1"); ERR_EXIT("Error 2");
```**展开后:**
```c
perror("Error 1"); exit(EXIT_FAILURE); perror("Error 2"); exit(EXIT_FAILURE);
```**问题:**
- `exit(EXIT_FAILURE);` 会导致程序立即退出,因此 `perror("Error 2");` 和第二个 `exit` 永远不会执行。
- 这种链式调用的意图无法实现。---
### **2. 使用 `do { ... } while (0)`**
如果使用 `do { ... } while (0)` 重新定义宏:
```c
#define ERR_EXIT(msg) \
do { \
perror(msg); \
exit(EXIT_FAILURE);\
} while (0)
```#### **优点 1:控制流安全**
在 `if` 语句中使用:
```c
if (condition)
ERR_EXIT("Error occurred");
else
printf("Everything is fine.\n");
```**展开后:**
```c
if (condition)
do { perror("Error occurred"); exit(EXIT_FAILURE); } while (0);
else
printf("Everything is fine.\n");
```**优点:**
- `do { ... } while (0)` 将多条语句组合成一个单一的语句块。
- `else` 能正确匹配 `if`,不会报错。#### **优点 2:支持链式调用**
尝试链式调用:
```c
ERR_EXIT("Error 1"); ERR_EXIT("Error 2");
```**展开后:**
```c
do { perror("Error 1"); exit(EXIT_FAILURE); } while (0);
do { perror("Error 2"); exit(EXIT_FAILURE); } while (0);
```**优点:**
- 每个 `do { ... } while (0)` 是独立的语句块,不会相互干扰。
- 虽然程序会在第一个 `exit` 时退出,但这种结构不会导致语法错误。---
### **3. 总结:有和没有的区别**
| 特性 | 不使用 `do { ... } while (0)` | 使用 `do { ... } while (0)` |
|---------------------|-------------------------------|----------------------------|
| **控制流安全性** | 容易出错(`if-else` 问题) | 安全(`if-else` 正常工作) |
| **链式调用支持** | 不支持(语句中断) | 支持(语句块独立) |
| **语句块完整性** | 多条语句独立,易出错 | 多条语句组合成一个块,安全 |
| **调试难度** | 调试时难以定位问题 | 调试时结构清晰,易于定位 |---
### **4. 使用场景总结**
- **不使用 `do { ... } while (0)`**:适用于非常简单的宏,只包含一条语句(如 `#define MAX(a, b) ((a) > (b) ? (a) : (b))`)。
- **使用 `do { ... } while (0)`**:适用于复杂的宏,包含多条语句,需要确保宏像函数一样安全使用。总之,`do { ... } while (0)` 是一种非常有用的技巧,可以显著提高宏的安全性和可维护性。
2.管道文件类
定义一个类来管理文件,创建管道文件和初始化管道文件名,还有删除管道文件。
class NamedFifo
{
public:NamedFifo(const std::string& path,const std::string& name):_path(path),_name(name){_fifoname=_path+"/"+_name;umask(0);//新建管道int n =mkfifo(_fifoname.c_str(),0666);if(n<0){ERR_EXIT("mkfifo");}else{std::cout<<"mkfifo sucess"<<std::endl;}}~NamedFifo(){//删除管道文件int n=unlink(_fifoname.c_str());if(n==0){ERR_EXIT("unlink");}else{std::cout<<"remove fifo failed"<<std::endl;}}private:std::string _path;std::string _name;std::string _fifoname;
};
3.管道文件操作类
这个类虽然与前一个类大体相似,但主要是为client中看不到管道文件的路径,只要server知道,且加入了以读形式打开文件方法,写形式打开文件方法,写和读操作的方法。
class FileOper
{public:FileOper(const std::string& path,const std::string& name):_path(path),_name(name),_fd(-1){_fifoname=_path+"/"+_name;}void OpenForRead(){_fd=open(_fifoname.c_str(),O_RDONLY);if(_fd<0)ERR_EXIT("open");std::cout<<"open fifo sucess"<<std::endl;}void OpenForWrite(){_fd=open(_fifoname.c_str(),O_WRONLY);if(_fd<0)ERR_EXIT("open");std::cout<<"open fifo sucess"<<std::endl;}void Write(){std::string message;int cnt=1;pid_t id=getpid();while(true){std::cout<<"Please Enter#";std::getline(std::cin,message);message+=(",message number: "+std::to_string(cnt++)+", ["+std::to_string(id)+"]");write(_fd,message.c_str(),message.size());}}void Read(){while(true){char buffer[1024];int number=read(_fd,buffer,sizeof(buffer)-1);if(number>0){buffer[number]=0;std::cout<<"Client say#"<<buffer<<std::endl;}else if(number==0){std::cout<<"Client quit! me too!"<<std::endl;break;}else{std::cerr<<"read error"<<std::endl;break;}}}void Close(){if(_fd>0)close(_fd);}~FileOper() {}private:std::string _path;std::string _name;std::string _fifoname;int _fd;
};
#include "comm.hpp"int main()
{//创建管道文件NamedFifo fifo(PATH,FILENAME);FileOper readerfile(PATH,FILENAME);readerfile.OpenForRead();readerfile.Read();readerfile.Close();return 0;
}
server.cc文件
#include "comm.hpp"int main()
{//创建管道文件NamedFifo fifo(PATH,FILENAME);FileOper readerfile(PATH,FILENAME);readerfile.OpenForRead();readerfile.Read();readerfile.Close();return 0;
}
client.cc文件
#include "comm.hpp"int main()
{//创建管道文件NamedFifo fifo(PATH,FILENAME);FileOper readerfile(PATH,FILENAME);readerfile.OpenForRead();readerfile.Read();readerfile.Close();return 0;
}
Makefile文件
.PHONY:all
all:client server
client:client.ccg++ -o $@ $^ -std=c++11
server:server.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f client server
2.system V共享内存
共享内存是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程数据传输不再涉及到内核,意思是进程不再通过执行进入内核的系统调用来传递彼此的数据。
共享内存示意图
俩个进程的进程地址空间都开辟一块共享区域,与物理内存上的step1形成映射关系通过页表,这样进程A在自己的进程地址空间读写操作,进程B的共享区也会发生改变。
共享内存数据结构
共享内存函数
shmget函数
`shmget` 是 System V IPC(进程间通信)机制中的一个函数,用于创建新的共享内存段或获取已存在的共享内存段的标识符。以下是 `shmget` 的详细说明和用法:
### 函数原型
```c
int shmget(key_t key, size_t size, int shmflg);
```### 参数说明
1. **`key_t key`**
- 用于标识共享内存的键值,可以是以下几种情况:
- **`IPC_PRIVATE`**:表示创建一个私有的共享内存段,仅对当前进程可见。
- **大于 0 的整数**:通常由 `ftok` 函数生成的唯一键。
- **`0`**:若与 `IPC_CREAT` 标志结合,同样会创建新的共享内存。2. **`size_t size`**
- 指定共享内存段的大小(以字节为单位)。实际分配时,系统会以页面大小为单位进行分配。3. **`int shmflg`**
- 控制共享内存的权限和行为,通常是一个权限标志(如 `0666`)与其他标志的组合:
- **`IPC_CREAT`**:如果键值对应的共享内存不存在,则创建一个新的共享内存。
- **`IPC_EXCL`**:与 `IPC_CREAT` 结合使用,如果共享内存已存在,则返回错误。### 返回值
- 成功时返回共享内存的标识符(`shm_id`)。
- 失败时返回 `-1`,并设置 `errno` 以指示错误。
这里是key值来保证共享内存存在和俩个进程是对应同一个共享区,key值的唯一性来找到唯一的哪一个,ftok函数可以返回一个唯一的key值使用。
代码示例
comm.hpp文件
#pragma once#include <iostream>
#include <cstdio>
#include <string>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>const int gdefaultid=-1;
const int gsize=4096;
const std::string pathname=".";
const int projid=0x66;#define ERR_EXIT(m) \do \{ \perror(m); \exit(EXIT_FAILURE); \} while (0)class Shm
{public:Shm():_shmid(gdefaultid),_size(gsize){}void Create(){key_t k=ftok(pathname.c_str(),projid);if(k<0){ERR_EXIT("ftok");}printf("key: 0x%X",k);_shmid=shmget(k,_size,IPC_CREAT|IPC_EXCL);if(_shmid<0)ERR_EXIT("shmget");printf("shmid: %d",_shmid);}~Shm(){}private:int _shmid;int _size;
};
server.cc文件
#include "comm.hpp"int main()
{Shm shm;shm.Create();return 0;
}
client.cc文件
#include "comm.hpp"int main()
{return 0;
}
Makefile文件
.PHONY:all
all:client server
client:client.ccg++ -o $@ $^ -std=c++11
server:server.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f client server