Das folgende Include ist bei der Entwicklung von EProxy entstanden, ist aber meiner Meinung nach inzwischen umfangreich genug für eine eigene Veröffentlichung.
Das SafeNetwork.pbi ...
- Erweitert die Das NetworkClientEvent() um das #PB_NetworkEvent_Disconnect
- Macht sämtliche Netzwerkbefehle Threadsicher
- Macht sämtliche Netzwerkbefehle 'kugelsicher' (ungültige Handles führen nicht zum Absturz)
- Optional, per CompilerSwitch: Wrappt die Verbindungshandles von SpeicherAdressen auf fortlaufende Nummern, nützlich für die Verwaltung
SafeNetwork.pbi
Code: Alles auswählen
; ------------------------------------------------------------------------------------
; Sichert die PB - Netzwerkbefehle ab
; ------------------------------------------------------------------------------------
; Client #PB_Network_Event_Disconnect support by DarkPlayer, PureFan
; http://www.purebasic.fr/english/viewtopic.php?f=12&t=42559
; Muss aktiviert werden falls UDP verwendet werden soll
#SafeNetwork_UDPWorkAround = #False
CompilerIf #SafeNetwork_UDPWorkAround = #True
; NICHT ÄNDERN! Muss für den UDP-WA abgeschaltet sein!
#SafeNetwork_UseVId = #False
CompilerElse
; Abstrahiert die Connection - Handes. (1,2,3,4, ... statt Speicheradressen, werden nicht wiederverwendet. Nützlich für Threads)
#SafeNetwork_UseVId = #False
CompilerEndIf
;{ Client - #PB_NetworkEvent_Disconnect - Erkennung
CompilerIf #PB_Compiler_OS = #PB_OS_Linux ;{
#FIONREAD = $541B
#__FD_SETSIZE = 1024
#__NFDBITS = 8 * SizeOf(LONG)
Macro __FDELT(d)
((d) / #__NFDBITS)
EndMacro
Macro __FDMASK(d)
(1 << ((d) % #__NFDBITS))
EndMacro
Structure FD_SET
fds_bits.l[#__FD_SETSIZE / #__NFDBITS]
EndStructure
Procedure.i __FD_SET(d.i, *set.FD_SET)
If d >= 0 And d < #__FD_SETSIZE
*set\fds_bits[__FDELT(d)] | __FDMASK(d)
EndIf
EndProcedure
Procedure.i __FD_ISSET(d.i, *set.FD_SET)
If d >= 0 And d < #__FD_SETSIZE
ProcedureReturn *set\fds_bits[__FDELT(d)] & __FDMASK(d)
EndIf
EndProcedure
Procedure.i __FD_ZERO(*set.FD_SET)
FillMemory(*set, SizeOf(FD_SET), 0, #PB_Byte)
EndProcedure
#FD_SETSIZE = #__FD_SETSIZE
#NFDBITS = #__NFDBITS
Macro FD_SET(fd, fdsetp)
__FD_SET(fd, fdsetp)
EndMacro
Macro FD_ISSET(fd, fdsetp)
__FD_ISSET(fd, fdsetp)
EndMacro
Macro FD_ZERO(fdsetp)
__FD_ZERO(fdsetp)
EndMacro
; Returns the minimum value for NFDS
Procedure.i _NFDS(*set.FD_SET)
Protected I.i, J.i
For I = SizeOf(FD_SET)/SizeOf(LONG) - 1 To 0 Step -1
If *set\fds_bits[I]
For J = (#__NFDBITS - 1) To 0 Step -1
If *set\fds_bits[I] & (1 << J)
ProcedureReturn I * #__NFDBITS + J + 1
EndIf
Next
EndIf
Next
ProcedureReturn 0
EndProcedure
;}
CompilerEndIf
CompilerIf #PB_Compiler_OS = #PB_OS_MacOS ;{
#IOC_OUT = $40000000 ;(__uint32_t)
Macro _IOR(g,n,t)
_IOC(#IOC_OUT, (g), (n), (t))
EndMacro
#IOCPARM_MASK = $1fff
Macro _IOC(inout,group,num,len)
((inout) | (((len) & #IOCPARM_MASK) << 16) | ((group) << 8) | (num))
EndMacro
#FIONREAD = _IOR('f', 127, SizeOf(LONG))
#__DARWIN_FD_SETSIZE = 1024
#__DARWIN_NBBY = 8
#__DARWIN_NFDBITS = SizeOf(LONG) * #__DARWIN_NBBY
Structure FD_SET
fds_bits.l[ (#__DARWIN_FD_SETSIZE + #__DARWIN_NFDBITS - 1) / #__DARWIN_NFDBITS ]
EndStructure
Procedure.i __DARWIN_FD_SET(fd.i, *p.FD_SET)
If fd >= 0 And fd < #__DARWIN_FD_SETSIZE
*p\fds_bits[fd / #__DARWIN_NFDBITS] | (1 << (fd % #__DARWIN_NFDBITS))
EndIf
EndProcedure
Procedure.i __DARWIN_FD_ISSET(fd.i, *p.FD_SET)
If fd >= 0 And fd < #__DARWIN_FD_SETSIZE
ProcedureReturn *p\fds_bits[fd / #__DARWIN_NFDBITS] & (1 << (fd % #__DARWIN_NFDBITS))
EndIf
EndProcedure
Procedure.i __DARWIN_FD_ZERO(*p.FD_SET)
FillMemory(*p, SizeOf(FD_SET), 0, #PB_Byte)
EndProcedure
#FD_SETSIZE = #__DARWIN_FD_SETSIZE
Macro FD_SET(n, p)
__DARWIN_FD_SET(n, p)
EndMacro
Macro FD_ISSET(n, p)
__DARWIN_FD_ISSET(n, p)
EndMacro
Macro FD_ZERO(p)
__DARWIN_FD_ZERO(p)
EndMacro
; Returns the minimum value for NFDS
Procedure.i _NFDS(*p.FD_SET)
Protected I.i, J.i
For I = SizeOf(FD_SET)/SizeOf(LONG) - 1 To 0 Step -1
If *p\fds_bits[I]
For J = (#__DARWIN_NFDBITS - 1) To 0 Step -1
If *p\fds_bits[I] & (1 << J)
ProcedureReturn I * #__DARWIN_NFDBITS + J + 1
EndIf
Next
EndIf
Next
ProcedureReturn 0
EndProcedure
;}
CompilerEndIf
CompilerIf #PB_Compiler_OS = #PB_OS_Windows ;{
; #FIONREAD is already defined
; FD_SET is already defined
Structure _fd_set
fd_count.i
fd_array.i[#FD_SETSIZE]
EndStructure
Macro fd_set
_fd_set
EndMacro
Macro FD_ZERO(set)
set\fd_count = 0
EndMacro
Procedure.i FD_SET(fd.i, *set.FD_SET)
If *set\fd_count < #FD_SETSIZE
*set\fd_array[ *set\fd_count ] = fd
*set\fd_count + 1
EndIf
EndProcedure
Procedure.i FD_ISSET(fd.i, *set.FD_SET)
Protected I.i
For I = *set\fd_count - 1 To 0 Step -1
If I < #FD_SETSIZE
If *set\fd_array[I] = fd
ProcedureReturn #True
EndIf
EndIf
Next
ProcedureReturn #False
EndProcedure
Procedure.i _NFDS(*set.FD_SET)
ProcedureReturn *set\fd_count
EndProcedure
;}
CompilerEndIf
CompilerIf Defined(TIMEVAL, #PB_Structure) = #False ;{
Structure TIMEVAL
tv_sec.l
tv_usec.l
EndStructure ;}
CompilerEndIf
;}
Structure __ConnectionInfo
Mutex.i
ConId.i
EndStructure
Structure __vId_Holder
Id.q
exist.a
EndStructure
Structure __NetworkProtector_Data
Map Connections.__ConnectionInfo()
CompilerIf #SafeNetwork_UseVId
Map vIdHolder.__vId_Holder()
Map vIdRevHolder.__vId_Holder()
vIdMutex.i
vId.i
CompilerEndIf
CompilerIf #SafeNetwork_UDPWorkAround
waUDPMutex.i
CompilerEndIf
Mutex.i
ECMutex.i
EventClient.i
EndStructure
Global __NetworkProtector.__NetworkProtector_Data
; vId - Manager abstrahiert sämtliche Connection - Ids
; Neue vId anlegen
Procedure __vId_Add(Connection)
Shared __NetworkProtector
Protected vId
CompilerIf #SafeNetwork_UseVId
With __NetworkProtector
If Connection
LockMutex(\vIdMutex)
If FindMapElement(\vIdRevHolder(), Str(Connection))
DeleteMapElement(\vIdHolder(), Str(\vIdRevHolder(Str(Connection))\Id)) ; Alten Holder löschen
DeleteMapElement(\vIdRevHolder(), Str(Connection)) ; Alten RevHolder löschen
EndIf
\vId + 1
; Überschlag
If \vId <= 0
\vId = 1
EndIf
vId = \vId
\vIdHolder(Str(vId))\Id = Connection
\vIdRevHolder(Str(Connection))\Id = vId
\vIdHolder()\exist = #True
\vIdRevHolder()\exist = #True
UnlockMutex(\vIdMutex)
EndIf
EndWith
CompilerElse
vId = Connection
CompilerEndIf
ProcedureReturn vId
EndProcedure
; vId in Connection - Id umwandeln
Procedure __vId_Get(vId)
Shared __NetworkProtector
Protected Connection
CompilerIf #SafeNetwork_UseVId
With __NetworkProtector
LockMutex(\vIdMutex)
If FindMapElement(\vIdHolder(), Str(vId))
Connection = \vIdHolder()\Id
EndIf
UnlockMutex(\vIdMutex)
EndWith
CompilerElse
Connection = vId
CompilerEndIf
ProcedureReturn Connection
EndProcedure
; Connection in vId umwandeln
Procedure __vId_RevGet(Connection)
Shared __NetworkProtector
Protected vId
CompilerIf #SafeNetwork_UseVId
With __NetworkProtector
LockMutex(\vIdMutex)
If FindMapElement(\vIdRevHolder(), Str(Connection))
vId = \vIdRevHolder()\Id
EndIf
UnlockMutex(\vIdMutex)
EndWith
CompilerElse
vId = Connection
CompilerEndIf
ProcedureReturn vId
EndProcedure
; vId austragen
Procedure __vId_Remove(vId)
Shared __NetworkProtector
Protected Connection, tvId
CompilerIf #SafeNetwork_UseVId
With __NetworkProtector
LockMutex(\vIdMutex)
Connection = \vIdHolder(Str(vId))
tvId = \vIdRevHolder(Str(Connection))
DeleteMapElement(\vIdHolder(), Str(vId))
If tvId = vId
DeleteMapElement(\vIdRevHolder(), Str(Connection))
EndIf
UnlockMutex(\vIdMutex)
EndWith
CompilerEndIf
EndProcedure
; Mutex sicher freigeben
Macro _SafeFreeMutex(_Mutex)
; Letzten Lock - Zyklus abwarten
LockMutex(_Mutex) : UnlockMutex(_Mutex)
LockMutex(_Mutex) : UnlockMutex(_Mutex)
FreeMutex(_Mutex) ; Mutex freigeben
EndMacro
; Erstellt die Verwaltung
Procedure __NetworkProtector_Init()
Shared __NetworkProtector
With __NetworkProtector
\Mutex = CreateMutex() ; Globale Schutzmutex für die Verwaltung
\ECMutex = CreateMutex() ; Globale Schutzmutex für den EventClient
CompilerIf #SafeNetwork_UDPWorkAround
\waUDPMutex = CreateMutex() ; Workaround für UDP
CompilerEndIf
CompilerIf #SafeNetwork_UseVId
\vIdMutex = CreateMutex() ; Globale Schutzmutex für die Virtuellen Ids
CompilerEndIf
EndWith
EndProcedure : __NetworkProtector_Init()
; Event - Client abstrahieren
Procedure __EventClient()
Shared __NetworkProtector
With __NetworkProtector
ProcedureReturn \EventClient
EndWith
EndProcedure
Procedure __NetworkServerEvent()
Protected Event, Mutex
Shared __NetworkProtector
With __NetworkProtector
LockMutex(\ECMutex)
Event = NetworkServerEvent()
UnlockMutex(\ECMutex)
If Event
If Event = #PB_NetworkEvent_Connect ; Connect - Event : Lokale Schutzmutex für jede Verbindung erzeugen
LockMutex(\ECMutex)
\EventClient = __vId_Add(EventClient())
UnlockMutex(\ECMutex)
LockMutex(\Mutex)
; Mutex erstellen
If Not \Connections(Str(\EventClient))\Mutex
\Connections()\Mutex = CreateMutex()
\Connections()\ConId = ConnectionID(__vId_Get(\EventClient))
EndIf
UnlockMutex(\Mutex)
ElseIf Event = #PB_NetworkEvent_Disconnect ; Disconnect - Event: Lokale Schutzmutex abbauen
LockMutex(\ECMutex)
\EventClient = __vId_RevGet(EventClient())
UnlockMutex(\ECMutex)
If \EventClient
LockMutex(\Mutex)
Mutex = \Connections(Str(\EventClient))\Mutex
If Mutex
; Mutex austragen
\Connections()\Mutex = #Null
DeleteMapElement(\Connections(), Str(\EventClient))
; virtuell freigeben
__vId_Remove(\EventClient)
; Freigeben
_SafeFreeMutex(Mutex)
EndIf
UnlockMutex(\Mutex)
Else
Event = #Null
EndIf
Else
LockMutex(\ECMutex)
\EventClient = __vId_RevGet(EventClient())
UnlockMutex(\ECMutex)
EndIf
EndIf
EndWith
ProcedureReturn Event
EndProcedure
Procedure __NetworkClientEvent(Connection)
Protected Event, Mutex, rConnection
Protected hSocket.i, tv.timeval, readfds.fd_set, Length.i
Shared __NetworkProtector
rConnection = __vId_Get(Connection)
With __NetworkProtector
LockMutex(\Mutex)
Mutex = \Connections(Str(Connection))\Mutex
hSocket = \Connections()\ConId
UnlockMutex(\Mutex)
If Mutex And rConnection
LockMutex(Mutex)
If hSocket < 65536 And hSocket > 0
Event = NetworkClientEvent(rConnection)
Else
Event = #PB_NetworkEvent_Disconnect
EndIf
If Not Event ; Erweiterung um das #PB_NetworkEvent_Disconnect zu erkennen
tv\tv_sec = 0 ; Dont even wait, just query status
tv\tv_usec = 0
If hSocket > 65536 Or hSocket < 1 ; Gültiger bereich
Event = #PB_NetworkEvent_Disconnect
Else
FD_ZERO(readfds)
FD_SET(hSocket, readfds)
; Check if there is something new
Event = select_(_NFDS(readfds), @readfds, #Null, #Null, @tv)
If Event < 0 ; Seems to be an error
Event = #PB_NetworkEvent_Disconnect
ElseIf Event = 0 Or Not FD_ISSET(hSocket, readfds) ; No data available
Event = #Null
Else
; Check if data is available?
CompilerIf #PB_Compiler_OS = #PB_OS_Windows
Event = ioctlsocket_(hSocket, #FIONREAD, @Length)
CompilerElse
Event = ioctl_(hSocket, #FIONREAD, @Length)
CompilerEndIf
If Event Or Length = 0 ; Not successful to query for data available OR no data available ? This seems to be an error!
Event = #PB_NetworkEvent_Disconnect
Else
Event = #Null
EndIf
EndIf
EndIf
EndIf
UnlockMutex(Mutex)
If Event = #PB_NetworkEvent_Disconnect ; Disconnect - Event: Mutex austragen
LockMutex(\Mutex)
Mutex = \Connections(Str(Connection))\Mutex
If Mutex
; Mutex Austragen
\Connections()\Mutex = #Null
DeleteMapElement(\Connections(), Str(Connection))
__vId_Remove(Connection)
; Freigeben
_SafeFreeMutex(Mutex)
EndIf
UnlockMutex(\Mutex)
EndIf
Else
Event = #PB_NetworkEvent_Disconnect
EndIf
EndWith
ProcedureReturn Event
EndProcedure
Procedure __OpenNetworkConnection(Host$, Port, Type = #PB_Network_TCP)
Protected Connection
Shared __NetworkProtector
With __NetworkProtector
Connection = OpenNetworkConnection(Host$, Port, Type)
If Connection
Connection = __vId_Add(Connection)
LockMutex(\Mutex)
\Connections(Str(Connection))\Mutex = CreateMutex()
\Connections()\ConId = ConnectionID(__vId_Get(Connection))
UnlockMutex(\Mutex)
EndIf
EndWith
ProcedureReturn Connection
EndProcedure
Procedure __CloseNetworkConnection(Connection)
Protected isOk, Mutex, rConnection
Shared __NetworkProtector
rConnection = __vId_Get(Connection)
With __NetworkProtector
If rConnection
LockMutex(\Mutex)
Mutex = \Connections(Str(Connection))\Mutex
UnlockMutex(\Mutex)
If Mutex
; Connection beenden
LockMutex(Mutex)
isOk = CloseNetworkConnection(rConnection)
UnlockMutex(Mutex)
LockMutex(\Mutex)
Mutex = \Connections(Str(Connection))\Mutex
If Mutex
\Connections()\Mutex = #Null
DeleteMapElement(\Connections(), Str(Connection))
__vId_Remove(Connection)
; Freigeben
_SafeFreeMutex(Mutex)
EndIf
UnlockMutex(\Mutex)
EndIf
EndIf
EndWith
ProcedureReturn isOk
EndProcedure
Procedure __ConnectionID(Connection)
Protected isOk, Mutex
Shared __NetworkProtector
With __NetworkProtector
LockMutex(\Mutex)
Mutex = \Connections(Str(Connection))\Mutex
isOk = \Connections()\ConId
UnlockMutex(\Mutex)
If Not Mutex
isOk = #Null
EndIf
EndWith
ProcedureReturn isOk
EndProcedure
Procedure __SendNetworkData(Connection, *Mem, MemSize)
Protected isOk, Mutex, rConnection
Shared __NetworkProtector
rConnection = __vId_Get(Connection)
With __NetworkProtector
LockMutex(\Mutex)
Mutex = \Connections(Str(Connection))\Mutex
UnlockMutex(\Mutex)
CompilerIf #SafeNetwork_UDPWorkAround
If Not Mutex
Mutex = \waUDPMutex
EndIf
CompilerEndIf
If Mutex And rConnection
LockMutex(Mutex)
isOk = SendNetworkData(rConnection, *Mem, MemSize)
UnlockMutex(Mutex)
EndIf
EndWith
ProcedureReturn isOk
EndProcedure
Procedure __SendNetworkString(Connection, String$)
Protected isOk, Mutex, rConnection
Shared __NetworkProtector
rConnection = __vId_Get(Connection)
With __NetworkProtector
LockMutex(\Mutex)
Mutex = \Connections(Str(Connection))\Mutex
UnlockMutex(\Mutex)
CompilerIf #SafeNetwork_UDPWorkAround
If Not Mutex
Mutex = \waUDPMutex
EndIf
CompilerEndIf
If Mutex And rConnection
LockMutex(Mutex)
isOk = SendNetworkString(rConnection, String$)
UnlockMutex(Mutex)
EndIf
EndWith
ProcedureReturn isOk
EndProcedure
Procedure __SendNetworkFile(Connection, File$)
Protected isOk, Mutex, rConnection
Shared __NetworkProtector
rConnection = __vId_Get(Connection)
With __NetworkProtector
LockMutex(\Mutex)
Mutex = \Connections(Str(Connection))\Mutex
UnlockMutex(\Mutex)
CompilerIf #SafeNetwork_UDPWorkAround
If Not Mutex
Mutex = \waUDPMutex
EndIf
CompilerEndIf
If Mutex And rConnection
LockMutex(Mutex)
isOk = SendNetworkFile(rConnection, File$)
UnlockMutex(Mutex)
EndIf
EndWith
ProcedureReturn isOk
EndProcedure
Procedure __ReceiveNetworkData(Connection, *Mem, MemSize)
Protected isOk, Mutex, rConnection
Shared __NetworkProtector
rConnection = __vId_Get(Connection)
With __NetworkProtector
LockMutex(\Mutex)
Mutex = \Connections(Str(Connection))\Mutex
UnlockMutex(\Mutex)
CompilerIf #SafeNetwork_UDPWorkAround
If Not Mutex
Mutex = \waUDPMutex
EndIf
CompilerEndIf
If Mutex And rConnection
LockMutex(Mutex)
isOk = ReceiveNetworkData(rConnection, *Mem, MemSize)
UnlockMutex(Mutex)
EndIf
EndWith
ProcedureReturn isOk
EndProcedure
; PB - Befehle überschreiben
Macro EventClient()
__EventClient()
EndMacro
Macro NetworkServerEvent()
__NetworkServerEvent()
EndMacro
Macro NetworkClientEvent(Connection)
__NetworkClientEvent(Connection)
EndMacro
Macro OpenNetworkConnection(ServerName, Port, Type = #PB_Network_TCP)
__OpenNetworkConnection(ServerName, Port, Type)
EndMacro
Macro CloseNetworkConnection(Connection)
__CloseNetworkConnection(Connection)
EndMacro
Macro ConnectionID(Connection)
__ConnectionID(Connection)
EndMacro
Macro SendNetworkData(Connection, Mem, MemSize)
__SendNetworkData(Connection, Mem, MemSize)
EndMacro
Macro SendNetworkString(Connection, String)
__SendNetworkString(Connection, String)
EndMacro
Macro SendNetworkFile(Connection, File)
__SendNetworkFile(Connection, File)
EndMacro
Macro ReceiveNetworkData(Connection, Mem, MemSize)
__ReceiveNetworkData(Connection, Mem, MemSize)
EndMacro