Fast multi-client TCP server


SUBMITTED BY: Guest

DATE: May 28, 2013, 5:34 p.m.

FORMAT: Text only

SIZE: 8.3 kB

HITS: 1406

  1. #cs ----------------------------------------------------------------------------
  2. AutoIt Version: 3.3.8.1
  3. Author: Ken Piper
  4. Script Function:
  5. Template multi-client server base code.
  6. Use as a base for making an efficient server program.
  7. This base will just accept connections and echo back what it receives,
  8. and kill the connection if it is dead or inactive for x seconds.
  9. It will not do any other work, that must be added seperately!
  10. #ce ----------------------------------------------------------------------------
  11. TCPStartup()
  12. Opt("TCPTimeout", 0)
  13. #region ;Safe-to-edit things are below
  14. Global $BindIP = "0.0.0.0" ;Listen on all addresses
  15. Global $BindPort = 8080 ;Listen on port 8080
  16. Global $Timeout = 15000 ;Max idle time is 15 seconds before calling a connection "dead"
  17. Global $PacketSize = 2048 ;Max packet size per-check is 2KB
  18. Global $MaxClients = 50 ;Max simultaneous clients is 50
  19. #endregion ;Stuff you shouldn't touch is below
  20. Global $Listen
  21. Global $Clients[1][4] ;[Index][Socket, IP, Timestamp, Buffer]
  22. Global $Ws2_32 = DllOpen("Ws2_32.dll") ;Open Ws2_32.dll, it might get used a lot
  23. Global $NTDLL = DllOpen("ntdll.dll") ;Open ntdll.dll, it WILL get used a lot
  24. Global $CleanupTimer = TimerInit() ;This is used to time when things should be cleaned up
  25. OnAutoItExitRegister("Close") ;Register this function to be called if the server needs to exit
  26. $Clients[0][0] = 0
  27. $Listen = TCPListen($BindIP, $BindPort, $MaxClients) ;Start listening on the given IP/port
  28. If @error Then Exit 1 ;Exit with return code 1 if something was already bound to that IP and port
  29. While 1
  30. USleep(5000, $NTDLL) ;This is needed because TCPTimeout is disabled. Without this it will run one core at ~100%.
  31. ;The USleep function takes MICROseconds, not milliseconds, so 1000 = 1ms delay.
  32. ;When working with this granularity, you have to take in to account the time it takes to complete USleep().
  33. ;1000us (1ms) is about as fast as this should be set. If you need more performance, set this from 5000 to 1000,
  34. ;but doing so will make it consume a bit more CPU time to get that extra bit of performance.
  35. Check() ;Check recv buffers and do things
  36. If TimerDiff($CleanupTimer) > 1000 Then ;If it has been more than 1000ms since Cleanup() was last called, call it now
  37. $CleanupTimer = TimerInit() ;Reset $CleanupTimer, so it is ready to be called again
  38. Cleanup() ;Clean up the dead connections
  39. EndIf
  40. Local $iSock = TCPAccept($Listen) ;See if anything wants to connect
  41. If $iSock = -1 Then ContinueLoop ;If nothing wants to connect, restart at the top of the loop
  42. Local $iSize = UBound($Clients, 1) ;Something wants to connect, so get the number of people currently connected here
  43. 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
  44. TCPCloseSocket($iSock) ;It has been reached, close the new connection and continue back at the top of the loop
  45. ContinueLoop
  46. EndIf
  47. ReDim $Clients[$iSize + 1][4] ;There is room for a new connection, allocate space for it here
  48. $Clients[0][0] = $iSize ;Update the number of connected clients
  49. $Clients[$iSize][0] = $iSock ;Set the socket ID of the connection
  50. $Clients[$iSize][1] = SocketToIP($iSock, $Ws2_32) ;Set the IP Address the connection is from
  51. $Clients[$iSize][2] = TimerInit() ;Set the timestamp for the last known activity timer
  52. $Clients[$iSize][3] = "" ;Blank the recv buffer
  53. WEnd
  54. Func Check() ;Function for processing
  55. If $Clients[0][0] < 1 Then Return ;If there are no clients connected, stop the function right now
  56. For $i = 1 To $Clients[0][0] ;Loop through all connected clients
  57. $sRecv = TCPRecv($Clients[$i][0], $PacketSize) ;Read $PacketSize bytes from the current client's buffer
  58. If $sRecv <> "" Then $Clients[$i][3] &= $sRecv ;If there was more data sent from the client, add it to the buffer
  59. If $Clients[$i][3] = "" Then ContinueLoop ;If the buffer is empty, stop right here and check more clients
  60. $Clients[$i][2] = TimerInit() ;If it got this far, there is data to be parsed, so update the activity timer
  61. #region ;Example packet processing stuff here. This is handling for a simple "echo" server with per-packet handling
  62. $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
  63. ;This does NOT pull the first complete packet, this pulls ALL complete packets, leaving only potentially incomplete packets in the buffer
  64. If $sRecv = "" Then ContinueLoop ;Check if there were any complete "packets"
  65. $Clients[$i][3] = StringTrimLeft($Clients[$i][3], StringLen($sRecv) + 1) ;remove what was just read from the client's buffer
  66. $sPacket = StringSplit($sRecv, @CRLF, 1) ;Split all complete packets up in to an array, so it is easy to work with them
  67. For $j = 1 To $sPacket[0] ;Loop through each complete packet; This is where any packet processing should be done
  68. TCPSend($Clients[$i][0], "Echoing line: " & $sPacket[$j] & @CRLF) ;Echo back the packet the client sent
  69. Next
  70. #endregion ;Example
  71. Next
  72. EndFunc
  73. Func Cleanup() ;Clean up any disconnected clients to regain resources
  74. If $Clients[0][0] < 1 Then Return ;If no clients are connected then return
  75. Local $iNewSize = 0
  76. For $i = 1 To $Clients[0][0] ;Loop through all connected clients
  77. $Clients[$i][3] &= TCPRecv($Clients[$i][0], $PacketSize) ;Dump any data not-yet-seen in to their recv buffer
  78. 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
  79. TCPCloseSocket($Clients[$i][0]) ;If yes, close the connection
  80. $Clients[$i][0] = -1 ;Set the socket ID to an invalid socket
  81. Else
  82. $iNewSize += 1
  83. EndIf
  84. Next
  85. If $iNewSize < $Clients[0][0] Then ;If any dead connections were found, drop them from the client array and resize the array
  86. Local $iSize = UBound($Clients, 2) - 1
  87. Local $aTemp[$iNewSize + 1][$iSize + 1]
  88. Local $iCount = 1
  89. For $i = 1 To $Clients[0][0]
  90. If $Clients[$i][0] = -1 Then ContinueLoop
  91. For $j = 0 To $iSize
  92. $aTemp[$iCount][$j] = $Clients[$i][$j]
  93. Next
  94. $iCount += 1
  95. Next
  96. $aTemp[0][0] = $iNewSize
  97. $Clients = $aTemp
  98. EndIf
  99. EndFunc
  100. Func Close()
  101. DllClose($Ws2_32) ;Close the open handle to Ws2_32.dll
  102. DllClose($NTDLL) ;Close the open handle to ntdll.dll
  103. For $i = 1 To $Clients[0][0] ;Loop through the connected clients
  104. TCPCloseSocket($Clients[$i][0]) ;Force the client's connection closed
  105. Next
  106. TCPShutdown() ;Shut down networking stuff
  107. EndFunc
  108. Func SocketToIP($iSock, $hDLL = "Ws2_32.dll") ;A rewrite of that _SocketToIP function that has been floating around for ages
  109. Local $structName = DllStructCreate("short;ushort;uint;char[8]")
  110. Local $sRet = DllCall($hDLL, "int", "getpeername", "int", $iSock, "ptr", DllStructGetPtr($structName), "int*", DllStructGetSize($structName))
  111. If Not @error Then
  112. $sRet = DllCall($hDLL, "str", "inet_ntoa", "int", DllStructGetData($structName, 3))
  113. If Not @error Then Return $sRet[0]
  114. EndIf
  115. Return "0.0.0.0" ;Something went wrong, return an invalid IP
  116. EndFunc
  117. Func USleep($iUsec, $hDLL = "ntdll.dll") ;A rewrite of the _HighPrecisionSleep function made by monoceres (Thanks!)
  118. Local $hStruct = DllStructCreate("int64")
  119. DllStructSetData($hStruct, 1, -1 * ($iUsec * 10))
  120. DllCall($hDLL, "dword", "ZwDelayExecution", "int", 0, "ptr", DllStructGetPtr($hStruct))
  121. EndFunc

comments powered by Disqus