我正在寻找一种快速而简单的方法,用于正确地测试一个给定的TCP端口是否在远程服务器上打开,从Shell脚本中。

我已经设法用telnet命令做到这一点,当端口被打开时,它工作得很好,但当它没有超时时,它似乎并没有超时,只是挂在那里……

下面是一个例子:

l_TELNET=`echo "quit" | telnet $SERVER $PORT | grep "Escape character is"`
if [ "$?" -ne 0 ]; then
  echo "Connection to $SERVER on port $PORT failed"
  exit 1
else
  echo "Connection to $SERVER on port $PORT succeeded"
  exit 0
fi

我要么需要一个更好的方法,或者一种方法强制telnet超时,如果它没有在8秒内连接,例如,并返回一些我可以在Shell中捕获的东西(返回代码,或stdout中的字符串)。

我知道Perl方法,它使用IO::Socket::INET模块,并编写了一个成功的测试端口的脚本,但如果可能的话,宁愿避免使用Perl。

注意:这是我的服务器正在运行(我需要从哪里运行这个)

SunOS 5.10 Generic_139556-08 i86pc i386 i86pc


当前回答

使用netcat,你可以像这样检查端口是否打开:

nc my.example.com 80 < /dev/null

如果TCP端口被打开,nc的返回值为success,如果不能建立TCP连接,则返回值为failure(通常为返回码1)。

当你尝试这样做时,某些版本的nc会挂起,因为它们即使在从/dev/null.接收到文件结束后也不会关闭它们的套接字的发送部分在我自己的Ubuntu笔记本电脑(18.04)上,我安装的netcat-openbsd版本的netcat提供了一个变通方案:必须使用-N选项才能立即得到结果:

nc -N my.example.com 80 < /dev/null

其他回答

这在幕后使用telnet,在mac/linux上似乎工作得很好。它不使用netcat,因为linux/mac版本之间的差异,这适用于默认的mac安装。

例子:

$ is_port_open.sh 80 google.com
OPEN

$ is_port_open.sh 8080 google.com
CLOSED

is_port_open.sh

PORT=$1
HOST=$2
TIMEOUT_IN_SEC=${3:-1}
VALUE_IF_OPEN=${4:-"OPEN"}
VALUE_IF_CLOSED=${5:-"CLOSED"}

function eztern()
{
  if [ "$1" == "$2" ]
  then
    echo $3
  else
    echo $4
  fi
}

# cross platform timeout util to support mac mostly
# https://gist.github.com/jaytaylor/6527607
function eztimeout() { perl -e 'alarm shift; exec @ARGV' "$@"; }

function testPort()
{
  OPTS=""

  # find out if port is open using telnet
  # by saving telnet output to temporary file
  # and looking for "Escape character" response
  # from telnet
  FILENAME="/tmp/__port_check_$(uuidgen)"
  RESULT=$(eztimeout $TIMEOUT_IN_SEC telnet $HOST $PORT &> $FILENAME; cat $FILENAME | tail -n1)
  rm -f $FILENAME;
  SUCCESS=$(eztern "$RESULT" "Escape character is '^]'." "$VALUE_IF_OPEN" "$VALUE_IF_CLOSED")

  echo "$SUCCESS"
}

testPort 

我猜现在给答案已经太迟了,而且这个答案可能不太好,但是你可以……

把它放在一个while循环中,并在上面加一个计时器。我更喜欢Perl而不是Solaris,但是根据你所使用的shell,你应该能够做一些像这样的事情:

TIME = 'date +%s' + 15
while TIME != `date +%s'
do whatever

然后在while循环中添加一个标志,这样如果它在完成之前超时,你可以引用超时作为失败的原因。

我怀疑telnet也有一个超时开关,但就我所知,我认为上面的方法是可行的。

使用netcat,你可以像这样检查端口是否打开:

nc my.example.com 80 < /dev/null

如果TCP端口被打开,nc的返回值为success,如果不能建立TCP连接,则返回值为failure(通常为返回码1)。

当你尝试这样做时,某些版本的nc会挂起,因为它们即使在从/dev/null.接收到文件结束后也不会关闭它们的套接字的发送部分在我自己的Ubuntu笔记本电脑(18.04)上,我安装的netcat-openbsd版本的netcat提供了一个变通方案:必须使用-N选项才能立即得到结果:

nc -N my.example.com 80 < /dev/null

对nc使用-z和-w TIMEOUT选项很容易,但并不是所有的系统都安装了nc。如果你有一个最新版本的bash,这将工作:

# Connection successful:
$ timeout 1 bash -c 'cat < /dev/null > /dev/tcp/google.com/80'
$ echo $?
0

# Connection failure prior to the timeout
$ timeout 1 bash -c 'cat < /dev/null > /dev/tcp/sfsfdfdff.com/80'
bash: sfsfdfdff.com: Name or service not known
bash: /dev/tcp/sfsfdfdff.com/80: Invalid argument
$ echo $?
1

# Connection not established by the timeout
$ timeout 1 bash -c 'cat < /dev/null > /dev/tcp/google.com/81'
$ echo $?
124

What's happening here is that timeout will run the subcommand and kill it if it doesn't exit within the specified timeout (1 second in the above example). In this case bash is the subcommand and uses its special /dev/tcp handling to try and open a connection to the server and port specified. If bash can open the connection within the timeout, cat will just close it immediately (since it's reading from /dev/null) and exit with a status code of 0 which will propagate through bash and then timeout. If bash gets a connection failure prior to the specified timeout, then bash will exit with an exit code of 1 which timeout will also return. And if bash isn't able to establish a connection and the specified timeout expires, then timeout will kill bash and exit with a status of 124.

虽然这是一个老问题,但我只是处理了它的一个变体,但这里的解决方案都不适用,所以我找到了另一个,并为子孙后代添加了它。是的,我知道OP说他们知道这个选项,它不适合他们,但对于任何跟随它的人来说,它可能是有用的。

在我的例子中,我想从docker构建中测试本地apt-cache -ng服务的可用性。这意味着在测试之前绝对不能安装任何东西。无nc、nmap、expect、telnet、python。perl然而是存在的,以及核心库,所以我使用这个:

perl -MIO::Socket::INET -e 'exit(! defined( IO::Socket::INET->new("172.17.42.1:3142")))'