Buchempfehlung
MySQL kurz & gut
MySQL kurz & gut
Das preiswerte Taschen- buch stellt MySQL-rele- vante Inhalte systematisch und knapp dar, sodass es sich optimal zum Nach- schlagen beim Pro- grammieren eignet. [Mehr Infos...]
FreeBASIC-Chat
Es sind Benutzer im FreeBASIC-Chat online.
(Stand:  )
FreeBASIC bei Twitter
Twitter FreeBASIC-Nachrichten jetzt auch über Twitter erhalten. Follow us!

Tutorial

Netzwerkprogrammierung mit TSNE_V3

von MitgliedThePuppetMasterSeite 7 von 8

Einen Server zu schreiben ist "ansich" auch nicht schwieriger, als es bei einem Client der Fall ist.
Grundsätzlich ist zu erwähnen, dass ein Server nichts anderes macht, als mehrere Client-Verbindungen zu verwalten. Sprich: das letzte Kapitel. Hinzu kommt jedoch eine kleine Erweiterung, die es uns ermöglicht, nicht eine Verbindung von uns aus herzustellen, sondern auf Verbindungswünsche von anderen zu reagieren.
Das bedeutet natürlich, dass wir die Hauptzeilen im Programm abändern müssen.

'...
For X as UInteger = 1 to 3
    RV = TSNE_Create_Client(G_Client_TSNEID(X), "www.google.de", 80, @TSNE_Disconnected, @TSNE_Connected, @TSNE_NewData)
    If RV <> TSNE_Const_NoError Then
        Print "[FEHLER] " & TSNE_GetGURUCode(RV)
        For Y as UInteger = 1 to 3
            If G_Client_TSNEID(X) > 0 Then
                TSNE_Disconnect(G_Client_TSNEID(X))
                TSNE_WaitClose(G_Client_TSNEID(X))
            End If
        Next
        End -1
    End If
Next
'...

Dieser Quellcode-Teil wird hiermit unnötig. Der Quellcode wurde bisher nur dafür verwendet, um Verbindungen von UNS ausgehend ZU einem Server herzustellen.
Da wir jetzt jedoch einen Server schreiben, müssen wir keine Verbindungen zu Clients herstellen, sondern müssen auf Anfragen von Clients "warten", bzw. auf diese "hören" und reagieren.

Hierfür erzeugen wir mithilfe von "TSNE_Create_Server" ein Socket, das als "Server" dient und nichts anderes tut, als Verbindungsanfragen von Clients zu empfangen.

'...
Dim G_Server_TSNEID as UInteger
'...
RV = TSNE_Create_Server(G_Server_TSNEID, 80, 100, @TSNE_NewConnection, 0)
If RV <> TSNE_Const_NoError Then
    Print "[FEHLER] " & TSNE_GetGURUCode(RV)
    End -1
End If
'...

Zuerst einmal ändern wir wieder die TSNEID-Variable ab. Wir nennen sie entsprechend ihrer Funktion zu "G_Server_TSNEID" um.
Da wir nur einen Server erzeugen, der alle Verbindungsanfragen empfängt und uns später meldet, brauchen wir kein Array, sondern nur eine einfache statische Variable.

Die Parameter, die dem Funktionsaufruf übergeben werden, haben folgende Bedeutung:

"80" stellt die Portnummer dar, auf dem nach Verbindungsanfragen "gelauscht" werden soll.

"100" gibt an, dass wir maximal 100 Anfragen gleichzeitg "zwischenspeichern" wollen. Man kann sich das so vorstellen, dass ein Array mit 100 Einträgen erzeugt wird, in welchem maximal 100 Anfragen eingelagert werden können.
Jede eingehende Anfrage wir uns automatisch mitgeteilt. Daraufhin bereiten wir eine Verbindung vor und leiten den Aufbau dieser ein. Erst dann wird diese Anfrage aus dem Array entfernt und es ist wieder Platz für einen weiteren Verbindungswunsch.
Im Idealfall ist dieser Zwischenspeicher nur mit 1 oder 2 gleichzeitigen Verbindungsanfragen gefüllt, sodass genügend Platz ist, um weitere entgegenzunehmen, während wir an der Bearbeitung der aktuellen Anfragen arbeiten.
Es kann natürlich vorkommen, dass die Belastung des Servers kurzfristig ansteigt und mehr als 2 oder 3 Verbindungsanfragen zur fast selben Zeit eingehen. Schneller, als wir diese verarbeiten können. Daher definieren wir die Grösse mit 100, um flexibel zu bleiben und genug Platz für kurzfristige Lastspitzen zu besitzen.

@TSNE_NewConnection ist, wie schon erkannt werden sollte, ein Pointer auf eine Ereignis-Routine. Sie ist quasi die Funktion, die uns mitteilt, dass eine Gegenstelle eine Verbindung mit uns wünscht.

Der letzte Parameter "0" könnte ebenfalls einen Pointer auf eine Routine enthalten. Da wir diese jedoch in diesem Kapitel nicht benötigen, schreiben wir eine 0 hinein.
Dadurch ignoriert TSNE den Aufruf der Routine, da diese ja nicht angegeben wurde .)


Wurde das Server-Socket erfolgreich erzeugt, gibt es keinen Fehler und wir können eine "Warteschleife" einbauen und ausführen lassen. Andernfalls beendet sich das Programm mit einer Fehlermeldung.

Die Warteschleife ist hier jetzt nicht mehr mit "TSNE_WaitClose" zu bewerkstelligen, da der Server prinzipiell endlos lange auf Anfragewünsche warten wird.
Schliesslich ist es ja nicht in unserem Interesse, nur 27 Anfragen zu bearbeiten oder nur 10std. auf Anfragen zu warten. Ganz im Gegenteil. Ein Server soll meist möglichst lange, ohne Unterbrechung auf Verbindungsanfragen warten und diese uns mitteilen.

Darum müssen wir, um das Programm an dieser Stelle zu beenden, die Serververbindung von Hand trennen.

'...
Do Until InKey() = Chr(27)
    Sleep 1, 1
Loop
TSNE_Disconnect(G_Server_TSNEID)
TSNE_WaitClose(G_Server_TSNEID)
'...

Wir warten in der Do-Loop-Schleife auf das Drücken der "ESC" Taste und verlassen erst dann die Endlosschleife.
Das Sleep 1, 1 ist absolut wichtig und sollte keinesfalls vergessen werden! Ansonsten arbeitet die Schleife unter Vollast, bremst das Betriebssystem aus und könnte auch TSNE ziemlich stören.

Nach dem Verlassen der Schleife beenden wir mit "TSNE_Disconnect" das Server-Socket, und warten, so wie auch bei Clients, mit "TSNE_WaitClose" auf das vollständige Ende des Sockets.

Nachdem der Server beendet wurde, werden keine weiteren Verbindungsanfragen von Gegenstationen mehr empfangen.
Sollten noch Client-Verbindungen bestehen, was bei einem Server genauso wie bei einem Client gegeben ist, müssen diese nun beendet werden. Schliesslich können wir als "Server-Betreiber" nicht darauf warten, das ein User erst seine Aktion beendet, bevor wir ein Update oder sonst eine Aktion, duchführen können.
Wir haben quasi das Recht die Verbindungen zu allen Gegenstellen einfach zu trennen und das müssen wir jetzt auch tun, bevor wie das Programm mit "End 0" beenden können.

Hierbei verwenden wir dasselbe Prinzip wie beim vorherigen Abbruch des 3-Clients Beispiels.

'...
For X as UInteger = 1 to 3
    If G_Client_TSNEID(X) > 0 Then
        TSNE_Disconnect(G_Client_TSNEID(X))
    End If
Next
For X as UInteger = 1 to 3
    If G_Client_TSNEID(X) > 0 Then
        TSNE_WaitClose(G_Client_TSNEID(X))
    End If
Next
End 0

Dieses Vorgehen wäre die "richtige" Lösung für einen Server, macht jedoch etwas mehr Aufwand nötig.

Gingen wir davon aus, dass der Server aktuell keine Verbindungen hält und bearbeitet, könnten wir uns den letzten Code sparen und damit auch ein paar Kleinigkeiten mehr.
Um das vergleichen zu können, werden wir uns erst der einfacheren Variante widmen und anschließend der "richtigeren" Lösung.

Aber zuerst einmal prüfen wir den aktuellen Stand des Quellcodes, der jetzt so aussehen sollte.

#Include once "TSNE_V3.bi"
Dim G_Server_TSNEID as UInteger
Dim RV as Integer

Sub TSNE_Disconnected(ByVal V_TSNEID as UInteger)
Print "[Disconnected] TSNEID: "; V_TSNEID
End Sub


Sub TSNE_Connected(ByVal V_TSNEID as UInteger)
Print "[Connected] TSNEID: "; V_TSNEID
End Sub


Sub TSNE_NewData(ByVal V_TSNEID as UInteger, ByRef V_Data as String)
Print "[NewData] TSNEID: "; V_TSNEID; " Datenlänge: "; Len(V_Data)
End Sub


RV = TSNE_Create_Server(G_Server_TSNEID, 80, 100, @TSNE_NewConnection, 0)
If RV <> TSNE_Const_NoError Then
    Print "[FEHLER] " & TSNE_GetGURUCode(RV)
    End -1
End If


Do Until InKey() = Chr(27)
    Sleep 1, 1
Loop
TSNE_Disconnect(G_Server_TSNEID)
TSNE_WaitClose(G_Server_TSNEID)
End 0

Ich habe in dem Quellcode im Ereignis "TSNE_Connected" das Erzeugen des Headers schon einmal gelöscht, da WIR nun der Server sind und nicht mehr der Client.
Folglich müssen wir auch keinen "Anfrage-Header" erzeugen, sondern eine Antwort.

Würden wir diesen Quellcode kompilieren, bekämen wir die Fehlermeldung, dass TSNE_NewConnection nicht gefunden würde. Darum fügen wir diese Ereignis-Routine als Nächstes hinzu.

'...
Sub TSNE_NewConnection(ByVal V_TSNEID as UInteger, ByVal V_RequestID as Socket, ByVal V_IPA as String)
'...
End Sub
'...

Dieses Ereignis wird immer dann ausgelöst bzw. diese Routine wird immer dann aufgerufen, wenn eine Gegenstelle bzw. ein Client eine Verbindung mit unserem Server wünscht.
Dabei übergibt uns die Variable V_TSNEID die TSNEID des Servers, der die Verbindungsanfrage empfangen hat.
V_RequestID ist eine IDentifikationsnummer, welche den anfragenden Computer identifiziert. Sie wird später benötigt, um den Anfragewunsch zu akzeptieren oder abzulehnen.
V_IPA ist, wie der Name schon vermuten lässt, die IP-Adresse des anfragenden Computers bzw. der Gegenstelle. Hiermit kann z.B. ein Filter, eine Black/White-List oder eine Status- Überwachung realisiert werden.

In diesem Ereignis müssen wir folglich die Akzeptierung oder Ablehnung der Verbindung verarbeiten.
Dazu wird zuerst einmal die Verbindung akzeptiert und eine TSNEID erzeugt, mit der wir anschließend alle weiteren Aufgaben durchführen können.
Um die TSNEID zu erhalten, nutzen wir die Funktion "TSNE_Create_Accept". Sie akzeptiert die Verbindung des anfragenden Computers und erzeugt die TSNE-ID.

'...
Sub TSNE_NewConnection(ByVal V_TSNEID as UInteger, ByVal V_RequestID as Socket, ByVal V_IPA as String)
Dim RV as Integer
Dim NewTSNEID as UInteger
RV = TSNE_Create_Accept(V_RequestID, NewTSNEID, , @TSNE_Disconnected, @TSNE_Connected, @TSNE_NewData)
If RV <> TSNE_Const_NoError Then
    Print "[ACCEPT] [FEHLER] " & TSNE_GetGURUCode(RV)
    Exit Sub
End If
Print "[ACCEPT] TSNEID: " & NewTSNEID; "  IPA: "; V_IPA
End Sub
'...

Die RV-Variable sollte bereits bekannt sein. Sie speichert den GURU-Code des Funktionsaufrufs zwischen, den wir später zur Kontrolle des Erfolgs heranziehen werden.
NewTSNEID ist die Variable, welche nach erfolgreicher "Akzeptierung" (Accept) die TSNEID der neuen Client-Verbindung enthält.
Diese beiden Parameter werden der "TSNE_Create_Accept"-Funktion übergeben, zusammen mit der "V_RequestID".
Der 3te Parameter (welcher ignoriert wird) ist die Rückgabe der IP-Adresse und stammt noch aus einer älteren Version von TSNE. Aufgrund von Kompatibilität zur neuen Version wurde der Funktionsaufbau beibehalten, kann aber ignoriert werden, was hier auch getan wird.
Die restlichen 3 Parameter sind die Pointer auf die Ereignis-Routinen, wie sie auch beim Aufbau einer Client-Verbindung benötigt werden.

Ab diesem Zeitpunkt kann man sich einen Server wie einen Client vorstellen.
Die Verbindung zwischen 2 Computern ist aus Sicht der Verbindung selbst auf beiden Seiten identisch. Auf jedem der beiden PCs wird das "Connected"- Ereignis benötigt, sowie das Ereignis für "Disconnected" und natürlich "NewData".
Der einzige Unterschied zwischen einem Client-System und einem Server-System ist, wie schon oben erwähnt, die Erweiterung, um auf eine Anfrage reagieren zu können.
Ansonten bleibt im grossen und ganzen alles identisch.

Im "NewConnection" Ereignis wird anstelle von "TSNE_Create_Client" das "TSNE_Create_Accept" verwendet, weil unser Programm nicht der aktive Computer ist, der eine Verbindung aufzubauen wünscht, sondern der passive, der einen Wunsch akzeptieren muss.

Aber zurück zum Quellcode. Nach dem Funktionsaufruf prüfen wir, selbstverständlich, ob ein Fehler aufgetreten ist oder nicht. Ist dies der Fall, geben wir, wie auch die Male zuvor, die Fehlermeldung aus und verlassen mit "Exit Sub" NUR die Routine und nicht das Programm.

Wenn eine Verbindung nicht akzeptiert werden kann, muss das nicht automatisch an uns liegen, sondern könnte mehrere, andere Ursachen haben. Unter anderem könnte dies an der Gegenstelle liegen.
Darum ignorieren wir die fehlgeschlagene Verbindung einfach und arbeiten, als wäre nichts geschehen, weiter.

Sollte jedoch kein Fehler aufgetreten sein, dann geben wie die neue TSNEID auf dem Bildschirm aus, sowie die IP-Adresse der Gegenstelle.


Ab jetzt ist die Verbindung stabil aufgebaut und kann zur Übertragung von Daten genutzt werden.

Zur besseren Übersicht vergleichen wir nochmal den fertigen Quellcode.

#Include once "TSNE_V3.bi"
Dim G_Server_TSNEID as UInteger
Dim RV As Integer

Sub TSNE_Disconnected(ByVal V_TSNEID as UInteger)
Print "[Disconnected] TSNEID: "; V_TSNEID
End Sub


Sub TSNE_Connected(ByVal V_TSNEID as UInteger)
Print "[Connected] TSNEID: "; V_TSNEID
End Sub


Sub TSNE_NewData(ByVal V_TSNEID as UInteger, ByRef V_Data as String)
Print "[NewData] TSNEID: "; V_TSNEID; " Datenlänge: "; Len(V_Data)
End Sub


Sub TSNE_NewConnection(ByVal V_TSNEID as UInteger, ByVal V_RequestID as Socket, ByVal V_IPA as String)
Dim RV as Integer
Dim NewTSNEID as UInteger
RV = TSNE_Create_Accept(V_RequestID, NewTSNEID, , @TSNE_Disconnected, @TSNE_Connected, @TSNE_NewData)
If RV <> TSNE_Const_NoError Then
    Print "[ACCEPT] [FEHLER] " & TSNE_GetGURUCode(RV)
    Exit Sub
End If
Print "[ACCEPT] TSNEID: " & NewTSNEID; "  IPA: "; V_IPA
End Sub


RV = TSNE_Create_Server(G_Server_TSNEID, 80, 100, @TSNE_NewConnection, 0)
If RV <> TSNE_Const_NoError Then
    Print "[FEHLER] " & TSNE_GetGURUCode(RV)
    End -1
End If


Do Until InKey() = Chr(27)
    Sleep 1, 1
Loop
TSNE_Disconnect(G_Server_TSNEID)
TSNE_WaitClose(G_Server_TSNEID)
End 0

Es fällt auf, dass der Aufbau in fast jedem Kapitel in grossen Zügen identisch ist. Das ist nicht nur beabsichtigt, sondern bei der Planung von TSNE auch so erwünscht, um die Übersichtlichkeit und die einfache Handhabung zu gewährleisten.
Desweiteren spart diese Variante reichlich Quellcode und macht den Umgang mit TCP oder UDP-Verbindungen sehr leicht.

Der bis jetzt geschriebene Quellcode ist voll funktionsfähig, jedoch nicht praktikabel.
Um dieses Problem zu lösen, das Beenden des Servers "richtig" zu gestalten und eingehende Daten jeder Client-Verbindung zwischenspeichern zu können, müssen wir einen Speicher erzeugen, der die nötigen Informationen aufnehmen kann.
Dazu erzeugen wir einen UDT, das alle Informationen hält.

'...
Type Client_Type
    V_InUse     as UByte

    V_TSNEID    as UInteger
    V_IPA       as String
    V_Data      as String
End Type
'...

Da wir ein Array nutzen werden, implementieren wir eine Variable (V_InUse) in den UDT, mit deren Hilfe wir herausfinden können, ob ein Element bereits in Nutzung ist oder nicht.
V_IPA wird die IP-Adresse speichern, um nach dem Accept zu jeder Zeit die IP-Adresse ausgeben zu können.
V_Data ist die Datenvariable. Sie wird die Anfrage (die unter Umständen in mehreren Blöcken übertragen wird) speichern.
V_TSNEID, wie der Name schon vermuten lässt, hält die TSNEID der Verbindung. Sie dient später zur Suche nach dem richtigen Element im Array

Dann noch ein Array erzeugen.

'...
Dim Shared G_ClientD() as Client_Type
Dim Shared G_ClientC as UInteger
Dim Shared G_ClientMutex as Any Ptr
'...

Das Array (G_ClientD) wird die nötigen Informationen zu jeder Client-Verbindung speichern.
G_ClientC speichert die aktuell maximale Anzahl Array-Einträge. Natürlich könnte man mit UBound auch arbeiten, mir persönlich ist jedoch eine feste Variable lieber. Zumal hierdurch auch die Geschwindigkeit erhöht wird.
Ein Mutex ist hier ebenfalls nötig, da TSNE mit Threads und Verbindungen simultan arbeitet. Greift man aus Client-Ereignissen heraus auf ein Array Eintrag zu, könnte dies problematisch werden und "Speicherzugriffsfehler" auslösen.
Die Variablen müssen als Shared ausgelegt werden, damit aus Routinen heraus auf das Array zugegriffen werden kann.

Das Ganze packen wir an den Anfang des Programms

#Include once "TSNE_V3.bi"

Type Client_Type
    V_InUse     as UByte

    V_TSNEID    as UInteger
    V_IPA       as String
    V_Data      as String
End Type
Dim Shared G_ClientD() as Client_Type
Dim Shared G_ClientC as UInteger
Dim Shared G_ClientMutex as Any Ptr

Dim G_Server_TSNEID as UInteger
'...

Anschliessend passen wir die Accept-Routine an, damit dort neue Clients im Array hinterlegt werden können. Dadurch stehen im späteren Verlauf der Verbindung die nötigen Informationen bereit.
Dies ist auch der Grund, warum "vorbereitende" Massnahmen zu einer Verbindung innerhalb des Accept-Ereignisses ausgeführt werden sollten. Ansonsten befindet sich z.B. beim Connected-Ereignis die Verbindung noch nicht im Array.
Auch dies kann unter ungünstigen Umständen zu Fehlern oder Problemen führen.

'...
Sub TSNE_NewConnection(ByVal V_TSNEID as UInteger, ByVal V_RequestID as Socket, ByVal V_IPA as String)
Dim CIndex as UInteger
MutexLock(ClientMutex)
For X as UInteger = 1 to G_ClientC
    If G_ClientD(X).V_InUse = 0 Then CIndex = X: Exit For
Next
Dim RV as Integer
Dim NewTSNEID as UInteger
If CIndex = 0 Then
    If G_ClientC = 250 Then
        RV = TSNE_Create_Accept(V_RequestID, NewTSNEID, , 0, 0, 0)
        If NewTSNEID  0 Then TSNE_Disconnect(NewTSNEID)
        Exit Sub
    End If
    G_ClientC += 1
    CIndex = G_CLient
    Redim Preserve G_ClientD(G_ClientC) as CLient_Type
End If
Dim TCUDT as Client_Type
G_ClientD(CIndex) = TCUDT
RV = TSNE_Create_Accept(V_RequestID, NewTSNEID, , @TSNE_Disconnected, @TSNE_Connected, @TSNE_NewData)
With G_ClientD(CIndex)
    .V_InUse = 1
    .V_TSNEID = NewTSNEID
    .V_IPA = V_IPA
    If RV <> TSNE_Const_NoError Then
        MutexUnLock(ClientMutex)
        Print "[ACCEPT] [FEHLER] " & TSNE_GetGURUCode(RV)
        Exit Sub
    End If
End With
MutexUnLock(ClientMutex)
Print "[ACCEPT] TSNEID: " & NewTSNEID; "  IPA: "; V_IPA
End Sub
'...

Hierbei suchen wir zuerst einmal einen freien Platz im Array, indem wir alle Elemente durchgehen und die Variable "V_InUse" überprüfen.
Finden wir einen Platz, dann speichern wir die Indexnummer in die Variable CIndex, um später auf das Element zugreifen zu können.
Hierbei muss darauf geachtet werden, dass das Mutex noch immer gesperrt bleibt. Über die Verwendung von Mutexe kann man sich im Mutex-Tutorial bzw. im Threading-Tutorial informieren.

Als nächsten Schritt prüfen wir (muss man nicht machen, ist aber zu empfehlen), ob die maximale Anzahl Clients bereits verbunden sind. Aber auch nur dann, wenn das Array voll ist.
Ist dies der Fall, dann wird die Verbindung akzeptiert, jedoch ohne die Pointer auf die Ereignis-Routinen zu übermitteln. Hierdurch wird die Verbindung zwar regulär akzeptiert, jedoch bekommt unser Programm davon nichts mit, da die entsprechenden Ereignis-Routinen nicht ausgelöst werden.
Regulär würde man jetzt eine Fehlermeldung an den Client senden. Da es aber nur ein kleines Beispiel ist, ersparen wir uns hier weitere Aktionen.
Anschliessend verlassen wir die SubRoutien, da es ab hier nichts mehr zu tun gibt.

Ist jedoch noch Platz im Array frei bzw. noch nicht die maximale Anzahl gleichzeitiger Verbindungen von 250 erreicht, dann erzeugen wir einen neuen Array-Eintrag.
Wir erzeugen eine temporäre Variable namens "TCUDT". Diese hält einen leeren UDT, der keine Werte gespeichert hat und in dem alle Variablen auf 0 stehen.
Hierdurch können wir in der nächsten Zeile komfortabel das Array-Element (gefunden oder neu erzeugt) leeren, ohne dass jede UDT-Variable einzeln auf 0 gesetzt wird.

Die restlichen Quellcodezeilen sollten ab hier selbsterklärend sein.


Nächster Änderungspunkt wäre die Anpassung der Disconnected-Ereignis-Routine.
Wenn ein Client seine Verbindung trennt, dann wird dieses Ereignis aufgerufen. Hier müssen wir natürlich das V_InUse im entsprechende Array-Element auf 0 setzen, damit dieses wieder frei wird.

'...
Sub TSNE_Disconnected(ByVal V_TSNEID as UInteger)
Print "[Disconnected] TSNEID: "; V_TSNEID
MutexLock(ClientMutex)
For X as UInteger = 1 to G_ClientC
    If G_ClientD(X).V_InUse = 1 Then
        If G_ClientD(X).V_TSNEID = V_TSNEID Then
            G_ClientD(X).V_InUse = 0
            Exit Sub
        End If
    End If
Next
End Sub
'...

Diese kleine Index-Suchfunktion kann natürlich in eine separate Funktion ausgelagert werden. Wegen der einfachen Lesbarkeit und der Portabilität schreibe ich dies jedoch in jede Routine neu hinein.

Zum Schluss müsste noch das Beenden des Programmes eingeleitet werden. Wie schon etwas weiter oben erwähnt, ist diese Art einen Server zu beenden die "bessere" Variante, da wir jetzt die Clients kennen, die verbunden sind, sowie deren TSNEID's.

Der folgende Code findet im Multi-Client Beispiel Verwendung.

'...
For X as UInteger = 1 to 3
    If G_Client_TSNEID(X) > 0 Then
        TSNE_Disconnect(G_Client_TSNEID(X))
    End If
Next
For X as UInteger = 1 to 3
    If G_Client_TSNEID(X) > 0 Then
        TSNE_WaitClose(G_Client_TSNEID(X))
    End If
Next
End 0

Diesen passen wir jetzt an das Array an.

'...
MutexLock(G_ClientMutex)
For X as UInteger = 1 to G_ClientC
    If G_ClientD(X).V_InUse > 0 Then
        If G_ClientD(X).V_TSNEID > 0 Then
            TSNE_Disconnect(G_ClientD(X).V_TSNEID)
        End If
    End If
Next
MutexUnLock(G_ClientMutex)
MutexLock(G_ClientMutex)
For X as UInteger = 1 to G_ClientC
    If G_ClientD(X).V_InUse > 0 Then
        If G_ClientD(X).V_TSNEID > 0 Then
        MutexUnLock(G_ClientMutex)
        TSNE_WaitClose(G_ClientD(X).V_TSNEID)
        MutexLock(G_ClientMutex)
        End If
    End If
Next
MutexUnLock(G_ClientMutex)
End 0

Hierbei nutzen wir wieder das Mutex, um den Zugriff auf das Array zu blockieren. Dabei muss beim Aufruf von TSNE_WaitClose das Mutex wieder entsperrt werden, da beim Beenden einer Verbindung noch das Disconnected-Ereignis erzeugt werden könnte. Würde das Mutex gesperrt bleiben, kann beim Aufruf dieses Ereignisses das Programm hängen bleiben.

Und damit wären alle Änderungen abgeschlosen und der Server ist "sicher" und stabil konzeptioniert.







 

Gehe zu Seite Gehe zu Seite  1  2  3  4  5  6  7  8  
Zusätzliche Informationen und Funktionen
  Bearbeiten Bearbeiten  

  Versionen Versionen