#cs ---------------------------------------------------------------------------- AutoIt Version: 3.3.8.1 Author: Ken Piper Script Function: Template multi-client server base code. Use as a base for making an efficient server program. This base will just accept connections and echo back what it receives, and kill the connection if it is dead or inactive for x seconds. It will not do any other work, that must be added seperately! #ce ---------------------------------------------------------------------------- TCPStartup() Opt("TCPTimeout", 0) #region ;Safe-to-edit things are below Global $BindIP = "0.0.0.0" ;Listen on all addresses Global $BindPort = 8080 ;Listen on port 8080 Global $Timeout = 15000 ;Max idle time is 15 seconds before calling a connection "dead" Global $PacketSize = 2048 ;Max packet size per-check is 2KB Global $MaxClients = 50 ;Max simultaneous clients is 50 #endregion ;Stuff you shouldn't touch is below Global $Listen Global $Clients[1][4] ;[Index][Socket, IP, Timestamp, Buffer] Global $Ws2_32 = DllOpen("Ws2_32.dll") ;Open Ws2_32.dll, it might get used a lot Global $NTDLL = DllOpen("ntdll.dll") ;Open ntdll.dll, it WILL get used a lot Global $CleanupTimer = TimerInit() ;This is used to time when things should be cleaned up OnAutoItExitRegister("Close") ;Register this function to be called if the server needs to exit $Clients[0][0] = 0 $Listen = TCPListen($BindIP, $BindPort, $MaxClients) ;Start listening on the given IP/port If @error Then Exit 1 ;Exit with return code 1 if something was already bound to that IP and port While 1 USleep(5000, $NTDLL) ;This is needed because TCPTimeout is disabled. Without this it will run one core at ~100%. ;The USleep function takes MICROseconds, not milliseconds, so 1000 = 1ms delay. ;When working with this granularity, you have to take in to account the time it takes to complete USleep(). ;1000us (1ms) is about as fast as this should be set. If you need more performance, set this from 5000 to 1000, ;but doing so will make it consume a bit more CPU time to get that extra bit of performance. Check() ;Check recv buffers and do things If TimerDiff($CleanupTimer) > 1000 Then ;If it has been more than 1000ms since Cleanup() was last called, call it now $CleanupTimer = TimerInit() ;Reset $CleanupTimer, so it is ready to be called again Cleanup() ;Clean up the dead connections EndIf Local $iSock = TCPAccept($Listen) ;See if anything wants to connect If $iSock = -1 Then ContinueLoop ;If nothing wants to connect, restart at the top of the loop Local $iSize = UBound($Clients, 1) ;Something wants to connect, so get the number of people currently connected here If $iSize - 1 > $MaxClients And $MaxClients > 0 Then ;If $MaxClients is greater than 0 (meaning if there is a max connection limit) then check if that has been reached TCPCloseSocket($iSock) ;It has been reached, close the new connection and continue back at the top of the loop ContinueLoop EndIf ReDim $Clients[$iSize + 1][4] ;There is room for a new connection, allocate space for it here $Clients[0][0] = $iSize ;Update the number of connected clients $Clients[$iSize][0] = $iSock ;Set the socket ID of the connection $Clients[$iSize][1] = SocketToIP($iSock, $Ws2_32) ;Set the IP Address the connection is from $Clients[$iSize][2] = TimerInit() ;Set the timestamp for the last known activity timer $Clients[$iSize][3] = "" ;Blank the recv buffer WEnd Func Check() ;Function for processing If $Clients[0][0] < 1 Then Return ;If there are no clients connected, stop the function right now For $i = 1 To $Clients[0][0] ;Loop through all connected clients $sRecv = TCPRecv($Clients[$i][0], $PacketSize) ;Read $PacketSize bytes from the current client's buffer If $sRecv <> "" Then $Clients[$i][3] &= $sRecv ;If there was more data sent from the client, add it to the buffer If $Clients[$i][3] = "" Then ContinueLoop ;If the buffer is empty, stop right here and check more clients $Clients[$i][2] = TimerInit() ;If it got this far, there is data to be parsed, so update the activity timer #region ;Example packet processing stuff here. This is handling for a simple "echo" server with per-packet handling $sRecv = StringLeft($Clients[$i][3], StringInStr($Clients[$i][3], @CRLF, 0, -1)) ;Pull all data to the left of the last @CRLF in the buffer ;This does NOT pull the first complete packet, this pulls ALL complete packets, leaving only potentially incomplete packets in the buffer If $sRecv = "" Then ContinueLoop ;Check if there were any complete "packets" $Clients[$i][3] = StringTrimLeft($Clients[$i][3], StringLen($sRecv) + 1) ;remove what was just read from the client's buffer $sPacket = StringSplit($sRecv, @CRLF, 1) ;Split all complete packets up in to an array, so it is easy to work with them For $j = 1 To $sPacket[0] ;Loop through each complete packet; This is where any packet processing should be done TCPSend($Clients[$i][0], "Echoing line: " & $sPacket[$j] & @CRLF) ;Echo back the packet the client sent Next #endregion ;Example Next EndFunc Func Cleanup() ;Clean up any disconnected clients to regain resources If $Clients[0][0] < 1 Then Return ;If no clients are connected then return Local $iNewSize = 0 For $i = 1 To $Clients[0][0] ;Loop through all connected clients $Clients[$i][3] &= TCPRecv($Clients[$i][0], $PacketSize) ;Dump any data not-yet-seen in to their recv buffer If @error Or TimerDiff($Clients[$i][2]) > $Timeout Then ;Check to see if the connection has been inactive for a while or if there was an error TCPCloseSocket($Clients[$i][0]) ;If yes, close the connection $Clients[$i][0] = -1 ;Set the socket ID to an invalid socket Else $iNewSize += 1 EndIf Next If $iNewSize < $Clients[0][0] Then ;If any dead connections were found, drop them from the client array and resize the array Local $iSize = UBound($Clients, 2) - 1 Local $aTemp[$iNewSize + 1][$iSize + 1] Local $iCount = 1 For $i = 1 To $Clients[0][0] If $Clients[$i][0] = -1 Then ContinueLoop For $j = 0 To $iSize $aTemp[$iCount][$j] = $Clients[$i][$j] Next $iCount += 1 Next $aTemp[0][0] = $iNewSize $Clients = $aTemp EndIf EndFunc Func Close() DllClose($Ws2_32) ;Close the open handle to Ws2_32.dll DllClose($NTDLL) ;Close the open handle to ntdll.dll For $i = 1 To $Clients[0][0] ;Loop through the connected clients TCPCloseSocket($Clients[$i][0]) ;Force the client's connection closed Next TCPShutdown() ;Shut down networking stuff EndFunc Func SocketToIP($iSock, $hDLL = "Ws2_32.dll") ;A rewrite of that _SocketToIP function that has been floating around for ages Local $structName = DllStructCreate("short;ushort;uint;char[8]") Local $sRet = DllCall($hDLL, "int", "getpeername", "int", $iSock, "ptr", DllStructGetPtr($structName), "int*", DllStructGetSize($structName)) If Not @error Then $sRet = DllCall($hDLL, "str", "inet_ntoa", "int", DllStructGetData($structName, 3)) If Not @error Then Return $sRet[0] EndIf Return "0.0.0.0" ;Something went wrong, return an invalid IP EndFunc Func USleep($iUsec, $hDLL = "ntdll.dll") ;A rewrite of the _HighPrecisionSleep function made by monoceres (Thanks!) Local $hStruct = DllStructCreate("int64") DllStructSetData($hStruct, 1, -1 * ($iUsec * 10)) DllCall($hDLL, "dword", "ZwDelayExecution", "int", 0, "ptr", DllStructGetPtr($hStruct)) EndFunc