将数据管道传输到 nc 时出现问题

将数据管道传输到 nc 时出现问题

我用 PHP 编写了一个小型 UDP 服务器,它侦听套接字、接受命令并执行操作。这确实是非常基本的。我可以像这样手动连接到它:

% nc -u host port

(其中 nc = Ncat:版本 7.50(https://nmap.org/ncat))

当我输入命令时,我会看到结果响应。有用确切地我希望它从命令行工作的方式。

但是,如果我只是像这样“cat”一个文件:

cat FILE| nc -u host port

或者像这样发送数据:

echo "command1\ncommand2\n" | nc -u host port

...然后我的 PHP 应用程序一次读取所有内容,包括行尾字符。我只想读到行尾。

当然,我可以环绕文件的内容,并将每一行发送到 nc:

for x in `cat <file>`; do
  echo $x | nc -u host port
done

...但这完全是浪费。我想要 1 个到 nc 的连接,而不是很多。

EOL 字符将其放入字符串中,因为当我在 PHP 应用程序中打印输出时,我看到:command1 command2 ...但它都在一个字符串中。

为什么交互模式的行为与非交互模式不同?

我整个下午都在尝试,但似乎无法成功。

我确信有一个解释。

感谢您提供的任何信息。

PS:PHP代码的基础知识是:

$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
socket_bind($sock, '0.0.0.0', 2000);
for (;;) {
  socket_recvfrom($sock, $cmd, 1024, 0, $ip, $port);
  echo "command is $cmd";
  $reply = process($cmd);
  echo "reply is $reply";
  socket_sendto($sock, $reply, strlen($reply), 0, $ip, $port);
}

使用 nc 交互输入文本时,您可以按 CTRL-D 来分隔数据包。如果我在 shell 脚本中做同样的事情:

printf 'command1\0014commandd2\0014'| nc -u host port

...这些命令全部显示为一个命令。

如果我将 PHP 代码中的数据包大小减小为 5,那么发送 20 字节长的数据,数据将被截断,并且不会分离。

我也在网上准备好了这可能是一个缓冲问题。我尝试使用:

stdbuf -oL -eL cat FILE | nc -u host port 

...但令人惊讶的是,这也没有产生任何影响。

最后,我发现如果我这样做:

for x in command1 command2; do 
  echo $x 
  sleep 1
done | nc -u host port

……一切都按计划进行。服务器接收第一个命令,然后接收第二个命令。

真正不清楚的是为什么 sleep 1 会产生影响。拿出来,就失败了。以上肯定比将每个回显发送到 nc 更好。

答案1

l0b0 解决了您的问题,但要回答您的其他一些观点:

当然,我可以环绕文件的内容,并将每一行发送到 nc:for x in $(cat <file>); do echo $x | nc -u host port; done但这完全是浪费。我想要 1 个到 nc 的连接,而不是很多。

1)实际上并不发送文件中的行,它发送由空格分隔的任何内容;空白可以是换行符,也可以是制表符或空格(前提是您没有更改 IFS)。仅当您对“命令”的理解仅限于单个单词时,这些命令才是相同的,但通常大多数 Unix(y) 命令都是多个单词。这些单词也会被处理以进行 shell 路径名扩展(通常称为“globbing”),因此如果它们包含用于的任何?*[..]值,$x则可能与文件中的值不同。

2) 没有连接被“浪费”,因为 UDP 是无连接的,甚至不存在连接。也许你的意思是你不想要处决程序的nc(本地)。

为什么交互模式的行为与非交互模式不同?

输入在终端在 Unix 中通常一次处理一行;当程序(此处nc)执行a 操作时read,它会等待您输入任意数量的字符 - 并可选择编辑它们 - 直到您按回车键(或等效键),然后输入行将传递给程序(用于nc发送它作为一个数据包)。 (除非程序的缓冲区太小;然后只传送适合的部分,并且任何剩余部分仍等待下一次读取。)可以选择将其关闭,并且与每个字符(或由一次击键产生的字符组)一样少例如功能键)可以单独提供,由诸如 -- 和 之类的程序使用vi,尽管它不太明显bash。官方称这些为“规范”和“非规范”模式,但历史上非规范模式被描述为“生”,规范模式被描述为“熟”。

从任何其他类型的文件(包括管道)读取数据都会忽略换行符(即换行符)并读取缓冲区中适合的内容,在您的情况下,缓冲区包含多行。

使用 nc 交互输入文本时,您可以按 CTRL-D 来分隔数据包。如果我在 shell 脚本中做同样的事情:printf 'command1\0014commandd2\0014'| nc -u host port...所有命令都显示为一个命令。

3) 终端上的control-D 是特殊的——或者更准确地说,eof在终端驱动程序中配置为设置的字符(通常是control-D 但可以更改)是特殊的。当您输入时当程序正在读取字符时,读取将终止,无需等待返回,也不在数据中包含 control-D。当 control-D 出现在文件中时,对应的字符代码并不特殊。

4) 并且你生成的角色无论如何都不是control-D。 Control-D 是\004用于许多版本的echo、 以及\x04其他一些上下文中的八进制表示法。是control-A,也称为STX,在Unix中实际上未使用(除了在使用它来控制多个虚拟终端窗口之间切换的\001程序中)。screen

我也在网上准备好了这可能是一个缓冲问题。我尝试使用:stdbuf -oL -eL cat FILE | nc -u host port... 但令人惊讶的是,这也没有产生任何影响。

5) stdbuf(仅)影响 C 库“stdio”例程,并且cat可能不使用这些例程,因为它被设计为同时处理文本和非文本“二进制”数据。在我最方便的测试系统(CentOS6)上strace确认 GNU-coreutils-8.4cat不受 影响stdbuf,但我不能排除其他实现可能会受到影响。像sed '' file; grep '' file; awk 1 file我确认的那样,使用旨在处理行中文本的程序stdbuf -oL确实会有所不同。

但它可能不会带来你想要的改变。编写器程序将行添加到管道和nc读取(和发送)它们之间会存在竞争条件,如果编写器速度更快,您仍然会将多行集中到一个传出数据包中。

最后,我发现如果我这样做:for x in command1 command2; do echo $x; sleep 1; done | nc -u host port一切都会按计划进行。服务器接收第一个命令,然后接收第二个命令。

真正不清楚的是为什么 sleep 1 会产生影响。 ...

6) 这避免了竞争条件。每次 shell 向管道写入一行时,nc在 shell 下一次写入之前肯定有时间读取(并发送)它。

答案2

您的代码大部分都在那里,您现在需要做的就是将其视为$cmd换行符终止字符串的队列。因为我已经很长时间没有写 PHP 了,所以使用朴素的 Python 伪代码:

input = ''
try:
    first_command, remaining_input = input.split('\n', 1)
    execute(first_command)
    input = remaining_input
except ValueError:
    # No newline yet; we have to wait for more input
    input = socket.receive()

通过这种方式,您可以处理输入流和单个交互式命令,这可能不是您的应用程序的使用方式。

相关内容