创建双向管道或任何类似行为的 Unix 方式?

Posted

技术标签:

【中文标题】创建双向管道或任何类似行为的 Unix 方式?【英文标题】:Unix way to create bi-directional pipes or anything which acts like that? 【发布时间】:2016-08-18 12:39:41 【问题描述】:

我需要一个基于双向文件系统(例如设备节点、命名套接字、命名管道或类似的东西)在 Linux(可能还有其他 UNIX 系统)下的进程间通信可能性,假设类似于一个串口,即你可以打开/dev/ttyS0进行读/写,你可以用any工具读/写它(包括一个普通的cat命令),就我而言,另一部分不是硬件(串行端口),而是另一个但完全不相关的过程。是的,我已经阅读了关于 SO 的类似问题,但是我仍然不确定是否有 other 类似的解决方案,如果命名管道在这里不适合我的需求。

我能看到的:

命名管道/FIFO 不是一个解决方案,因为它是单向的(至少在某些类 UNIX 系统上,包括 Linux),两个命名管道(一个用于 RX,一个用于 TX)不是解决方案

(named) 套接字不是解决方案,因为它们需要特别小心,例如,我不能只是回显“进入”它,但我需要 connect() 等,所以我需要一个自定义客户端进行交互与(见下文)

我对 socketpair() 有点不确定,因为它会创建两个连接的套接字,但即便如此,另一个进程仍然需要特殊准备(我列表中的上一个问题)

openpty() 之类的技巧可以成为解决方案(然后由客户端使用 /dev/pts/...,虽然我不确定在 OSX 上),但是它很丑陋,不那么便携......

李>

像内核模块这样的解决方案不是解决方案,因为它根本不可移植,而且无论如何都需要 root 访问权限

不是基于文件系统的 IPC 可能性也不是我的解决方案,因为问题的性质

最好的方法是双向命名管道(一个进程写入,另一个进程读取,反之亦然,如果它是半双工的,那么问题不大),它适用于所有 UNIX - 至少包括 Linux 和 OSX 的系统,但是我找不到像这样工作的任何东西。

我想要的是有一种方法来模拟(仅从用户空间)类似串行端口的东西,所以我什至可以将它与一个简单的终端客户端一起使用(当然 ioctl 设置串行参数不会工作,这不是问题!),或者我可以简单地用echo mmmmm > ...cat ... 写/读它。我需要它来为(计算机的)仿真器提供类似于真机串行端口的东西,因此我可以通过它与仿真机进行交互。我不能使用其他工具(如socat 并使用命名套接字,甚至 TCP/IP 或其他工具),因为我还有一个无法更改的闭源客户端软件,它需要一个文件名作为“串行端口设备” “,但是我已经测试过它不会在各种与串行端口相关的 ioctl() 调用上失败(它可以在命名管道上工作,那么单向性是我的问题),所以我需要“只”解决这个问题,它会工作。

【问题讨论】:

我想不出除了使用伪终端之外的任何其他解决方案。但是,如果我正确理解了这个问题,我认为这对您来说确实是一个解决方案。 OSX 的可移植性应该是可能的,因为也有 BSD 伪终端 我不相信你想要的东西可以做到——这可能就是你找不到任何东西的原因。第二个进程如何知道连接事物的方式——它应该响应第一个进程,还是重新开始?如何处理第三个并发连接? 无并发。事实上,从模拟机器的角度来看,它是一个(模拟的)“类似串行端口的实体”(尽管没有任何设置,如波特率等),所以不能有更多的客户端,只有一个客户端。如果有更多的客户端尝试连接,它不会工作,这不是问题,因为它也不能与真正的串行端口一起工作。所以第一个客户端获胜,但连接更多也不应该发生......简单(命名)套接字很好,我可以看到 minicom 支持-D unix#/tmp/socket 但是这是有问题的,因为我上面提到的自定义客户端不允许这样做. 我还考虑过将套接字与 AF_UNIX 和 SOCK_DGRAM 一起使用。我不确定当时会发生什么(奇怪的 errno 值......)只是我想了一下,因为带有互联网协议的 SOCK_DGRAM 通常意味着无连接的 UDP,所以我想我可以用命名套接字将 connect() 的东西保存为好吧,但似乎不是那样...... socketpair() 为您提供了一对已经连接的套接字,从而减少了connect(2) 它们的需要。你从哪里得到你必须连接的? 【参考方案1】:

唯一允许您在每个描述符上分别使用read(2)write(2) 的系统对象是套接字和pty。这些是唯一具有两个用于发送和接收数据的队列的对象。所有其他 pipe 事物只有一个,并且当您收到一对描述符(例如对于未命名的管道)时,如果您接下来阅读,您所写的内容将立即收到。

在这种情况下我不推荐使用pty接口,因为正如你所说,在能够传输之前你必须做一些家务,你必须处理设备参数(如termios config,设备级别的波特率和字符解释)

getsocketpair(3) 是一个库函数,它已经为您提供了一对已连接的套接字,并允许您在打电话。

int res, sfds[2];
res = getsocketpair(PF_LOCAL, SOCK_STREAM, 0, sfds);

将在成功的情况下返回0,并在sfds 数组中返回两个已连接的套接字。在 BSD 系统(从 OS/X 派生而来)中,getsocketpair(2) 是一个系统调用,它允许内核选择一个没有网络协议带来的开销的高效版本(如示例所示)。

注意

正如您在 cmets 中所说,您的程序很可能不仅会调用 read(2)write(2),而且还是一个从用户那里获取原始输入的程序(例如切换无回显模式以获取密码或将原始输入作为交互式程序,例如vi(1)emacs(1) do)

在这种情况下,我建议您下载我的程序slowtty 并学习并修改它以适合您。它完全符合您的要求。它分配一对ptys 并进行输入/输出。它还要求您使用 tty 行,但这仅仅是因为它将终端设置从您的 stdin 复制到 pty 行。这是一个简单的程序,旨在教人们如何使用pty(7) 界面,它在 FreeBSD、Mac/OS X 和 linux 中运行良好。因此,该程序将 tty 输入/输出减慢到在从 pty 中调整的波特率,因此它允许您查看输出,就像您使用旧的串行 tty 线一样。减速是在一个单独的例程中进行的,因此可以很容易地从源代码中消除。

【讨论】:

好的,但我不确定它是否能解决我的问题,即我的闭源客户端我不会“连接”到任何想要将给定路径作为文件打开的东西.. .而且我必须用它来与进程通信...该客户端最初设计用于与/dev/ttyS0 一样的串行端口,这是我的问题,我无法更改客户端...唯一容易部分,似乎客户端不关心是否无法完成串行参数设置 ioctl 等,因此我也可以“伪造”其他基于文件系统的对象,但不需要首先连接()-ed,这意味着更改客户端代码! 你会收到两个已经连接的套接字描述符,所以你不需要connect(2)他们。这些套接字描述符已准备好在两个方向上相互发送数据,并且没有字符转换,就像伪 tty 发生的那样。您的客户旨在与终端一起使用并不是您最初的问题,那么 pty 解决方案可能会更好。你挖过expect(1) 实用程序吗?它与具有此类要求的程序进行通信。 expect(1) 允许您与基于终端的(可能是交互式的)程序交谈。它向它发送消息并接收响应并根据响应采取行动......根本不需要编程。 我最初的问题是我有一个客户端需要一个文件名作为普通文件打开并且只使用读/写。即没有预先设置串行参数的串行端口(例如/dev/ttyS?)。我需要使用这个客户端,因为它具有额外的功能来解释通信。刚才我不想通过串行端口谈论硬件,而是通过模拟和用户进程来谈论硬件。 socketpairs - 恕我直言 - 连接在一起,但要从另一个进程连接到其中一个,仍然需要连接,还是我错了? @LGBGáborLénárt,你说即串行端口。从您提到的上下文中,我理解“例如”。此外,需要终端通常意味着您的程序也在使用isatty(3) 函数,该函数还执行一些ioctl(2) 系统调用,这些调用依赖于串行线路(例如切换到原始模式,就像vi(1) 之类的程序一样)你没有在你的问题中提到任何这些,所以如果你的程序只做read(2)write(2) 它就足够了getsocketpair(2) 系统调用.

以上是关于创建双向管道或任何类似行为的 Unix 方式?的主要内容,如果未能解决你的问题,请参考以下文章

损坏管道上的发送行为

行为类似于线程的 C# 可重用或持久任务

命名管道类似于“mkfifo”创建,但双向

如何实现类似MacOS(EN Intl)的德国变音符行为?

使用mediatR管道行为添加验证。

grep -l 在到 xargs 的管道上的行为不符合预期[重复]