查找未使用的本地端口的最简单方法是什么?

myb*_*ael 78 shell networking shell-script netstat

查找未使用的本地端口的最简单方法是什么?

目前我正在使用类似的东西:

port=$RANDOM
quit=0

while [ "$quit" -ne 1 ]; do
  netstat -a | grep $port >> /dev/null
  if [ $? -gt 0 ]; then
    quit=1
  else
    port=`expr $port + 1`
  fi
done
Run Code Online (Sandbox Code Playgroud)

感觉非常迂回,所以我想知道是否有更简单的路径,例如我错过的内置函数。

小智 66

我的解决方案是绑定到端口 0,它要求内核从它的 ip_local_port_range 分配一个端口。然后,关闭套接字并在您的配置中使用该端口号。

这是有效的,因为内核似乎不会重用端口号,直到它绝对必须。后续绑定到端口 0 将分配不同的端口号。蟒蛇代码:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 0))
addr = s.getsockname()
print addr[1]
s.close()
Run Code Online (Sandbox Code Playgroud)

这仅给出了一些端口,例如。60123.

运行这个程序 10 000 次(你应该同时运行它们),你会得到 10 000 个不同的端口号。因此,我认为使用端口是非常安全的。

  • 这是一个单行(适用于 Python 2 和 Python 3):`python -c 'import socket; s=socket.socket(); s.bind(("", 0)); 打印(s.getsockname()[1]);s.close()'` (32认同)
  • 我进行了上述实验,并非所有结果都是独一无二的。我的直方图是:`{ 1: 7006, 2: 1249, 3: 151, 4: 8, 5: 1, 6: 1}` (5认同)
  • 是否有一种简单的方法来检查端口未被防火墙阻止,或者只搜索开放端口? (2认同)
  • @dshepherd我相信如果你不关闭前一个端口(并最后一次关闭它们),你会得到不同的端口。 (2认同)
  • Ruby 2.3.1 的一个行: `ruby -e ' puts Addrinfo.tcp("", 0).bind { |s| s.local_address.ip_port }'` (2认同)
  • @FranklinYu - 这对我不起作用(ruby 2.1.6p336)。这样做了:`ruby -e '需要“socket”;puts Addrinfo.tcp("", 0).bind {|s| s.local_address.ip_port }'` (2认同)

Chr*_*own 38

如果您的应用程序支持它,您可以尝试将端口 0 传递给应用程序。如果您的应用程序将此传递给内核,则该端口将在请求时动态分配,并保证不被使用(如果所有端口都已被使用,则分配将失败)。

否则,您可以手动执行此操作。您的答案中的脚本具有竞争条件,避免它的唯一方法是通过尝试打开它来自动检查它是否打开。如果端口正在使用中,程序应该退出,但无法打开端口。

例如,假设您正在尝试使用 GNU netcat 进行收听。

#!/bin/bash
read lower_port upper_port < /proc/sys/net/ipv4/ip_local_port_range
while :; do
    for (( port = lower_port ; port <= upper_port ; port++ )); do
        nc -l -p "$port" 2>/dev/null && break 2
    done
done
Run Code Online (Sandbox Code Playgroud)

  • 此端口尝试使用第一个可用端口。当您有两个并发进程时,刚刚检查的端口可能会被重用。重新阅读您的答案,您似乎建议重试绑定可用端口,直到所有端口都用完为止。假设有问题的程序可以区分“正在使用的端口”和其他错误,它应该没问题(尽管随机化仍然会使不可预测性更好)。 (2认同)
  • @Lekensteyn成功的端口绑定会导致内核返回 EADDRINUSE 如果您尝试再次使用它,则“刚刚检查的端口可能会被重用”是不可能的。 (2认同)

ste*_*ino 23

单线

我已经整理了一个很好的单行代码,可以快速达到目的,允许在任意范围内获取任意数量的端口(这里分为 4 行以提高可读性):

comm -23 \
<(seq "$FROM" "$TO" | sort) \
<(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) \
| shuf | head -n "$HOWMANY"
Run Code Online (Sandbox Code Playgroud)

逐行

comm是一个实用程序,用于比较必须按字母顺序排列的两个文件中的行。它输出三列:仅出现在第一个文件中的行、仅出现在第二个文件中的行和公共行。通过指定-23我们抑制后一列,只保留第一列。我们可以使用它来获得两个集合的差异,表示为文本行序列。我是从comm 这里了解到的。

第一个文件是我们可以选择的端口范围。seq生成从$FROM到的有序数字序列$TO。结果按字母顺序(而不是数字顺序,以符合comms 要求)排序,并comm使用进程替换作为第一个文件传输。

第二个文件是端口的排序列表,我们通过调用ss命令获得它(-t意味着 TCP 端口,-a意味着所有 - 已建立和正在侦听 - 和-n数字 - 不要尝试解析,例如,22to ssh)。然后我们只选择带有 的第四列awk,其中包含本地地址和端口。我们使用分隔符cut分割地址和端口,:只保留后者 ( -f2)。然后我们comm通过sorting 不重复来遵守的要求-u

现在我们的开放端口排序列表,我们可以shufFLE来再抢第一"$HOWMANY"的人用head -n

例子

抓取私有范围内的三个随机开放端口 (49152-65535)

comm -23 <(seq 49152 65535 | sort) <(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) | shuf | head -n 3
Run Code Online (Sandbox Code Playgroud)

例如可以返回

54930
57937
51399
Run Code Online (Sandbox Code Playgroud)

笔记

  • 切换-t-uinss以获得免费的 UDP 端口。
  • 更换shufsort -n,如果你喜欢以获取可用的端口,而不是数字排序的随机

  • @whoan 感谢您提供有关标题的提示,我将其作为答案的一部分。在第一个进程替换中进行`sort`ing 确保`seq` 的输出按_alphabetically_ 而不是_numerically_(这是`seq` 的默认值)排序,以符合`comm` 对输入进行`sort` 的要求像那样。我已经修改了答案,使其更加明确。再次感谢! (2认同)

小智 12

#!/bin/bash
read LOWERPORT UPPERPORT < /proc/sys/net/ipv4/ip_local_port_range
while :
do
        PORT="`shuf -i $LOWERPORT-$UPPERPORT -n 1`"
        ss -lpn | grep -q ":$PORT " || break
done
echo $PORT
Run Code Online (Sandbox Code Playgroud)

归功于 Chris Down


San*_*eep 11

显然,TCP 连接可以在 bash/zsh 中用作linux 上的文件描述符。以下函数使用该技术并且应该比调用 netcat/telnet 更快。

function EPHEMERAL_PORT() {
    LOW_BOUND=49152
    RANGE=16384
    while true; do
        CANDIDATE=$[$LOW_BOUND + ($RANDOM % $RANGE)]
        (echo "" >/dev/tcp/127.0.0.1/${CANDIDATE}) >/dev/null 2>&1
        if [ $? -ne 0 ]; then
            echo $CANDIDATE
            break
        fi
    done
}
Run Code Online (Sandbox Code Playgroud)

使用说明:将输出绑定到一个变量并在脚本中使用。在 Ubuntu 16.04 上测试

root@ubuntu:~> EPHEMERAL_PORT
59453
root@ubuntu:~> PORT=$(EPHEMERAL_PORT)
Run Code Online (Sandbox Code Playgroud)

  • 这会将 `\n` 发送到任何侦听端口 :) 我建议添加 `-n`。这仍将尝试打开连接但不发送任何内容而是立即断开连接。 (2认同)

w00*_*00t 8

这是一个跨平台、高效的“oneliner”,它包含所有使用中的端口,并为您提供 3000 个以后的第一个可用端口:

netstat -aln | awk '
  $6 == "LISTEN" {
    if ($4 ~ "[.:][0-9]+$") {
      split($4, a, /[:.]/);
      port = a[length(a)];
      p[port] = 1
    }
  }
  END {
    for (i = 3000; i < 65000 && p[i]; i++){};
    if (i == 65000) {exit 1};
    print i
  }
'
Run Code Online (Sandbox Code Playgroud)

您可以简单地加入所有行以将其放在一行上。如果您想从不同的端口号获得第一个可用的,请将分配更改为ifor循环中。

它适用于 Mac 和 Linux,这[:.]就是需要正则表达式的原因。

  • @stefanct BSD netstat 没有 `-t`,至少是 Apple 附带的那个,而且 `ss` 在 macOS 上也不存在。`netstat -aln` 甚至适用于 Solaris。 (2认同)

小智 5

这是我使用的版本:

while
  port=$(shuf -n 1 -i 49152-65535)
  netstat -atun | grep -q "$port"
do
  continue
done

echo "$port"
Run Code Online (Sandbox Code Playgroud)

该命令shuf -n 1 -i 49152-65535为您提供动态范围内的“随机”端口。如果已使用,则尝试该范围内的另一个端口。

该命令会netstat -atun列出所有 (-a) TCP (-t) 和 UDP (-u) 端口,而不会浪费时间来确定主机名 (-n)。