一: 简介
在类linux系统中管道分为有名管道和匿名管道。两者都能单方向的跨进程通信。
- 匿名管道(
pipe
): 必须是父子进程之间,而且子进程只能由父进程fork() 出来的,才能继承父进程的管道句柄,一般mac 开发用的很少。 - 有名管道(
fifo
)又叫命名管道: 可以在同一台机器,没有关系的进程间通信。 其本质是本地创建一个文件,然后使用其路径作为纽带。open
后再内核空间产生管道,不同进程之间分别连接管道的读和写的端口进行通信。
这里主要针对有名管道
进行研究。
二:主要函数
以下函数在macOS
和 Linux
下均适用。 Windows
不行,其创建的关键函数是:CreateNamedPipe(...)
1. int mkfifo(const char *, mode_t);
第一个参数是路径,可以放在tmp
路径下,比如const char *fifoName = "/tmp/com.jimbo.fifo";
mode_t
代表赋予的权限,测试用0777
就可以了。
运行 ls -la /tmp/
下,明显存在我们创建的管道文件。
有名管道会在 在第一列将会显示类型 s
这里还有其他类型的文件。其中p
表示命名管道文件,d
表示目录文件,l
表示符号连接文件,-
表示普通文件,s
表示socket文件,c
表示字符设备文件,b
表示块设备文件。
2. int open(const char *, int, ...)
- 返回值为,打开的管道的操作句柄,读写都需要它
- 第一个参数是路径,同上
- 第二个参数为打开模式:
c
#define O_RDONLY 0x0000 /* open for reading only */ 只读
#define O_WRONLY 0x0001 /* open for writing only */ 只写
#define O_NONBLOCK 0x00000004 /* no delay */ 不阻塞
...
demo主要使用上面三种模式,
- 发送端使用
O_WRONLY
- 接收端使用
O_WRONLY
O_NONBLOCK
代表open文件的时候,这个方法是否需要阻塞,默认不传是阻塞的.
3. ssize_t read(int, void *, size_t)
往管道读取数据。常规操作,传入open
后的返回句柄,和字符串地址和最大长度
4. ssize_t write(int __fd, const void * __buf, size_t __nbyte)
往管道写入数据。常规操作,传入open
后的返回句柄,和字符串地址和字符串长度
三:demo代码
如下图,创建了两个app,分别为发送端(写数据)和接收端(读数据)。
macOS App 来说貌似需要双方都是 非沙盒的才行。 不然文件访问不了。
1. 发送端主要逻辑
-
主要创建了
mkfifo
一个管道 -
创建了子进程
-
open()
阻塞式的等子进程打开管道文件 -
上一步阻塞过了后,点击
writeMsg
往管道中写入消息。
主要代码: ViewController.mm
文件代码
objectivec
//发送端
#import "ViewController.h"
#include <sys/unistd.h>
#include <sys/stat.h>
static const char *fifoName = "/tmp/com.jimbo.fifo";
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.view.window setTitle:@"Main window"];
int ret = 0;
if (access(fifoName, F_OK) == -1) {
//管道不存在,创建一个新的
ret = mkfifo(fifoName, 0777);
if (ret != 0) {
NSLog(@"mkfifo failed! ret:%i", ret);
return;
}
}
//启动子进程
NSString *subAppp = [[NSBundle mainBundle] pathForResource:@"PipeApp_Sub" ofType:@"app"];
subAppp = [NSString stringWithFormat:@"%@/Contents/MacOS/PipeApp_Sub", subAppp];
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:subAppp];
NSError *error;
[task launchAndReturnError:&error];
//阻塞监听子进程打开 读端。
self.writePipeID = open(fifoName,O_WRONLY);
printf("open fd:%i\n", self.writePipeID);
if(self.writePipeID<0){
perror("writer open err");
return ;
}
}
- (IBAction)writeMsg:(id)sender {
//往管道发送消息,消息为ui的文本框的数据
const char *text = [self.textLabel.stringValue UTF8String];
ssize_t writeSize = write(self.writePipeID, text, strlen(text)+1);
NSLog(@"write succed size:%zi", writeSize);
}
2. 接收端主要逻辑
-
收到非阻塞的
O_NONBLOCK
打开只读管道,(打开后发送端的阻塞会通过) -
等到发送端发送了数据后。。。
-
点击接收数据的按钮
receiveMsg
,read()
函数读取管道中的数据,并显示在ui的textView
中
objectivec
//接收端
#import "ViewController.h"
#include <sys/stat.h>
@interface ViewController()
@property (nonatomic, assign) int pipeReadID;
@property (unsafe_unretained) IBOutlet NSTextView *textView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
const char *fifoName = "/tmp/com.jimbo.fifo";
// 由于是ui 主线程,所以选择了 非阻塞式的打开 管道
self.pipeReadID = open(fifoName, O_RDONLY | O_NONBLOCK);
if (self.pipeReadID < 0) {
NSLog(@"open 失败了");
self.textView.string = @"open 失败了";
} else {
self.textView.string = @"点击接收按钮,接收数据";
}
}
- (IBAction)receiveMsg:(id)sender {
size_t n;
char line[PIPE_BUF+1];
n = read(self.pipeReadID, line, PIPE_BUF);
NSLog(@"count:%zu get msg: %s", n ,line);
if (n > PIPE_BUF || n < 0) {
return;
}
self.textView.string = [NSString stringWithFormat:@"收到的数据:%@", [[NSString alloc] initWithBytes:line length:n encoding:NSUTF8StringEncoding]];
}
- (void)dealloc {
close(self.pipeReadID);
}
@end