Posted: 24th Jun 2023 3:53
Hello everyone, I'm studying a lot about online multiplayer games, and I'd like to find an effective way to create them, I had a question if it's effective to use the commands Network -> HostNetwork and the like to create them, would anyone know how to tell me more about the efficiency of it? if it is possible and it makes sense to use in multiplayer games that require some speed like fps, rpge etc ....
everywhere says that udp is the right one. but what about agk's multiplayer commands?

Thank you all for the attention.
Posted: 25th Jun 2023 20:41
In general for fast paced/real-time games, UDP is the way to go for performance (the 'CreateUDPListener/SendUDPNetworkMessage' approach in AGK/S). The 'HostNetwork'/SendNetworkMessage' approach in AGK/S is TCP, which is good for guaranteed messages like text chat exchanges, setting exchanges, scores, player list, and/or other elements that don't need to be updated at a rapid pace. TCP can also be good for turned-based games and other approaches that may only need occasional state/move updates. So ultimately, you may want to blend the two, TCP for connecting and critical packets, UDP for player data/gamestate. Or stick to UDP exclusively and design a guarantee mechanism for the most important packets as may be needed. With UDP, you will need to keep track of the IP address and port for every player you want to send packets out to. So that is something you'll need to be aware of using that protocol. You also may want to come up with your own player list management system, depending on if you design a client-server or peer-to-peer model. Easier to keep track of each IP and port for every player that way anyway. But that's more on the design side of things. To get back to your original question, if your game is real-time/FPS/action, you'll likely want to utilize UDP exclusively or at least incorporate it into the network connection for optimal packet transmission performance.
Posted: 26th Jun 2023 7:51
For me the best way has been to have a ID value in the packet so the system can keep track of last packet processed so to not have charactera jerking all over the place due to processing packets in the wrong order. UDP is fastest but unreliable, TCP is more reliable BUT this does not give 100% reliability as connections are vastly different in capabilities (for example dialup connecting to broadband servers etc).

Another thing which i learned early on was dont spam packets send maybe ten or twenty a second max and have the program tween between the two to give reliable player experience otherwise you are going to overload your nerworking system and cause a bottleneck there.
Posted: 28th Jun 2023 4:07
Thanks to SFSW and EdzUp for clarifications.
but I confess that it is still being quite a challenge to try to create a server and client for an online game using AppGameKit ... both in tier 1 and in tier2 , it is being very painful, do you have any examples for me to have notion of how to create something similar to a UDP Client that sends and receives information from a server on a specific port?
Posted: 28th Jun 2023 12:07
OK I have been studying this today and I get the setup, I have 2 apps talking via UPD and a deeper understanding of how this function set is implemented

UDP is NOT a client/server system its 2 independent listeners that send data to each other this poses some rather large limitations but I can overcome this by adding a data class and logging the incoming connections on the server, save the IP address and assign a client id then the server knows who to send updates to, a quick solution but not a good one, the problem with this is as there is no direct connection I can not directly check for a disconnect unless I ping all known connections and wait for a return ... and run the active messages and handle all the other tasks my viral game would demand! , again a quick solution but not a good one (unless I am missing something in the function set but I see no way of checking if a UPD connection is valid or any way to check if a message made it to its target without replying ..... thats 3 messages for every 1 sent ... that is not going to work)

so that being said, and as my needs would probably demand ....

this system also needs a tcp connection to run in tandem for the mundane stuff, login, ping, chat server ... low priority stuff and use the UDP for position updates and real time data that needs to be moved fast, this way I can utilize the client/server aspect of the tcp connection to have a more robust system and the speed of UDP for the realtime data ......

Anyone know of any free VPS I can pipe into for remote testing?
Posted: 28th Jun 2023 15:16
this is incredible, I had already thought of using tcp and udp as a two-way street, it would be quite effective for games that require network reliability.

is c++ compatible?
Posted: 28th Jun 2023 16:21
Its all C++, Tier1 is great dont get me wrong but I have become a custom to C++ now so I'll stick with it, the more I code, the more I learn, the more I can code

just so you know where I am going with this here is my server code so far, its by no means complete and barley functional but will show my way of thinking
+ Code Snippet
// Includes
#include "AGK_Common.h"
#include "AGK_Server.h"
#include "cHashedList.h"
#include <string>
// Namespace
using namespace AGK;

app App;


class cClient
{
public:



private:
	UINT ID;
	std::string m_IPAddress;
	std::string m_Name;

	// other client data
	// password
	// in game items etc etc

};

class cAGKServer
{
public:

	//############################
	// UDP Server
	bool StartUDP(const char* host, int port)
	{
		m_iUDP_ID = agk::CreateUDPListener(host, port);
		if (m_iUDP_ID)
		{
			m_OutputLog.push_back("UDP Listener Started");
			return true;
		}
		else
		{
			m_OutputLog.push_back("Error Creating UDP Listener");
			return false;
		}
	}

	void StopUDP()
	{
		if (m_iUDP_ID)
		{
			agk::DeleteUDPListener(m_iUDP_ID);
			m_OutputLog.push_back("UDP Listener Stopped");
			m_iUDP_ID = 0;
			// TODO!
			// notify clients etc etc
		}
	}

	void UpdateUDP()
	{
		UINT iMessageID = agk::GetUDPNetworkMessage(m_iUDP_ID);
		if (iMessageID > 0)
		{
			int iMessageType = agk::GetNetworkMessageInteger(iMessageID);
			switch (iMessageType)
			{
			case CONNECT_TO_SERVER:
			{
				m_OutputLog.push_back("Client Connected");
			}
			break;

			case 999:
			{
				m_OutputLog.push_back("999 Sent from client");
				m_OutputLog.push_back(agk::GetNetworkMessageFromIP(iMessageID));
				m_OutputLog.push_back(std::to_string(agk::GetNetworkMessageFromPort(iMessageID)));
			}
			break;

			}

			agk::DeleteNetworkMessage(iMessageID);
		}

	}

	//############################
	// TCP Server
	bool StartTCP(const char* host, int port)
	{
		m_iTCP_ID = agk::HostNetwork(host, "Host", port);
		if (agk::IsNetworkActive(m_iTCP_ID))
		{
			m_OutputLog.push_back("TCP Server Started");
			return true;
		}
		else
		{
			m_OutputLog.push_back("Error Creating TCP Server");
			return false;
		}
	}

	void StopTCP()
	{
		if (bool(agk::GetNetworkExists(m_iTCP_ID)))
		{
			// TODO!
			// notify clients etc etc


			agk::CloseNetwork(m_iTCP_ID);
		}
	}

	void HandleTCPDisconnects()
	{
		agk::Print(agk::GetNetworkExists(m_iTCP_ID));
		UINT iClientID = agk::GetNetworkFirstClient(m_iTCP_ID);
		while (iClientID)
		{
			if (iClientID = agk::GetNetworkMyClientID(m_iTCP_ID))
			{// Client Is Server
				agk::Print("Client Is Server");
			}
			else
			{// Client Is Player

				// Check for disconnects
				if (agk::GetNetworkClientDisconnected(m_iTCP_ID, iClientID) == 1)
				{
					// if the first item of client user data is not set (0 is default)
					if (agk::GetNetworkClientUserData(m_iTCP_ID, iClientID, 0) == 0)
					{
						// set the first item of client user data to 1 
						// so that we know we have already cleaned up 
						// (clients persist for a few cycles after being deleted)
						agk::SetNetworkClientUserData(m_iTCP_ID, iClientID, 0, 1);
						// delete the client (this will not be instant)
						agk::DeleteNetworkClient(m_iTCP_ID, iClientID);

						// TODO!
						// remove client from data

						// send UDP to all remaining clients to notifi disconnect
					}
				}
				else
				{
					// if the client is connected, print its unique identifying name (chosen in the joinnetwork or hostnetwork commands)
					agk::Print("     Client Connected");
					std::string client_name = "     Name: " + std::string(agk::GetNetworkClientName(m_iTCP_ID, iClientID));
					agk::Print(client_name.c_str());
				}
			}

			iClientID = agk::GetNetworkNextClient(m_iTCP_ID);
		}
	}

	void HandleTCPMessages()
	{
		// Handle incoming messages
		UINT iIncomingMessageID = agk::GetNetworkMessage(m_iTCP_ID);
		if (iIncomingMessageID > 0)
		{
			// Get message sender info
			static UINT iClientID = agk::GetNetworkMessageFromClient(iIncomingMessageID);
			static char* sClientIP = agk::GetNetworkMessageFromIP(iIncomingMessageID);
			static int iClinentPort = agk::GetNetworkMessageFromPort(iIncomingMessageID);

			// first int should always  be the message type
			static int iIncomingMessageType = agk::GetNetworkMessageInteger(iIncomingMessageID);
			switch (iIncomingMessageType)
			{
			case CONNECT_TO_SERVER:
			{
				// user connected to the TCP server, lets do a handshake to verify the connection

				//TODO!
				// add to client list
				// notify other clients

			}
			break;

			case PING_SERVER:
			{
				// use this for initial testing

			}
			break;
			}
		}
	}

	void UpdateTCP()
	{
		if (m_iTCP_ID > 0 && agk::IsNetworkActive(m_iTCP_ID))
		{
			// print details of the network, including client number (this number will include the host)
			agk::Print("Network Active");
			std::string output = "Number of Clients: " + std::to_string(agk::GetNetworkNumClients(m_iTCP_ID));
			agk::Print(output.c_str());

			HandleTCPDisconnects();
			HandleTCPMessages();

		}
	}


	std::vector<std::string> m_OutputLog;

private:
	
	UINT m_iUDP_ID; 
	UINT m_iTCP_ID;

	Messages m_Message;
    cHashedList<cClient>* cClient;
};


and example usage, I am aiming for simplicity, the above class does the heavy lifting freeing the "App" functions to take care of UI and other server logic, AI, Bots etc etc
+ Code Snippet
cAGKServer server;

void app::Begin(void* hWnd)
{
	
	server.StartUDP("127.0.0.1", 60000);
	server.StartTCP("127.0.0.1", 60500);


	agk::SetWindowAllowResize(0);
	agk::SetVirtualResolution (800, 600);
	agk::SetClearColor( 0,0,0 );
	agk::SetSyncRate(120,0);
	agk::SetScissor(0,0,0,0);
	agk::SetPrintSize(14);
}

int app::Loop (void)
{
	agk::PrintC("AGK Server: FPS: ");
	agk::Print(agk::ScreenFPS());

	server.UpdateUDP();
	server.UpdateTCP();

    // Server related UI code here



	// For now just print data to screen
	for (auto log : server.m_OutputLog)
		agk::Print(log.c_str());

	
	agk::Sync();
	return 0; // return 1 to close app
}


void app::End (void)
{
	// Call "Stop" functions for later clean up code 
	// (notify connected clients etc etc)
	server.StopUDP();
	server.StopTCP();
}


Now i just need to work on the client interface and can start trashing out a proper messaging system
Posted: 28th Jun 2023 22:45
For a Tier1 example of NAT punchthrough for UDP, I posted this a while ago: https://forum.thegamecreators.com/thread/228937

And it can be reduced down to just a basic connection, skipping the STUN server and punchthrough to just connect over UDP (signal confirmation check included). But that's if you just want to use UDP only. UDP is primarily a 'connectionless' type protocol, packets are sent regardless of whether a handshake between systems is done or not. Only a listener on the other end will or will not receive the packet, so handling connection state (ie delay/timer checks for lost packets) and other related elements all need to be managed by the code if you stick with UDP only.

As mentioned, TCP with UDP can be a preferred way to go and offers several advantages. The 'Getting Started' example included with AGK2 in 'Projects\Multiplayer\GettingStarted' is a pretty good Tier1 example of just getting a basic connection and gamestate system in place using TCP. You can then just add the UDP side of things onto it using a different port to transfer the gamestate side of the data for improved performance.
Posted: 29th Jun 2023 2:14
I confess that I'm also getting quite used to using agk with c++ , and you've given me an excellent insight into how to use sockets in agk.

Thank you very much for the knowledge that you are passing on to me.
PartTimeCoder and SFSW
Posted: 29th Jun 2023 13:24
its somewhat shameful of me to admit that in my 3 decades of coding I avoided C++ like it was a virus, my experience with C gave me a phobia of unmanaged languages (anyone that knows C should understand that), but my fears were unfounded C++ is not the demon lurking in the dark that
I Imagined it to be and after many 100's of hours I have a firm-ish grasp of what the compiler expects from me .... but, learn more every time I open the IDE.

That being said my examples are going to start getting quite complex with lots of sub classes managing various parts of the game especially on the client side so I will show now the basic setup and rather than post long threads of code I will just github the entire project

https://github.com/PartTimeCode/AGK-Network

Download the repo and unpack

Launch AppGameKit Network.sln, and compile AppGameKit Server and AppGameKit Client, ignore AppGameKit Common this is a static library that holds stuff shared between both client and server.

Open the "Final" folder, launch the server, then launch 3 or 4 clients and see the info change in the server output

the server detects new connections and disconnects (check the code for how)

I will keep pushing to this repo untill we have a small game type system but it would be helpful to you if you saw the system now before it grows and maybe this is enough to get you started?
Posted: 29th Jun 2023 15:45
the way you did it was incredible, even a layman like me can get an idea using this example ... how do you send messages to specific clients or even to all?


I am very happy that you can share this example.
Posted: 29th Jun 2023 16:31
I am working on that now

I first have to understand how TCG have implemented the socket code and after studying the source and it seems fairly intuitive, all clients have access to all aspects of the connection data, each client knows who is connected without having to query the server and a bunch of functions are setup to handle variables that are shared across all clients, it all seems fairly easy to setup a game but this system is intended for the server (host) to also be part of the game world so I am wondering if its just better to write our own socket system or utilize this behavior to our advantage and have the server client be more of a "super client"

I do foresee a problem though, most VPS are intended for remote access and dont have graphical capability rendering this whole thing useless in a production environment unless hosted on a system with graphical support.

if the window cant open, the server simply cant start, it should be a console application so I am thinking maybe you had the right idea in the first place by writing the server in PureBasic .....

for now I will continue with this example because its a good learning exercise but I dont think its going to power the next WOW or EvE online!
Posted: 29th Jun 2023 18:46
ok I got a bit side tracked on this a little but it might be worth it

the issue I previously mentioned in PM about not being able to get PureBasic and AppGameKit to "talk" was because I was using the wrong functions, a direct socket connection works just fine I have 2 way communication running with a handful lines of code but it needs wrapping up into something usable

I think given the previous mentioned issue regarding VPS this approach would be the best

Posted: 30th Jun 2023 0:00
dude, you are a genius...
this is pretty awesome.

A few years ago I looked for help on the purebasic forum, to get the answers coming from agkSocket : https://www.purebasic.fr/english/viewtopic.php?t=70261

but from what I looked at your code, it makes it too easy.
Posted: 30th Jun 2023 5:02
well reading the values in PureBasic is easy because PB intelligently splits the data into the chunks it was received but AppGameKit does not seem to do the same it just sends a slab of data, the first message is fine but when I made functions and spammed the client only the first string makes it then the whole data structure loses it!!

I gave it a few hours of debugging and walked away, I will revisit it tomorrow maybe mk-soft was onto something with adding a null to the packet, when I stepped into the code with the debugger the right amount of bytes were in the buffer but when I tried to read the int for the next message ID i got garbage so there is a buffer misalignment somewhere.

but at least now we know it can be done, your original idea of having a PureBasic app as the server and the client in Tier1 with a PB network plugin is starting to sound like a good idea..... its all native functions then and none of this nonsense! lol

Virtual Nomad put me onto Photon a while ago I gave it a quick look then but it baffeled me a bit, having another look now I understand a lot more, most of the info out there is for Unity but the C++ examples cover most things, there is a free tier for testing and if a game draws attention it would be worth renting time ..... many pies! lol
Posted: 30th Jun 2023 18:54
ok this is the TCP part, I will figure out the UDP later


C++, complete example, open a new agk template and paste this code
+ Code Snippet
// AppGameKit / PureBasic Server Framework v1.0
// By: PartTimeCoder(PTC)
// 30 / 06 / 2023
// Released under MIT license


// Includes
#include "template.h"
#include <string> 

// Namespace
using namespace AGK;
app App;


// these defines must match the server defines
/* In PureBasic use
#MESSAGE_TYPE_INTEGER = 1;
#MESSAGE_TYPE_FLOAT = 2;
#MESSAGE_TYPE_STRING = 3;
*/
constexpr int MESSAGE_TYPE_INTEGER = 1;
constexpr int MESSAGE_TYPE_FLOAT = 2;
constexpr int MESSAGE_TYPE_STRING = 3;

// typically this would be in file "cCustomNetwork.hpp"
// .hpp because we define the header and function body in the same file
class cCustomNetwork
{
public:
	// Connect to the server
	// we need to expand this function to include client details (name, password etc etc)
	bool Connect(std::string sHost, int iPort)
	{
		m_iSocketID = agk::ConnectSocket("127.0.0.1", 6832, 3000);
		if (m_iSocketID != 0)
		{
			return false;
		}
		return false;
	}

	// Disconnect from the server
	void Disconnect()
	{
		if (IsConnected())
		{
			agk::DeleteSocket(m_iSocketID);
			m_iSocketID = 0;
		}
	}

	// Send a string to the server
	// set bFlush false to stack the value into a single packet
	void SendInteger(int iValue, bool bFlush = false)
	{
		if (IsConnected())
		{
			// tell server the next message will be a string
			agk::SendSocketInteger(m_iSocketID, MESSAGE_TYPE_INTEGER);

			// add the string to the buffer
			agk::SendSocketInteger(m_iSocketID, iValue);

			// Send the message
			if (bFlush)
			{
				agk::FlushSocket(m_iSocketID);
			}
		}
	}

	// Send a string to the server
	// set bFlush false to stack the value into a single packet
	void SendFloat(float fValue, bool bFlush = false)
	{
		if (IsConnected())
		{
			// tell server the next message will be a string
			agk::SendSocketInteger(m_iSocketID, MESSAGE_TYPE_FLOAT);

			// add the string to the buffer
			agk::SendSocketFloat(m_iSocketID, fValue);

			// Send the message
			if (bFlush)
			{
				agk::FlushSocket(m_iSocketID);
			}
		}
	}

	// Send a string to the server
	// set bFlush false to stack the value into a single packet
	void SendString(const char* sValue, bool bFlush = false)
	{
		if (IsConnected())
		{
			// tell server the next message will be a string
			agk::SendSocketInteger(m_iSocketID, MESSAGE_TYPE_STRING);
			
			// add the string to the buffer
			agk::SendSocketString(m_iSocketID, sValue);

			// Send the message
			if (bFlush)
			{
				agk::FlushSocket(m_iSocketID);
			}
		}
	}

	/* From AGK Help!!
	Immediately sends any waiting data to the remote host.If you do not call this then data will 
	wait forever in the buffer until you write more than 1400 bytes to the socket, at which 
	point it will automatically be flushed.Every time you call this command a new packet will be sent, 
	so when you are sending multiple values you should not call it after each value, as that would waste 
	network bandwidth.You should write all the values and then flush so they will be sent together.
	If there is no data waiting to be sent this command does nothing.
	Returns 1 if the socket was successfully flushed, 0 if the socket disconnected.
	*/
	bool FlushSocket()
	{
		if (IsConnected())
		{
			if (agk::FlushSocket(m_iSocketID) == 1)
			{
				return true;
			}
			return false;
		}
	}

	// Checks if the socket is valid and connected
	bool IsConnected()
	{
		if (m_iSocketID != 0 && agk::GetSocketExists(m_iSocketID) && agk::GetSocketConnected(m_iSocketID))
		{
			return true;
		}
		return false;
	}

	void Update()
	{
		if (IsConnected())
		{
			int iBytesAvailable = agk::GetSocketBytesAvailable(m_iSocketID);

			// no message should contain less that 8 bytes
			// 1st 4 bytes are the message type
			// for integer and float the 2nd 4 bytes are the value
			// for string the 2nd 4 bytes is the size (x) of the string,
			// then a string (x) bytes long
			if (iBytesAvailable >= 8) 
			{
				// first 4 bytes of message is the message type
				// expand this section for custom message types
				int iMessageType = agk::GetSocketInteger(m_iSocketID);
				switch (iMessageType)
				{
					// server sent a integer value
					case MESSAGE_TYPE_INTEGER:
					{
						ReceiveInteger(agk::GetSocketInteger(m_iSocketID));
					}
					break;

					// server sent a float value
					case MESSAGE_TYPE_FLOAT:
					{
						ReceiveFloat(agk::GetSocketFloat(m_iSocketID));
					}
					break;

					// server sent a string value
					case MESSAGE_TYPE_STRING:
					{
						ReceiveString(agk::GetSocketString(m_iSocketID));
					}
					break;
				}
			}
		}
	}

public:
	// Virtual functions for derived class
	virtual void ReceiveInteger(int iValue) {};
	virtual void ReceiveFloat(float fValue) {};
	virtual void ReceiveString(std::string sValue) {};

private:



	UINT m_iSocketID;




};



// #################################
// Application Entry
// #################################
// now we make a derived class from the main class above
// here we flesh out the client behaviour
// typically this would be in file "MyClient.h"
class MyClient : public cCustomNetwork
{
public:
	// first we override the virtual functions defined in the parent class
	void ReceiveInteger(int iValue) override;
	void ReceiveFloat(float fValue) override;
	void ReceiveString(std::string sValue) override;

private:

};




// typically this would be in file "MyClient.cpp"
// and here we define the function body

// create a vector to hold received data (just for testing)
std::vector<int> vIntegerValue;
void MyClient::ReceiveInteger(int iValue)
{
	vIntegerValue.push_back(iValue);
}

// create a vector to hold received data (just for testing)
std::vector<float> vFloatValue;
void MyClient::ReceiveFloat(float fValue)
{
	vFloatValue.push_back(fValue);
}

// create a vector to hold received data (just for testing)
std::vector<std::string> vStringValue;
void MyClient::ReceiveString(std::string sValue)
{
	vStringValue.push_back(sValue);
}


// now we create an instance of our derived class
MyClient client;

void app::Begin()
{
	// connect to the server
	client.Connect("127.0.0.1", 6832);

	while (!client.IsConnected())
	{
		// brute force wait for connection
		// NEVER DO THIS!!
		// this is for testing only
	}

	// doing it this way we can send quite a lot of data in a single packed
	// for a game though you would want to structure this better so the server knows
	// what to do with the data, who to forward it to etc etc
	client.SendFloat(254.3f);
	client.SendFloat(74.54f);
	client.SendFloat(545.36f);
	client.SendFloat(97.14f);

	client.SendInteger(123);
	client.SendInteger(456);
	client.SendInteger(789);
	client.SendInteger(123456);

	client.SendString("Hello from AppGAmeKit 1");
	client.SendString("Hello from AppGAmeKit 2");
	client.SendString("Hello from AppGAmeKit 3");
	client.SendString("Hello from AppGAmeKit 4");
	client.SendString("Hello from AppGAmeKit 5");

	client.FlushSocket();


	agk::SetVirtualResolution(1024, 768);
	agk::SetClearColor(0, 0, 0);
	agk::SetSyncRate(60, 0);
	agk::SetScissor(0, 0, 0, 0);
	agk::SetPrintSize(16);
}

int app::Loop(void)
{
	// call the client loop
	client.Update();


	// print out the vecter test data
	for (auto value : vIntegerValue)
	{
		agk::PrintC("Integer Value = ");
		agk::Print(value);
	}

	for (auto value : vFloatValue)
	{
		agk::PrintC("Float Value = ");
		agk::Print(value);
	}

	for (auto value : vStringValue)
	{
		agk::PrintC("String Value = ");
		agk::Print(value.c_str());
	}

	// your game logic here


	agk::Print(agk::ScreenFPS());
	agk::Sync();

	return 0; // return 1 to close app
}


void app::End(void)
{
	client.Disconnect();
}


PureBasic, same, open a new file and paste the code
+ Code Snippet
; AppGameKit/PureBasic Server Framework v1.0
; By: PartTimeCoder (PTC)
; 30/06/2023
; Released under MIT license



#MESSAGE_TYPE_INTEGER = 1;
#MESSAGE_TYPE_FLOAT = 2;
#MESSAGE_TYPE_STRING = 3;
   


Structure CLIENT_INFO
  ID.i
  Name.s
EndStructure 
Global NewList Clients.CLIENT_INFO()

; Forward declare these functions as they are defined after the event loop
Declare ClientConnected(iClientID.i)
Declare ClientDisonnected(iClientID.i)
Declare ReceiveInteger(iClientID.i, iValue.i)
Declare ReceiveFloat(iClientID.i, fValue.f)
Declare ReceiveString(iClientID.i, sValue.s) 


;-###################################
;- Communication 
;-###################################
Procedure SendString(iClientID.i, sString.s)
  
  Protected iStrngLength.i, *OutputBuffer, Result.i
  
  ; the string size in bytes (Never use Len() for allocating memory!)
  iStrngLength.i = StringByteLength(sString, #PB_Ascii)+1
    
  ; AGK requires that we send a 4 byte int of the size of the string, 
  ; allocate a buffer of the string size + 4 bytes
  *OutputBuffer = AllocateMemory(iStrngLength+8)
  
  ; poke the message type first
  PokeI(*OutputBuffer, #MESSAGE_TYPE_STRING) 
  
  ; poke the 4 byte string length (this is so AGK knows how big the sring buffer is)
  PokeI(*OutputBuffer+4, iStrngLength)

  ; now poke the string into the buffer setting a 4 byte offset
  PokeS(*OutputBuffer+8, sString, iStrngLength, #PB_Ascii)
  
  ; and send the buffer to the client
  Result = SendNetworkData(iClientID, *OutputBuffer, iStrngLength+8)
  
  ; clean up memory
  FreeMemory(*OutputBuffer)
    
EndProcedure

Procedure SendInteger(iClientID.i, iValue.i)
  
  Protected *OutputBuffer, Result.i
  
  ; allocate an 8 byte buffer (4 for the message, 4 for the value)
  *OutputBuffer = AllocateMemory(8)
  
  ; poke the message type first
  PokeI(*OutputBuffer, #MESSAGE_TYPE_INTEGER) 
  
  ; poke the integer at a 4 byte offset
  PokeI(*OutputBuffer+4, iValue)
  
  ; and send the buffer to the client
  Result = SendNetworkData(iClientID, *OutputBuffer, 8)
  
  ; clean up memory
  FreeMemory(*OutputBuffer)    

EndProcedure
  
Procedure SendFloat(iClientID.i, fValue.f)
  
  Protected *OutputBuffer, Result.i
  
  ; allocate an 8 byte buffer (4 for the message, 4 for the value)
  *OutputBuffer = AllocateMemory(8)
  
  ; poke the message type first
  PokeI(*OutputBuffer, #MESSAGE_TYPE_FLOAT) 
  
  ; poke the float at a 4 byte offset
  PokeF(*OutputBuffer+4, fValue) 
  
  ; and send the buffer to the client
  Result = SendNetworkData(iClientID, *OutputBuffer, 8)
  
  ; clean up memory
  FreeMemory(*OutputBuffer) 
  
EndProcedure

;-###################################
;- Client Functions
;-###################################
; These ae used to keep track of who is connected
; Later we expand this to hold all client data
; like XP, points and other account stuff
Procedure FindClient(iClientID.i)
  ForEach Clients()
    If Clients()\ID = iClientID
      ProcedureReturn ListIndex(Clients())
    EndIf
  Next
  ProcedureReturn -1
EndProcedure

Procedure AddClient(iClientID.i)
  AddElement(Clients())
  Clients()\ID = iClientID
  Clients()\Name = "New Name"
EndProcedure

Procedure RemoveClient(iClientID.i)
  iIndex.i = FindClient(iClientID)
  If iIndex <> -1
    DeleteElement(Clients(), #True)
  EndIf
EndProcedure


;-###################################
;- Server Functions
;-###################################
Procedure UpdateServer()
  
  Protected ServerEvent.i, ClientID.i, *MessageType, *Length, *String, *Integer, *Float
  
  ServerEvent = NetworkServerEvent()
  If ServerEvent

    ClientID = EventClient()

    Select ServerEvent
    
      Case #PB_NetworkEvent_Connect
        
        ; a client connectd
        ; call the callback and add to linked list
        ClientConnected(ClientID)
        AddClient(ClientID)

      Case #PB_NetworkEvent_Data
        
        ; a client sent some data
        
        ; get the message type
        ; first 4 bytes of all data is the message type
        *MessageType = AllocateMemory(4)
        ReceiveNetworkData(ClientID, *MessageType, 4)
        
        ; handle string messages
        If PeekI(*MessageType) = #MESSAGE_TYPE_STRING
          
          ; get the length of the string
          *Length = AllocateMemory(4)
          ReceiveNetworkData(ClientID, *Length, 4) 
          
          ; now allocate enough memory to pull the string from the input buffer
          *String = AllocateMemory(PeekI(*Length))
          ReceiveNetworkData(ClientID, *String, PeekI(*Length))
          
          ; now invoke the callback and send the string
          ReceiveString(ClientID, PeekS(*String, -1, #PB_Ascii))
          
          ; clean up
          FreeMemory(*Length)
          FreeMemory(*String)
        EndIf
        
        ; handle integer messages
        If PeekI(*MessageType) = #MESSAGE_TYPE_INTEGER
          
          ; pull the 4 byte integer from the input buffer
          *Integer = AllocateMemory(4)
          ReceiveNetworkData(ClientID, *Integer, 4)
          
          ; now invoke the callback and send the integer
          ReceiveInteger(ClientID, PeekI(*Integer))
          
          ; clean up
          FreeMemory(*Integer)
        EndIf
        
        ; handle float messages
        If PeekI(*MessageType) = #MESSAGE_TYPE_FLOAT
          
          ; pull the 4 byte float from the input buffer
          *Float = AllocateMemory(4)
          ReceiveNetworkData(ClientID, *Float, 4)
          
          ; now invoke the callback and send the float
          ReceiveFloat(ClientID, PeekF(*Float))
          
          ; clean up
          FreeMemory(*Float)
        EndIf
        
        ; clean up
        FreeMemory(*MessageType)
        
      Case #PB_NetworkEvent_Disconnect
        ; client disconnected, invoke the callback and remove from linkedlist
        ClientDisonnected(ClientID)
        RemoveClient(ClientID)
    EndSelect
  EndIf 
  
EndProcedure
  
  
  
  
; #################################
; Callback Functions
; #################################
; The below functions are called automatically by the event loop
Procedure ClientConnected(iClientID.i)
  Debug "A new client has connected !"
  
  SendInteger(iClientID, 454545)
  SendInteger(iClientID, 8686)
  SendInteger(iClientID, 454545)
  SendInteger(iClientID, 232323)
  SendInteger(iClientID, 464646)
  SendInteger(iClientID, 949494)
  SendInteger(iClientID, 785785) 
  
  SendFloat(iClientID, 463.3)
  SendFloat(iClientID, 45.7463)
  SendFloat(iClientID, 3634.4634)
  
  SendString(iClientID, "Hello from PureBasic 1")
  SendString(iClientID, "Hello from PureBasic 2")          
  SendString(iClientID, "New String!! Hello")
  SendString(iClientID, "Hello from PureBasic 4")

  SendFloat(iClientID, 485.86)
  SendFloat(iClientID, 4853.343)
  SendFloat(iClientID, 33856.66) 
  
EndProcedure

Procedure ClientDisonnected(iClientID.i)
   Debug "Client "+iClientID+" has closed the connection"
EndProcedure

Procedure ReceiveInteger(iClientID.i, iValue.i)
  Debug "Client ("+Str(iClientID)+") is sending a integer"
  Debug "Integer value is: "+Str(iValue)
EndProcedure

Procedure ReceiveFloat(iClientID.i, fValue.f)
  Debug "Client ("+Str(iClientID)+") is sending a float"
  Debug "Float value is: "+StrF(fValue)
EndProcedure

Procedure ReceiveString(iClientID.i, sValue.s)
  Debug "Client ("+Str(iClientID)+") is sending a string"
  Debug "String value is: "+sValue
EndProcedure


; #################################
; Application Entry
; #################################
Port = 6832


If CreateNetworkServer(0, Port, #PB_Network_IPv4 | #PB_Network_TCP, "127.0.0.1")

  Debug "Server created (Port "+Port+")."
   
  Repeat
    
    UpdateServer()
    
    ; server logic, AI, GUI etc etc etc

    
  Until Quit = 1
  
  CloseNetworkServer(0)
  
Else
  Debug "Can't create the server (port in use ?)."
EndIf

  
End


I wont go into detail how it all works the code for both is heavily commented, its a good base for a TCP socket server/client system, I will work on adding some more "game" type logic to it later for now I am taking a break, I been coding 6 hours lol
Posted: 1st Jul 2023 5:20
this example got me really excited by the possibility. I thought it was a lot of work to implement the socket from agk to purebasic . I don't even know how to thank you for all your help and effort.
I tested it and it worked perfectly.
Posted: 1st Jul 2023 17:17
Not perfectly!

As I am expanding this to be a proper game client/server setup I am noticing 1 or 2 bugs in my code, first and foremost is this

bool cGameNetwork::IsConnected(), in the case where the server is not running its reporting that its connected

change this
+ Code Snippet
bool cGameNetwork::IsConnected()
{
	if (m_iSocketID != 0 && agk::GetSocketExists(m_iSocketID) && agk::GetSocketConnected(m_iSocketID))
	{
		return true;
	}
	return false;
}


to this
+ Code Snippet
bool cGameNetwork::IsConnected()
{
	if (m_iSocketID != 0 && agk::GetSocketExists(m_iSocketID) == 1 && agk::GetSocketConnected(m_iSocketID) == 1)
	{
		return true;
	}
	return false;
}

.
Returns 1 if the specified socket is connected, 0 if it is still in the process of connecting. If the socket becomes disconnected or fails to connect then this will return -1



Edit:

Actually a better way to do this is ....
+ Code Snippet
int cGameNetwork::GetConnectionState()
{
	if (m_iSocketID != 0 && agk::GetSocketExists(m_iSocketID) == 1)
	{
		return agk::GetSocketConnected(m_iSocketID);
	}
	return -1;
}


and check the connection like so
+ Code Snippet
constexpr int CONNECTION_STATE_PENDING = 0;
constexpr int CONNECTION_STATE_NOSERVER = -1;
constexpr int CONNECTION_STATE_VALID = 1;


	switch (m_Network.GetConnectionState())
	{
		case CONNECTION_STATE_PENDING:
		{
			agk::Print("Connecting... Please Wait...");
			break;
		}
		case CONNECTION_STATE_NOSERVER:
		{
			agk::Print("Could not find remote host, is server active?");
			break;
		}
		case CONNECTION_STATE_VALID:
		{
			agk::Print("Connected To Server");

			if (m_Network.IsValidated())
			{
				agk::Print("Connection Is Validated");
			}
			else
			{
				agk::Print("Connection Is NOT Validated");
			}

			break;
		}
	}


but cGameNetwork::IsConnected() is being called in the network class so I'll have to edit those functions to

AS I move forward there will probably be 101 things I'll have to change to turn this from basic example to functional system!!
Posted: 2nd Jul 2023 15:46
Using TCP sockets worked for me;

https://play.google.com/store/apps/details?id=denklem.v0000

P.S. Game GUI will be updated in a few months, I guess...
Posted: 5th Jul 2023 19:40
I use sockets, But i also like to talk to python and other devices as well as games. Here is a socket server. it is set up for tcp atm but just add a second udp listener and add it to the message queue. here is the general gist of it though had to strip out alot of custom stuff thats running my mmo server so it will not run but may offer some good examples
+ Code Snippet
global path as string
	path="raw:"+getreadpath()+"media/" //set the dir to  check

Server.ip="127.0.0.1"
Server.port=3856
Server.listener=1
//startup
status$=StartServer(server.Listener,Server.IP,Server.Port)


Global Server as Servertype
type ServerType
	IP as string
	Port as integer
	Name as string
	listener as integer
	Timeout as float
endtype

global Client as clientType []
type ClientType
	ID as integer
	IP as string
	Name as string
	SockID as integer
	Timeout as float
	ClientString as string []
	CountRecieved as integer
	badattempts as integer
	LoginShips as shiptype []
	PendingMsgsToSend as string []
	
endtype


type FileUsersType
	Name as string
	Pass as string
endtype

type bannedUsersType
	IP as string
	Duration as float
	bannedTime as string
endtype

global BannedUsers as bannedUsersType []
global FileUsers as FileUsersType []
do
	if client.length >-1
		for i=0 to client[0].PendingMsgsToSend.length
			print(client[0].PendingMsgsToSend[i])
		next
	endif

	for i=0 to MessagesSent.length
		print(MessagesSent[i])
	next

	print("Status: "+status$)
	for i =0 to client.length
		print("Clients: "+str(client.length))
		print("Name: "+client[i].name)
		Print("Data Received: "+str(client[i].CountRecieved))
	next 
	for i = 0 to BannedUsers.length
		print(BannedUsers[i].IP)
		print(BannedUsers[i].bannedTime)
		print(BannedUsers[i].Duration)
	next
	runserver(6)
	sync()
loop

FUNCTION StartServer(listener as integer ,ServerIP as string,ServerPort as integer) //returns 1 or 0
	LoadUserList()
	LoadBanList()
	fail=0
	retry:
	status=CreateSocketListener(listener ,ServerIP,ServerPort) 
	if status =0
		if fail = 3
			Error("Failed to connect to "+ServerIP+ "on Port"+ str(ServerPort))
		endif
		inc fail,1
		gosub retry:
	endif
	if status=1 
		status$="Running"
	else
		Status$="Failed to Load Server"
	endif
endfunction status$



function LoadUserList()
	if GetFileExists("users.txt") 
		file=OpenToRead("users.txt")
		while FileEOF(File) = 0
	    		UserStr$=readline(File)
	    		Found=FindString(UserStr$,":",1,1)
	    		if Found >1
	    			Name$=left(UserStr$,found-1)
	    			password$=right(UserStr$,len(UserStr$)-found)
	    			Temp as FileUsersType
	    			Temp.Name=Name$
	    			Temp.Pass=Password$
	    			FileUsers.insert(temp)
	    		endif
		endwhile
	endif	
endfunction


`Get new connections to server 
function AcceptRefuseConnections()
	sockID=GetSocketListenerConnection(server.listener)  
	if sockid > 1  
		tempip$=getsocketremoteip(sockid)
		`if ban delete sock send msg 
		`else add to temp
		temp as clientType
		temp.ip=tempip$
		temp.SockID=SockID
		temp.Timeout=timer()
		client.insert(temp)
	endif
endfunction





function LoadBanList() `working good
	if GetFileExists("banlist.txt") 
		file=OpenToRead("banlist.txt")
		while FileEOF(File) = 0
        		UserStr$=readline(File)
        		found=FindString(UserStr$,":",1,1)
        		if found >1
        			ip$=left(UserStr$,found-1)
        			duration$=right(UserStr$,len(UserStr$)-found)
        			Temp as BannedUsersType
        			Temp.IP=IP$
        			Temp.Duration=val(Duration$)
        			BannedUsers.Insert(temp)
        		endif
    		endwhile
	endif	
endfunction

function runserver(ServerTimeout)
	AcceptRefuseConnections()
	for id=0 to client.length
		SendHeartbeats(id)
		SendMsgsRoundRobin(id)
		ParseClientstring(id)
		RemoveDeadClients(id)
	next
endfunction

function SendMsgsRoundRobin(ClientID)
	`if client[ClientID].PendingMsgsToSend.length >100 `prevent crash on heavy load from message buildup in memory
		`client[clientID].PendingMsgsToSend.length=10
	`endif
	if client[ClientID].PendingMsgsToSend.length >-1
		msg$=client[clientID].PendingMsgsToSend[0]
		SendMessage(clientID,Msg$)
		FlushSock(client[clientID].SockID)
		client[clientID].PendingMsgsToSend.remove(0)
		global MessagesSent as string []
		messagessent.insert(msg$)
	endif
endfunction

function SendMessage(clientID,data$)
	if GetSocketExists(Client[clientID].sockid)=1 `dont check later just send with no error mode
		if GetSocketConnected(Client[clientID].sockid)=1
			SendSocketString(Client[clientID].sockid,data$)
		endif
	endif
endfunction


function sendHeartbeats(i)
	if timer()-client[i].Timeout>2
	if GetSocketExists(Client[i].sockid)=1
		if GetSocketConnected(Client[i].sockid)=1
			SendSocketString(Client[i].sockid,"Pulse")
		endif
	endif
endif
endfunction

function Error(msg as string)
		print(msg)
endfunction


function FlushSock(id)
	if GetSocketExists(id)=1
		flushsocket(id)
	endif
endfunction

function RemoveDeadClients(ClientID)
	print(timer())
	print(client[clientID].Timeout)
	print(timer()-client[clientID].Timeout)
	if timer()-client[clientID].Timeout > 6.0
		DeleteSocket(client[clientID].SockID)
		client.remove(clientID)
		print("terminated")
	endif
endfunction