2011-09-07 150 views
9

我正在使用Python腳本向服務器發送UDP數據包,該服務器將註冊我的腳本以接收來自服務器的通知。該協議要求我發送我自己的IP地址和端口,我希望接收這些通知,因此這應該是從服務器網絡可以訪問的地址。Python:獲取用於將IP數據發送到特定遠程IP地址的本地IP地址

解決方案應該至少可以在Windows XP及更高版本上運行,最好是Mac OS X,因爲這是我的開發平臺。

第一步是獲取我的任何接口的IP地址。我目前正在使用通常建議的方法來解析機器自己的主機名:

def get_local_address(): 
    return socket.gethostbyname(socket.gethostname()) 

這隻有時纔有效。當前它返回172.16.249.1,這是VM Ware使用的虛擬接口的地址,所以這是一個問題。

更好的辦法是獲取默認界面的IP地址,這應該是大多數時候的正確界面。

更好的辦法是通過面向連接的協議(如TCP)獲得用於連接到服務器的實際IP地址。實際上,我可以得到該地址,但不能沒有試圖打開連接:

def get_address_to_connect_to(server_addr): 
    non_open_port = 50000 
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 

    try: 
     s.connect((server_addr, non_open_port)) 
    except socket.error: 
     pass 
    else: 
     s.close() # just in case 

    return s.getsockname()[0] 

這將阻塞,直到有TCP超時在服務器無法訪問或與一個邪惡的防火牆封鎖ICMP的情況下已經達到數據包。有沒有更好的方法來獲取這些信息?

回答

11

我不得不一次解決同樣的問題,花了相當多的時間試圖找到更好的方法,但沒有成功。我也開始使用gethostname,但是像你一樣發現它並不總是返回正確的結果,甚至可以在hosts文件有問題的情況下拋出異常。

我能找到的跨平臺可靠工作的唯一解決方案就是嘗試連接,就像你在做的那樣。代碼的一個改進是使用UDP而不是TCP,即SOCK_DGRAM而不是SOCK_STREAM。這避免了連接超時,並且該功能立即完成。

如果您將此代碼部署到可能正在運行防火牆的客戶端位置,請確保記錄了它執行此檢查的事實。否則,他們可能會看到針對端口50000的出站連接的防火牆警告,不明白其用途,並認爲它是一個錯誤。

編輯:這裏是我大致使用的代碼:

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 

try: 
    s.connect((host, 9)) 
    client = s.getsockname()[0] 
except socket.error: 
    client = "Unknown IP" 
finally: 
    del s 
return client 

我認爲你得到0.0.0.0因爲你調用getsockname之前關閉套接字。另一個提示是我使用的端口9;它是RFC863的UDP丟棄端口,因此比一些隨機的高編號端口更少怪異。

+0

啊,甜蜜的同理心!使用'SOCK_DGRAM',它將近乎完美!但's.getsockname()'返回'('0.0.0.0',51770)'在我的情況下。我試過's = socket.socket(socket.AF_INET,socket.SOCK_DGRAM); s.sendto(b'hello',('1.2.3.4',50000)); s.getsockname()[0]'。如果你將你的代碼添加到你的答案中,那會很棒! – Feuermurmel

+0

查看我編輯的答案。 – DNS

+0

完美的作品;它甚至使用我不知道的[Discard Protocol](http://en.wikipedia.org/wiki/Discard_Protocol)!我用'with'語句取代了'del s'。 – Feuermurmel

0

發送方是否需要知道其IP地址?

在發送UDP「註冊」後,客戶端只能監聽它試圖訂閱的數據(在所有接口上),並讓服務器開始將數據發送到數據報所源自的地址?

+0

異端!該服務可以做很多事情,讓我的生活更輕鬆。但同樣的問題出現在也是該項目一部分的SIP實施中。它必須在'REGISTER'請求的'Contact'頭部中發送自己的IP地址。 – Feuermurmel