Python 如何为子流程选择空闲端口?

Python 如何为子流程选择空闲端口?,python,linux,macos,networking,network-programming,Python,Linux,Macos,Networking,Network Programming,我正在围绕Appium服务器编写Python包装器。Appium接受要绑定到的本地端口的命令行参数。不幸的是,Appium无法自动为自己选择一个空闲端口,因此它要么绑定到显式指定的端口,要么使用EADDRINUSE失败。即使告诉它绑定到端口0,它也会成功启动,但不会显示它绑定到的端口 如果我自己在Python包装器中找到一个空闲端口,就不能保证在我将它传递给Appium的同时,其他进程不会绑定到同一端口。如果我自己不先发布它,Appium将无法绑定到它,所以我不得不这么做 我知道这在实践中不太可

我正在围绕Appium服务器编写Python包装器。Appium接受要绑定到的本地端口的命令行参数。不幸的是,Appium无法自动为自己选择一个空闲端口,因此它要么绑定到显式指定的端口,要么使用
EADDRINUSE
失败。即使告诉它绑定到端口
0
,它也会成功启动,但不会显示它绑定到的端口

如果我自己在Python包装器中找到一个空闲端口,就不能保证在我将它传递给Appium的同时,其他进程不会绑定到同一端口。如果我自己不先发布它,Appium将无法绑定到它,所以我不得不这么做


我知道这在实践中不太可能发生,但在以跨平台方式(Linux、macOS、Windows)将本地端口号传递给另一个进程之前,“保留”本地端口号的“正确方式”是什么?

多亏了注释中的@rodrigo建议,我最终得到了以下代码:

import platform
import re
import subprocess
from typing import Set

if platform.system() == 'Windows':
    def _get_ports(pid):
        sp = subprocess.run(['netstat', '-anop', 'TCP'],
                            stdout=subprocess.PIPE,
                            stderr=subprocess.DEVNULL,
                            check=True)

        rx_socket = re.compile(br'''(?x) ^
                                    \s* TCP
                                    \s+ 127.0.0.1 : (?P<port>\d{1,5})
                                    \s+ .*?
                                    \s+ LISTENING
                                    \s+ (?P<pid>\d+)
                                    \s* $''')

        for line in sp.stdout.splitlines():
            rxm = rx_socket.match(line)
            if rxm is None:
                continue

            sock_port, sock_pid = map(int, rxm.groups())
            if sock_pid == pid:
                yield sock_port
else:
    def _get_ports(pid):
        sp = subprocess.run(['lsof', '-anlPFn', '+w',
                             f'-p{pid}', '-i4TCP@127.0.0.1', '-sTCP:LISTEN'],
                            stdout=subprocess.PIPE,
                            stderr=subprocess.DEVNULL,
                            check=True)

        for line in sp.stdout.splitlines():
            if line.startswith(b'n'):
                host, port = line.rsplit(b':', 1)
                port = int(port)
                yield port


def get_ports(pid: int) -> Set[int]:
    """Get set of local-bound listening TCPv4 ports for given process.

    :param pid: process ID to inspect
    :returns: set of ports
    """

    return set(_get_ports(pid))

print(get_ports(12345))
导入平台
进口稀土
导入子流程
从键入导入集
如果platform.system()=“Windows”:
def_获取_端口(pid):
sp=子进程.run(['netstat','-anop','TCP'],
stdout=子流程.PIPE,
stderr=subprocess.DEVNULL,
检查=真)
rx_套接字=重新编译(br“”(?x)^
\s*TCP
\s+127.0.0.1:(?P\d{1,5})
\s+*?
\s+倾听
\s+(?P\d+)
\s*$“”)
对于sp.stdout.splitlines()中的行:
rxm=rx_插座匹配(线路)
如果rxm为无:
持续
sock\u端口,sock\u pid=map(int,rxm.groups())
如果sock_pid==pid:
输出插座端口
其他:
def_获取_端口(pid):
sp=子流程运行(['lsof','-anlPFn','+w',
f'-p{pid}','-i4TCP@127.0.0.1“,”-sTCP:听”],
stdout=子流程.PIPE,
stderr=subprocess.DEVNULL,
检查=真)
对于sp.stdout.splitlines()中的行:
如果line.startswith(b'n'):
主机,端口=line.rsplit(b':',1)
端口=int(端口)
出油口
def get_端口(pid:int)->设置[int]:
“”“获取给定进程的本地绑定侦听TCPv4端口集。
:param pid:要检查的进程ID
:返回:端口集
"""
返回集(获取端口(pid))
打印(获取端口(12345))
它在Linux、macOS和Windows上工作,并查找给定进程处于侦听状态的所有本地绑定TCPv4端口。它还跳过所有类型的主机/端口/用户名反向查找,以加快查找速度,并且不需要提升权限


最后,我们的想法是让Appium(或其他任何东西)从
0.0.0.0:0
开始,它将自己绑定到操作系统提供的第一个可用端口,然后检查它现在监听的端口。无竞争条件。

Selenium库使用以下技巧:

导入套接字
def自由_端口():
"""
使用套接字确定可用端口。
"""
空闲套接字=socket.socket(socket.AF\u INET,socket.SOCK\u流)
自由套接字绑定(('0.0.0.0',0))
释放插座。听(5)
port=free_socket.getsockname()[1]
释放插座。关闭()
返回端口
如果将套接字绑定到端口0,内核将为其分配一个空闲端口。它适用于windows和Linux

对于TCP/IP,如果端口指定为零,则服务提供程序 从动态客户端端口为应用程序分配唯一端口 射程

在ip_local_port_范围内,您可以阅读以下内容:

临时端口分配给下面的套接字 情况:

  • 套接字地址中的端口号在以下情况下指定为0: 调用绑定(2)

getsockname()用于知道选择了哪个端口。

您可能可以选择一个随机端口,将其传递给Appium,并检查是否有正确的错误消息。您不能尝试任意端口,如果它返回
EADDRINUSE
则增加该端口并循环,直到找到一个可用端口吗?@AlexHall,这就是我现在正在做的。但是,问题是关于“正确的方式”——例如,是否有办法为子流程保留端口号?@rodrigo,如果我是唯一使用该端口的人,这将起作用。但是我想把它传递给Appium,所以我必须先释放它,然后,当Appium启动时,其他进程可能会占用它(因为我运行的服务器都是动态分配端口的)。@toriningen:啊,一个沉重的服务器。。。实际上我对阿皮姆一无所知,但我还是会给你一些建议。如果使用
0
成功启动,我可以想出两种解决方案:1。修补程序Appium以报告所使用的端口(毕竟它是开源的);2.使用类似于
lsof-p-i4-p-n | grep LISTEN
的命令来发现它正在使用的端口。虽然这可能会提供问题的答案,但需要一些解释。请更新问题,并解释此解决方案的工作方式和原因。您的代码片段只选择了第一个可用端口,这与问题无关。诀窍是在不引入竞争条件的情况下将其传递给第三方子流程,而不仅仅是选择端口。