2 While Schleifen? Wird Multithreading verlangt?

  • Hallo :)
    Ich habe ein kleines Problem.. und zwar habe ich einen kleinen Server geschrieben (für einen Chat) und der Konflikt ist der , dass wenn man den Button drückt (um den Status zu ändern) er in der zweiten While-Schleife hängen bleibt bzw. die äußere nicht mehr beachtet (Das Programm handelt sogesehen komplett logisch und richtig nur möchte ich , dass beide Schleifen komplett unabhängig von einander agieren)
    Also einmal die Whileschleife für die Form selbst (z. B. fürs Beenden der Anwendung) und eine für das Empfangen der Pakete.
    Wie setze ich das am besten um? Und hat das überhaupt was mit Multithreading zutun oder bin ich einfach zu blöd?

    [autoit]

    #include <ButtonConstants.au3>
    #include <EditConstants.au3>
    #include <GUIConstantsEx.au3>
    #include <WindowsConstants.au3>

    [/autoit][autoit][/autoit][autoit]

    $Form1 = GUICreate("Form1", 437, 467, 327, 133)
    $Aus = GUICtrlCreateButton("OFFLINE", 24, 405, 385, 33)
    GUICtrlSetColor($Aus,0xff0000)

    [/autoit][autoit][/autoit][autoit]

    GUICtrlCreateEdit("", 24, 16, 385, 377)
    GUISetState(@SW_SHOW)

    [/autoit][autoit][/autoit][autoit][/autoit][autoit]

    $i = 0; ein aus 0 = aus 1 = an
    While 1
    $nMsg = GUIGetMsg()
    Switch $nMsg
    Case $GUI_EVENT_CLOSE
    Exit

    Case $Aus

    if $i = "0" Then

    GUICtrlSetData($Aus,"ONLINE")
    GUICtrlSetColor($Aus,0x009900)
    $i = 1

    TCPStartup() ; TCP wird initialisiert´
    $mainsocket = TCPListen("127.0.0.1", 4321) ; mainsocket erstellen
    While 1 ; Endlosschleife
    $acceptedSocket = TCPAccept($mainsocket) ; versuchen eine mögliche Verbindung anzunehmen

    If $acceptedSocket <> -1 Then ; Wenn $acceptedSocket ungleich -1 ist, ...
    $nachricht= TCPRecv($acceptedSocket, 2048) ; Wenn eine Socketverbindung hergestellt wurde, empfange ein Paket vom Client ($connectedSocket)
    MsgBox(64, "Nachricht empfangen!", "Inhalt der Nachricht " & $nachricht)
    TCPCloseSocket($acceptedSocket)
    EndIf
    WEnd


    ElseIf $i = "1" Then
    GUICtrlSetData($Aus,"OFFLINE")
    GUICtrlSetColor($Aus,0xff0000)
    $i = 0
    TCPShutdown();
    EndIf

    EndSwitch
    WEnd

    [/autoit][autoit][/autoit][autoit][/autoit][autoit][/autoit]


    [img]data:image/gif,GIF89a

  • Pack den Inhalt der 2. While-Schleife in ne Funktion, etwa so:
    (Nicht geprüft)

    Spoiler anzeigen
    [autoit]

    #include <ButtonConstants.au3>
    #include <EditConstants.au3>
    #include <GUIConstantsEx.au3>
    #include <WindowsConstants.au3>

    [/autoit] [autoit][/autoit] [autoit]

    $Form1 = GUICreate("Form1", 437, 467, 327, 133)
    $Aus = GUICtrlCreateButton("OFFLINE", 24, 405, 385, 33)
    GUICtrlSetColor($Aus, 0xff0000)

    [/autoit] [autoit][/autoit] [autoit]

    GUICtrlCreateEdit("", 24, 16, 385, 377)
    GUISetState(@SW_SHOW)

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    $i = 0; ein aus 0 = aus 1 = an
    While 1
    $nMsg = GUIGetMsg()
    Switch $nMsg
    Case $GUI_EVENT_CLOSE
    Exit
    Case $Aus
    If $i = 0 Then
    GUICtrlSetData($Aus, "ONLINE")
    GUICtrlSetColor($Aus, 0x009900)
    $i = 1
    TCPStartup() ; TCP wird initialisiert´
    $mainsocket = TCPListen("127.0.0.1", 4321) ; mainsocket erstellen
    ElseIf $i = 1 Then
    GUICtrlSetData($Aus, "OFFLINE")
    GUICtrlSetColor($Aus, 0xff0000)
    $i = 0
    TCPShutdown();
    EndIf
    EndSwitch
    If $i = 1 Then _Chat() ;Hier wird der Inhalt der "inneren" Schleife durchlaufen falls $i = 1
    WEnd

    [/autoit] [autoit][/autoit] [autoit]

    Func _Chat()
    $acceptedSocket = TCPAccept($mainsocket) ; versuchen eine mögliche Verbindung anzunehmen
    If $acceptedSocket <> -1 Then ; Wenn $acceptedSocket ungleich -1 ist, ...
    $nachricht = TCPRecv($acceptedSocket, 2048) ; Wenn eine Socketverbindung hergestellt wurde, empfange ein Paket vom Client ($connectedSocket)
    MsgBox(64, "Nachricht empfangen!", "Inhalt der Nachricht " & $nachricht)
    TCPCloseSocket($acceptedSocket)
    EndIf
    EndFunc ;==>_Chat

    [/autoit]
    UNPLEASANT SPOILER

    You just lost the game!

  • Einige Anmerkungen vorweg:

    [autoit]

    GUICtrlCreateEdit("", 24, 16, 385, 377)

    [/autoit]


    Du solltest dem Edit auch eine Variable zuweisen, sonst kannst du es später weder auslesen noch mit Daten befüllen.

    [autoit]

    #include <ButtonConstants.au3>
    if $i = "0" Then
    ...
    ElseIf $i = "1" Then

    [/autoit]

    Mach sowas bitte nie! Zahlen sollte man tunlichst nicht mit Strings vergleichen, andernfalls könnten ungewollte Ergebnisse rauskommen. Änder diese beiden Vergleiche besser mal.


    Nun zum eigentlichen Problem:

    Zum einen kann Autoit kein Multithreading, du wirst also immer das Problem haben, dass dein Code brav nacheinander und niemals zeitgleich abläuft.

    Was man aber tun kann ist Code periodisch einschieben (Stichwort: Adlibregister).
    Eine weitere Möglichkeit wäre es mehrere Prozesse zu verwenden. Das verlangt aber, dass deine Prozesse untereinander kommunizieren. Realisierbar ist das über jedemenge Methoden, z.B. TCP/UDP, Stdout/in, Namedpipes, Window Message uvm. (Stichwort: Interprozesskommunikation)

    Problematisch ist es aber immer wenn man blockierende Funktionen wie msgbox() nutzt die das Script pausieren, da hilft dann auch Adlibregister oder die beste Interprozesskommunikation nichts mehr.


    Eine mögliche Lösung ohne großen Aufwand wie z.B. multiple Prozesse oder der Einsatz von Adlibregister könnte so ausschaun:

    Spoiler anzeigen
    [autoit]


    #include <ButtonConstants.au3>
    #include <EditConstants.au3>
    #include <GUIConstantsEx.au3>
    #include <WindowsConstants.au3>

    [/autoit] [autoit][/autoit] [autoit]

    TCPStartup() ; TCP wird initialisiert´<------- reicht wenn das einmal bei Programmstart gemacht wird...

    [/autoit] [autoit][/autoit] [autoit]

    Global $mainsocket = -1
    Global $acceptedSocket = -1

    [/autoit] [autoit][/autoit] [autoit]

    $Form1 = GUICreate("Form1", 437, 467, 327, 133)
    $Aus = GUICtrlCreateButton("OFFLINE", 24, 405, 385, 33)
    GUICtrlSetColor($Aus,0xff0000)
    $edit = GUICtrlCreateEdit("", 24, 16, 385, 377)

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    GUISetState(@SW_SHOW)

    [/autoit] [autoit][/autoit] [autoit]

    $i = 0; ein aus 0 = aus 1 = an
    While 1
    $nMsg = GUIGetMsg()
    Switch $nMsg
    Case $GUI_EVENT_CLOSE
    If $mainsocket <> -1 Then
    If $acceptedSocket <> -1 Then TCPCloseSocket($acceptedSocket)
    TCPCloseSocket($mainsocket)
    EndIf
    TCPShutdown() ; <---------- wer tcp Funktionalität initialisiert sollte es auch wieder sauber herunterfahren...
    ConsoleWrite("Verbindung zum Client wurde getrennt, Server wurde heruntergefahren und Programm wird beendet..." & @CRLF)
    Exit
    Case $Aus
    if $i = 0 Then
    GUICtrlSetData($Aus,"ONLINE")
    GUICtrlSetColor($Aus,0x009900)
    $i = 1
    $mainsocket = TCPListen("127.0.0.1", 4321) ; <---------- server starten
    ConsoleWrite("Server wurde gestartet..." & @CRLF)
    ElseIf $i = 1 Then
    GUICtrlSetData($Aus,"OFFLINE")
    GUICtrlSetColor($Aus,0xff0000)
    $i = 0
    If $acceptedSocket <> -1 then ; <---------- wenn client verbunden, dann trennen
    TCPCloseSocket($acceptedSocket)
    $acceptedSocket = -1
    ConsoleWrite("Client Verbindung wurde getrennt..." & @CRLF)
    EndIf
    TCPCloseSocket($mainsocket) ; <----------- server herunterfahren
    $mainsocket = -1
    ConsoleWrite("Server wurde heruntergefahren..." & @CRLF)
    EndIf
    EndSwitch

    [/autoit] [autoit][/autoit] [autoit]

    If $mainsocket <> -1 And $acceptedSocket = -1 Then ; <----------- nur wenn der Server an ist und noch keine Verbindung zu einem Client besteht...
    $acceptedSocket = TCPAccept($mainsocket) ; versuchen eine mögliche Verbindung anzunehmen
    If $acceptedSocket <> -1 Then ConsoleWrite("Client hat sich verbunden..." & @CRLF)
    Else
    If $acceptedSocket <> -1 Then ; <------------ wenn ein Client verbunden ist
    $nachricht= TCPRecv($acceptedSocket, 2048) ; Wenn eine Socketverbindung hergestellt wurde, empfange ein Paket vom Client ($connectedSocket)
    if $nachricht <> "" Then
    GUICtrlSetData($edit,"Nachricht empfangen! Inhalt der Nachricht " & $nachricht) ; <-- Nachricht in gui ctrl schreiben, kannst du natürlich auch ein anderes Control dafür verwenden..
    ConsoleWrite("Neue Nachricht wurde in GUI eingetragen: " & @CRLF & $nachricht & @CRLF)
    EndIf
    EndIf
    EndIf
    WEnd

    [/autoit]

    Anmerkung:

    Das Script erlaubt nur eine zeitgleiche Verbindung und erfordert den Neustart des Servers per Button wenn sich nochmal jemand verbinden können soll.
    Außerdem sollte der client nach dem Verbindungsaufbau einige ms warten bevor er Daten sendet, da die Nachricht vom Server sonst möglicherweise nicht erfolgreich empfangen wird.
    Der Client bleibt beliebig lange verbunden und kann beliebig viele Nachrichten an den Server senden.

    Sollen mehrere Verbindungen möglich sein musst du die Verbindungs Sockets in einem Array speichern anstatt in einer einzigen Variable. Diesbezüglich sinnvolle Lektüre: http://www.bug-fix.info/array_tut.htm
    Um getrennte Verbindungen (clients die offline gehen...) zu erkennen kannst du tcpsend an den Verbindungssocket nutzen und bei Misserfolg den Verbindungssocket reseten ($acceptedSocket=-1) oder aus dem Array entfernen. So werdn nicht mehr genutzte Sockets wieder für neue Clients frei.

    Das ganze kann man natürlich noch deutlich weiter ausbauen, aber das würde nun zu weit gehen. Ich denke mit dem obigen Script hast du schonmal einen Ansatz wie man auch ohne Multithreading GUI und Funktionalität unter einen Hut bekommen kann. Wichtig ist nur, dass du die Hauptschleife nicht zu sehr verlangsamst oder gar blockierst.

  • Ich kann dir nur den Opt("GUIOnEventMode", 1) empfehlen damit lässt sich in den meisten fällen besser arbeiten als mit den Endlosschleifen. Der Einstieg mag ein bisschen Zeitaufwendiger sein aber hat man das Prinzip mal kapiert ist es um einiges übersichtlicher.
    Spätestens wenn du innerhalb der Endlosschleife Funktionen aufrufst ist Spaghetticode vorprogrammiert. Also besser gleich mit Funktionen arbeiten.

    Wird auch in der Hilfe empfohlen für GUIs welche nicht die ganze Zeit auf eine User Aktion warten müssen. Sondern z.b. Berechnetes ausgeben, darstellen etc.

    Zitat von OnEvent Mode

    This mode is best for GUIs where the GUI is of secondary importance and your script has other tasks to perform in addition to looking after the GUI.

  • Es würde auch einfach reichen die 2. Schleife mit der ersten zu verbinden. Umschreiben musst du dein Skript sowieso ein bisschen, da sich die 2. Schleife momentan noch nicht beenden lässt. Sollte mit ein bisschen Logik aber zu schaffen sein.

  • Hey, ich hab mir mal die Mühe gemacht und dein Skript ein wenig umgeschrieben sodass es effizienter und einfacher zu handhaben ist. Die Variablen hab ich jetzt ein wenig umbenannt weil ich's so besser lesen konnte xD

    Kannst sie ja auch wieder selber umbenennen. Sind ja nicht so viele...

    Spoiler anzeigen
    [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit] [autoit][/autoit] [autoit]

    #include <GUIConstants.au3>
    Global $hGUI, $idState, $bState, $sMessage
    Global $iAcceptSocket = -1

    [/autoit] [autoit][/autoit] [autoit]

    TCPStartup()
    Global Const $iMainSocket = TCPListen('127.0.0.01', 4321)

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    $hGUI = GUICreate('Server', 437, 467)
    $idState = GUICtrlCreateButton('OFFLINE', 24, 405, 385, 33)
    GUICtrlSetColor($idState, 0xFF0000)
    GUICtrlCreateEdit('', 24, 16, 385, 377)
    GUISetState()

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    While True
    Switch GUIGetMsg()
    Case $GUI_EVENT_CLOSE
    TCPCloseSocket($iAcceptSocket)
    TCPCloseSocket($iMainSocket)
    TCPShutdown()
    Exit
    Case $idState
    $bState = Not $bState
    If $bState Then
    GUICtrlSetData($idState, 'ONLINE')
    GUICtrlSetColor($idState, 0x009900)
    Else
    GUICtrlSetData($idState, 'OFFLINE')
    GUICtrlSetColor($idState, 0xFF0000)
    EndIf
    EndSwitch

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    If $iAcceptSocket = -1 Then $iAcceptSocket = TCPAccept($iMainSocket)
    $sMessage = TCPRecv($iAcceptSocket, 2048)
    If $bState And $sMessage Then MsgBox(64, 'Message', $sMessage)
    WEnd

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit]

    3 Mal editiert, zuletzt von Yjuq (2. Februar 2014 um 14:59)

  • Hey, ich hab mir mal die Mühe gemacht und dein Skript ein wenig umgeschrieben sodass es effizienter und einfacher zu handhaben ist.

    Das wird so sicher nicht funktionieren. tcpaccept() muss regelmässig abgefragt werden wenn sich Clients verbinden können sollen. Dein Script erlaubt nur zum Programmstart für wenige ms eine Verbindung, danach werden alle Verbindungsversuche ignoriert.