resolved : Two programs Talking

Just starting out? Need help? Post your questions and find answers here.
collectordave
Addict
Addict
Posts: 1309
Joined: Fri Aug 28, 2015 6:10 pm
Location: Portugal

resolved : Two programs Talking

Post by collectordave »

Tired eyes now.

I have two programs, both written with PB 5.7.

The first program I have called Slave and compiled to 'Slave.app' this is a standalone programme in its own right so a user can use the programme. However I need my second programme to check if the slave is running and if not start it then when running pass parameters to it to modify the Slave behaviour.

I can start the slave app now as below but cannot communicate with it.

There is no need for two way communications.

Some Code:

Slave.app

Code: Select all

Global Window_0
Global txtAction

Global Event.i,Quit.i

Global Action.s

Procedure CheckAction()
  
  Select Action

    Case "Action 1"
      
      SetGadgetText(txtAction,"Doing Action 1")
      
      Case "Action 2"
      
      SetGadgetText(txtAction,"Doing Action 2") 
      
      Default
      
        SetGadgetText(txtAction,"Just Idling" )  
        
    EndSelect
    
  EndProcedure
  
      
  Window_0 = OpenWindow(#PB_Any, 0, 0, 600, 400, "", #PB_Window_SystemMenu)
  txtAction = TextGadget(#PB_Any, 40, 30, 370, 20, "")
  
    Quit = #False
    
    CheckAction()
    
    
  Repeat
    
    Event = WaitWindowEvent()
    
  Select Event
    Case #PB_Event_CloseWindow
      Quit = #True



    Case #PB_Event_Gadget
      Select EventGadget()
      EndSelect
  EndSelect
Until Quit = #True

End
And Now Master.app

Code: Select all

Global Window_0

Global btnRunSlave, btnAction1, btnAction2

Global Event.i,Quit.i

Global ExtProg.i

  Window_0 = OpenWindow(#PB_Any, 0, 0,600, 400, "", #PB_Window_SystemMenu)
  btnRunSlave = ButtonGadget(#PB_Any, 20, 20, 190, 30, "Run")
  btnAction1 = ButtonGadget(#PB_Any, 20, 70, 190, 30, "Action 1")
  btnAction2 = ButtonGadget(#PB_Any, 20, 120, 190, 30, "Action 2")
  
      Quit = #False

    
    
  Repeat
    
    Event = WaitWindowEvent()
    
  Select Event
    Case #PB_Event_CloseWindow
      Quit = #True

    Case #PB_Event_Gadget
      Select EventGadget()
          
        Case btnRunSlave

          ExtProg =  RunProgram("open","Slave.app","", #PB_Program_Open|#PB_Program_Write )    
          
        Case btnAction2
          
          WriteProgramString(ExtProg,"Action 2")
          
      EndSelect
  EndSelect
Until Quit = #True

End
When the Action 2 button is pressed on the master there slave should change to Doing Action 2 but i cannot even get them to talk!

Any ideas welcome.

CD
Last edited by collectordave on Fri Dec 13, 2019 4:12 am, edited 1 time in total.
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Two programs Talking

Post by wilbert »

Have you seen this macOS thread ?
viewtopic.php?f=19&t=68047
It might be what you are looking for.
Windows (x64)
Raspberry Pi OS (Arm64)
collectordave
Addict
Addict
Posts: 1309
Joined: Fri Aug 28, 2015 6:10 pm
Location: Portugal

Re: Two programs Talking

Post by collectordave »

Hi Wilbert

Cannot see it to advanced for me.

I just want to send some text to another application started by my application.


First problem was starting the second app changed to:

Code: Select all

ExtProg =  RunProgram(GetCurrentDirectory() + "Slave.app/Contents/MacOS/Slave","","", #PB_Program_Open|#PB_Program_Write )    
 
and the slave app starts but cannot get it to respond to writeprogramstring.

Thanks
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
User avatar
mk-soft
Always Here
Always Here
Posts: 5409
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Two programs Talking

Post by mk-soft »

Don't create an App, create a console program as slave
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
infratec
Always Here
Always Here
Posts: 6883
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Two programs Talking

Post by infratec »

As mk-soft mentioned:

WriteProgramString() writes to stdin, which is a console handle.
A windows program has no stdin console handle.

You need shared memory or a UDP connection.

In windows you also can use sendmessage_() to do some actions.
collectordave
Addict
Addict
Posts: 1309
Joined: Fri Aug 28, 2015 6:10 pm
Location: Portugal

Re: Two programs Talking

Post by collectordave »

Thanks for the hints.

Found this topic viewtopic.php?f=12&t=51134 about UDP.

Modified a little and it seems to work. removed console bit and added Stringbytelength() instead of Len().

I think a server sends the data so maybe got them the wrong way round.

Here is my client code: Run this you will see nothing on screen,

Code: Select all

InitNetwork()

Define serverNumber.i
Define portNumber.i = 9000



serverNumber = CreateNetworkServer(#PB_Any, portNumber, #PB_Network_UDP)

Procedure ProcessRequest(clientNumber)
    Protected result.i
    Protected *memoryLocation
    Protected memLength.i = 2048

    *memoryLocation = AllocateMemory(memLength)

    result = ReceiveNetworkData(clientNumber, *memoryLocation, memLength)
   


   Debug "Client " + Str(clientNumber) + " received " + Str(result) + " bytes: " + PeekS(*memoryLocation, result) 

   FreeMemory(*memoryLocation)
 
  EndProcedure
  
  Repeat
    NSEvent = NetworkServerEvent()                 ; if we receive data, it will be indicated here

    Select NSEvent
    Case #PB_NetworkEvent_Data                     ; raw data has been received
        thisClient = EventClient()                 ; get the event client identifier

        ProcessRequest(thisClient)
        
    Default                                        ; some other server event, or no event occurred
        Delay(50)                                  ; sleep so we don't hammer the processor
    EndSelect

ForEver
  
After you have this running run what I call the server:

Code: Select all

InitNetwork()

Global SendLine.s

connectionID = OpenNetworkConnection("127.0.0.1", 9000, #PB_Network_UDP)

  sendLine = "c:\users\Dave\testFiles\things that go wrong"

  
        lenSendLine = StringByteLength(sendLine)
       If lenSendLine
         SendNetworkData(connectionID, @sendLine, lenSendLine)
        EndIf
        
        sendLine = " twenty "
        
                lenSendLine = StringByteLength(sendLine)
        If lenSendLine
          SendNetworkData(connectionID, @sendLine, lenSendLine)
        EndIf
        
                sendLine = "Action 2"
        
                lenSendLine = StringByteLength(sendLine)
        If lenSendLine
         SendNetworkData(connectionID, @sendLine, lenSendLine)
       EndIf
The strings are received!

Just a bit more trying to do to get what I want I think.
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
infratec
Always Here
Always Here
Posts: 6883
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Two programs Talking

Post by infratec »

If you always send strings use

Code: Select all

SendNetworkString()
It makes life easier :wink:
collectordave
Addict
Addict
Posts: 1309
Joined: Fri Aug 28, 2015 6:10 pm
Location: Portugal

Re: Two programs Talking

Post by collectordave »

Couldn't find ReceiveNetworkstring()

Did try it though i get more consistent results with the above.

Cheers
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
User avatar
TI-994A
Addict
Addict
Posts: 2512
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: Two programs Talking

Post by TI-994A »

A Windows-only solution.

to be compiled as Master.exe

Code: Select all

; the master program

#masterEvent1 = 1
#masterEvent2 = 2
#masterEvent3 = 3
#slaveLaunched = 4

Procedure messageSlave(message)
  
  slaveHwnd = FindWindow_(0, "Slave")   ; must conform to the title of the slave program
  SendMessage_(slaveHwnd, #WM_COMMAND, message, 0)
  SetForegroundWindow_(slaveHwnd) 
  
EndProcedure

wFlags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered
OpenWindow(0, #PB_Ignore, #PB_Ignore, 200, 400, "Master", wFlags)
ButtonGadget(0, 10, 10, 180, 30, "Launch Slave")
ButtonGadget(1, 10, 50, 180, 30, "Perform Action 1")
ButtonGadget(2, 10, 90, 180, 30, "Perform Action 2")
ButtonGadget(3, 10, 130, 180, 30, "Perform Action 3")
ListViewGadget(4, 10, 170, 180, 220)
ResizeWindow(0, WindowX(0) - 110, #PB_Ignore, #PB_Ignore, #PB_Ignore)

Repeat
  
  Select WaitWindowEvent()
      
    Case #PB_Event_CloseWindow
      appQuit = 1
      
    Case #PB_Event_Gadget
      
      Select EventGadget()
          
        Case 0
          RunProgram("d:\Slave.exe")   ; ensure the correct path
          
        Case 1
          messageSlave(#masterEvent1)
          
        Case 2
          messageSlave(#masterEvent2)
          
        Case 3          
          messageSlave(#masterEvent3)
          
      EndSelect
      
    Case #PB_Event_Menu
      
      Select EventMenu()
          
        Case #slaveLaunched 
          AddGadgetItem(4, -1, "slave launched successfully")
          
        Case #masterEvent1          
          AddGadgetItem(4, -1, "slave received event 1")
          
        Case #masterEvent2
          AddGadgetItem(4, -1, "slave received event 2")
          
        Case #masterEvent3
          AddGadgetItem(4, -1, "slave received event 3")
          
      EndSelect      
      
  EndSelect
  
Until appQuit = 1
to be compiled as Slave.exe

Code: Select all

; the slave program

#masterEvent1 = 1
#masterEvent2 = 2
#masterEvent3 = 3
#slaveLaunched = 4

Procedure messageMaster(message)
  
  masterHwnd = FindWindow_(0, "Master")   ; must conform to the title of the master program
  SendMessage_(masterHwnd, #WM_COMMAND, message, 0)
  SetForegroundWindow_(masterHwnd) 
  
EndProcedure

wFlags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered
OpenWindow(0, #PB_Ignore, #PB_Ignore, 200, 400, "Slave", wFlags)
ListViewGadget(0, 10, 10, 180, 380)
ResizeWindow(0, WindowX(0) + 110, #PB_Ignore, #PB_Ignore, #PB_Ignore)

messageMaster(#slaveLaunched)

Repeat
  
  Select WaitWindowEvent()
      
    Case #PB_Event_CloseWindow
      appQuit = 1
      
    Case #PB_Event_Menu
      
      Select EventMenu()
          
        Case #masterEvent1          
          AddGadgetItem(0, -1, "Received master event 1")
          messageMaster(#masterEvent1)
          
        Case #masterEvent2
          AddGadgetItem(0, -1, "Received master event 2")
          messageMaster(#masterEvent2)
          
        Case #masterEvent3
          AddGadgetItem(0, -1, "Received master event 3")
          messageMaster(#masterEvent3)
          
      EndSelect
      
  EndSelect
  
Until appQuit = 1
Hope it works! :D
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
infratec
Always Here
Always Here
Posts: 6883
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Two programs Talking

Post by infratec »

This works:

Code: Select all

EnableExplicit


Procedure ProcessRequest(clientNumber)
  
  Protected result.i
  Protected *memoryLocation
  Protected memLength.i = 2048
  
  *memoryLocation = AllocateMemory(memLength)
  If *memoryLocation
    
    result = ReceiveNetworkData(clientNumber, *memoryLocation, memLength)
    If result
      Debug "Client " + Str(clientNumber) + " received " + Str(result) + " bytes: " + PeekS(*memoryLocation, result)
    EndIf
    
    FreeMemory(*memoryLocation)
  EndIf
  
EndProcedure




Define serverNumber.i, NSEvent.i
Define portNumber.i = 9000

InitNetwork()

serverNumber = CreateNetworkServer(#PB_Any, portNumber, #PB_Network_UDP, "127.0.0.1")

Repeat
  NSEvent = NetworkServerEvent(serverNumber)                 ; if we receive data, it will be indicated here
  
  Select NSEvent
    Case #PB_NetworkEvent_Data                     ; raw data has been received
      ProcessRequest(EventClient())
      
    Default                                        ; some other server event, or no event occurred
      Delay(50)                                    ; sleep so we don't hammer the processor
      
  EndSelect
  
ForEver
And

Code: Select all

EnableExplicit

Define ConnectionID.i, SendLine.s

InitNetwork()

connectionID = OpenNetworkConnection("127.0.0.1", 9000, #PB_Network_UDP)
If ConnectionID
  
  sendLine = "c:\users\Dave\testFiles\things that go wrong"
  SendNetworkString(connectionID, sendLine, #PB_Unicode)
  
  sendLine = " twenty "
  SendNetworkString(connectionID, sendLine, #PB_Unicode)
  
  sendLine = "Action 2"
  SendNetworkString(connectionID, sendLine, #PB_Unicode)
  
  CloseNetworkConnection(ConnectionID)
EndIf
collectordave
Addict
Addict
Posts: 1309
Joined: Fri Aug 28, 2015 6:10 pm
Location: Portugal

Re: Two programs Talking

Post by collectordave »

Brilliant

Created procedures to create the udp connection in each application.

Added:

Code: Select all

  
Repeat
    
    NSEvent = NetworkServerEvent(serverNumber) 
 
   If  NSEvent = #PB_NetworkEvent_Data 
      ProcessRequest(EventClient())
    EndIf
at the start of my main repeat loop


sent string and the controlled application responded.

Just one question if I have another pair of applications that need to talk can i change the port number to something different for each pair?

I do not want the string to be received by all just the one that needs it.

CD
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
infratec
Always Here
Always Here
Posts: 6883
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Two programs Talking

Post by infratec »

If your master starts the programs, then you can use for each program an other port.
It makes sense, that the slave gets the port as parameter at start.

You also should check (at the master) if you can open the port, then you know that it is free and not in use by an other program.
User avatar
mk-soft
Always Here
Always Here
Posts: 5409
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Two programs Talking

Post by mk-soft »

You must also check that the entire string has arrived. It is best to transfer the string length as well.

Server

Code: Select all

;-TOP

; Version v1.01

CompilerIf #PB_Compiler_Thread = 0
  CompilerError "Use Compileroption Threadsafe!"
CompilerEndIf

EnableExplicit

Enumeration Windows
  #WinMain
EndEnumeration

Enumeration #PB_Event_FirstCustomValue
  #My_Event_Logging
EndEnumeration

; -----------------------------------------------------------------------------

Structure udtClientData
  ConnectionID.i
  Date.i
  Text.s
EndStructure

Structure udtServerData
  *ThreadID
  *ServerID
  ExitServer.i
  Map Client.udtClientData() 
EndStructure

; -----------------------------------------------------------------------------

Structure udtAny
  StructureUnion
    aVal.a[0]
    bVal.b[0]
    cVal.c[0]
    wVal.w[0]
    uVal.u[0]
    iVal.i[0]
    lVal.l[0]
    qVal.q[0]
    fVal.f[0]
    dVal.d[0]
  EndStructureUnion
EndStructure

Structure udtNetString
  len.l
  pData.udtAny
EndStructure

; -----------------------------------------------------------------------------

; Bei Linux und MacOS kann man Gadgets nicht aus Threads ändern. Daher werden die Texte über PostEvent zur Liste gesendet.

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 ;PeekS(*Text)
    FreeStructure(*Text)
  EndIf
  ProcedureReturn result
EndProcedure

; Logging aus Threads
Procedure thLogging(Text.s)
  PostEvent(#My_Event_Logging, 0, 0, 0, AllocateString(Text))
EndProcedure

; Logging aus Mainscope
Procedure Logging(Text.s)
  Protected rows
  AddGadgetItem(0, -1, Text)
  rows = CountGadgetItems(0)
  SetGadgetState(0, rows - 1)
  SetGadgetState(0, -1)
EndProcedure

; -----------------------------------------------------------------------------

; Um ein String über Netzwerk zu versenden wird am Anfang die Länge in Bytes von den String eingetragen.
; Somit weiss man wie viele Bytes aus dem Empfangsbuffer gelesen werden muss um den gesamten String zu erhalten

Procedure NetSendString(ConnectionID, Text.s)
  Protected *Buffer.udtNetString, len, cnt
  len = StringByteLength(Text)
  If len > 65536 - SizeOf(Long)
    ProcedureReturn 0; Daten zu lang
  EndIf
  *Buffer = AllocateMemory(len + SizeOf(Long))
  *Buffer\len = Len
  CopyMemory(@Text, *Buffer\pData, len)
  len + SizeOf(Long)
  cnt = SendNetworkData(ConnectionID, *Buffer, len)
  FreeMemory(*Buffer)
  ProcedureReturn cnt
EndProcedure

; -----------------------------------------------------------------------------

Procedure.s NetReceiveString(ConnectionID, *Error.Integer = 0)
  Protected len, cnt, size, timeout, error, *Buffer, NetStringLen, *NetString, result.s
  ; Stringlänge aus Empfangsbuffer lesen
  len = ReceiveNetworkData(ConnectionID, @NetStringLen, 4)
  If NetStringLen
    *NetString = AllocateMemory(NetStringLen + SizeOf(Character))
  Else
    ProcedureReturn ""
  EndIf
  *Buffer = AllocateMemory(65536)
  size = 65536
  If size > NetStringLen
    size = NetStringLen
  EndIf
  ; String aus Empfangsbuffer lesen bis dieser vollständig ist
  Repeat
    len = ReceiveNetworkData(ConnectionID, *Buffer, size)
    If len > 0
      CopyMemory(*Buffer, *NetString + cnt, len)
      cnt + len
      If size > (NetStringLen - cnt)
        size = (NetStringLen - cnt)
      EndIf
      timeout = 0
    Else
      Delay(10)
      timeout + 10 ; Millisecond
      If timeout >= 5000
        error = #True ; Communication error
        Break
      EndIf
    EndIf
  Until cnt >= NetStringLen
  result = PeekS(*NetString)
  If *Error
    *Error\i = error
  EndIf
  FreeMemory(*Buffer)
  FreeMemory(*NetString)
  ProcedureReturn result
EndProcedure

; -----------------------------------------------------------------------------

; Dies ist der Server-Dienst der die Daten im Hintergrund verarbeitet

Procedure ThreadServer(*ServerData.udtServerData)
  Protected Event, ConnectionID, keyConnectionID.s, count, Text.s
  With *ServerData
    Repeat
      Event = NetworkServerEvent(\ServerID)
      If Event
        ConnectionID = EventClient()
        keyConnectionID = Hex(ConnectionID)
      EndIf
      Select Event
        Case #PB_NetworkEvent_Connect
          ; Daten für neuen Client anlegen
          If FindMapElement(\Client(), keyConnectionID)
            DeleteMapElement(\Client(), keyConnectionID) ; Sollte nicht passieren
          EndIf
          AddMapElement(\Client(), keyConnectionID)
          \Client()\ConnectionID = ConnectionID
          \Client()\Date = Date()
          thLogging("Network: Client connected: ID " + keyConnectionID)
          
        Case #PB_NetworkEvent_Data
          ; String aus Empfangbuffer lesen
          Text = NetReceiveString(ConnectionID)
          If FindMapElement(\Client(), keyConnectionID)
            \Client()\Date = Date()
            \Client()\Text = Text
          EndIf
          ; An alle anderen Clients senden
          ForEach \Client()
            If \Client()\ConnectionID <> ConnectionID
              NetSendString(\Client()\ConnectionID, "Empfang: " + Text)
            EndIf
          Next
          ; Bestätigung senden
          count = MapSize(\Client()) - 1
          NetSendString(ConnectionID, "Senden ("+ count + "): " + Text)
         
        Case #PB_NetworkEvent_Disconnect
          ; Daten von Client entfernen
          thLogging("Network: Client disconnected: ID " + keyConnectionID)
          If FindMapElement(\Client(), keyConnectionID)
            DeleteMapElement(\Client(), keyConnectionID)
          EndIf
         
        Default
          Delay(10)
         
      EndSelect
    Until \ExitServer
   
    ; Server Shutdown Text an alle Clients senden
    ForEach \Client()
      NetSendString(\Client()\ConnectionID, "Server Shutdown")
    Next
   
    ; Server beenden, Daten bereinigen und Thread verlassen
    CloseNetworkServer(\ServerID)
    \ThreadID = 0
    \ServerID = 0
    \ExitServer = 0
    ClearMap(\Client())
  EndWith
EndProcedure

; -----------------------------------------------------------------------------

; Hier wird der Server angelegt und beim erfolg der Thread gestartet der die Server-Dienste ausführt

Procedure InitServer(*ServerData.udtServerData, Port, BindedIP.s = "")
  Protected ServerID
 
  With *ServerData
    ServerID = CreateNetworkServer(#PB_Any, Port, #PB_Network_TCP, BindedIP)
    If ServerID
      \ServerID = ServerID
      \ThreadID = CreateThread(@ThreadServer(), *ServerData)
      Logging("Network: Init Server: ID " + Hex(ServerID))
    Else
      Logging("Network: Error Init Network Server")
    EndIf
    ProcedureReturn ServerID
  EndWith
EndProcedure

; -----------------------------------------------------------------------------

; Hier wird das Beenden des Servers angestossen
; Sollte diese nicht erfolgreich sein, wird der Server und der Thread zwangsweise geschlossen

Procedure CloseServer(*ServerData.udtServerData)
  Protected timeout
 
  With *ServerData
    If \ServerID = 0
      ProcedureReturn 0
    EndIf
    Logging("Network: Close Network Server: ID " + \ServerID)
    \ExitServer = 1
    Repeat
      If \ExitServer = 0
        Break
      Else
        timeout + 100
        If timeout > 10000
          CloseNetworkServer(\ServerID)
          KillThread(\ThreadID)
          \ThreadID = 0
          \ServerID = 0
          \ExitServer = 0
          ClearMap(\Client())
          Logging("Network: Error - Kill Network Server: ID " + \ServerID)
          Break
        EndIf
      EndIf
      Delay(100)
    ForEver
  EndWith
EndProcedure

; -----------------------------------------------------------------------------

Global ExitApplication
Global ServerData.udtServerData

Procedure Main()
  Protected Event, rows
 
  If OpenWindow(#WinMain, #PB_Ignore, #PB_Ignore, 600, 400, "MiniChat-Server",#PB_Window_SystemMenu)
    CreateStatusBar(0, WindowID(#WinMain))
    AddStatusBarField(100)
    AddStatusBarField(#PB_Ignore)
    ListViewGadget(0, 0, 0, WindowWidth(0), WindowHeight(0) - StatusBarHeight(0))
   
    ; Init Server
    InitServer(ServerData, 6037)
   
    ; LOOP
    Repeat
      Event = WaitWindowEvent()
      Select Event
        Case #PB_Event_CloseWindow
          Select EventWindow()
            Case #WinMain
              CloseServer(ServerData)
              ExitApplication = #True
          EndSelect
        Case #My_Event_Logging
          Logging(FreeString(EventData()))
         
      EndSelect
     
    Until ExitApplication And ServerData\ExitServer = 0
  EndIf
 
EndProcedure

InitNetwork()
Main()
Client

Code: Select all

;-TOP
; Version v1.01

CompilerIf #PB_Compiler_Thread = 0
  CompilerError "Use Compileroption Threadsafe!"
CompilerEndIf

EnableExplicit

Enumeration Windows
  #WinMain
EndEnumeration

Enumeration MenuItems
  #MenuItem_Send
  #MenuItem_Connect
  #MenuItem_Disconnect
EndEnumeration

Enumeration #PB_Event_FirstCustomValue
  #My_Event_Logging
EndEnumeration

; -----------------------------------------------------------------------------

Structure udtClientData
  *ThreadID
  *ConnectionID
  ExitClient.i
EndStructure

; -----------------------------------------------------------------------------

Structure udtAny
  StructureUnion
    aVal.a[0]
    bVal.b[0]
    cVal.c[0]
    wVal.w[0]
    uVal.u[0]
    iVal.i[0]
    lVal.l[0]
    qVal.q[0]
    fVal.f[0]
    dVal.d[0]
  EndStructureUnion
EndStructure

Structure udtNetString
  len.l
  pData.udtAny
EndStructure

; -----------------------------------------------------------------------------

; Bei Linux und MacOS kann man Gadgets nicht aus Threads ändern. Daher werden die Texte über PostEvent zur Liste gesendet.

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

; Logging aus Threads
Procedure thLogging(Text.s)
  PostEvent(#My_Event_Logging, 0, 0, 0, AllocateString(Text))
EndProcedure

; Logging aus Mainscope
Procedure Logging(Text.s)
  Protected rows
  AddGadgetItem(0, -1, Text)
  rows = CountGadgetItems(0)
  SetGadgetState(0, rows - 1)
  SetGadgetState(0, -1)
EndProcedure

; -----------------------------------------------------------------------------

; Um ein String über Netzwerk zu versenden wird am Anfang die Länge in Bytes von den String eingetragen.
; Somit weiss man wie viele Bytes aus dem Empfangsbuffer gelesen werden muss um den gesamten String zu erhalten

Procedure NetSendString(ConnectionID, Text.s)
  Protected *Buffer.udtNetString, len, cnt
  len = StringByteLength(Text)
  If len > 65536 - SizeOf(Long)
    ProcedureReturn 0; Daten zu lang
  EndIf
  *Buffer = AllocateMemory(len + SizeOf(Long))
  *Buffer\len = Len
  CopyMemory(@Text, *Buffer\pData, len)
  len + SizeOf(Long)
  cnt = SendNetworkData(ConnectionID, *Buffer, len)
  FreeMemory(*Buffer)
  ProcedureReturn cnt
EndProcedure

; -----------------------------------------------------------------------------

Procedure.s NetReceiveString(ConnectionID, *Error.Integer = 0)
  Protected len, cnt, size, timeout, error, *Buffer, NetStringLen, *NetString, result.s
  ; Stringlänge aus Empfangsbuffer lesen
  len = ReceiveNetworkData(ConnectionID, @NetStringLen, 4)
  If NetStringLen
    *NetString = AllocateMemory(NetStringLen + SizeOf(Character))
  Else
    ProcedureReturn ""
  EndIf
  *Buffer = AllocateMemory(65536)
  size = 65536
  If size > NetStringLen
    size = NetStringLen
  EndIf
  ; String aus Empfangsbuffer lesen bis dieser vollständig ist
  Repeat
    len = ReceiveNetworkData(ConnectionID, *Buffer, size)
    If len > 0
      CopyMemory(*Buffer, *NetString + cnt, len)
      cnt + len
      If size > (NetStringLen - cnt)
        size = (NetStringLen - cnt)
      EndIf
      timeout = 0
    Else
      Delay(10)
      timeout + 10 ; Millisecond
      If timeout >= 5000
        error = #True ; Communication error
        Break
      EndIf
    EndIf
  Until cnt >= NetStringLen
  result = PeekS(*NetString)
  If *Error
    *Error\i = error
  EndIf
  FreeMemory(*Buffer)
  FreeMemory(*NetString)
  ProcedureReturn result
EndProcedure

; -----------------------------------------------------------------------------

; Dies ist der Client-Dienst der die Daten im Hintergrund verarbeitet

Procedure ThreadClient(*ClientData.udtClientData)
  Protected Event, count, Text.s
  With *ClientData
    Repeat
      Event = NetworkClientEvent(\ConnectionID)
      Select Event
        Case #PB_NetworkEvent_Data
          ; String aus Empfangbuffer lesen
          Text = NetReceiveString(\ConnectionID)
          thLogging(Text)
         
        Case #PB_NetworkEvent_Disconnect
          ; Server hat die Verbindung beendet
          \ExitClient = 1
         
        Default
          Delay(10)
         
      EndSelect
    Until \ExitClient
   
    ; Exit Thread
    CloseNetworkConnection(\ConnectionID)
    \ThreadID = 0
    \ConnectionID = 0
    \ExitClient = 0
  EndWith
EndProcedure

; -----------------------------------------------------------------------------

; Hier wird die Verbindung zum Server angelegt und beim erfolg der Thread gestartet der die Client-Dienste ausführt

Procedure InitClient(*ClientData.udtClientData, IP.s, Port, Timeout = 0)
  Protected ConnectionID
 
  With *ClientData
    If \ConnectionID
      ProcedureReturn \ConnectionID
    EndIf
    ConnectionID = OpenNetworkConnection(IP, Port, #PB_Network_TCP, Timeout)
    If ConnectionID
      \ConnectionID = ConnectionID
      \ThreadID = CreateThread(@ThreadClient(), *ClientData)
      Logging("Network: Init Client: ID " + Hex(ConnectionID))
    Else
      Logging("Network: Error Init Connection")
    EndIf
    ProcedureReturn ConnectionID
  EndWith
EndProcedure

; -----------------------------------------------------------------------------

; Hier wird das Beenden der Verbindung zu Server angestossen
; Sollte diese nicht erfolgreich sein, wird die Verbindung und der Thread zwangsweise geschlossen

Procedure CloseClient(*ClientData.udtClientData)
  Protected timeout
 
  With *ClientData
    If \ConnectionID = 0
      ProcedureReturn 0
    EndIf
    Logging("Network: Close Network Connection: ID " + \ConnectionID)
    \ExitClient = 1
    Repeat
      If \ExitClient = 0
        Break
      Else
        timeout + 100
        If timeout > 10000
          CloseNetworkConnection(\ConnectionID)
          KillThread(\ThreadID)
          \ThreadID = 0
          \ConnectionID = 0
          \ExitClient = 0
          Logging("Network: Error - Kill Network Connection: ID " + \ConnectionID)
          Break
        EndIf
      EndIf
      Delay(100)
    ForEver
  EndWith
EndProcedure

; -----------------------------------------------------------------------------

Global ExitApplication
Global ClientData.udtClientData
Global Host.s = "127.0.0.1"
Global Port = 6037

Procedure Main()
  Protected Event, rows, text.s
 
  If OpenWindow(#WinMain, #PB_Ignore, #PB_Ignore, 600, 400, "MiniChat-Client",#PB_Window_SystemMenu)
    CreateStatusBar(0, WindowID(#WinMain))
    AddStatusBarField(100)
    AddStatusBarField(#PB_Ignore)
    CreateMenu(0, WindowID(#WinMain))
    MenuTitle("Datei")
    MenuItem(#MenuItem_Connect, "Connect")
    MenuItem(#MenuItem_Disconnect, "Disconnect")
   
    ListViewGadget(0, 0, 0, WindowWidth(0), WindowHeight(0) - StatusBarHeight(0) - 35)
    StringGadget(1, 5, GadgetHeight(0) + 5, WindowWidth(0) - 10, 25, "")
    AddKeyboardShortcut(#WinMain, #PB_Shortcut_Return, #MenuItem_Send)
   
    ; LOOP
    Repeat
      Event = WaitWindowEvent()
      Select Event
        Case #PB_Event_Menu
          Select EventMenu()
            Case #MenuItem_Connect
              InitClient(ClientData, Host, Port)
             
            Case #MenuItem_Disconnect
              CloseClient(ClientData)
             
            Case #MenuItem_Send
              If GetActiveGadget() = 1 And ClientData\ConnectionID
                text = GetGadgetText(1)
                If text > ""
                  NetSendString(ClientData\ConnectionID, text)
                  SetGadgetText(1, "")
                EndIf
              EndIf
             
          EndSelect
         
        Case #PB_Event_CloseWindow
          Select EventWindow()
            Case #WinMain
              CloseClient(ClientData)
              ExitApplication = #True
          EndSelect
        Case #My_Event_Logging
          Logging(FreeString(EventData()))
         
      EndSelect
     
    Until ExitApplication And ClientData\ExitClient = 0
  EndIf
 
EndProcedure


InitNetwork()
Main()
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
infratec
Always Here
Always Here
Posts: 6883
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Two programs Talking

Post by infratec »

mk-soft wrote:You must also check that the entire string has arrived. It is best to transfer the string length as well.
This makes only sense if the string length in byte is larger than one packet (1500 byte)
At the moment with unicode: 750 characters.

If larger strings are needed but less than 1500 byte, then use #PB_UTF8.
collectordave
Addict
Addict
Posts: 1309
Joined: Fri Aug 28, 2015 6:10 pm
Location: Portugal

Re: Two programs Talking

Post by collectordave »

Thanks To All

Just put it together as two simple apps A and B

A talks to B and B talks to A

Run both enter text in one and click send.

The only puzzling thing left is that the receiving app does not update until you move the mouse over it?

A and B use different port numbers apart from that both apps are the same.


App A

Code: Select all

Global Window_0

Global btnSend, strSend, txtHello

Global serverNumber.i,NSEvent.i

Procedure ProcessRequest(clientNumber)
  
  
  Protected result.i
  Protected *memoryLocation
  Protected memLength.i = 2048
  Protected CmdString.s
  *memoryLocation = AllocateMemory(memLength)
  If *memoryLocation
    
    result = ReceiveNetworkData(clientNumber, *memoryLocation, memLength)
    If result
     ; Debug "Client " + Str(clientNumber) + " received " + Str(result) + " bytes: " + PeekS(*memoryLocation, result)
      CmdString = PeekS(*memoryLocation, result) 
      
      SetGadgetText(txtHello,CmdString)
      
      EndIf
    EndIf
    
    FreeMemory(*memoryLocation)

  
EndProcedure

Procedure CreateUDP()
  
;  Define serverNumber.i, NSEvent.i
Define portNumber.i = 9000

InitNetwork()

serverNumber = CreateNetworkServer(#PB_Any, portNumber, #PB_Network_UDP, "127.0.0.1")
  
  
EndProcedure

Procedure SendText()
  
  Define connectionID.i,lenSendLine
  Define Sendline.s
  connectionID = OpenNetworkConnection("127.0.0.1", 8999, #PB_Network_UDP)

  Sendline = GetGadgetText(strSend)

        lenSendLine = StringByteLength(Sendline)
       If lenSendLine
         SendNetworkData(connectionID, @Sendline, lenSendLine)
       EndIf
       
       CloseNetworkConnection(connectionID)
       
EndProcedure
     
  
  
  Window_0 = OpenWindow(#PB_Any, 0, 0, 630, 160, "Application A", #PB_Window_SystemMenu)
  btnSend = ButtonGadget(#PB_Any, 390, 60, 90, 30, "Send")
  strSend = StringGadget(#PB_Any, 170, 60, 210, 30, "")
  txtHello = TextGadget(#PB_Any, 170, 10, 250, 20, "Waiting")

  CreateUDP()
  
  
  Repeat
    
    NSEvent = NetworkServerEvent(serverNumber) 
 
    If  NSEvent = #PB_NetworkEvent_Data
      ProcessRequest(EventClient())
    EndIf
    
    
    Event = WaitWindowEvent()
    
  Select Event
    Case #PB_Event_CloseWindow
      End
      
    Case #PB_Event_Gadget
      Select EventGadget()
          
        Case btnSend
          
          SendText()
          
          
      EndSelect

      
  EndSelect
  
ForEver
App B

Code: Select all

Global Window_0

Global btnSend, strSend, txtHello

Global serverNumber.i,NSEvent.i

Procedure ProcessRequest(clientNumber)
  
  
  Protected result.i
  Protected *memoryLocation
  Protected memLength.i = 2048
  Protected CmdString.s
  *memoryLocation = AllocateMemory(memLength)
  If *memoryLocation
    
    result = ReceiveNetworkData(clientNumber, *memoryLocation, memLength)
    If result
     ; Debug "Client " + Str(clientNumber) + " received " + Str(result) + " bytes: " + PeekS(*memoryLocation, result)
      CmdString = PeekS(*memoryLocation, result) 
      
      
      
          SetGadgetText(txtHello,CmdString)
      EndIf
    EndIf
    
    FreeMemory(*memoryLocation)

  
EndProcedure

Procedure CreateUDP()
  
;  Define serverNumber.i, NSEvent.i
Define portNumber.i = 8999

InitNetwork()

serverNumber = CreateNetworkServer(#PB_Any, portNumber, #PB_Network_UDP, "127.0.0.1")
  
  
EndProcedure

Procedure SendText()
  
  Define connectionID.i,lenSendLine
  Define Sendline.s
  connectionID = OpenNetworkConnection("127.0.0.1", 9000, #PB_Network_UDP)

  Sendline = GetGadgetText(strSend)

        lenSendLine = StringByteLength(Sendline)
       If lenSendLine
         SendNetworkData(connectionID, @Sendline, lenSendLine)
       EndIf
       
       CloseNetworkConnection(connectionID)
       
EndProcedure

  
  Window_0 = OpenWindow(#PB_Any, 0, 0, 630, 160, "Application B", #PB_Window_SystemMenu)
  btnSend = ButtonGadget(#PB_Any, 390, 60, 90, 30, "Send")
  strSend = StringGadget(#PB_Any, 170, 60, 210, 30, "")
  txtHello = TextGadget(#PB_Any, 170, 10, 250, 20, "Waiting")

  CreateUDP()

  Repeat
    
    NSEvent = NetworkServerEvent(serverNumber) 
 
    If  NSEvent = #PB_NetworkEvent_Data
      ProcessRequest(EventClient())
    EndIf
    
    
    Event = WaitWindowEvent()
    
  Select Event
    Case #PB_Event_CloseWindow
      End
      
    Case #PB_Event_Gadget
      Select EventGadget()
          
        Case btnSend
          
          SendText()
          
          
      EndSelect

      
  EndSelect
  
ForEver
regards

CD
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
Post Reply