Python 如何为子流程选择空闲端口?
我正在围绕Appium服务器编写Python包装器。Appium接受要绑定到的本地端口的命令行参数。不幸的是,Appium无法自动为自己选择一个空闲端口,因此它要么绑定到显式指定的端口,要么使用Python 如何为子流程选择空闲端口?,python,linux,macos,networking,network-programming,Python,Linux,Macos,Networking,Network Programming,我正在围绕Appium服务器编写Python包装器。Appium接受要绑定到的本地端口的命令行参数。不幸的是,Appium无法自动为自己选择一个空闲端口,因此它要么绑定到显式指定的端口,要么使用EADDRINUSE失败。即使告诉它绑定到端口0,它也会成功启动,但不会显示它绑定到的端口 如果我自己在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
的命令来发现它正在使用的端口。虽然这可能会提供问题的答案,但需要一些解释。请更新问题,并解释此解决方案的工作方式和原因。您的代码片段只选择了第一个可用端口,这与问题无关。诀窍是在不引入竞争条件的情况下将其传递给第三方子流程,而不仅仅是选择端口。