關於連接埠複用
一個通訊端不能同時綁定多個連接埠,如果用戶端想綁定連接埠號碼,一定要調用發送資訊函數之前綁定( bind )連接埠,因為在發送資訊函數( sendto, 或 write ),系統會自動給當前網路程式分配一個隨機連接埠號碼,這相當於隨機綁定了一個連接埠號碼,這裡只會分配一次,以後通訊就以這個隨機連接埠通訊,我們再綁定連接埠號碼的話,就會綁定失敗。如果我們放在發送資訊函數( sendto, 或 write )之前綁定,那樣程式將以我們綁定的連接埠號碼發送資訊,不會再隨機分配一個連接埠號碼。實際上,預設的情況下,如果一個網路應用程式的一個通訊端 綁定了一個連接埠,這時候,別的通訊端就無法使用這個連接埠。那如何讓兩個通訊端都能成功綁定一個連接埠呢?這時候就需要要到連接埠複用了。連接埠複用允許在一個應用程式可以把 n 個通訊端綁在一個連接埠上而不出錯。
連接埠複用能在系統已開放的連接埠上進行通訊,只對輸入的資訊進行字元匹配,不對網路資料進行任何攔截、複製類操作,所以對網路資料的傳輸效能絲毫不受影響。
但要注意,建立串連後服務端程式佔用極少系統資源,被控端不會在系統效能上有任何察覺,通常被後門木馬所利用。
在winsock的實現中,對於伺服器的綁定是可以多重綁定的,在確定多重綁定使用誰的時候,根據一條原則是誰的指定最明確則將包遞交給誰,而且沒有許可權之分,也就是說低級許可權的使用者是可以重綁定在進階許可權如服務啟動的連接埠上的,這是非常重大的一個安全隱患。
Python解決UDP連接埠複用問題
一直覺得UDP協議很簡單,但是今天問題讓我感覺到網路的基礎真是博大精深。
廢話少說,來看問題吧。由於協議的需要,我得實現一個UDP的用戶端和伺服器端,並且從同一個連接埠讀寫資料。
最初不以為然,無非就是用兩個socket,一個監聽並從這個連接埠讀取資料(伺服器端採用了twisted),另一個向這個連接埠寫入資料,用python實現只要10行左右的代碼。
def startServer(queue, port): reactor.listenUDP(port, DhtResponseHandler(queue)) reactor.run() def sendUdpMsg(self, addr, msg): socketHandler = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) socketHandler.bind(("", self.port)) socketHandler.sendto(msg, addr) socketHandler.close()
由於要向同一個連接埠寫資料,於是client必須有bind,但是運行後發現server先bind了這個連接埠,client運行時會報錯
複製代碼 代碼如下:
error: [Errno 10048] Only one usage of each socket address (protocol/network address/port) is normally permitted
一般這種錯誤時因為多個socket不能同時bind同一個地址
由於基礎不夠紮實,我開始瘋狂的搜尋,發現有人說連接埠複用的問題,所謂的連接埠複用,是指一個通訊端釋放掉一個連接埠後有一個wait_time,另一個通訊端如果接著bind就會報錯。雖然我的問題不完全一樣,但是我欣喜若狂的使用了。即在client bind前加上如下一句
socketHandler.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
但是仍然報錯:
複製代碼 代碼如下:
error: [Errno 10013] An attempt was made to access a socket in a way forbidden by its access permissions
(順便一提,還有另一個參數叫SO_REUSEPORT,即複用連接埠,另外有一個叫SO_EXCLUSIVEADDRUSE,即不準複用該連接埠,其他socket的參數還有很多,可以參考winsockhttp://msdn.microsoft.com/en-us/library/aa924071.aspx或者unix下的socket)
這個10013錯誤讓我百思不得其解,搜尋一下,主要有兩種解釋,有人說是需要提升應用程式的許可權為管理員,我用的是eclipse+pydev,提升完eclipse許可權沒用,實際上還要修改python.exe的許可權,方法是在這個程式上右鍵,相容性一欄中勾上以系統管理員身份運行;有人說是跟其他程式地址或者連接埠衝突。但是我測試過發現都不行。
另外,啟動並執行時候發現,twisted的伺服器端一定是要在主線程中,否則會報signal一定要在主線程才能接受的錯誤,但是twisted的reactor一運行起來就阻塞了。
在twisted文檔中翻到,原來還有一種UDP叫做connected UDP,變態吧,所謂connected UDP,就是只能向一個地址收發資料,看起來貌似可以,但是不符合可以向多個地址接收資料。
最後在一篇文章中翻到說需要兩個連接埠都設定重用,於是我試著重新寫一個伺服器,與之前的用戶端配合,運行良好,完全無錯
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(("", port)) data, address = sock.recvfrom(4096)
好吧,看來問題在調用twisted了,不知道他是否有這樣的設定,進去將這部分代碼翻了一下,找不到這樣設定的參數。
class Port(abstract.FileHandle): def __init__(self, port, proto, interface='', maxPacketSize=8192, reactor=None): """ Initialize with a numeric port to listen on. """ self.port = port self.protocol = proto self.readBufferSize = maxPacketSize self.interface = interface self.setLogStr() self._connectedAddr = None abstract.FileHandle.__init__(self, reactor) skt = socket.socket(self.addressFamily, self.socketType) addrLen = _iocp.maxAddrLen(skt.fileno()) self.addressBuffer = _iocp.AllocateReadBuffer(addrLen) # WSARecvFrom takes an int self.addressLengthBuffer = _iocp.AllocateReadBuffer( struct.calcsize('i')) def startListening(self): """ Create and bind my socket, and begin listening on it. This is called on unserialization, and must be called after creating a server to begin listening on the specified port. """ self._bindSocket() self._connectToProtocol() def createSocket(self): return self.reactor.createSocket(self.addressFamily, self.socketType) def _bindSocket(self): try: skt = self.createSocket() skt.bind((self.interface, self.port)) except socket.error, le: raise error.CannotListenError, (self.interface, self.port, le) # Make sure that if we listened on port 0, we update that to # reflect what the OS actually assigned us. self._realPortNumber = skt.getsockname()[1] log.msg("%s starting on %s" % ( self._getLogPrefix(self.protocol), self._realPortNumber)) self.connected = True self.socket = skt self.getFileHandle = self.socket.fileno
難道說twisted就完全不提供這樣的功能?最終在multicast中翻到這樣一段,也就是,多播的情況是支援地址複用的,動手測起來。
class MulticastPort(MulticastMixin, Port): """ UDP Port that supports multicasting. """ implements(interfaces.IMulticastTransport) def __init__(self, port, proto, interface='', maxPacketSize=8192, reactor=None, listenMultiple=False): Port.__init__(self, port, proto, interface, maxPacketSize, reactor) self.listenMultiple = listenMultiple def createSocket(self): skt = Port.createSocket(self) if self.listenMultiple: skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) if hasattr(socket, "SO_REUSEPORT"): skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) return skt
將server端改成如下代碼,運行通過!
reactor.listenMulticast(port, DhtResponseHandler(queue), listenMultiple=True) reactor.run()
感觸良多,底層的知識比較重要,浮沙築高台果然危險。