[UDF] Asynchrone TCP-Verarbeitung per Notifications

  • Guten Morgen,

    AU3 hat ja eigene TCP/UDP-Anweisungen, die durchaus nützliche asynchrone Nachrichtenverarbeitung ist aber leider nicht dabei. Ich habe mich früher schon mal damit beschäftigt, und weil es in diesem Forum dazu noch nichts gibt, stelle ich das hier ein.

    Client.au3
    [autoit]

    ; --------------------------------------------------------------------------------------------------------------------------------
    ; TCP CLIENTSCRIPT
    ; --------------------------------------------------------------------------------------------------------------------------------
    #include <GuiConstantsEx.au3>
    #include <EditConstants.au3>
    #include <ListBoxConstants.au3>
    #include <StaticConstants.au3>
    #include "TCP.au3"
    ; --------------------------------------------------------------------------------------------------------------------------------
    ; IP-Adresse und -Port des Servers:
    Dim Const $ServerAddr = "127.0.0.1"
    Dim Const $ServerPort = "8765"
    ; --------------------------------------------------------------------------------------------------------------------------------
    ; TCP initialisieren
    If Not TCPStartup() Then
    MsgBox(16, "TCP Client", "Fehler bei TCPStartup() -> " & @error)
    Exit
    EndIf
    OnAutoItExitRegister("_Exit")
    ; --------------------------------------------------------------------------------------------------------------------------------
    ; GUI
    Dim $aSplit = StringSplit($ServerAddr, ".")
    Opt("GUIOnEventMode", 1)
    $hGUI = GUICreate("TCP Client", 440, 350)
    GUISetOnEvent($GUI_EVENT_CLOSE, "_Exit")
    GUICtrlCreateLabel("Socket", 10, 10, 50)
    GUICtrlSetColor(-1, 0x000080)
    GUICtrlCreateLabel("Text", 70, 10, 300)
    GUICtrlSetColor(-1, 0x000080)
    $idSocket = GUICtrlCreateEdit("", 10, 30, 50, 20, $ES_READONLY)
    GUICtrlSetState(-1, $GUI_DISABLE)
    $idData = GUICtrlCreateInput("", 70, 30, 300, 20)
    GUICtrlSetState(-1, $GUI_DISABLE)
    $idSend = GUICtrlCreateButton("Send", 380, 30, 50, 20)
    GUICtrlSetState(-1, $GUI_DEFBUTTON)
    GUICtrlSetState(-1, $GUI_DISABLE)
    GUICtrlSetOnEvent(-1, "_Send")
    $idLOG = GUICtrlCreateEdit("Not connected!", 10, 60, 420, 250, $ES_READONLY)
    GUICtrlCreateLabel("IP:", 10, 320, -1, -1, $SS_CENTERIMAGE)
    GUICtrlSetColor(-1, 0x000080)
    $idIP1 = GUICtrlCreateInput($aSplit[1], 30, 320, 30, 20, $ES_NUMBER + $ES_CENTER)
    GUICtrlSetLimit(-1, 3)
    $idIP2 = GUICtrlCreateInput($aSplit[2], 70, 320, 30, 20, $ES_NUMBER + $ES_CENTER)
    GUICtrlSetLimit(-1, 3)
    $idIP3 = GUICtrlCreateInput($aSplit[3], 110, 320, 30, 20, $ES_NUMBER + $ES_CENTER)
    GUICtrlSetLimit(-1, 3)
    $idIP4 = GUICtrlCreateInput($aSplit[4], 150, 320, 30, 20, $ES_NUMBER + $ES_CENTER)
    GUICtrlSetLimit(-1, 3)
    GUICtrlCreateLabel("Port:", 200, 320, -1, -1, $SS_CENTERIMAGE)
    GUICtrlSetColor(-1, 0x000080)
    $idPort = GUICtrlCreateInput($ServerPort, 230, 320, 50, 20, $ES_NUMBER + $ES_CENTER)
    GUICtrlSetLimit(-1, 5)
    $idConnect = GUICtrlCreateButton("Connect", 350, 320, 80, 22)
    GUICtrlSetOnEvent(-1, "_Connect")
    GUISetState()
    ; --------------------------------------------------------------------------------------------------------------------------------
    ; Routine für die Behandlung der Notifications registrieren
    GUIRegisterMsg(_TCP_MessageNum(), "_NewTPCEvent")
    ; --------------------------------------------------------------------------------------------------------------------------------
    While True
    Sleep(1000)
    WEnd
    ; --------------------------------------------------------------------------------------------------------------------------------
    Func _Connect()
    Local $Enable, $Disable, $IP, $Item, $Port, $Socket
    Local $Action = GUICtrlRead($idConnect)
    Switch $Action
    Case "Connect"
    $IP = GUICtrlRead($idIP1) & "." & GUICtrlRead($idIP2) & "." & GUICtrlRead($idIP3) & "." & GUICtrlRead($idIP4)
    $Port = GUICtrlRead($idPort)
    $Socket = TCPConnect($IP, $Port)
    If $Socket < 1 Then
    MsgBox(16, "TCP Client", "Fehler bei TCPConnect() -> " & @error, 0, $hGUI)
    Return $GUI_RUNDEFMSG
    EndIf
    ; --------------------------------------------------------------------------------------------------------------------
    ; Asynchrone Eventverarbeitung per Notification aktivieren
    If Not _TCP_AsyncSelect($Socket, @GUI_WinHandle, _TCP_MessageNum(), "FD_CLOSE|FD_CONNECT|FD_READ") Then
    MsgBox(16, "TCP Server", "Fehler bei _TCP_AsyncSelect() -> " & _TCP_GetLastError(), 0, $hGUI)
    Return $GUI_RUNDEFMSG
    EndIf
    $Enable = $GUI_ENABLE
    $Disable = $GUI_DISABLE
    GUICtrlSetData($idConnect, "Disconnect")
    GUICtrlSetData($idSocket, $Socket)
    _UpdateLog($Socket & " >>> Connected to " & $IP & ":" & $Port)
    Case Else
    $Socket = GUICtrlRead($idSocket)
    ; --------------------------------------------------------------------------------------------------------------------
    ; Asynchrone Eventverarbeitung per Notification deaktivieren
    If Not _TCP_AsyncSelect($Socket, @GUI_WinHandle, 0, 0) Then
    MsgBox(16, "TCP Server", "Fehler bei _TCP_AsyncSelect() -> " & _TCP_GetLastError(), 0, $hGUI)
    Return $GUI_RUNDEFMSG
    EndIf
    If Not TCPCloseSocket($Socket) Then
    MsgBox(16, "TCP Client", "Fehler bei TCPCloseSocket() -> " & @error, 0, $hGUI)
    Return $GUI_RUNDEFMSG
    EndIf
    $Enable = $GUI_DISABLE
    $Disable = $GUI_ENABLE
    GUICtrlSetData($idConnect, "Connect")
    GUICtrlSetData($idSocket, "")
    _UpdateLog($Socket & " >>> Connection closed!")
    EndSwitch
    GUICtrlSetState($idSocket, $ENABLE)
    GUICtrlSetState($idData, $ENABLE)
    GUICtrlSetState($idSend, $ENABLE)
    GUICtrlSetState($idIP1, $DISABLE)
    GUICtrlSetState($idIP2, $DISABLE)
    GUICtrlSetState($idIP3, $DISABLE)
    GUICtrlSetState($idIP4, $DISABLE)
    GUICtrlSetState($idPort, $DISABLE)
    If $Action = "Connect" Then
    GUICtrlSetState($idData, $GUI_FOCUS)
    Else
    GUICtrlSetState($idIP1, $GUI_FOCUS)
    EndIf
    EndFunc
    ; --------------------------------------------------------------------------------------------------------------------------------
    Func _Exit()
    TCPShutdown()
    Exit
    EndFunc
    ; --------------------------------------------------------------------------------------------------------------------------------
    Func _NewTPCEvent($hWnd, $iMsg, $wParam, $lParam)
    ; Zuerst wird die Eventhandler-Routine aufgerufen, um das Event zu verarbeiten, ...
    Local $Data = _TCP_EventHandler($hWnd, $iMsg, $wParam, $lParam)
    ; ... dann können wir machen, was wir wollen!
    If $Data Then _UpDateLog($Data)
    EndFunc
    ; --------------------------------------------------------------------------------------------------------------------------------
    Func _Send()
    Local $Socket, $Data
    $Socket = GUICtrlRead($idSocket)
    $Data = GUICtrlRead($idData)
    If Not TCPSend($Socket, $Data) Then
    MsgBox(16, "TCP Client", "Fehler bei TCPSend() -> " & @error, 0, $hGUI)
    Else
    _UpdateLog($Socket & " >>> " & $Data)
    EndIf
    GUICtrlSetData($idData, "")
    GUICtrlSetState($idData, $GUI_FOCUS)
    EndFunc
    ; --------------------------------------------------------------------------------------------------------------------------------
    Func _UpdateLog($Data)
    Global $idLOG
    GUICtrlSendMsg($idLOG, $EM_SETSEL, -2, -1)
    GUICtrlSetData($idLOG, @CRLF & $Data, 1)
    EndFunc

    [/autoit]
    Server.au3
    [autoit]

    ; --------------------------------------------------------------------------------------------------------------------------------
    ; TCP SERVERSCRIPT
    ; --------------------------------------------------------------------------------------------------------------------------------
    #include <GuiConstantsEx.au3>
    #include <ComboConstants.au3>
    #include <EditConstants.au3>
    #include <WindowsConstants.au3>
    #include "TCP.au3"
    ; --------------------------------------------------------------------------------------------------------------------------------
    ; IP-Adresse und Port des servers:
    Dim Const $IPAddr = "127.0.0.1"
    Dim Const $IPPort = "8765"
    ; --------------------------------------------------------------------------------------------------------------------------------
    ; TCP initialisieren
    If Not TCPStartup() Then
    MsgBox(16, "TCP Server", "Fehler bei TCPStartup() -> " & @error)
    Exit
    EndIf
    OnAutoItExitRegister("_Exit")
    ; --------------------------------------------------------------------------------------------------------------------------------
    ; Listener für Server erzeugen
    Dim $Listener = TCPListen($IPAddr, $IPPort)
    If @error Or $Listener < 1 Then
    MsgBox(16, "TCP Server", "Fehler bei TCPListen() -> " & @error)
    Exit
    EndIf
    ; --------------------------------------------------------------------------------------------------------------------------------
    ; GUI
    Opt("GUIOnEventMode", 1)
    $hGUI = GUICreate("TCP Server " & $IPAddr & ":" & $IPPort , 460, 320)
    GUISetOnEvent($GUI_EVENT_CLOSE, "_Exit")
    GUICtrlCreateLabel("Socket", 10, 10, 50)
    GUICtrlSetColor(-1, 0x000080)
    GUICtrlCreateLabel("Text", 90, 10, 300)
    GUICtrlSetColor(-1, 0x000080)
    $idSocket = GUICtrlCreateCombo("", 10, 30, 70, -1, BitOR($CBS_DROPDOWNLIST, $CBS_SORT, $WS_VSCROLL))
    $idData = GUICtrlCreateInput("", 90, 30, 300, 20)
    GUICtrlSetState($idData, $GUI_FOCUS)
    $idSend = GUICtrlCreateButton("Send", 400, 30, 50, 20)
    GUICtrlSetState(-1, $GUI_DEFBUTTON)
    GUICtrlSetOnEvent(-1, "_Send")
    $idLOG = GUICtrlCreateEdit("Waiting for connections ...", 10, 60, 440, 250, $ES_READONLY)
    GUISetState()
    ; --------------------------------------------------------------------------------------------------------------------------------
    ; Asynchrone Eventverarbeitung per Notification aktivieren, der Server muss zunächst einmal per "FD_ACCEPT"
    ; gemeldete Verbindungswünsche akzeptieren
    If Not _TCP_AsyncSelect($Listener, $hGUI, _TCP_MessageNum(), "FD_ACCEPT") Then
    MsgBox(16, "TCP Server", "Fehler bei _TCP_AsyncSelect() -> " & _TCP_GetLastError(), 0, $hGUI)
    Exit
    EndIf
    ; --------------------------------------------------------------------------------------------------------------------------------
    ; Routine für die Behandlung der Notifications registrieren
    GUIRegisterMsg(_TCP_MessageNum(), "_NewTPCEvent")
    ; --------------------------------------------------------------------------------------------------------------------------------
    While True
    Sleep(1000)
    WEnd
    ; --------------------------------------------------------------------------------------------------------------------------------
    Func _Exit()
    TCPShutdown()
    Exit
    EndFunc
    ; --------------------------------------------------------------------------------------------------------------------------------
    Func _NewTPCEvent($hWnd, $iMsg, $wParam, $lParam)
    Local Static $FD_ACCEPT = 8 ; Notification of incoming connections.
    Local Static $FD_CLOSE = 32 ; Notification when connection has been closed.
    Local $ThisEvent = BitAND($lParam, 0xFFFF)
    Local $Data = _TCP_EventHandler($hWnd, $iMsg, $wParam, $lParam)
    Switch $ThisEvent
    Case $FD_ACCEPT
    If StringInStr($Data, "accepted") Then
    Local $aRX = StringRegExp($Data, "\A(\d+)", 1)
    GUICtrlSetData($idSocket, $aRX[0], $aRX[0])
    GUICtrlSetState($idData, $GUI_FOCUS)
    EndIf
    Case $FD_CLOSE
    If StringInStr($Data, "closed") Then
    Local $aRX = StringRegExp($Data, "\A(\d+)", 1)
    Local $Index = GUICtrlSendMsg($idSocket, $CB_FINDSTRING, -1, $aRX[0])
    If $Index > -1 Then
    GUICtrlSendMsg($idSocket, $CB_DELETESTRING, $Index, 0)
    GUICtrlSendMsg($idSocket, $CB_SETCURSEL, 0, 0)
    GUICtrlSetState($idSend, $GUI_SHOW)
    EndIf
    GUICtrlSetState($idData, $GUI_FOCUS)
    EndIf
    EndSwitch
    _UpDateLog($Data)
    EndFunc
    ; --------------------------------------------------------------------------------------------------------------------------------
    Func _Send()
    Local $Socket, $Data
    $Socket = GUICtrlRead($idSocket)
    If $Socket Then
    $Data = GUICtrlRead($idData)
    If Not TCPSend($Socket, $Data) Then
    MsgBox(16, "TCP Client", "Fehler bei TCPSend() -> " & @error, 0, $hGUI)
    Else
    _UpdateLog($Socket & " >>> " & $Data)
    EndIf
    EndIf
    GUICtrlSetData($idData, "")
    GUICtrlSetState($idData, $GUI_FOCUS)
    EndFunc
    ; --------------------------------------------------------------------------------------------------------------------------------
    Func _UpdateLog($Data)
    Global $idLOG
    GUICtrlSendMsg($idLOG, $EM_SETSEL, -2, -1)
    GUICtrlSetData($idLOG, @CRLF & $Data, 1)
    EndFunc

    [/autoit]
    TCP.au3
    [autoit]

    ; #INDEX# =======================================================================================================================
    ; Title .........: TCP
    ; AutoIt Version : 3.3.6.1
    ; Language ......: Deutsch
    ; Description ...: Funktionen für die asynchrone Verarbeitung von TCP-Events per "notification messages"
    ; Author(s) .....: Großvater (http://www.autoit.de)
    ; Dll(s) ........: WS2_32.dll
    ; ===============================================================================================================================

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

    ; #CURRENT# =====================================================================================================================
    ;_TCP_AsyncSelect
    ;_TCP_EventHandler
    ;_TCP_GetLastError
    ;_TCP_MessageNum
    ; ===============================================================================================================================

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

    ; #FUNCTION# ====================================================================================================================
    ; Name...........: _TCP_AsyncSelect
    ; Description ...: Registriert eine "notification message" für einen Socket und vorgegebene TCP-Events
    ; Syntax.........: _TCP_AsyncSelect($Socket, $hWnd, $Msg, $Events)
    ; Parameters ....: $Socket - gültiger TCP-Socket
    ; $hWnd - hWnd des Fensters, dem die Nachricht gesendet wird
    ; $Msg - Nachrichtennummer für die "notification message" (sollte zwischen 0x1000 und 0x7FFF liegen)
    ; $Events - String mit durch Pipe (|) getrennter Liste von TCP-Events.
    ; Z.Zt. werden folgende Events unterstützt:
    ; FD_ACCEPT : Server: eingehende Verbindung
    ; FD_CLOSE : Client/Server: Verbindung wurde beendet
    ; FD_CONNECT : Client: erfolgreiche Verbindung
    ; FD_READ : Client/Server: Daten wurden empfangen
    ; Return values .: Erfolg - True
    ; Fehler - False
    ; Author ........: Großvater (http://www.autoit.de)
    ; Modified.......:
    ; Remarks .......:
    ; Related .......:
    ; Link ..........:
    ; Example .......:
    ; ===============================================================================================================================
    Func _TCP_AsyncSelect($Socket, $hWnd, $Msg, $Events)
    Local Static $FD_READ = 1 ; Daten wurden empfangen
    Local Static $FD_ACCEPT = 8 ; Verbindungswunsch wurde empfangen
    Local Static $FD_CONNECT = 16 ; Verbindung wurde erfolgreich aufgebaut
    Local Static $FD_CLOSE = 32 ; Verbindung wurde getrennt
    Local $ThisEvents = 0
    Local $aSplit = StringSplit($Events, "|")
    For $I = 1 To $aSplit[0]
    If IsDeclared($aSplit[$I]) Then $ThisEvents += Eval($aSplit[$I])
    Next
    Local $aResult = DllCall("Ws2_32.dll", "Int", "WSAAsyncSelect", "UInt", $Socket, "UInt", $hWnd, "Int", $Msg, "Int", $ThisEvents)
    If $aResult[0] Then Return False
    Return True
    EndFunc

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

    ; #FUNCTION# ====================================================================================================================
    ; Name...........: _TCP_EventHandler
    ; Description ...: Eventhandler für die mit _TCP_AsyncSelect() registrierten Events.
    ; Syntax.........: _TCP_EventHandler($hWnd, $Msg, $Socket, $lParam)
    ; Parameters ....: $hWnd - hWnd des Fensters, dem die Nachricht gesendet wurde
    ; $Msg - Nachrichtennummer
    ; $Socket - TCP-Socket (wParam)
    ; $lParam - zusätzliche Informationen:
    ; Low Word : TCP-Event
    ; High Word: TCP-Error
    ; Return values .: Erfolg - eventabhängige Meldung
    ; Fehler - False
    ; Author ........: Großvater (http://www.autoit.de)
    ; Modified.......:
    ; Remarks .......:
    ; Related .......:
    ; Link ..........:
    ; Example .......:
    ; ===============================================================================================================================
    Func _TCP_EventHandler($hWnd, $Msg, $Socket, $lParam)
    Local Static $FD_READ = 1 ; Daten wurden empfangen
    Local Static $FD_ACCEPT = 8 ; Verbindungswunsch wurde empfangen
    Local Static $FD_CONNECT = 16 ; Verbindung wurde erfolgreich aufgebaut
    Local Static $FD_CLOSE = 32 ; Verbindung wurde getrennt
    Local Static $WSAEWOULDBLOCK = 10035
    Local Static $WSAECONNRESET = 10054
    Local Static $MaxData = 4096 ; Maximallänge für die empfangenen Daten in Bytes
    Local $ThisEvent = BitAND($lParam, 0xFFFF)
    Local $ThisError = BitShift($lParam, 16)
    Switch $ThisEvent
    Case $FD_ACCEPT
    Local $NewSocket = TCPAccept($Socket)
    If $NewSocket = -1 Then
    TCPSend($NewSocket, "Connection rejected!")
    Return Number($NewSocket) & " >>> Connection rejected!"
    Else
    _TCP_AsyncSelect($NewSocket, $hWnd, _TCP_MessageNum(), "FD_CLOSE|FD_CONNECT|FD_READ")
    TCPSend($NewSocket, "Connection accepted!")
    Return Number($NewSocket) & " >>> Connection accepted!"
    EndIf
    Case $FD_CLOSE
    TCPCloseSocket($Socket)
    Return Number($Socket) & " >>> Connection closed!"
    Case $FD_CONNECT
    Return Number($Socket) & " >>> Connection accepted!"
    Case $FD_READ
    Local $Buffer = ""
    Local $Received = TCPRecv($Socket, $MaxData)
    While $Received And Not @error
    $Buffer &= $Received
    $Received = TCPRecv($Socket, $MaxData)
    WEnd
    If @error Then
    Switch @error
    Case $WSAEWOULDBLOCK ; WSAEWOULDBLOCK (keine weiteren Daten vorhanden, gibt es nicht unter AU3!!!)
    Case Not $WSAECONNRESET ; WSAECONNRESET (der Netzwerkdienst wurde vom System beendet)
    ; Unerwartete Fehler werden gemeldet
    MsgBox(0, "TCP Error", "recv() indicated Winsock error " & @error)
    Exit
    EndSwitch
    EndIf
    If $Buffer Then Return Number($Socket) & " <<< " & $Buffer
    EndSwitch
    Return False
    EndFunc

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

    ; #FUNCTION# ====================================================================================================================
    ; Name...........: _TCP_GetLastError
    ; Description ...: Letzten TCP-Fehlercode holen.
    ; Syntax.........: _TCP_GetLastError()
    ; Parameters ....:
    ; Return values .: WSA Errorcode
    ; Author ........: Großvater (http://www.autoit.de)
    ; Modified.......:
    ; Remarks .......:
    ; Related .......:
    ; Link ..........:
    ; Example .......:
    ; ===============================================================================================================================
    Func _TCP_GetLastError()
    Local $aResult = DllCall("Ws2_32.dll", "Int", "WSAGetLastError")
    Return $aResult[0]
    EndFunc

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

    ; #FUNCTION# ====================================================================================================================
    ; Name...........: _TCP_MessageNum
    ; Description ...: Vordefinierte Nachrichtennummer holen/setzen
    ; Syntax.........: _TCP_MessageNum([$iMsgNum = 0])
    ; Parameters ....: $iMsgNum - Neue Nachrichtennummer
    ; Return values .: Nachrichtennummer
    ; Author ........: Großvater (http://www.autoit.de)
    ; Modified.......:
    ; Remarks .......:
    ; Related .......:
    ; Link ..........:
    ; Example .......:
    ; ===============================================================================================================================
    Func _TCP_MessageNum($iMsgNum = 0)
    Local Static $TCPMsg = 0x5555
    If ($iMsgNum) Then $TCPMsg = $iMsgNum
    Return $TCPMsg
    EndFunc

    [/autoit]


    Ich hoffe, die enthaltenen Kommentare erklären das Ganze ausreichend, wenn nicht, einfach fragen. ;)