2017-07-02 86 views
2

我正在編寫圍繞Appium服務器的Python包裝。 Appium接受本地端口綁定的命令行參數。不幸的是,Appium不能自動爲自己選擇一個空閒端口,因此它要麼綁定到明確指定的端口,要麼以失敗EADDRINUSE。即使告訴它綁定到端口0,它也會成功啓動,但不會顯示它綁定的端口。如何爲子進程選擇一個空閒端口?

如果我自己在Python包裝器中找到一個自由端口,則不保證其他進程不會綁定到相同的端口,同時我將它傳遞給Appium。如果我不自己先釋放它,Appium將無法綁定它,所以我必須這樣做。我知道在實踐中這種情況不太可能發生,但是在以一種跨平臺的方式將其保留到另一個進程之前「保留」本地端口號的「正確方法」(Linux,macOS,Windows )?

+0

你也許可以選擇一個隨機端口,將它傳遞給Appium,然後檢查是否有正確的錯誤消息。 –

+0

難道你不能嘗試一個任意的端口,如果它返回'EADDRINUSE'增加它並循環,直到你找到一個免費的端口? – rodrigo

+0

@AlexHall,這是我現在正在做的。然而,問題是關於「正確的方式」 - 例如有沒有辦法爲子流程保留一個端口號? – toriningen

回答

1

由於在評論@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}', '[email protected]', '-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)) 

它可以在Linux,MacOS和Windows中,並找出給定的過程,都是本地綁定TCPv4端口在LISTEN狀態。它還會跳過各種主機/端口/用戶名反向查找,使其更快,並且不需要提升權限。因此,最終的想法是讓Appium(或其他)在0.0.0.0:0上啓動,它將自己綁定到OS提供的第一個可用端口,然後檢查它正在監聽的端口。沒有比賽條件。

1

硒庫使用這種伎倆:

https://github.com/SeleniumHQ/selenium/blob/master/py/selenium/webdriver/common/utils.py#L31

import socket 

def free_port(): 
    """ 
    Determines a free port using sockets. 
    """ 
    free_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
    free_socket.bind(('0.0.0.0', 0)) 
    free_socket.listen(5) 
    port = free_socket.getsockname()[1] 
    free_socket.close() 
    return port 

如果你綁定一個插座端口0,內核將它分配一個空閒端口。它適用於Windows和Linux。

https://msdn.microsoft.com/en-us/library/windows/desktop/ms737550.aspx

對於TCP/IP,如果端口被指定爲零,服務提供者 唯一的端口分配給從動態客戶端端口 範圍中的應用。

http://man7.org/linux/man-pages/man7/ip.7.html

在ip_local_port_range,您可以閱讀以下內容:

臨時端口分配給一個插座在以下 情況:

  • 的端口號 調用綁定(2)時,套接字地址被指定爲0;

getsockname()用於知道哪個端口已被選中。

+0

雖然這可能會提供問題的答案,但需要進行一些解釋。請更新這個問題,並解釋一下這個解決方案的工作方式和原因。 –

+1

你的代碼片段只是選擇第一個可用的端口,這與問題無關。訣竅是將它傳遞給第三方子進程而不引入競爭條件,而不僅僅是選擇端口。 – toriningen