CodeMaster Rapture Posted November 26, 2006 Share Posted November 26, 2006 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 expandcollapse popup#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 More sharing options...
CodeMaster Rapture Posted November 26, 2006 Author Share Posted November 26, 2006 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: expandcollapse popupGlobal $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 More sharing options...
GaryFrost Posted November 26, 2006 Share Posted November 26, 2006 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 More sharing options...
CodeMaster Rapture Posted November 26, 2006 Author Share Posted November 26, 2006 DOH! Thanx Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now