【问题标题】:Making STDIN unbuffered under Windows in Perl在 Perl 的 Windows 下使 STDIN 无缓冲
【发布时间】:2021-12-01 23:39:21
【问题描述】:

我正在尝试在 Perl 中异步进行输入处理(从控制台)。我的第一种方法是使用IO::Select,但这在 Windows 下不起作用。

然后我看到了Non-buffered processor in Perl 的帖子,大致暗示了这一点:

binmode STDIN;
binmode STDOUT;
STDIN->blocking(0) or warn $!;
STDOUT->autoflush(1);

while (1) {
    my $buffer;
    my $read_count = sysread(STDIN, $buffer, 4096);

    if (not defined($read_count)) {
        next;
    } elsif (0 == $read_count) {
        exit 0;
    }
}

这对于常规 Unix 系统可以正常工作,但不适用于 Windows,sysread 实际上会阻塞。我已经在带有 64 位 Strawberry Perl 5.32.1 的 Windows 10 上进行了测试。

当您检查blocking() 的返回值(如上面的代码中所做的那样)时,结果表明调用失败并显示有趣的错误消息“尝试对非套接字的操作进行操作”。

编辑:我的应用程序是一个国际象棋引擎,理论上可以在终端中交互式运行,但通常通过管道与 GUI 进行通信。因此,Win32::Console 没有帮助。

自博文发布后发生了什么变化?作者明确声称这种方法适用于 Windows。我可以使用任何其他选项,也许是 Win32:: 命名空间中的某个模块?

【问题讨论】:

  • 顺便说一句,perldoc perlport 没有提到blocking()
  • Win32::Console,参见$CONSOLE->InputChar(1)。不确定它是否阻塞。如果是这样,也使用$CONSOLE->GetEvents()
  • 还有一种快速而肮脏的方法:使用线程。线程可以使用充当管道的套接字(例如 Win32::SocketPair)与主程序通信,从而允许您在主程序中进行非阻塞读取。
  • 我已经编辑了这个问题,提到STDIN 通常不是一个 tty 而是一个管道。因此Win32::Console 不是一个选项。此外,当您在控制台中用鼠标选择某些内容时,甚至GetEvent 也会阻塞。

标签: windows perl io nonblocking


【解决方案1】:

我现在在https://github.com/gflohr/Chess-Plisco/blob/main/lib/Chess/Plisco/Engine.pm中实现的解决方案(搜索__msDosSocket()的方法)可以概括如下:

  1. 如果检测到 Windows 为操作系统,则创建一个临时文件作为 Unix 域套接字,并使用 IO::Socket::Unix 进行写入。

  2. 做一个fork(),它实际上在Perl for Windows中创建了一个线程,因为系统没有真正的fork()

  3. 在“父”中,创建另一个IO::Socket::Unix的实例,读取路径相同。

  4. 在“孩子”中,使用getline() 从标准输入中读取。当然,这会阻止。读取的每一行都会回显到套接字的写入端。

  5. “父”使用套接字的读取端作为标准输入的替代,并将其置于非阻塞模式。即使在 Windows 下也可以,因为它是一个套接字。

从这里开始,一切都和 Unix 下一样:所有输入都以非阻塞模式读取,IO::Select

通过环回接口路由通信可能更明智,而不是 Unix 域套接字,因为在 Windows 下,很难保证在进程终止时删除临时文件,因为在使用时无法取消链接。 cmets 中还指出,IO::Socket::UNIX 可能无法在较旧的 Windows 版本下工作,因此 inet 套接字可能更便于使用。

我也无法终止两个线程。致电kill() 似乎不起作用。在我的例子中,程序实现的协议是从标准输入读取的命令“退出”应该导致程序终止。因此,子线程检查读取的行是否“退出”并在这种情况下以exit 终止。正确的解决方案应该是找到更好的方法让父母杀死孩子。

我懒得忽略 SIGCHLD(因为它在 Windows 下不存在)或调用 wait*(),因为 fork 在 Windows 下不会产生新的进程映像,而只会产生一个新线程。

这种方法与问题的其中一个 cmets 中建议的方法接近,只是线程伪装成fork() 创建的子进程。

另一个建议是使用模块Win32::Console。这不起作用有两个原因:

  1. 顾名思义,它只适用于控制台。但我的软件是 GUI 前端的后端,很少在控制台中运行。

  2. 底层 API 用于键盘鼠标事件。它适用于击键和大多数鼠标事件,但是一旦用户用鼠标选择了某些东西,就会阻止轮询事件。因此,即使对于真正的控制台应用程序,这种方法也行不通。基于Win32::Console 构建的解决方案还必须处理按下CTRL、ALT 或Shift 键等事件,因为它们不能保证可以立即从tty 读取输入。

有点令人惊讶的是,像文件描述符上的非阻塞 I/O 这样微不足道的任务在 Perl 中很难以可移植的方式实现,因为 Windows 实际上有一个类似的概念,称为“重叠”I/O。我试图理解这个概念,但失败了,并得出结论,这符合 Windows 的格言“让简单的事情变得困难,让困难的事情变得不可能”。因此,我不能责怪 Perl 开发人员没有使用它来模拟非阻塞 I/O。也许根本不可能。

【讨论】:

  • 很酷,IO::Socket::Unix 正在 Windows 上工作。根据this,它至少需要 Windows 10 1803,并且Socket.pm 版本 >= 2.029。
  • 我认为IO::Async 模块无济于事,因为我的程序需要线程之间的永久通信。我还编辑了答案以提及IO::Socket::UNIX的兼容性问题。
猜你喜欢
  • 2014-06-23
  • 2011-11-28
  • 1970-01-01
  • 2011-07-11
  • 1970-01-01
  • 2014-05-01
  • 2012-05-10
  • 2021-09-16
  • 2014-01-15
相关资源
最近更新 更多