我用 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()
通过这种方式,您可以处理输入流和单个交互式命令,这可能不是您的应用程序的使用方式。