これは、非常によく書かれ、コメントされているマルチクライアント TCP サーバーの 1 つです。ケン・パイパーの功績。
#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
次にリストビューへの配列について、
Dim $array[5]
$array[1] = "value 1"
$array[2] = "value 2"
$array[3] = "value 3"
$array[4] = "value 4"
$array[0] = UBound($array) - 1
For $i = 1 To $array[0]
GUICtrlSetData($listview,$array[$i])
Next