2010-04-05 162 views
3

我目前有一個使用UDP和Python套接字模塊的問題。我們有一臺服務器和客戶端。當我們向用戶發送數據時會出現問題。用戶可能已經通過客戶端崩潰關閉了與服務器的連接,通過ISP斷開連接或其他一些不正確的方法。因此,可以將數據發送到封閉的套接字。UDP數據接收上的Python套接字錯誤。 (10054)

當然用UDP你不能,如果數據真的達到或知道它是封閉的,因爲它不照顧(ATLEAST,它並不帶來了一個例外)。但是,如果您發送數據並關閉數據,則會以某種方式返回數據(???),最終會給sock.recvfrom帶來套接字錯誤。 [Errno 10054]現有連接被遠程主機強制關閉。幾乎看起來像來自連接的自動響應。

雖然這是罰款,並可以通過嘗試來處理:除了:塊(即使它降低了服務器一點點的性能)。問題是,我無法分辨這是從哪裏來的,或者哪個socket被關閉了。無論如何找出'誰'(IP,套接字#)發送這個?這將是非常好的,因爲我可以立即斷開它們並將它們從數據中移除。有什麼建議麼?謝謝。

服務器:

import socket 

class Server(object): 
    def __init__(self): 
     self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 
     self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
     self.connected = {} 

    def connect(self): 
     self.socket.bind(('127.0.0.1', 5579)) 

    def find_data(self): 
     while 1: 
      data, address = self.socket.recvfrom(1024) 
      self.got_data(data,address) 
      if self.connected.has_key(address): 
       pass 
      else: 
       self.connected[address] = None 

    def got_data(self, data, address): 
     print "GOT",data,"FROM",address 
     for people in self.connected: 
      print people 
      self.send_data('hi', people) 

    def send_data(self, data, address): 
     self.socket.sendto(data,address) 


if __name__ == '__main__': 
    server = Server() 
    server.connect() 
    print "NOW SEARCHING FOR DATA" 
    server.find_data() 

客戶:

import socket, time 

class Client(object): 
    def __init__(self): 
     self.socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 

    def connect(self): 
     self.socket.connect(('127.0.0.1', 5579)) 

    def send_data(self): 
     self.socket.sendto('hi',('127.0.0.1', 5579)) 

    def got_data(self, data, address): 
     print "GOT",data,"FROM",address 


if __name__ == '__main__': 
    client = Client() 
    client.connect() 
    while 1: 
     client.send_data() 
     time.sleep(5) 
+0

它似乎不是沒有連接的UDP問題。你能顯示導致問題的最小代碼嗎?請嘗試隔離客戶端和服務器上最小的代碼塊。 – 2010-04-05 04:45:51

+0

好吧,我用這個示例代碼很容易地重新創建了它。我不確定我可以在這裏粘貼所有的東西,所以我會使用粘貼位置。 服務器:http://paste-it.net/public/o230dad/ 客戶端:http://paste-it.net/public/k194612/ 基本上,只需打開服務器和兩個客戶端。等待它循環got_data,然後關閉一個客戶端。繁榮,在服務器的套接字錯誤。 – Charles 2010-04-05 05:24:05

+0

也許這個回答很有用。對我來說,這是:http://stackoverflow.com/questions/13844711/udp-hole-punching-c-winsock – 2016-03-29 15:12:50

回答

1

嗯,這似乎是顯而易見的。

  1. UDP沒有連接,所以Client.connect是錯誤的
  2. 要存儲的客戶端地址在Server.connecteddict。當客戶關閉時,沒有人會在那裏接收您發送的內容。

因爲socket庫太低級別(它是C套接字的薄包裝),所以很難獲得網絡通信。代碼中遺漏了很多細節。我建議嘗試更高級別的庫,如twisted。這裏有一些example on UDP讓你開始。

+0

udp沒有連接,但連接有模仿它們的udp套接字的行爲。 – xaxxon 2016-10-20 13:38:12

2

問題就簡單得多了比它的外觀。使用socket.recv()而不是socket.recvfrom() - 我在本地進行了此更改,然後您的代碼就可以工作。

7

首先這可能是特定的平臺,你不提,你運行的平​​臺;然而,10054是WSAECONNRESET所以我猜測某種Windows平臺。

其次如前面所指出的存在與UDP沒有任何聯繫。您在客戶端Connect()調用只是使您的客戶端機器上的網絡代碼,讓你發起Send()呼叫,而不是SendTo()電話和簡單的默認要發送數據時發出Send()調用提供給呼叫的地址到地址到Connect()

第三,我很驚訝你得到WSAECONNRESET而不是ERROR_PORT_UNREACHABLE;然而其根本原因可能是相同的。如果您要發送到的端口上沒有打開的套接字,遠程計算機上的UDP堆棧可能會發送ICMP端口無法訪問錯誤。因此,如果您的客戶端發送數據,然後關閉套接字,然後您的服務器將數據發送回客戶端地址,您將得到一個端口無法訪問,某些版本的Windows可能會將其轉換爲連接重置錯誤...

這些ICMP端口不可達錯誤的問題是,它們通過Winsock代碼通過未通過掛起的UDP Recv/RecvFrom調用進行報告。正如我解釋here和問題here UDP堆棧明顯知道生成端口無法訪問的地址,但它不會將該信息傳遞給調用者,因此沒有任何可以將這些消息映射到有用的東西。您有可能在Vista之前的Windows版本上運行,並且UDP堆棧正在對地址進行一些有用的操作,並且它會將錯誤報告給正確的套接字,但不要在其上下注。

最後你還是有問題; ICMP端口不可達錯誤不能可靠地傳送,所以如果嘗試將UDP數據發送到已消失的客戶端,則無法確定會發生錯誤。恕我直言,這意味着你不應該依賴它,即使它有時候有效。

你顯然是試圖在UDP之上構建某種面向連接的協議(爲什麼你的服務器會保留客戶端的地址)。你必須做更多的事情才能通過UDP創建一個可行的僞連接,並且要實現的第一件事之一是你可以知道客戶端何時離開的唯一方法是設置你自己的超時並斷開連接僞連接,如果你在一段時間內沒有收到它們的消息。

當然這不能回答你的問題;你如何避免這個例外。我希望你不能。異常的原因可能是來自Recv()RecvFrom()調用的「失敗」返回代碼,python網絡代碼可能會將所有此類故障返回轉換爲您的例外情況。

+0

感謝您提供豐富翔實的回覆。 :)我也懷疑我們無法避免這個例外。回答幾個問題:我正在使用Windows 7.另外我們試圖分離IP和套接字#的原因是,如果我們有5個不同的客戶端,它們都將具有單獨的數據和帳戶(服務器端)。如果沒有這些信息,服務器如何區分誰是誰,數據明智?也許我們正在接近這個錯誤的方式? – Charles 2010-04-05 16:17:13

+0

爲什麼你不使用TCP?通過TCP,您可以獲知連接何時不再打開的實際連接。保持與UDP意味着你必須建立自己的連接,所以要麼依賴於客戶端地址和端口不變(這可能是好的,但可能不是與一些NAT),或者你在數據報中嵌入了一個「連接ID」。因爲你有一個不可靠的面向連接的系統,但你無法知道客戶何時離開;所以你需要超時... – 2010-04-05 18:10:46

+0

不幸的是TCP對我們所需要的太慢了,這是一場遊戲。也許解決方案是介於兩者之間的?用於處理登錄信息的TCP和用於處理髮送到遊戲服務器的大部分數據的UDP。 – Charles 2010-04-05 18:39:44