""" The MIT License (MIT) Copyright © 2019 Jean-Christophe Bos & HC² (www.hc2.fr) """ from _thread import allocate_lock, start_new_thread from time import sleep from select import select import socket import ssl try : from time import perf_counter except : from time import ticks_ms def perf_counter() : return ticks_ms() / 1000 # ============================================================================ # ===( XAsyncSocketsPool )==================================================== # ============================================================================ class XAsyncSocketsPoolException(Exception) : pass class XAsyncSocketsPool : def __init__(self) : self._processing = False self._threadsCount = 0 self._opLock = allocate_lock() self._asyncSockets = { } self._readList = [ ] self._writeList = [ ] self._handlingList = [ ] # ------------------------------------------------------------------------ def _incThreadsCount(self) : self._opLock.acquire() self._threadsCount += 1 self._opLock.release() # ------------------------------------------------------------------------ def _decThreadsCount(self) : self._opLock.acquire() self._threadsCount -= 1 self._opLock.release() # ------------------------------------------------------------------------ def _addSocket(self, socket, asyncSocket) : if socket : socketno = id(socket) self._opLock.acquire() ok = (socketno not in self._asyncSockets) if ok : self._asyncSockets[socketno] = asyncSocket self._opLock.release() return ok return False # ------------------------------------------------------------------------ def _removeSocket(self, socket) : if socket : socketno = id(socket) self._opLock.acquire() ok = (socketno in self._asyncSockets) if ok : del self._asyncSockets[socketno] if socket in self._readList : self._readList.remove(socket) if socket in self._writeList : self._writeList.remove(socket) self._opLock.release() return ok return False # ------------------------------------------------------------------------ def _socketListAdd(self, socket, socketsList) : self._opLock.acquire() ok = (id(socket) in self._asyncSockets and socket not in socketsList) if ok : socketsList.append(socket) self._opLock.release() return ok # ------------------------------------------------------------------------ def _socketListRemove(self, socket, socketsList) : self._opLock.acquire() ok = (id(socket) in self._asyncSockets and socket in socketsList) if ok : socketsList.remove(socket) self._opLock.release() return ok # ------------------------------------------------------------------------ _CHECK_SEC_INTERVAL = 1.0 def _processWaitEvents(self) : self._incThreadsCount() timeSec = perf_counter() while self._processing : try : try : rd, wr, ex = select( self._readList, self._writeList, self._readList, self._CHECK_SEC_INTERVAL ) except KeyboardInterrupt as ex : raise ex except : continue if not self._processing : break for socketsList in ex, wr, rd : for socket in socketsList : asyncSocket = self._asyncSockets.get(id(socket), None) if asyncSocket and self._socketListAdd(socket, self._handlingList) : if socketsList is ex : asyncSocket.OnExceptionalCondition() elif socketsList is wr : asyncSocket.OnReadyForWriting() else : asyncSocket.OnReadyForReading() self._socketListRemove(socket, self._handlingList) sec = perf_counter() if sec > timeSec + self._CHECK_SEC_INTERVAL : timeSec = sec for asyncSocket in list(self._asyncSockets.values()) : if asyncSocket.ExpireTimeSec and \ timeSec > asyncSocket.ExpireTimeSec : asyncSocket._close(XClosedReason.Timeout) except KeyboardInterrupt : self._processing = False self._decThreadsCount() # ------------------------------------------------------------------------ def AddAsyncSocket(self, asyncSocket) : try : socket = asyncSocket.GetSocketObj() except : raise XAsyncSocketsPoolException('AddAsyncSocket : "asyncSocket" is incorrect.') return self._addSocket(socket, asyncSocket) # ------------------------------------------------------------------------ def RemoveAsyncSocket(self, asyncSocket) : try : socket = asyncSocket.GetSocketObj() except : raise XAsyncSocketsPoolException('RemoveAsyncSocket : "asyncSocket" is incorrect.') return self._removeSocket(socket) # ------------------------------------------------------------------------ def GetAllAsyncSockets(self) : return list(self._asyncSockets.values()) # ------------------------------------------------------------------------ def GetAsyncSocketByID(self, id) : return self._asyncSockets.get(id, None) # ------------------------------------------------------------------------ def NotifyNextReadyForReading(self, asyncSocket, notify) : try : socket = asyncSocket.GetSocketObj() except : raise XAsyncSocketsPoolException('NotifyNextReadyForReading : "asyncSocket" is incorrect.') if notify : self._socketListAdd(socket, self._readList) else : self._socketListRemove(socket, self._readList) # ------------------------------------------------------------------------ def NotifyNextReadyForWriting(self, asyncSocket, notify) : try : socket = asyncSocket.GetSocketObj() except : raise XAsyncSocketsPoolException('NotifyNextReadyForWriting : "asyncSocket" is incorrect.') if notify : self._socketListAdd(socket, self._writeList) else : self._socketListRemove(socket, self._writeList) # ------------------------------------------------------------------------ def AsyncWaitEvents(self, threadsCount=0) : if self._processing or self._threadsCount : return self._processing = True if threadsCount > 0 : try : for i in range(threadsCount) : start_new_thread(self._processWaitEvents, ()) while self._processing and self._threadsCount < threadsCount : sleep(0.001) except : self._processing = False raise XAsyncSocketsPoolException('AsyncWaitEvents : Fatal error to create new threads...') else : self._processWaitEvents() # ------------------------------------------------------------------------ def StopWaitEvents(self) : self._processing = False while self._threadsCount : sleep(0.001) # ------------------------------------------------------------------------ @property def WaitEventsProcessing(self) : return (self._threadsCount > 0) # ============================================================================ # ===( XClosedReason )======================================================== # ============================================================================ class XClosedReason() : Error = 0x00 ClosedByHost = 0x01 ClosedByPeer = 0x02 Timeout = 0x03 # ============================================================================ # ===( XAsyncSocket )========================================================= # ============================================================================ class XAsyncSocketException(Exception) : pass class XAsyncSocket : def __init__(self, asyncSocketsPool, socket, recvBufSlot=None, sendBufSlot=None) : if type(self) is XAsyncSocket : raise XAsyncSocketException('XAsyncSocket is an abstract class and must be implemented.') self._asyncSocketsPool = asyncSocketsPool self._socket = socket self._recvBufSlot = recvBufSlot self._sendBufSlot = sendBufSlot self._expireTimeSec = None self._state = None self._onClosed = None try : socket.settimeout(0) socket.setblocking(0) if (recvBufSlot is not None and type(recvBufSlot) is not XBufferSlot) or \ (sendBufSlot is not None and type(sendBufSlot) is not XBufferSlot) : raise Exception() asyncSocketsPool.AddAsyncSocket(self) except : raise XAsyncSocketException('XAsyncSocket : Arguments are incorrects.') # ------------------------------------------------------------------------ def _setExpireTimeout(self, timeoutSec) : try : if timeoutSec and timeoutSec > 0 : self._expireTimeSec = perf_counter() + timeoutSec except : raise XAsyncSocketException('"timeoutSec" is incorrect to set expire timeout.') # ------------------------------------------------------------------------ def _removeExpireTimeout(self) : self._expireTimeSec = None # ------------------------------------------------------------------------ def _close(self, closedReason=XClosedReason.Error, triggerOnClosed=True) : if self._asyncSocketsPool.RemoveAsyncSocket(self) : try : self._socket.close() except : pass self._socket = None if self._recvBufSlot is not None : self._recvBufSlot.Available = True self._recvBufSlot = None if self._sendBufSlot is not None : self._sendBufSlot.Available = True self._sendBufSlot = None if triggerOnClosed and self._onClosed : try : self._onClosed(self, closedReason) except Exception as ex : raise XAsyncSocketException('Error when handling the "OnClose" event : %s' % ex) return True return False # ------------------------------------------------------------------------ def GetAsyncSocketsPool(self) : return self._asyncSocketsPool # ------------------------------------------------------------------------ def GetSocketObj(self) : return self._socket # ------------------------------------------------------------------------ def Close(self) : return self._close(XClosedReason.ClosedByHost) # ------------------------------------------------------------------------ def OnReadyForReading(self) : pass # ------------------------------------------------------------------------ def OnReadyForWriting(self) : pass # ------------------------------------------------------------------------ def OnExceptionalCondition(self) : self._close() # ------------------------------------------------------------------------ @property def SocketID(self) : return id(self._socket) if self._socket else None @property def ExpireTimeSec(self) : return self._expireTimeSec @property def OnClosed(self) : return self._onClosed @OnClosed.setter def OnClosed(self, value) : self._onClosed = value @property def State(self) : return self._state @State.setter def State(self, value) : self._state = value # ============================================================================ # ===( XAsyncTCPServer )====================================================== # ============================================================================ class XAsyncTCPServerException(Exception) : pass class XAsyncTCPServer(XAsyncSocket) : @staticmethod def Create(asyncSocketsPool, srvAddr, srvBacklog=256, bufSlots=None) : try : srvSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) except : raise XAsyncTCPServerException('Create : Cannot open socket (no enought memory).') try : srvSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) srvSocket.bind(srvAddr) srvSocket.listen(srvBacklog) except : raise XAsyncTCPServerException('Create : Error to binding the TCP server on this address.') if not bufSlots : bufSlots = XBufferSlots(256, 4096, keepAlloc=True) xAsyncTCPServer = XAsyncTCPServer( asyncSocketsPool, srvSocket, srvAddr, bufSlots ) asyncSocketsPool.NotifyNextReadyForReading(xAsyncTCPServer, True) return xAsyncTCPServer # ------------------------------------------------------------------------ def __init__(self, asyncSocketsPool, srvSocket, srvAddr, bufSlots) : try : super().__init__(asyncSocketsPool, srvSocket) self._srvAddr = srvAddr self._bufSlots = bufSlots self._onClientAccepted = None except : raise XAsyncTCPServerException('Error to creating XAsyncTCPServer, arguments are incorrects.') # ------------------------------------------------------------------------ def OnReadyForReading(self) : try : cliSocket, cliAddr = self._socket.accept() except : return recvBufSlot = self._bufSlots.GetAvailableSlot() sendBufSlot = self._bufSlots.GetAvailableSlot() if not recvBufSlot or not sendBufSlot or not self._onClientAccepted : if recvBufSlot : recvBufSlot.Available = True if sendBufSlot : sendBufSlot.Available = True cliSocket.close() return asyncTCPCli = XAsyncTCPClient( self._asyncSocketsPool, cliSocket, self._srvAddr, cliAddr, recvBufSlot, sendBufSlot ) try : self._onClientAccepted(self, asyncTCPCli) except Exception as ex : asyncTCPCli._close() raise XAsyncTCPServerException('Error when handling the "OnClientAccepted" event : %s' % ex) # ------------------------------------------------------------------------ @property def SrvAddr(self) : return self._srvAddr @property def OnClientAccepted(self) : return self._onClientAccepted @OnClientAccepted.setter def OnClientAccepted(self, value) : self._onClientAccepted = value # ============================================================================ # ===( XAsyncTCPClient )====================================================== # ============================================================================ class XAsyncTCPClientException(Exception) : pass class XAsyncTCPClient(XAsyncSocket) : @staticmethod def Create( asyncSocketsPool, srvAddr, connectTimeout = 5, recvBufLen = 4096, sendBufLen = 4096, connectAsync = True ) : try : size = max(256, recvBufLen) recvBufSlot = XBufferSlot(size=size, keepAlloc=True) size = max(256, sendBufLen) sendBufSlot = XBufferSlot(size=size, keepAlloc=True) except : raise XAsyncTCPClientException('Create : Out of memory?') try : cliSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) except : raise XAsyncTCPClientException('Create : Cannot open socket (no enought memory).') asyncTCPCli = XAsyncTCPClient( asyncSocketsPool, cliSocket, srvAddr, None, recvBufSlot, sendBufSlot ) ok = False try : if connectAsync and hasattr(cliSocket, 'connect_ex') : errno = cliSocket.connect_ex(srvAddr) if errno == 0 or errno == 36 : asyncTCPCli._setExpireTimeout(connectTimeout) ok = True else : try : addr = socket.getaddrinfo( srvAddr[0], srvAddr[1], socket.AF_INET ) except : addr = socket.getaddrinfo( srvAddr[0], srvAddr[1] ) addr = addr[0][-1] if connectAsync : asyncTCPCli._setExpireTimeout(connectTimeout) else : cliSocket.settimeout(connectTimeout) cliSocket.setblocking(1) try : cliSocket.connect(srvAddr) except OSError as ex : if not connectAsync or str(ex) != '119' : raise ex if not connectAsync : cliSocket.settimeout(0) cliSocket.setblocking(0) ok = True except : pass if ok : asyncSocketsPool.NotifyNextReadyForWriting(asyncTCPCli, True) return asyncTCPCli asyncTCPCli._close() return None # ------------------------------------------------------------------------ def __init__(self, asyncSocketsPool, cliSocket, srvAddr, cliAddr, recvBufSlot, sendBufSlot) : try : super().__init__(asyncSocketsPool, cliSocket, recvBufSlot, sendBufSlot) self._srvAddr = srvAddr self._cliAddr = cliAddr if cliAddr else ('0.0.0.0', 0) self._onFailsToConnect = None self._onConnected = None self._onDataRecv = None self._onDataRecvArg = None self._onDataSent = None self._onDataSentArg = None self._sizeToRecv = None self._rdLinePos = None self._rdLineEncoding = None self._rdBufView = None self._wrBufView = None self._socketOpened = (cliAddr is not None) except : raise XAsyncTCPClientException('Error to creating XAsyncTCPClient, arguments are incorrects.') # ------------------------------------------------------------------------ def Close(self) : if self._wrBufView : try : self._socket.send(self._wrBufView) except : pass try : self._socket.shutdown(socket.SHUT_RDWR) except : pass return self._close(XClosedReason.ClosedByHost) # ------------------------------------------------------------------------ def OnReadyForReading(self) : while True : if self._rdLinePos is not None : # In the context of reading a line, while True : try : try : b = self._socket.recv(1) except ssl.SSLError as sslErr : if sslErr.args[0] != ssl.SSL_ERROR_WANT_READ : self._close() return except BlockingIOError as bioErr : if bioErr.errno != 35 : self._close() return except : self._close() return except : self._close() return if b : if b == b'\n' : lineLen = self._rdLinePos self._rdLinePos = None self._asyncSocketsPool.NotifyNextReadyForReading(self, False) self._removeExpireTimeout() if self._onDataRecv : line = self._recvBufSlot.Buffer[:lineLen] try : line = bytes(line).decode(self._rdLineEncoding) except : line = None try : self._onDataRecv(self, line, self._onDataRecvArg) except Exception as ex : raise XAsyncTCPClientException('Error when handling the "OnDataRecv" event : %s' % ex) if self.IsSSL and self._socket.pending() > 0 : break return elif b != b'\r' : if self._rdLinePos < self._recvBufSlot.Size : self._recvBufSlot.Buffer[self._rdLinePos] = ord(b) self._rdLinePos += 1 else : self._close() return else : self._close(XClosedReason.ClosedByPeer) return elif self._sizeToRecv : # In the context of reading data, recvBuf = self._rdBufView[-self._sizeToRecv:] try : try : n = self._socket.recv_into(recvBuf) except ssl.SSLError as sslErr : if sslErr.args[0] != ssl.SSL_ERROR_WANT_READ : self._close() return except BlockingIOError as bioErr : if bioErr.errno != 35 : self._close() return except : self._close() return except : try : n = self._socket.readinto(recvBuf) except : self._close() return if not n : self._close(XClosedReason.ClosedByPeer) return self._sizeToRecv -= n if not self._sizeToRecv : data = self._rdBufView self._rdBufView = None self._asyncSocketsPool.NotifyNextReadyForReading(self, False) self._removeExpireTimeout() if self._onDataRecv : try : self._onDataRecv(self, data, self._onDataRecvArg) except Exception as ex : raise XAsyncTCPClientException('Error when handling the "OnDataRecv" event : %s' % ex) if not self.IsSSL or self._socket.pending() == 0 : return else : return # ------------------------------------------------------------------------ def OnReadyForWriting(self) : if not self._socketOpened : if hasattr(self._socket, "getsockopt") : if self._socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) : self._close(XClosedReason.Error, triggerOnClosed=False) if self._onFailsToConnect : try : self._onFailsToConnect(self) except Exception as ex : raise XAsyncTCPClientException('Error when handling the "OnFailsToConnect" event : %s' % ex) return self._cliAddr = self._socket.getsockname() self._removeExpireTimeout() self._socketOpened = True if self._onConnected : try : self._onConnected(self) except Exception as ex : raise XAsyncTCPClientException('Error when handling the "OnConnected" event : %s' % ex) if self._wrBufView : try : n = self._socket.send(self._wrBufView) except : return self._wrBufView = self._wrBufView[n:] if not self._wrBufView : self._asyncSocketsPool.NotifyNextReadyForWriting(self, False) if self._onDataSent : try : self._onDataSent(self, self._onDataSentArg) except Exception as ex : raise XAsyncTCPClientException('Error when handling the "OnDataSent" event : %s' % ex) # ------------------------------------------------------------------------ def AsyncRecvLine(self, lineEncoding='UTF-8', onLineRecv=None, onLineRecvArg=None, timeoutSec=None) : if self._rdLinePos is not None or self._sizeToRecv : raise XAsyncTCPClientException('AsyncRecvLine : Already waiting asynchronous receive.') if self._socket : self._setExpireTimeout(timeoutSec) self._rdLinePos = 0 self._rdLineEncoding = lineEncoding self._onDataRecv = onLineRecv self._onDataRecvArg = onLineRecvArg self._asyncSocketsPool.NotifyNextReadyForReading(self, True) return True return False # ------------------------------------------------------------------------ def AsyncRecvData(self, size=None, onDataRecv=None, onDataRecvArg=None, timeoutSec=None) : if self._rdLinePos is not None or self._sizeToRecv : raise XAsyncTCPClientException('AsyncRecvData : Already waiting asynchronous receive.') if self._socket : if size is None : size = self._recvBufSlot.Size elif not isinstance(size, int) or size <= 0 : raise XAsyncTCPClientException('AsyncRecvData : "size" is incorrect.') if size <= self._recvBufSlot.Size : self._rdBufView = memoryview(self._recvBufSlot.Buffer)[:size] else : try : self._rdBufView = memoryview(bytearray(size)) except : raise XAsyncTCPClientException('AsyncRecvData : No enought memory to receive %s bytes.' % size) self._setExpireTimeout(timeoutSec) self._sizeToRecv = size self._onDataRecv = onDataRecv self._onDataRecvArg = onDataRecvArg self._asyncSocketsPool.NotifyNextReadyForReading(self, True) return True return False # ------------------------------------------------------------------------ def AsyncSendData(self, data, onDataSent=None, onDataSentArg=None) : if self._socket : try : if bytes([data[0]]) : if self._wrBufView : self._wrBufView = memoryview(bytes(self._wrBufView) + data) else : self._wrBufView = memoryview(data) self._onDataSent = onDataSent self._onDataSentArg = onDataSentArg self._asyncSocketsPool.NotifyNextReadyForWriting(self, True) return True except : pass raise XAsyncTCPClientException('AsyncSendData : "data" is incorrect.') return False # ------------------------------------------------------------------------ def AsyncSendSendingBuffer(self, size=None, onDataSent=None, onDataSentArg=None) : if self._wrBufView : raise XAsyncTCPClientException('AsyncSendBufferSlot : Already waiting to send data.') if self._socket : if size is None : size = self._sendBufSlot.Size if size > 0 and size <= self._sendBufSlot.Size : self._wrBufView = memoryview(self._sendBufSlot.Buffer)[:size] self._onDataSent = onDataSent self._onDataSentArg = onDataSentArg self._asyncSocketsPool.NotifyNextReadyForWriting(self, True) return True return False # ------------------------------------------------------------------------ def _doSSLHandshake(self) : count = 0 while count < 10 : try : self._socket.do_handshake() break except ssl.SSLError as sslErr : count += 1 if sslErr.args[0] == ssl.SSL_ERROR_WANT_READ : select([self._socket], [], [], 1) elif sslErr.args[0] == ssl.SSL_ERROR_WANT_WRITE : select([], [self._socket], [], 1) else : raise XAsyncTCPClientException('SSL : Bad handshake : %s' % sslErr) except Exception as ex : raise XAsyncTCPClientException('SSL : Handshake error : %s' % ex) # ------------------------------------------------------------------------ def StartSSL( self, keyfile = None, certfile = None, server_side = False, cert_reqs = 0, ca_certs = None ) : if not hasattr(ssl, 'SSLContext') : raise XAsyncTCPClientException('StartSSL : This SSL implementation is not supported.') if self.IsSSL : raise XAsyncTCPClientException('StartSSL : SSL already started.') try : self._asyncSocketsPool.NotifyNextReadyForWriting(self, False) self._asyncSocketsPool.NotifyNextReadyForReading(self, False) self._socket = ssl.wrap_socket( self._socket, keyfile = keyfile, certfile = certfile, server_side = server_side, cert_reqs = cert_reqs, ca_certs = ca_certs, do_handshake_on_connect = False ) except Exception as ex : raise XAsyncTCPClientException('StartSSL : %s' % ex) self._doSSLHandshake() # ------------------------------------------------------------------------ def StartSSLContext(self, sslContext, serverSide=False) : if not hasattr(ssl, 'SSLContext') : raise XAsyncTCPClientException('StartSSLContext : This SSL implementation is not supported.') if not isinstance(sslContext, ssl.SSLContext) : raise XAsyncTCPClientException('StartSSLContext : "sslContext" is incorrect.') if self.IsSSL : raise XAsyncTCPClientException('StartSSLContext : SSL already started.') try : self._asyncSocketsPool.NotifyNextReadyForWriting(self, False) self._asyncSocketsPool.NotifyNextReadyForReading(self, False) self._socket = sslContext.wrap_socket( self._socket, server_side = serverSide, do_handshake_on_connect = False ) except Exception as ex : raise XAsyncTCPClientException('StartSSLContext : %s' % ex) self._doSSLHandshake() # ------------------------------------------------------------------------ @property def SrvAddr(self) : return self._srvAddr @property def CliAddr(self) : return self._cliAddr @property def IsSSL(self) : return ( hasattr(ssl, 'SSLContext') and \ isinstance(self._socket, ssl.SSLSocket) ) @property def SendingBuffer(self) : return self._sendBufSlot.Buffer @property def OnFailsToConnect(self) : return self._onFailsToConnect @OnFailsToConnect.setter def OnFailsToConnect(self, value) : self._onFailsToConnect = value @property def OnConnected(self) : return self._onConnected @OnConnected.setter def OnConnected(self, value) : self._onConnected = value # ============================================================================ # ===( XAsyncUDPDatagram )==================================================== # ============================================================================ class XAsyncUDPDatagramException(Exception) : pass class XAsyncUDPDatagram(XAsyncSocket) : @staticmethod def Create(asyncSocketsPool, localAddr=None, recvBufLen=4096, broadcast=False) : try : udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) except : raise XAsyncUDPDatagramException('Create : Cannot open socket (no enought memory).') if broadcast : udpSocket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) openRecv = (localAddr is not None) if openRecv : try : udpSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) udpSocket.bind(localAddr) except : raise XAsyncUDPDatagramException('Create : Error to binding the UDP Datagram local address.') try : size = max(256, recvBufLen) recvBufSlot = XBufferSlot(size=size, keepAlloc=False) except : raise XAsyncUDPDatagramException('Create : Out of memory?') else : recvBufSlot = None xAsyncUDPDatagram = XAsyncUDPDatagram(asyncSocketsPool, udpSocket, recvBufSlot) if openRecv : asyncSocketsPool.NotifyNextReadyForReading(xAsyncUDPDatagram, True) return xAsyncUDPDatagram # ------------------------------------------------------------------------ def __init__(self, asyncSocketsPool, udpSocket, recvBufSlot) : try : super().__init__(asyncSocketsPool, udpSocket, recvBufSlot, None) self._wrDgramFiFo = XFiFo() self._onFailsToSend = None self._onDataSent = None self._onDataSentArg = None self._onDataRecv = None except : raise XAsyncUDPDatagramException('Error to creating XAsyncUDPDatagram, arguments are incorrects.') # ------------------------------------------------------------------------ def OnReadyForReading(self) : try : n, remoteAddr = self._socket.recvfrom_into(self._recvBufSlot.Buffer) datagram = memoryview(self._recvBufSlot.Buffer)[:n] except : try : buf, remoteAddr = self._socket.recvfrom(self._recvBufSlot.Size) datagram = memoryview(buf) except : return if self._onDataRecv : try : self._onDataRecv(self, remoteAddr, datagram) except Exception as ex : raise XAsyncUDPDatagramException('Error when handling the "OnDataRecv" event : %s' % ex) # ------------------------------------------------------------------------ def OnReadyForWriting(self) : if not self._wrDgramFiFo.Empty : datagram = None remoteAddr = ('0.0.0.0', 0) try : datagram, remoteAddr = self._wrDgramFiFo.Get() self._socket.sendto(datagram, remoteAddr) except : if self._onFailsToSend : try : self._onFailsToSend(self, datagram, remoteAddr) except Exception as ex : raise XAsyncUDPDatagramException('Error when handling the "OnFailsToSend" event : %s' % ex) if not self._wrDgramFiFo.Empty : return self._asyncSocketsPool.NotifyNextReadyForWriting(self, False) if self._onDataSent : try : self._onDataSent(self, self._onDataSentArg) except Exception as ex : raise XAsyncUDPDatagramException('Error when handling the "OnDataSent" event : %s' % ex) # ------------------------------------------------------------------------ def AsyncSendDatagram(self, datagram, remoteAddr, onDataSent=None, onDataSentArg=None) : if self._socket : try : if bytes([datagram[0]]) and len(remoteAddr) == 2 : self._wrDgramFiFo.Put( (datagram, remoteAddr) ) self._onDataSent = onDataSent self._onDataSentArg = onDataSentArg self._asyncSocketsPool.NotifyNextReadyForWriting(self, True) return True except : pass raise XAsyncUDPDatagramException('AsyncSendDatagram : Arguments are incorrects.') return False # ------------------------------------------------------------------------ @property def LocalAddr(self) : try : return self._socket.getsockname() except : return ('0.0.0.0', 0) @property def OnDataRecv(self) : return self._onDataRecv @OnDataRecv.setter def OnDataRecv(self, value) : self._onDataRecv = value @property def OnFailsToSend(self) : return self._onFailsToSend @OnFailsToSend.setter def OnFailsToSend(self, value) : self._onFailsToSend = value # ============================================================================ # ===( XBufferSlot )========================================================== # ============================================================================ class XBufferSlot : def __init__(self, size, keepAlloc=True) : self._available = True self._size = size self._keepAlloc = keepAlloc self._buffer = bytearray(size) if keepAlloc else None @property def Available(self) : return self._available @Available.setter def Available(self, value) : if value and not self._keepAlloc : self._buffer = None self._available = value @property def Size(self) : return self._size @property def Buffer(self) : self._available = False if self._buffer is None : self._buffer = bytearray(self._size) return self._buffer # ============================================================================ # ===( XBufferSlots )========================================================= # ============================================================================ class XBufferSlots : def __init__(self, slotsCount, slotsSize, keepAlloc=True) : self._slotsCount = slotsCount self._slotsSize = slotsSize self._slots = [ ] self._lock = allocate_lock() for i in range(slotsCount) : self._slots.append(XBufferSlot(slotsSize, keepAlloc)) def GetAvailableSlot(self) : ret = None self._lock.acquire() for slot in self._slots : if slot.Available : slot.Available = False ret = slot break self._lock.release() return ret @property def SlotsCount(self) : return self.slotsCount @property def SlotsSize(self) : return self.slotsSize @property def Slots(self) : return self._slots # ============================================================================ # ===( XFiFo )================================================================ # ============================================================================ class XFiFoException(Exception) : pass class XFiFo : def __init__(self) : self._lock = allocate_lock() self._first = None self._last = None def Put(self, obj) : self._lock.acquire() if self._first : self._last[1] = [obj, None] self._last = self._last[1] else : self._last = [obj, None] self._first = self._last self._lock.release() def Get(self) : self._lock.acquire() if self._first : obj = self._first[0] self._first = self._first[1] self._lock.release() return obj else : self._lock.release() raise XFiFoException('Get : XFiFo is empty.') def Clear(self) : self._first = None self._last = None @property def Empty(self) : return (self._first is None) # ============================================================================ # ============================================================================ # ============================================================================