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