Jump to content

Trouble with arrays


Recommended Posts

Greetings all,

I'm moving forward in my re-make of my Au3 MUD and getting a nice foundation setup. Although, there is a nasty little bug I'm trying to squash and I'm at a loss as to how to fix it. My server runs like so:

1.) Check for new connections

-- Add news ones and send MOTD

2.) Recieve data from connected users

-- Parses the data and executes user-sent commands

-- User commands that send info back will check for connection validity and delete accordingly.

3.) Sends a keep-alive blank string to each connection to ensure they are still connected, deleting if not there.

The problem is when a connection terminates (in Step 2 or 3) and is deleted from my connections list, the main loop (which is polling thru the array via a For loop) the subscript will exceed the new array size and crash. I've tried ensuring the iterator doesn't exceed the array size by checking the number of connections and the iterator, but that doesn't seem to work. So, I'm looking for a way to fix the problem. For those who need code to stare at, here ya go:

Server.au3

#cs ----------------------------------------------------------------------------
    AutoIt Version: 3.2.1.3 (beta)
    Author:         CodeMaster Rapture
    
    Script Function:
    Server for Au3 MUD
#ce ----------------------------------------------------------------------------

#include <Array.au3>

;Initialize TCP/IP
If (TCPStartup() == 0) Then
    _AddLog("Failed to initialize TCP/IP protocol. Exiting.")
    Exit
EndIf

#region Global Constants
Global Const $iListen_Port = 4000
Global Const $iMaxConnections = 25
Global Const $iMaxPendingConnections = 10
Global Const $iMaxDataLength = 1024
Global Const $bAllowMultiConnect = True
Global Const $sockMain = TCPListen(@IPAddress1, $iListen_Port, $iMaxPendingConnections)

;See region: Do-Functions
Global Const $szCommandList[3] = ["quit", "who", "whois"]
Global Const $szFunctionList[3] = ["_DoQuit", "_DoWho", "_DoWhoIs"]
#endregion Global Constants

#region Globals
Global $bLogEnable = True
Global $Connections[1] = [0]
#endregion Globals

If ($sockMain == -1) Then
    _AddLog("Error! Failed to create server socket! Exiting.")
    Exit
Else
    _AddLog("Waiting for connections on port: " & $iListen_Port)
EndIf

While 1
    #region Look for a connection:
    If ($Connections[0] < $iMaxConnections) Then
        Local $sockNewConnection = TCPAccept($sockMain)
        
        ;If it is a valid connection, add them to the user list!
        If ($sockNewConnection <> -1) Then
            _AddUser($sockNewConnection)
            _AddLog("Number of connections: " & $Connections[0])
            _SendToUser($sockNewConnection, "Welcome to Au3 MUD server version 1.02b" & @CRLF & @CRLF)
            _SendToUser($sockNewConnection, "The following commands are implemented:" & @CRLF)
            Local $iter = 0
            For $iter = 0 To (UBound($szCommandList) - 1)
                _SendToUser($sockNewConnection, " *" & $szCommandList[$iter] & @CRLF)
            Next
            _SendToUser($sockNewConnection, @CRLF & "Enjoy your stay." & @CRLF & @CRLF)
        EndIf

    EndIf
    #endregion Look for New Connections
    
    #region Recv/Send
    If ($Connections[0] > 0) Then
        Local $iter = 1
        For $iter = 1 To $Connections[0]
            
            #region Get data from connections
            Local $szData = _RecvFromUser($Connections[$iter])
            If (StringLen($szData) > 0) Then
                _AddLog("Recieved Data [" & $szData & "] from User: " & $Connections[$iter])
                _ParseData($Connections[$iter], $szData)
            EndIf
            #endregion Get Data
            
            #region Send Data to connections
            _SendToUser($Connections[$iter], "") ;Keep alive
            #endregion Send Data
        Next
    EndIf
    #endregion Recv/Send
    Sleep(1)
WEnd

_Exit()

Func _AddUser($sockUser = -1)
    If ($sockUser == -1) Then
        _AddLog("Error: _AddUser(), invalid user.")
        Return False
    EndIf
    
    Local $UserIP = SocketToIP($sockUser), $iter
    _AddLog("User connected: " & $sockUser & " (" & $UserIP & ")")
    
    If ($bAllowMultiConnect == False) Then
        ;Check for duplicate connection:
        If ($Connections[0] > 0) Then
            For $iter = 1 To $Connections[0]
                If ($UserIP == SocketToIP($Connections[$iter])) Then
                    _SendToUser($sockUser, "Sorry, only one connection per IP allowed. Goodbye.")
                    TCPCloseSocket($sockUser)
                    _AddLog("Kicked " & $UserIP & " [Duplicate IP found]")
                    Return False
                EndIf
            Next
        EndIf
    EndIf

    _ArrayAdd($Connections, $sockUser)  ;Let's add them to the connection list
    $Connections[0] = UBound($Connections) - 1
    
    Return True
EndFunc   ;==>_AddUser

Func _DeleteUser($sockUser = -1, $szReason = "")
    Local $iter = 1
    
    If ($sockUser == -1) Then
        _AddLog("Error: _DeleteUser(), invalid user.")
        Return False
    EndIf
    
    If ($szReason == "") Then
        $szReason = "None given."
    EndIf
    
    If ($Connections[0] > 0) Then
        For $iter = 1 To $Connections[0]
            If ($Connections[$iter] == $sockUser) Then
                $bFound = True
                ;Inform the user why they were kicked:
                _SendToUser($Connections[$iter], "You have been kicked from the server for the following reason: " & $szReason & @CRLF)
                
                ;Kill the connection:
                _AddLog("Kicked user -- " & $sockUser & " (" & SocketToIP($sockUser) & ") -- for reason: " & $szReason)
                TCPCloseSocket($sockUser)
                
                ;Update the connection list
                _ArrayDelete($Connections, $iter)
                $Connections[0] = UBound($Connections) - 1
                Return True
            EndIf
        Next
    EndIf
    
    _AddLog("Attempted to kick nonexistant user: " & $sockUser)
    Return False
EndFunc   ;==>_DeleteUser

Func _SendToUser($sockUser = -1, $szMsg = "")
    If ($sockUser == -1) Then
        _AddLog("Error: _SendToUser(), invalid user.")
        Return False
    EndIf
    
    $szMsg = _zCompress($szMsg)
    TCPSend($sockUser, $szMsg)
    If (@error) Then ;User disconnected/timed out?
        _DeleteUser($Connections[$iter], "Timeout/Disconnect")
    EndIf
    
    Return True
EndFunc   ;==>_SendToUser

Func _RecvFromUser($sockUser = -1)
    If ($sockUser == -1) Then
        _AddLog("Error: _RecvFromUser(), invalid user.")
        Return False
    EndIf
    
    Local $szData = TCPRecv($Connections[$iter], $iMaxDataLength)
    If (@error) Then ;User disconnected/timed out?
        _DeleteUser($Connections[$iter], "Timeout/Disconnect")
    EndIf
    
    Return _zUnCompress($szData)
EndFunc   ;==>_RecvFromUser

Func _Exit()
    TCPShutdown()
    Exit
EndFunc   ;==>_Exit

Func _ParseData($sockUser = -1, $szData = "")
    If ($sockUser == -1) Then
        _AddLog("Error: _ParseData(), invalid user.")
        Return False
    EndIf
    
    If (StringLen($szData) == 0) Then
        Return False
    EndIf
    
    Local $szCommand, $szArgument, $iter, $iPosOfWS
    
    $szData = StringStripWS($szData, 1) ;Strip leading spaces
    
    $iPosOfWS = StringInStr($szData, " ")
    If ($iPosOfWS > 0) Then
        $szCommand = StringLower(StringLeft($szData, $iPosOfWS - 1))
        $szArgument = StringTrimLeft($szData, $iPosOfWS)
    Else
        $szCommand = StringLower($szData)
        $szArgument = ""
    EndIf
    
    For $iter = 0 To (UBound($szCommandList) - 1)
        If ($szCommand == $szCommandList[$iter]) Then
            Call($szFunctionList[$iter], $sockUser, $szArgument)
            Return True
        EndIf
    Next
    
    _SendToUser($sockUser, "Unknown command: " & $szCommand & @CRLF)
    Return False
EndFunc   ;==>_ParseData

#region Do-Functions:
Func _DoQuit($sockUser = -1, $szArgs = "")
    If ($sockUser == -1) Then
        _AddLog("Error: _DoQuit(), invalid user.")
        Return False
    EndIf
    
    Return _DeleteUser($sockUser, $szArgs)
EndFunc   ;==>_DoQuit

Func _DoWho($sockUser = -1, $szArgs = "")
    If ($sockUser == -1) Then
        _AddLog("Error: _DoWho(), invalid user.")
        Return False
    EndIf
    
    _SendToUser($sockUser, "===============[Who's Online]===============" & @CRLF)
    Local $iter
    For $iter = 1 To $Connections[0]
        If ($Connections[$iter] == $sockUser) Then
            _SendToUser($sockUser, "  " & $iter & ".)   You (" & $Connections[$iter] & ")" & @CRLF)
        Else
            _SendToUser($sockUser, "  " & $iter & ".)   " & $Connections[$iter] & @CRLF)
        EndIf
    Next
    _SendToUser($sockUser, "==========================================" & @CRLF)
    
    Return True
EndFunc   ;==>_DoWho

Func _DoWhoIs($sockUser = -1, $szArgs = "")
    If ($sockUser == -1) Then
        _AddLog("Error: _DoWhoIs(), invalid user.")
        Return False
    EndIf
    
    $szArgs = StringStripCR($szArgs)
    $szArgs = StringStripWS($szArgs, 1) ;Strip leading whitespaces
    $szArgs = StringStripWS($szArgs, 2) ;String trailing whitespaces
    $szArgs = StringStripWS($szArgs, 4) ;Strip double (or more) whitespaces between words
    
    If ($szArgs == "") Then
        _SendToUser($sockUser, "--Syntax: Whois <User>" & @CRLF & "--Example: Whois 1337" & @CRLF)
        Return False
    EndIf
    
    Local $bFoundUser = False, $iter = 1
    For $iter = 1 To $Connections[0]
        If (Number($szArgs) == $Connections[$iter]) Then
            $bFoundUser = True
            _SendToUser($sockUser, "***************[" & $Connections[$iter] & "]***************" & @CRLF)
            _SendToUser($sockUser, "     IP Address   : " & SocketToIP($Connections[$iter]) & @CRLF)
            _SendToUser($sockUser, "     Connection # : " & $iter & @CRLF)
            _SendToUser($sockUser, "************************************" & @CRLF)
            ExitLoop
        EndIf
    Next
    
    If ($bFoundUser == False) Then
        _SendToUser($sockUser, "Unknown user (" & $szArgs & "), please refer to WHO for users online." & @CRLF)
        Return False
    EndIf
    
    Return True
EndFunc   ;==>_DoWhoIs
#endregion Do-Functions

#region Utility functions:
Func _AddLog($szLogMsg)
    If (($bLogEnable == False) Or (StringLen($szLogMsg) == 0)) Then
        Return False
    EndIf
    
    Local $szDateNow = @YEAR & "-" & @MON & "-" & @MDAY
    Local $szTimeNow = @HOUR & ":" & @MIN & ":" & @SEC
    Local $szMsg = $szDateNow & " " & $szTimeNow & " : " & $szLogMsg
    Local $hOpenFile = FileOpen("Log.txt", 1)
    
    If ($hOpenFile == -1) Then
        MsgBox(0, "Error", "Failed to open log file! Future logging disabled.")     ;Prevents spamage of MsgBox()
        $bLogEnable = False
        Return False
    EndIf
    
    ConsoleWrite($szMsg & @CRLF)
    If (FileWriteLine($hOpenFile, $szMsg) == -1) Then
        MsgBox(0, "Error", "Failed to write to log file! Future logging disabled.") ;Prevents spamage of MsgBox()
        $bLogEnable = False
        Return False
    EndIf
    
    If (FileClose($hOpenFile)) Then
        Return True
    Else
        MsgBox(0, "Error", "Log file doesn't exist. Failed to close it!")
        Return False
    EndIf
EndFunc   ;==>_AddLog

Func SocketToIP($SHOCKET)
    Local $sockaddr = DllStructCreate("short;ushort;uint;char[8]")

    Local $aRet = DllCall("Ws2_32.dll", "int", "getpeername", "int", $SHOCKET, _
            "ptr", DllStructGetPtr($sockaddr), "int_ptr", DllStructGetSize($sockaddr))
    If Not @error And $aRet[0] = 0 Then
        $aRet = DllCall("Ws2_32.dll", "str", "inet_ntoa", "int", DllStructGetData($sockaddr, 3))
        If Not @error Then $aRet = $aRet[0]
    Else
        $aRet = 0
    EndIf

    $sockaddr = 0

    Return $aRet
EndFunc   ;==>SocketToIP

Func _zCompress($szData)
    $szData = StringReplace($szData, @CRLF, "\n\r")
    $szData = StringReplace($szData, @CR, "\n")
    $szData = StringReplace($szData, @LF, "\r")
    $szData = StringReplace($szData, @TAB, "\t")
    $szData = StringReplace($szData, " ", "·")
    
    ;ToDo-- Add actual compression!
    
    Return $szData
EndFunc   ;==>_zCompress

Func _zUnCompress($szData)
    $szData = StringReplace($szData, "\n\r", @CRLF)
    $szData = StringReplace($szData, "\n", @CR)
    $szData = StringReplace($szData, "\r", @LF)
    $szData = StringReplace($szData, "\t", @TAB)
    $szData = StringReplace($szData, "·", " ")
    
    ;ToDo-- Add actual decompression!
    
    Return $szData
EndFunc   ;==>_zUnCompress
#endregion Utility fuctions

I haven't documented the code very well because I'm writing the docs in HTML and don't want to post those aswell. If you have any questions about the code, please lemme know. It is pretty straight forward though. I would post the client code aswell, but that got some code I don't want leaked just yet.

Thanx for your time,

-CMR

P.S. To the authors of UDFs I'm using, credits go to them.

Link to comment
Share on other sites

Well,

I fix one bug and find another! ;)

I fixed the connections issue with a little modification. Instead of using _ArrayAdd/Delete, I decided to fill my array with "NULL" and go from there:

Global $Connections[$iMaxConnections + 1]

;Fill the connection list with NULL connections:
Local $iter
For $iter = 1 To $iMaxConnections
    $Connections[$iter] = "NULL"
Next
$Connections[0] = 0

While 1
    #region Look for a connection:
    ;Snip snip
    #endregion Look for New Connections
    
    #region Recv/Send
    If ($Connections[0] > 0) Then
        Local $iter = 1
        For $iter = 1 To $Connections[0]
            
            #region Get data from connections
            If ($Connections[$iter] <> "NULL") Then    ;I see said the blind man!
                ;Snip snip
            EndIf
            #endregion Get Data
            
            #region Send Data to connections
            If ($Connections[$iter] <> "NULL") Then
                _SendToUser($Connections[$iter], "") ;Keep alive
            EndIf
            #endregion Send Data
        Next
    EndIf
    #endregion Recv/Send
    Sleep(1)
WEnd

Func _AddUser($sockUser = -1)
    If ($sockUser == -1) Then
        _AddLog("Error: _AddUser(), invalid user.")
        Return False
    EndIf
    
    Local $UserIP = SocketToIP($sockUser), $iter
    _AddLog("User connected: " & $sockUser & " (" & $UserIP & ")")
    
    If ($bAllowMultiConnect == False) Then
        ;Check for duplicate connection:
        If ($Connections[0] > 0) Then
            For $iter = 1 To $Connections[0]
                If ($UserIP == SocketToIP($Connections[$iter])) Then
                    _SendToUser($sockUser, "Sorry, only one connection per IP allowed. Goodbye.")
                    TCPCloseSocket($sockUser)
                    _AddLog("Kicked " & $UserIP & " [Duplicate IP found]")
                    Return False
                EndIf
            Next
        EndIf
    EndIf

    
    If ($Connections[0] == 0) Then
        $Connections[1] = $sockUser     ;Let's add them to the connection list
        $Connections[0] += 1
        Return True
    Else
        Local $iter = 1
        For $iter = 1 To $iMaxConnections
            If ($Connections[$iter] == "NULL") Then
                $Connections[$iter] = $sockUser     ;Let's add them to the connection list
                $Connections[0] += 1
                Return True
            EndIf
        Next
    EndIf
    
    _AddLog("Error: _AddUser(), failed to add user (" & $sockUser & "). Killing connection.")
    _SendToUser($sockUser,"We are sorry, the server failed to accept your connection." & @CRLF & "Please try again later." & @CRLF)
    TCPCloseSocket($sockUser)
    
    Return False
EndFunc   ;==>_AddUser

Func _DeleteUser($sockUser = -1, $szReason = "")
    Local $iter = 1
    
    If ($sockUser == -1) Then
        _AddLog("Error: _DeleteUser(), invalid user.")
        Return False
    EndIf
    
    If ($szReason == "") Then
        $szReason = "None given."
    EndIf
    
    If ($Connections[0] > 0) Then
        For $iter = 1 To $iMaxConnections
            If ($Connections[$iter] == $sockUser) Then
                $bFound = True
                ;Inform the user why they were kicked:
                _SendToUser($Connections[$iter], "You have been kicked from the server for the following reason: " & $szReason & @CRLF)
                
                ;Kill the connection:
                _AddLog("Kicked user -- " & $sockUser & " (" & SocketToIP($sockUser) & ") -- for reason: " & $szReason)
                TCPCloseSocket($sockUser)
                
                ;Update the connection list
                $Connections[$iter] = "NULL"
                $Connections[0] -= 1
                Return True
            EndIf
        Next
    EndIf
    
    _AddLog("Attempted to kick nonexistant user: " & $sockUser)
    Return False
EndFunc   ;==>_DeleteUser

Alrighty, that's one way to fix it (even if it isn't good on resources). Now I've got a new problem:

C:\XXXXXXXXXX\Server.au3 (185) : ==> Recursion level has been exceeded - AutoIt will quit to prevent stack overflow.:

TCPSend($sockUser, _zCompress($szMsg))

How is recursion defined in Au3? I mean, the $szMsg is only going 3 levels deep...

1.) _SendToUser($sockUser,$szMsg)

2.) _TCPSend($sockUser,_zCompress($szMsg))

3.) _zCompress($szMsg)

???

-CMR

Link to comment
Share on other sites

From a quick look at the code if you get an error in the _SendToUser function it calls _DeleteUser.

Guess what function _DeleteUser calls (_SendToUser) now you have an endless recursion due to an error.

SciTE for AutoItDirections for Submitting Standard UDFs

 

Don't argue with an idiot; people watching may not be able to tell the difference.

 

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...