Would it be possible to make Socket UDP reliable by working side by side with TCP?

Just starting out? Need help? Post your questions and find answers here.
User avatar
skinkairewalker
Enthusiast
Enthusiast
Posts: 799
Joined: Fri Dec 04, 2015 9:26 pm

Would it be possible to make Socket UDP reliable by working side by side with TCP?

Post by skinkairewalker »

Hi guys,
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

Maybe something in the code is inconsistent, but it's something on that basis... xD
plouf
Enthusiast
Enthusiast
Posts: 282
Joined: Fri Apr 25, 2003 6:35 pm
Location: Athens,Greece

Re: Would it be possible to make Socket UDP reliable by working side by side with TCP?

Post by plouf »

Whats rhe problem ? The fact that server crash ? Or tou loose data ? Or rhe "words someone tell" ?

If server its another issue
UDP. Is connectionless by definition
If you need "reliable" udp you have to use a custom software crc kind of test. And if faill rerequest data.
This is part of tcp stack so no need there

Also keep in mind that under some circumentances udp packeta may arrrive in diffirend order that the order send !

All of these must be taken are in app level software .
Christos
User avatar
Caronte3D
Addict
Addict
Posts: 1371
Joined: Fri Jan 22, 2016 5:33 pm
Location: Some Universe

Re: Would it be possible to make Socket UDP reliable by working side by side with TCP?

Post by Caronte3D »

I don't know if relevant, but is better not use KillThread() to terminate threads, much better to do some break inside so the thread ends it self.
At least, for me, the KillThread is delicate
User avatar
mk-soft
Always Here
Always Here
Posts: 6320
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Would it be possible to make Socket UDP reliable by working side by side with TCP?

Post by mk-soft »

You have programmed yourself a memory leak when rewriting thUDPServer.

Client()\text is no longer corrected.

In addition, you have removed the Repeat Until, which means that the data in the receive buffer can no longer be separated. There may be several sent data in the receive buffer that need to be separated. So [STX]text[ETX][STX]text ... the rest is still missing, next run.

So set up a little differently

Not tested

Code: Select all


;

Procedure DispatchData(Connection, Text.s)
  
  tmpm$ = Text
  client = Connection
  
  msgSignal = StringField(tmpm$,1,":")
  msgParams = StringField(tmpm$,2,":")
  
  If (msgSignal = "ping-tcp") 
    SendNetworkString(client,#STX$+"pong-tcp"+#ETX$)
    Debug "ping tcp"
  ElseIf (msgSignal = "ping-udp")   
    SendNetworkString(client,#STX$+"pong-udp"+#ETX$)
    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,#STX$+"GoToCharSelect"+#ETX$)
    
  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,#STX$+"EnterOnWorld"+#ETX$)
    
  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,#STX$+"EnterOnWorld"+#ETX$) 
    
  EndIf   
  
EndProcedure

; ----

Procedure thUdpServer(*ServerData.udtServer)
  Protected client, *buffer, cnt, *text, text.s, 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())
          
        Case #PB_NetworkEvent_Data ; TCP and UDP
          client = EventClient()
          
          ;Debug "IP: " + IPString(GetClientIP(client))
          
          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()
            Repeat
              stx = FindString(\Client()\Text, #STX$)
              If stx
                etx = FindString(\Client()\Text, #ETX$, stx)
                If etx
                  stx + 1
                  len = etx - stx
                  ;*text = AllocateString("Port " + GetClientPort(\Client()\Connection) + ": " + Mid(\Client()\Text, stx, len))
                  text = Mid(\Client()\Text, stx, len)
                  \Client()\Text = Mid(\Client()\Text, etx + 1)
                  ;PostEvent(#MyEvent_ServerMessage_Data, 0, client, 0, *text)
                  DispatchData(\Client()\Connection, text)
                Else
                  Break
                EndIf
              Else
                Break
              EndIf
            ForEver
          ElseIf cnt < 0
            \Error = 3
            PostEvent(#MyEvent_ServerMessage_Error, 0, 0, 0, 3)
          EndIf
          
        Case #PB_NetworkEvent_Disconnect ; Only TCP
          PostEvent(#MyEvent_ServerMessage_Disconnect, 0, 0, 0, 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(10)
          
      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

; ----
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
mk-soft
Always Here
Always Here
Posts: 6320
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Would it be possible to make Socket UDP reliable by working side by side with TCP?

Post by mk-soft »

Caronte3D wrote: Thu Apr 18, 2024 8:53 am I don't know if relevant, but is better not use KillThread() to terminate threads, much better to do some break inside so the thread ends it self.
At least, for me, the KillThread is delicate
KillThread is only called if WaitThread was not successful after time
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
skinkairewalker
Enthusiast
Enthusiast
Posts: 799
Joined: Fri Dec 04, 2015 9:26 pm

Re: Would it be possible to make Socket UDP reliable by working side by side with TCP?

Post by skinkairewalker »

mk-soft wrote: Thu Apr 18, 2024 10:52 pm You have programmed yourself a memory leak when rewriting thUDPServer.

Client()\text is no longer corrected.

In addition, you have removed the Repeat Until, which means that the data in the receive buffer can no longer be separated. There may be several sent data in the receive buffer that need to be separated. So [STX]text[ETX][STX]text ... the rest is still missing, next run.

So set up a little differently

Not tested

Code: Select all


;

Procedure DispatchData(Connection, Text.s)
  
  tmpm$ = Text
  client = Connection
  
  msgSignal = StringField(tmpm$,1,":")
  msgParams = StringField(tmpm$,2,":")
  
  If (msgSignal = "ping-tcp") 
    SendNetworkString(client,#STX$+"pong-tcp"+#ETX$)
    Debug "ping tcp"
  ElseIf (msgSignal = "ping-udp")   
    SendNetworkString(client,#STX$+"pong-udp"+#ETX$)
    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,#STX$+"GoToCharSelect"+#ETX$)
    
  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,#STX$+"EnterOnWorld"+#ETX$)
    
  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,#STX$+"EnterOnWorld"+#ETX$) 
    
  EndIf   
  
EndProcedure

; ----

Procedure thUdpServer(*ServerData.udtServer)
  Protected client, *buffer, cnt, *text, text.s, 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())
          
        Case #PB_NetworkEvent_Data ; TCP and UDP
          client = EventClient()
          
          ;Debug "IP: " + IPString(GetClientIP(client))
          
          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()
            Repeat
              stx = FindString(\Client()\Text, #STX$)
              If stx
                etx = FindString(\Client()\Text, #ETX$, stx)
                If etx
                  stx + 1
                  len = etx - stx
                  ;*text = AllocateString("Port " + GetClientPort(\Client()\Connection) + ": " + Mid(\Client()\Text, stx, len))
                  text = Mid(\Client()\Text, stx, len)
                  \Client()\Text = Mid(\Client()\Text, etx + 1)
                  ;PostEvent(#MyEvent_ServerMessage_Data, 0, client, 0, *text)
                  DispatchData(\Client()\Connection, text)
                Else
                  Break
                EndIf
              Else
                Break
              EndIf
            ForEver
          ElseIf cnt < 0
            \Error = 3
            PostEvent(#MyEvent_ServerMessage_Error, 0, 0, 0, 3)
          EndIf
          
        Case #PB_NetworkEvent_Disconnect ; Only TCP
          PostEvent(#MyEvent_ServerMessage_Disconnect, 0, 0, 0, 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(10)
          
      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

; ----
Thank you very much plouf, Caronte3D and mk-soft for the review, but when bombarding the server with spam messages, it still crashes.
User avatar
mk-soft
Always Here
Always Here
Posts: 6320
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Would it be possible to make Socket UDP reliable by working side by side with TCP?

Post by mk-soft »

If no valid data is received (never [STX]text[ETX]) you can kick out the client. CloseNerkConnection
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
skinkairewalker
Enthusiast
Enthusiast
Posts: 799
Joined: Fri Dec 04, 2015 9:26 pm

Re: Would it be possible to make Socket UDP reliable by working side by side with TCP?

Post by skinkairewalker »

mk-soft wrote: Fri Apr 19, 2024 3:02 pm If no valid data is received (never [STX]text[ETX]) you can kick out the client. CloseNerkConnection
great idea.

Another question: has anyone tried doing the reliability steps in udp?
  • *Checksum control*: Verify data integrity to prevent transmission errors.
  • *Retransmission of lost packets*: If a packet isn't received, it's retransmitted.
  • *Flow control*: Regulate the transmission rate to avoid overload or packet loss.
  • *Receipt confirmation*: The receiver sends an ACK (Acknowledgment) to confirm the reception of each packet.
  • *Timeout*: If the ACK isn't received within a certain time period, the packet is retransmitted.
Post Reply