2013-07-11 44 views
6

如果我使用自動分配的臨時端口範圍(5000-65534)範圍內的端口連接到本地主機,我可以可靠地將Winsock套接字自動獲取到connect()。特別是,Windows似乎有一個系統範圍的滾動端口號,它是下一個將嘗試分配爲客戶端套接字的本地端口號的端口。如果我創建套接字直到分配的數字低於我的目標端口號,然後重複創建一個套接字並嘗試連接到該端口號,我通常可以讓套接字連接到它自己。爲什麼套接字連接()到它自己的臨時端口?

我第一次發現它在一個應用程序中反覆嘗試連接到本地主機上的某個端口,並且當服務沒有監聽時,它很少成功地建立連接並接收它最初發送的消息(發生這種情況成爲Redis PING命令)。

一個例子,在Python(什麼也沒有聽目標端口上運行):

import socket 

TARGET_PORT = 49400 

def mksocket(): 
    return socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) 

while True: 
    sock = mksocket() 
    sock.bind(('127.0.0.1', 0)) 
    host, port = sock.getsockname() 
    if port > TARGET_PORT - 10 and port < TARGET_PORT: 
     break 
    print port 

while port < TARGET_PORT: 
    sock = mksocket() 
    err = None 
    try: 
     sock.connect(('127.0.0.1', TARGET_PORT)) 
    except socket.error, e: 
     err = e 
    host, port = sock.getsockname() 
    if err: 
     print 'Unable to connect to port %d, used local port %d: %s' % (TARGET_PORT, port, err) 
    else: 
     print 'Connected to port %d, used local port %d' (TARGET_PORT, port) 

在我的Mac機,這最終與Unable to connect to port 49400, used local port 49400終止。在我的Windows 7機器上,連接成功建立並打印Connected to port 49400, used local port 49400。生成的套接字接收發送給它的任何數據。

這是Winsock中的錯誤嗎?這是我的代碼中的錯誤?

編輯:這裏是所示的TcpView違例的連接截圖:

python.exe 8108 TCP (my HOSTNAME) 49400 localhost 49400 ESTABLISHED

+0

這裏有一些相關的問題:http://stackoverflow.com/questions/4949858/how-can-you-have-a-tcp-connection-back-to-the-same-port http:// stackoverflow。 com/questions/5139808/tcp-simultaneous-open-and-self-connect-prevention –

+0

我在我的系統中定期發生這個問題,它涉及至少五個本地端口,服務器可能運行或不運行,客戶不斷嘗試連接。我無法想到在連接層面以獨立於平臺的方式解決這個問題。 –

回答

2

RFC 793描述#3.4這似乎是一個「同步啓動」。請參見圖8.請注意,任何階段都不處於LISTEN狀態。在你的情況下,兩端是相同的:這將導致它完全按照RFC中所述工作。

+0

這當然可以解釋TCP如何不會崩潰,但是如果你只有一個套接字,它幾乎是'同時'的。 Winsock在這裏仍然在做一些不尋常的事情。 –

+0

它是同步的,因爲套接字有兩個端口,本地和遠程,並且它們都如上所述同時連接。他們碰巧是一樣的,但是有兩件事同時發生。否則,連接無法完成。 Winsock的行爲與#3.4和圖8中描述的完全一樣。 – EJP

+0

好吧,但即使我明確嘗試在我的Mac上也無法完成此操作:我可以將套接字綁定到本地端口,但是當我嘗試'連接()'相同的套接字到我得到'EINVAL'的端口。當然這是一本教科書「同時啓動一個套接字」的場景。 –

0

它是在你的代碼邏輯錯誤。

首先,只有較新版本的Windows使用5000-65534作爲臨時端口。舊版本改爲使用1025-5000。

您正在創建多個明確爲的套接字綁定到隨機臨時端口,直到您綁定的套接字小於目標端口的10個端口內。但是,如果這些套接字中的任何一個碰巧實際綁定到實際的目標端口,則會忽略該套接字並保持循環。因此,您可能會或可能以綁定到目標端口的套接字結束,並且您可能會或可能不會以實際上小於目標端口的最終port值結束。

之後,如果port恰好是低於您的目標端口(這是不保證的),你再創造更多的插座是隱含綁定到不同的隨機可用短週期端口調用connect()時(它隱式bind()如果bind()尚未被調用),其中沒有一個將與您明確綁定的短暫端口相同,因爲這些端口已被使用且不能再次使用。

在任何時候,你都沒有任何給定的套接字從短暫端口連接到相同的臨時端口。除非另一個應用程序碰巧已經綁定到您的目標端口正在該端口上有效偵聽,那麼connect()無法成功連接到您創建的任何套接字上的目標端口,因爲沒有他們正處於傾聽狀態。並且getsockname()在未綁定的套接字上無效,並且如果connect()失敗,則不保證連接套接字被綁定。因此,考慮到您所顯示的代碼,您認爲發生的症狀實際上是不可能的。你的記錄只是做出錯誤的假設,因此記錄錯誤的東西,給你一個虛假的存在狀態。

嘗試更多的東西像這樣的相反,你會看到真正的端口是什麼:

import socket 

TARGET_PORT = 49400 

def mksocket(): 
    return socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) 

while True: 
    sock = mksocket() 
    sock.bind(('127.0.0.1', 0)) 
    host, port = sock.getsockname() 
    print 'Bound to local port %d' % (port) 
    if port > TARGET_PORT - 10 and port < TARGET_PORT: 
     break 

if port >= TARGET_PORT: 
    print 'Bound port %d exceeded target port %d' % (port, TARGET_PORT) 
else: 
    while port < TARGET_PORT: 
     sock = mksocket() 
     # connect() would do this internal anyway, so this is just to ensure a port is available for logging even if connect() fails 
     sock.bind(('127.0.0.1', 0)) 
     err = None 
     try: 
      sock.connect(('127.0.0.1', TARGET_PORT)) 
     except socket.error, e: 
      err = e 
     host, port = sock.getsockname() 
     if err: 
      print 'Unable to connect to port %d using local port %d' % (TARGET_PORT, port) 
     else: 
      print 'Connected to port %d using local port %d' % (TARGET_PORT, port) 
+0

Python在被覆蓋時正在關閉套接字,所以理論上可以重用這些端口(雖然它們最終處於TIME_WAIT狀態,並且可能不會被重用)。而且我知道,如果這個腳本每次都做它我想做的事情,它就沒有定義,它通常就像我所描述的那樣。至少在Windows上,我不能在連接之前調用'getsockname()',但是我可以在連接後調用它(即使它失敗了)。即使未定義,即使沒有建立連接,Windows似乎也會讓隱式綁定成功。 –

+0

我還應該澄清,我已經通過僅使用第一個循環,「啓動」當前臨時端口,並且檢查了TcpView(來自Sysinternals)並驗證實際上有一個端口與兩個遠程端口和本地端口相同。我不相信你在第五段的聲明是準確的。 (明天我會試着運行你修改的腳本。) –

+0

@JohnCalsbeek:是的,關閉的端口將處於TIME_WAIT狀態,並且不能在幾分鐘內重複使用,除非在關閉之前關閉了關閉,或者它們用啓用S​​O_REUSEADDR選項。是的,您無法在未綁定的套接字上調用getsockname(),只有在連接成功的情況下,在未綁定的套接字上調用connect()纔會綁定它。如果'connect()'失敗,則不能保證套接字將被綁定。 –

相關問題