Recently I was making a very simple 2d mmo game to play with the college students, I borrowed a VPS from digital ocean for a test period, and on the server I used Purebasic with TCP & UDP modules together ( following the ENet concept that uses TCP together with UDP to make the UDP connection reliable; ), we were doing load testing, at some point the server crashes or there is a memory leak in ubuntu 23.04.
*Several people advise me not to use UDP because of its limitations and unreliability.
*Is it not possible to make purebasic UDP reliable over WAN?
I feel somewhat confident in the way purebasic handles TCP and UDP socket connections.
If anyone can help me improve this code and even give tips to make it reliable and effective.
screenshot 1: https://prnt.sc/ZbHXvoPPrzws
screenshot 2-1 : https://prnt.sc/fJJ8FSb9J3yY
screenshot 2-2 : https://prnt.sc/NzQK9QlWwp24
Code ( inspiration from the modules of: mk-soft ):
reference links in the code header in ServersCores.pb
main-server.pb
Code: Select all
IncludeFile "ServersCores.pb"
; Global Variable
Global ExitApplication
Global Server.udtServer
Global Server2.udtServer
Global TcpPort = 20010
Global UdpPort = 20020
; CompilerIf #PB_Compiler_Debugger
; CompilerError "The debugger must be turned OFF for this example"
; CompilerEndIf
; Main
Procedure Main()
Protected event, style, dx, dy, text.s, i
If OpenConsole("Main Server 1")
If OpenDatabase(0, GetCurrentDirectory()+"ServerData/db.db", "", "")
Print("[Database] ")
ConsoleColor(10, 0)
PrintN("Sqlite Database is Connected.")
FinishDatabaseQuery(0)
ConsoleColor(7, 0)
EndIf
; Init Server
If Not InitTcpServer(@Server, TcpPort )
Debug "Error Init Server"
End
EndIf
If Not InitUdpServer(@Server2, UdpPort )
Debug "Error Init Server"
End
EndIf
PrintN("Server Runnin on TCP[6832] UDP[6833]")
; Main Loop
Repeat
Until ExitApplication
If Server\ThreadID
Server\Exit = #True
If WaitThread(Server\ThreadID, 5000) = 0
KillThread(Server\ThreadID)
EndIf
EndIf
EndIf
EndProcedure : Main()
End
ServerCores.pb
Code: Select all
;-TOP
; Comment : UDP Local Network Short Text Sending
; Author : mk-soft
; Version : v1.07.0
; Link : https://www.purebasic.fr/english/viewtopic.php?t=74200
; *************************************************
; Comment : Network SendTo for UDP Server (All OS)
; Author : mk-soft
; Version : v1.01.1
; Create : 30.12.2022
; Update :
; Link : https://www.purebasic.fr/english/viewtopic.php?t=80367
; Description
; Socket = ServerID(Server)
CompilerIf #PB_Compiler_Thread = 0
CompilerError "Use Compileroption Threadsafe!"
CompilerEndIf
;EnableExplicit
Global NewMap WorldPlayers.s()
Global tmpm$ = ""
Global msgSignal.s,msgParams.s = ""
Global usernameTmp$,passwTmp$ = ""
Global userid.i,tmpChars.s,tmpCharacters.s = ""
Global tmpCharsAmount.i = 0
Structure PlayerStruct
;ACCOUNT DETAILS
Username.s
Account_id.i
;CONNECTION
;Tcp_conn.i
Udp_conn.i
;CHAR SELECTED
CharSelected_id.i
CharSelected_meshclass.i
CharSelected_PosX.f
CharSelected_PosY.f
CharSelected_PosZ.f
; CharSelected_Inventory.i[999]
EndStructure
Global NewMap Players.PlayerStruct()
UseSQLiteDatabase()
CompilerIf Not Defined(AF_INET, #PB_Constant)
#AF_INET = 2
CompilerEndIf
CompilerIf Not Defined(SOCKADDR_IN, #PB_Structure)
Structure SOCKADDR_IN
sin_family.w
sin_port.w
sin_addr.l
sin_zero.b[8]
EndStructure
CompilerEndIf
Procedure SendNetworkStringTo(Socket, IP.s, Port, Text.s, Format = #PB_UTF8)
Protected r1, *ip, *sendbuf, lenbuf, RecvAddr.sockaddr_in
Select Format
Case #PB_Ascii
*sendbuf = Ascii(Text)
lenbuf = StringByteLength(Text, #PB_Ascii)
Case #PB_UTF8
*sendbuf = UTF8(Text)
lenbuf = StringByteLength(Text, #PB_UTF8)
Case #PB_Unicode
*sendbuf = @Text
lenbuf = Len(Text)
EndSelect
*ip = Ascii(IP)
RecvAddr\sin_family = #AF_INET
RecvAddr\sin_port = htons_(port)
RecvAddr\sin_addr = inet_addr_(*ip)
r1 = sendto_(socket, *sendbuf, lenbuf, 0, RecvAddr, SizeOf(sockaddr_in))
If *sendbuf
FreeMemory(*sendbuf)
EndIf
FreeMemory(*ip)
ProcedureReturn r1
EndProcedure
Procedure SendNetworkDataTo(Socket, IP.s, Port, *Buffer, Size)
Protected r1, *ip, RecvAddr.sockaddr_in
*ip = Ascii(IP)
RecvAddr\sin_family = #AF_INET
RecvAddr\sin_port = htons_(port)
RecvAddr\sin_addr = inet_addr_(*ip)
r1 = sendto_(socket, *Buffer, Size, 0, RecvAddr, SizeOf(sockaddr_in))
FreeMemory(*ip)
ProcedureReturn r1
EndProcedure
; *************************************************
;-- MacOS NapStop
CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
; Author : Danilo
; Date : 25.03.2014
; Link : https://www.purebasic.fr/english/viewtopic.php?f=19&t=58828
; Info : NSActivityOptions is a 64bit typedef - use it with quads (.q) !!!
#NSActivityIdleDisplaySleepDisabled = 1 << 40
#NSActivityIdleSystemSleepDisabled = 1 << 20
#NSActivitySuddenTerminationDisabled = (1 << 14)
#NSActivityAutomaticTerminationDisabled = (1 << 15)
#NSActivityUserInitiated = ($00FFFFFF | #NSActivityIdleSystemSleepDisabled)
#NSActivityUserInitiatedAllowingIdleSystemSleep = (#NSActivityUserInitiated & ~#NSActivityIdleSystemSleepDisabled)
#NSActivityBackground = $000000FF
#NSActivityLatencyCritical = $FF00000000
Procedure BeginWork(Option.q, Reason.s= "MyReason")
Protected NSProcessInfo = CocoaMessage(0,0,"NSProcessInfo processInfo")
If NSProcessInfo
ProcedureReturn CocoaMessage(0, NSProcessInfo, "beginActivityWithOptions:@", @Option, "reason:$", @Reason)
EndIf
EndProcedure
Procedure EndWork(Activity)
Protected NSProcessInfo = CocoaMessage(0, 0, "NSProcessInfo processInfo")
If NSProcessInfo
CocoaMessage(0, NSProcessInfo, "endActivity:", Activity)
EndIf
EndProcedure
CompilerEndIf
; ----
Enumeration CustomEvent #PB_Event_FirstCustomValue
#MyEvent_ServerMessage_Connect ; Only TCP
#MyEvent_ServerMessage_Data ; UDP and TCP
#MyEvent_ServerMessage_Disconnect ; Only TCP
#MyEvent_ServerMessage_Error
EndEnumeration
Structure udtClient
Connection.i
Time.i
Text.s
EndStructure
Structure udtServer
*ThreadID
*ServerID
*Socket
Mutex.i
BindIP.s
Port.i
Error.i
Exit.i
Map Client.udtClient()
EndStructure
; ----
CompilerIf #PB_Compiler_Version < 600
InitNetwork()
CompilerEndIf
; ----
Procedure AllocateString(Text.s)
Protected *mem.String
*mem = AllocateStructure(String)
*mem\s = Text
ProcedureReturn *mem
EndProcedure
Procedure.s FreeString(*Text.String)
Protected result.s
If *Text
result = *text\s
FreeStructure(*Text)
EndIf
ProcedureReturn result
EndProcedure
; ----
Procedure thUdpServer(*ServerData.udtServer)
Protected client, *buffer, cnt, *text, stx, etx, len, time, lock
CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
Protected StopNap = BeginWork(#NSActivityLatencyCritical | #NSActivityUserInitiated, Hex(*ServerData))
CompilerEndIf
With *ServerData
*Buffer = AllocateMemory(2048)
Repeat
If Not lock
LockMutex(\Mutex) : lock = #True
EndIf
Select NetworkServerEvent()
Case #PB_NetworkEvent_Connect ; Only TCP
;PostEvent(#MyEvent_ServerMessage_Connect, 0, 0, 0, EventClient())
PrintN("User Connected = "+Str(EventClient()))
Case #PB_NetworkEvent_Data ; TCP and UDP
FillMemory(*buffer,2048)
client = EventClient()
If Not FindMapElement(\Client(), Str(client))
AddMapElement(\Client(), Str(client))
\Client()\Connection = client
EndIf
cnt = ReceiveNetworkData(client, *buffer, 2048)
If cnt > 0
\Client()\Text + PeekS(*buffer, cnt, #PB_UTF8 | #PB_ByteLength)
\Client()\Time = ElapsedMilliseconds()
stx = FindString(\Client()\Text, #STX$)
If stx
etx = FindString(\Client()\Text, #ETX$, stx)
If etx
stx + 1
len = etx - stx
EndIf
EndIf
tmpm$ = \Client()\Text
msgSignal = StringField(tmpm$,1,":")
msgParams = StringField(tmpm$,2,":")
If (msgSignal = "ping-tcp")
SendNetworkString(client,"pong-tcp")
Debug "ping tcp"
ElseIf (msgSignal = "ping-udp")
SendNetworkString(client,"pong-udp")
Debug "ping udp"
EndIf
If (msgSignal = "log")
;PrintN("logando")
usernameTmp$ = StringField(msgParams,1,"/")
passwTmp$ = StringField(msgParams,2,"/")
ElseIf (msgSignal = "uuid")
PrintN("[AuthServer] Uuid "+client+" registered !")
UuidTmp$ = msgParams
WorldPlayers(Str(client)) = UuidTmp$
ElseIf (msgSignal = "CharacterUpdate")
PrintN("[AuthServer] Characters Loaded !")
SendNetworkString(client,"GoToCharSelect")
ElseIf (msgSignal = "join-world")
UdpConnTmp$ = StringField(msgParams,1,"/")
CharNameTmp$ = StringField(msgParams,2,"/")
PrintN("[GameServer] Joining Client "+client+" to world !")
ForEach WorldPlayers()
If WorldPlayers() = UdpConnTmp$
WorldPlayers(MapKey(WorldPlayers())) = Str(client)
EndIf
Next
SendNetworkString(client,"EnterOnWorld")
ElseIf (msgSignal = "joinworld_request")
UdpConnTmp$ = StringField(msgParams,1,"/")
CharIdTmp$ = StringField(msgParams,2,"/")
CharMeshIdTmp$ = StringField(msgParams,3,"/")
PrintN("[GameServer] Joining Client [CharID] "+CharIdTmp$+" To world !")
SendNetworkString(client,"EnterOnWorld")
EndIf
ElseIf cnt < 0
\Error = 3
EndIf
Case #PB_NetworkEvent_Disconnect ; Only TCP
;PostEvent(#MyEvent_ServerMessage_Disconnect, 0, 0, 0, EventClient())
PrintN("User Disconnected = "+Str(EventClient()))
Case #PB_NetworkEvent_None
; Clear resources
time = ElapsedMilliseconds()
ForEach \Client()
If (time - \Client()\Time) >= 300000 ; 5 Minutes
;CloseNetworkConnection(\Client()\Connection)
;DeleteMapElement(\Client())
EndIf
Next
UnlockMutex(\Mutex) : lock = #False
Delay(1)
EndSelect
Until \Exit
If Not lock
LockMutex(\Mutex)
EndIf
CloseNetworkServer(\ServerID)
FreeMemory(*buffer)
\ServerID = 0
\Socket = 0
\Exit = 0
ClearMap(\Client())
UnlockMutex(\Mutex)
FreeMutex(\Mutex)
\Mutex = 0
EndWith
CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
EndWork(StopNap)
CompilerEndIf
EndProcedure
; ----
Procedure InitTcpServer(*ServerData.udtServer, Port, BindIP.s = "")
With *ServerData
\BindIP = BindIP
\Port = Port
\Error = 0
\Exit = 0
\ServerID = CreateNetworkServer(#PB_Any, \Port, #PB_Network_TCP, \BindIP)
If Not \ServerID
\Error = 1
ProcedureReturn 0
EndIf
\ThreadID = CreateThread(@thUdpServer(), *ServerData)
If Not \ThreadID
CloseNetworkServer(\ServerID)
\ServerID = 0
\Error = 2
ProcedureReturn 0
EndIf
\Socket = ServerID(\ServerID)
\Mutex = CreateMutex()
ProcedureReturn 1
EndWith
EndProcedure
Procedure InitUdpServer(*ServerData.udtServer, Port, BindIP.s = "")
With *ServerData
\BindIP = BindIP
\Port = Port
\Error = 0
\Exit = 0
\ServerID = CreateNetworkServer(#PB_Any, \Port, #PB_Network_UDP, \BindIP)
If Not \ServerID
\Error = 1
ProcedureReturn 0
EndIf
\ThreadID = CreateThread(@thUdpServer(), *ServerData)
If Not \ThreadID
CloseNetworkServer(\ServerID)
\ServerID = 0
\Error = 2
ProcedureReturn 0
EndIf
\Socket = ServerID(\ServerID)
\Mutex = CreateMutex()
ProcedureReturn 1
EndWith
EndProcedure
; ----
Procedure SendString(*Server.udtServer, Port, Text.s)
Protected r1, IP
With *Server
If StringByteLength(Text, #PB_UTF8) > 2046
ProcedureReturn 0
EndIf
LockMutex(\Mutex)
r1 = SendNetworkStringTo(\Socket, "127.0.0.1", Port, #STX$ + Text + #ETX$)
UnlockMutex(\Mutex)
EndWith
ProcedureReturn r1
EndProcedure
; ----
Procedure SendStringIP(*Server.udtServer, IP.s, Port, Text.s)
Protected r1
With *Server
If StringByteLength(Text, #PB_UTF8) > 2046
ProcedureReturn 0
EndIf
LockMutex(\Mutex)
r1 = SendNetworkStringTo(\Socket, IP, Port, #STX$ + Text + #ETX$)
UnlockMutex(\Mutex)
EndWith
ProcedureReturn r1
EndProcedure


