#include-once #include #include #include "TCP.au3" #cs PTCP.au3 - packetized TCP handling a helper for packetized TCP connections. Author : Neil Fisher The standard TCP functions work fine where you have complete control over client and server, however where you must work with 3rd party protocols, it's nice to be concerned only with sending and receiving entire packets in their "native" protocol - PTCP gives you that interface in a flexible way. Very similar to the standard TCP functions, but focused on packetized data, so we need to specify how to "find" a packet, we can ask if any packets are completely received, get received packets etc ****************************************************************************************************************************** Functions: _PTCPStartup() - called before any other usage! _PTCPCOnnect( $sIP, $iPort, $sDelim ) - connect to port $iPort of $sIP, packets are defined by $sDelim _PTCPSend( $ihandle, $sData ) - send string (packet) to socket _PTCPAvailable( $ihandle ) - return true if a packet is available _PTCPRecv( $ihandle ) - return an available packet _PTCPRecv_Blocking( $ihandle, $iTimeout ) - return an available packet, waiting up to $iTimeout mS for one to arrive _PTCPShutdown() - finish using PTCP Usage: Make sure to include this file in your script: #include "PTCP.au3" Make sure to have Kip's TCP.au3 Then your code will be like... Local $handle, $data ; variables - a socket handle and data ... _PTCPStartup() ; start packetized TCP ... $ihandle = _PTCPConnect( $sIP, $iPort, $sDelim ); Open a socket to IP/Port, using $sDelim to find packets ... if( _PTCPAvailable( $ihandle ) ) Then ; If we have a waiting packet ... ... $data = _PTCPRecv( $ihandle ) ; get a packet ... _PTCPSend( $ihandle, $data ) ; send a packet ... _PTCPRecv_Blocking( $iHandle, $iTimeout ) ; wait for a packet to arrive, timing out as required (default 3 seconds) ... _PTCPDisconnect( $ihandle ) ; close the socket ... _PTCPShutdown() ; finished with PTCP Specifying packet delimiter We pass an AutoIt expression that specifies how to determine a full packet has arrived. If this expression results in less than 1 (one), we do not have an answer, so the answer is no packet available if this expression results on any other number, then once that number of bytes is in the buffer, we do have a packet! You can use the following variables: $aPacket - the packet data itself, as an array $iAvail - the number of bytes currently in the packet buffer So if, for eg, you have a packet format of: 0xAA55 - packet header 0xxxyy - packet size, LSB first (little endian) - the actual data - checksum data (one byte) 0xc003 - packet trailer and packet size includes header and trailer, you could use: ($iAvail>4)?((BitAND($aPacket[2], '0xff'))+(BitShift(BitAND($aPacket[3],'0xff'),-8))):0 IOW, if more than 4 bytes are available (packet length has been received), re-arrange data and return the length, otherwise return 0 NOTE that we are checking if there are already enough bytes in the $aPacket array to extract the length completely. This is very important to check! Ternery op "?:" is usefull here. Or if packet size only specified data size, you could add "+7" to add (2 x header) + (2 x size) + (1 x CRC) + (2x trailer) = 7 bytes Alternately, if your packet length is fixed, you might use: 15 for packets of 15 bytes in length. The packet delimiter is checked when you call PTCPConnect, but only for being syntacically correct - it cannot tell you that you made a mistake specifying a packet end-point, only that the expression you supplied is a legal AutoIt expression. Received data is buffered and a complete packet is checked for when you call _PTCPAvailable() or _PTCPRecv_Blocking() *************************************************************************************************************************************** #ce ; ; Constants ; ; ; Error constants ; ; General errors ; Global Const $PTCP_ERR_UNRECOG = 101 ; unrecognized command Global Const $PTCP_ERR_BADARG = 102 ; bad/missing command args Global Const $PTCP_ERR_CHILD = 103 ; Child helper process error Global Const $PTCP_ERR_SOCKET = 104 ; TCP error Global Const $PTCP_ERR_UNDEF = 105 ; unspecified error Global Const $PTCP_ERR_NOPACK = 106 ; no rx packet available ; ; socket errors ; Global Const $PTCP_ERR_NOSOCK = 201 ; send but no open socket Global Const $PTCP_ERR_SOCKFAIL = 202 ; other socket error Global Const $PTCP_ERR_ALREADYOPEN = 203 ; open request when we already have an open socket Global Const $PTCP_ERR_BADIP = 204 ; not a valid IP address! Global Const $PTCP_ERR_BADPORT = 205 ; not a valid port number! ; ; helper errors ; Global Const $PTCP_ERR_CHILDRUN = 301 ; child process run error Global Const $PTCP_ERR_CHILDSTOP = 302 ; Child stop process error Global Const $PTCP_ERR_CHILDOPEN = 302 ; child not started Global Const $PTCP_ERR_HANDLE = 304 ; undefined handle to object Global Const $PTCP_ERR_BADCHECK = 305 ; packet test fragment won't run Global Const $PTCP_ERR_COMMS = 306 ; bad/unexpected result string from child ; ; Misc ; Global Const $PTCP_ERR_MISC = 401 ; miscellaneous failure, no further detail ; ; local vars ; Local $PID[0], $Delim[0], $buffer[0], $pbuffer[0], $status[0] ; ; If you need to debug, use this! ; Local $DEBUG = 0 ; ; 0 = no debug output, 1...6 increasing level of verbosity ; Func _PTCPStartup() ; ; PTCPStartup - get ready to use PTCP functions ; ; ; You should call this function once at the start of your script, or at least before you call any other PTCP function ; ; input parms : none ; ; return value : -1 ; ; ; no need to do anything here... ; If( $DEBUG > 5 ) Then ConsoleWrite( "_PTCPStartup: starting" & @CR ) If( $DEBUG > 5 ) Then ConsoleWrite( "_PTCPStartup: ending" & @CR ) return -1 EndFunc Func _PTCPShutdown() ; ; PTCPShutdown - finished using PTCP ; ; You should call this function once at the end of your script, or at least when you will no longer be using any PTCP functions ; ; input parms : none ; ; return value : -1 ; ; just close any open client and return Local $hclient, $i If( $DEBUG > 5 ) Then ConsoleWrite( "_PTCPShutdown: starting" & @CR ) for $i = 1 to UBound( $PID ) _TCP_Client_Stop( $PID[$i-1] ) Next If( $DEBUG > 5 ) Then ConsoleWrite( "_PTCPShutdown: ending" & @CR ) return -1 EndFunc Func _PTCPConnect( $IP, $Port, $Delimiter ) ; ; PTCPConnect - connect to an IP/Port ; ; Tries to connect an IP address / port number pair ; ; input parms : ; $IP : The IP address to try and connect to as a normal string (eg. '192.168.0.1') ; $Port : The port to try and conenct to as a number (1-65535) ; $Delim : Expression (AutoIt code fragment) that determines packet fully rx'ed ; See "Specifying packet delimiter" above ; ; return value : ; on success : the client socket handle ; on failure : 0 plus set @error & @extended ; ; ; just use Kip's connect, save the handle, check and save the delimiter and tie events Local $hclient, $current Local $iAvail = 1 Local $aPacket[] = ['1','2','3','4'] If( $DEBUG > 5 ) Then ConsoleWrite( "_PTCPConnect: starting" & @CR ) Execute( $Delimiter ) If( @error ) Then If( $DEBUG > 0 ) Then ConsoleWrite( "PTCPConnect: Error: '" & $Delimiter & "' not valid!" & @CR ) If( $DEBUG > 5 ) Then ConsoleWrite( "_PTCPConnect: ending" & @CR ) Return SetError( $PTCP_ERR_BADARG, $PTCP_ERR_BADCHECK, 0 ) EndIf If( $DEBUG > 5 ) Then ConsoleWrite( "_PTCPConnect: calling _TCP_Client_Create" & @CR ) $hclient = _TCP_Client_Create( $IP, $Port ) If( $DEBUG > 5 ) Then ConsoleWrite( "_PTCPConnect: registering callbacks" & @CR ) _TCP_RegisterEvent( $hclient, $TCP_RECEIVE, "__PTCP_Rx" ) _TCP_RegisterEvent( $hclient, $TCP_CONNECT, "__PTCP_Conn" ) _TCP_RegisterEvent( $hclient, $TCP_DISCONNECT, "__PTCP_Discon" ) _TCP_RegisterEvent( $hclient, $TCP_SEND, "__PTCP_Tx" ) $current = UBound( $PID ) If( $DEBUG > 5 ) Then ConsoleWrite( "_PTCPConnect: adding internal tracking info, new array size " & $current & @CR ) _ArrayAdd( $PID, $hclient ) _ArrayAdd( $Delim, $Delimiter ) _ArrayAdd( $buffer, '') _ArrayAdd( $status, 0 ) _ArrayAdd( $pbuffer, ObjCreate( 'System.Collections.ArrayList' ) ) If( $DEBUG > 5 ) Then ConsoleWrite( "_PTCPConnect: ending, new handle is " & Ubound( $PID ) & @CR ) return UBound( $PID ) EndFunc Func __PTCP_IndexFromHandle( $handle ) Local $i, $found = 0, $which = -1 If( $DEBUG > 5 ) Then ConsoleWrite( "__PTCPIndexFromHandle: starting" & @CR ) For $i = 0 to Ubound( $PID ) - 1 If( $PID[$i] = $handle ) Then $found = -1 $which = $i ExitLoop EndIf Next If( $found ) Then If( $DEBUG > 5 ) Then ConsoleWrite( "__PTCPIndexFromHandle: ending, found " & $handle & " --> " & $which & @CR ) Return $which Else If( $DEBUG > 5 ) Then ConsoleWrite( "__PTCPIndexFromHandle: ending, " & $handle & " not found" & @CR ) Return -1 EndIf EndFunc ; ; These are callbacks - do NOT use debugging in these! ; Func __PTCP_Rx( $hSocket, $sReceived, $iError ) Local $myh, $got, $tmp $myh = __PTCP_IndexFromHandle( $hSocket ) ; ; add to rx buffer ; $buffer[$myh] &= Binary($sReceived) ;ConsoleWrite( "Got packet:" & $sReceived & @CR) EndFunc Func __PTCP_Conn( $hSocket, $iError ) Local $myh $myh = __PTCP_IndexFromHandle( $hSocket ) ;$status[$myh] = ($iError)?(0):(-1) EndFunc Func __PTCP_Discon( $hSocket, $iError ) Local $myh $myh = __PTCP_IndexFromHandle( $hSocket ) ;$status[$myh] = 0 EndFunc Func __PTCP_Tx( $hSocket, $iError ) ; we don't use this - yet! EndFunc ; ; end of callbacks ; Func _PTCPDisconnect( $Handle ) ; ; PTCPDisconnect - disconnect the socket of the supplied handle ; ; input parms : $Handle - the connection to disconnect ; ; return value : ; on success : -1 ; on failure : 0 + sets @error & @extended ; Local $tmp ; ; sanity check our arg ; If( ($Handle > UBound( $PID )) or ($Handle < 1 ) ) Then Return SetError( $PTCP_ERR_CHILD, $PTCP_ERR_HANDLE, 0 ) EndIf $tmp = _TCP_Client_Stop( $PID[$Handle-1] ) ; ; invalidate our TCP.au3 handle - they get re-used! ; $PID[$Handle-1] = -1 Return $tmp EndFunc Func _PTCPSend( $Handle, $Packet ) ; ; PTCPSend - send data to the socket ; Local $count, $Str, $lo, $hi, $ss If( $DEBUG > 5 ) Then ConsoleWrite( "_PTCPSend: starting" & @CR ) ; ; sanity check args ; If( ($Handle > UBound( $PID )) or ($Handle < 1 ) ) Then If( $DEBUG > 1 ) Then ConsoleWrite( "_PTCPSend: invalid handle: " & $Handle & @CR ) Return SetError( $PTCP_ERR_CHILD, $PTCP_ERR_HANDLE, 0 ) EndIf ; ; send the data ; If( $DEBUG > 5 ) Then ConsoleWrite( "_PTCPSend: ending with call to _TCP_Send" & @CR ) Return _TCP_Send( $PID[$Handle-1], $Packet ) EndFunc Func _PTCPAvailable( $Handle ) ; ; PTCPAvailable - check the supplied handle connection for a complete packet being ready ; ; input parms : $Handle - the connection to check ; ; return value : ; on success : the boolean state of the completed packet buffer (eg, 0 = none available, -1 at least one available) ; on failure : 0 + sets @error & @extended ; ; ; sanity check our arg ; Local $myh = $Handle-1 If( $DEBUG > 5 ) Then ConsoleWrite( "_PTCPAvailable: starting with handle " & $Handle & @CR ) ; ; check if buffer is a full packet, if so move to this handles packet buffer ; Local $iAvail = BinaryLen( $buffer[$myh] ) Local $aPacket = binary($buffer[$myh]) If( ($Handle > UBound( $PID )) or ($Handle < 1 ) ) Then Return SetError( $PTCP_ERR_CHILD, $PTCP_ERR_HANDLE, 0 ) EndIf $got = Execute( $Delim[$myh] ) If( $DEBUG > 5 ) Then ConsoleWrite( "$iAvail is " & $iAvail & @CR ) ConsoleWrite( "test returned value: " & $got & @CR ) ConsoleWrite( "Input buffer is now:" & @CR ) EndIf If( $got ) Then If( $iAvail >= $got ) Then If( $DEBUG > 5 ) Then ConsoleWrite( "Avail: Found a packet in :" & $aPacket & @CR ) $tmp = BinaryMid( $buffer[$myh], 1, $got ) If( $DEBUG > 5 ) Then ConsoleWrite( " Got packet" & $tmp & @CR ) ConsoleWrite( " Queuing to buffer: " & $myh & @CR ) EndIf $buffer[$myh] = BinaryMid( $buffer[$myh], $got+1 ) $pbuffer[$myh].Add( $tmp ) if( $DEBUG > 5 ) then ConsoleWrite( "Input buffer is now:" & $buffer[$myh] & @CR ) EndIf EndIf If( $DEBUG > 5 ) Then ConsoleWrite( "_PTCPAvailable: ending" & @CR ) Return $pbuffer[$myh].Count() EndFunc Func _PTCPRecv( $Handle ) ; ; PTCPRecv - get an available packet ; ; input parms : $Handle - the connection to get packet from ; ; return value : ; on success : an array holding the data ; on failure : 0 + set @error & @extended ; Local $tmp If( $DEBUG > 5 ) Then ConsoleWrite( "_PTCPRecv: starting" & @CR ) ; ; sanity check our arg ; If( ($Handle > UBound( $PID )) or ($Handle < 1 ) ) Then ;MsgBox( 0,"PTCP", "Bad handle in PTCPRecv call!" ) Return SetError( $PTCP_ERR_CHILD, $PTCP_ERR_HANDLE, 0 ) EndIf If( _PTCPAvailable( $Handle ) ) Then $tmp = $pbuffer[$handle-1].Item(0) $pbuffer[$handle-1].RemoveAt(0) Return $tmp Else Return SetError( $PTCP_ERR_NOPACK, $PTCP_ERR_MISC, 0 ) EndIf EndFunc Func _PTCPRecv_Blocking( $Handle, $Timeout = 3000 ) ; ; PTCPRecv_Blocking - get an available packet, wait up to $Timeout mS for one to arrive ; ; input parms : ; $Handle - the connection to get packet from ; $Timeout - how many mS (milliseconds!) to wait at most ; ; return value : ; on success : an array holding the data ; on failure : 0 + set @error & @extended ; Local $count, $pack, $ret ; ; sanity check our arg ; If( ($Handle > UBound( $PID )) or ($Handle < 1 ) ) Then Return SetError( $PTCP_ERR_CHILD, $PTCP_ERR_HANDLE, 0 ) EndIf $count = 0 While( $count < $Timeout ) If( _PTCPAvailable( $Handle ) > 0 ) Then ExitLoop EndIf $count += 10 Sleep( 10 ) WEnd If( $count < $Timeout ) Then if( $DEBUG > 5 ) Then _ArrayDisplay( $pbuffer ) $pack=$pbuffer[$handle-1].Item(0) $pbuffer[$handle-1].RemoveAt(0) If( $DEBUG > 5 ) Then ConsoleWrite( "_PTCPRecv: ending, packet is available" & @CR ) Return $pack Else If( $DEBUG > 5 ) Then ConsoleWrite( "_PTCPRecv: ending, no packet" & @CR ) Return SetError( $PTCP_ERR_NOPACK, $PTCP_ERR_MISC, 0 ) EndIf EndFunc