Initial commit

This commit is contained in:
2026-04-03 00:22:39 -05:00
commit eca1e8c458
945 changed files with 218160 additions and 0 deletions

403
ZNet/ZNetServer.cpp Normal file
View File

@@ -0,0 +1,403 @@
/*
ZNetServer.cpp
Author: Patrick Baggett <ptbaggett@762studios.com>
Created: 7/18/2013
Purpose:
ZNetServer -- extends ZNetHost to provide a server
License:
Copyright 2013, 762 Studios
*/
/*************************************************************************/
#include <ZNet/ZNetServer.hpp>
#include <ZNet/ZNetPeer.hpp>
#include <ZNet/ZNetPacket.hpp>
#include <ZUtil/ZBinaryBufferReader.hpp>
#include <SST/SST_Assert.h> //assertions
#include <SST/SST_Time.h>
#include <new> //std::nothrow
#include "ZNetPrivate.hpp"
#include <stdio.h> //TEMP
/*************************************************************************/
ZNetServer::ZNetServer()
{
connectCallback = (ZNetConnectCallback)NULL;
callbackParam = NULL;
peers = NULL;
maxClients = 0;
clientCount = 0;
listenFlag = false;
for(size_t i=0; i<ZNET_MAX_SERVER_SOCKETS; i++)
sockets[i] = 0;
}
ZNetServer::~ZNetServer()
{
delete[] peers;
for(uint32_t i=0; i<ZNET_MAX_SERVER_SOCKETS; i++)
{
if(sockets[i] != 0)
SST_Net_Close(sockets[i]);
}
}
/*************************************************************************/
bool ZNetServer::Initialize(uint32_t _maxClients, uint32_t _channelCount)
{
SST_OS_DebugAssert(_maxClients > 0, "Peer count must be at least one");
SST_OS_DebugAssert(_channelCount > 0, "Channel count must be at least one");
//Allocate peer classes
peers = new(std::nothrow) ZNetPeer[_maxClients];
if(peers == NULL)
return false;
this->maxClients = _maxClients;
this->channelCount = _channelCount;
return true;
}
/*************************************************************************/
bool ZNetServer::AddSocket(SST_Socket s)
{
if(s == 0)
return false;
//Scan for empty/already existing
for(uint32_t i=0; i<ZNET_MAX_SERVER_SOCKETS; i++)
{
//Already added?
if(sockets[i] == s)
return true;
else if(sockets[i] == 0) //Empty slot?
{
//Enable non-blocking mode
//TODO: once libsst-net support socket sets, use those and update API to support wait times
if(SST_Net_SetNonblock(s, 1) != SSTNETRESULT_SUCCESS)
return false;
sockets[i] = s;
return true;
}
}
return false;
}
/*************************************************************************/
int ZNetServer::Update()
{
bool hitError = false;
uint64_t now = SST_OS_GetMilliTime();
//Update bandwidth allocation
outBW.Update(now);
//Check for packets from each socket
for(size_t i=0; i<ZNET_MAX_SERVER_SOCKETS; i++)
{
SST_NetAddress sender;
size_t nrRecv;
uint8_t data[9000]; //On IP networks, jumbo packet size are canonically 9000 bytes.
//Empty socket slot
if(sockets[i] == 0)
continue;
//Empty network packet queue
SST_NetResult res;
do
{
res = SST_Net_RecvFrom(sockets[i], data, sizeof(data), 0, &sender, &nrRecv);
//It is common that no data is available. In this case simply go to the next socket
if(res == SSTNETRESULT_WOULDBLOCK)
break;
if(res == SSTNETRESULT_SUCCESS)
this->HandlePacket(sockets[i], &sender, data, (uint32_t)nrRecv);
else if(res == SSTNETRESULT_WOULDBLOCK)
break;
else
hitError = true;
} while(res == SSTNETRESULT_SUCCESS);
}
//TODO: kind of hacky, refactor. should be able to share a lot of code with client
now = SST_OS_GetMilliTime(); //since we could have processed a lot of data, update timestamp
for(uint32_t i=0; i<maxClients; i++)
{
ZNetPeer* peer = &peers[i];
if(peer->GetState() != STATE_UNCONNECTED)
{
if(!this->SendToPeer(peer))
hitError = true;
//printf("Peer %p has %u packets queued\n"
peer->SendAcksForAllChannels();
peer->ProcessLocalAcks();
}
}
if(hitError)
return -1;
return (HasEvent() ? 1 : 0);
}
/*************************************************************************/
void ZNetServer::SendPacket(ZNetPacket* packet, ZNetPeer* peer, uint8_t channelId)
{
SST_OS_DebugAssert(packet != NULL, "Cannot send NULL packet");
SST_OS_DebugAssert(peer != NULL, "Cannot send to NULL peer");
SST_OS_DebugAssert(peer >= this->peers && peer <= &this->peers[maxClients], "Peer pointer isn't part of this group!");
ZNetPacketChannel* channel = peer->GetPacketChannel(channelId); //GetPacketChannel() does assertion checks for valid channels
channel->QueueForSending(packet, ZNETCMD_DATA);
}
/*************************************************************************/
void ZNetServer::BroadcastPacket(ZNetPacket* packet, uint8_t channelId)
{
SST_OS_DebugAssert(packet != NULL, "Cannot send NULL packet");
for(uint32_t i=0; i<maxClients; i++)
{
if(peers[i].GetState() == STATE_CONNECTED)
{
peers[i].GetPacketChannel(channelId)->QueueForSending(packet, ZNETCMD_DATA);
}
}
}
/*************************************************************************/
void ZNetServer::HandlePacket(SST_Socket s, const SST_NetAddress* addr, const uint8_t* data, uint32_t dataSize)
{
ZNetPeer* peer = PeerForAddress(addr);
ZBinaryBufferReader reader(data, dataSize, ZNET_BYTEORDER);
//basically: assert if peer != NULL then state != unconnected. otherwise, don't test anything
SST_OS_DebugAssert(peer != NULL ? (peer->GetState() != STATE_UNCONNECTED) : true, "Peer state is invalid");
//printf("Handling packet of %u length", dataSize);
//Valid packet header?
if(!ZNetPrivate::ParseWirePacketHeader(&reader))
{
printf("...but it is invalid\n");
return;
}
//printf("\n");
ZNetPrivate::ZNetMessageContainer container;
int retval;
uint64_t now = SST_OS_GetMilliTime();
bool wasValid = true;
do
{
retval = ZNetPrivate::ReadNextMessage(&reader, &container);
if(retval > 0)
{
switch(container.command)
{
//==============================================================
//Data received
case ZNETCMD_DATA:
{
printf("ZNETCMD_DATA found, ch = %u, length = %u, offset = %u, maxSize = %u\n",
container.channel, container.parsed.data.length, container.parsed.data.offset,
container.parsed.data.maxSize);
if(container.channel < peer->GetNumberChannels())
{
ZNetPacketChannel* ch = peer->GetPacketChannel(container.channel);
ch->QueueData(&container);
}
break;
}
//==============================================================
//A new client wants to connect.
case ZNETCMD_CONNECT:
{
printf("ZNETCMD_CONNECT, peer = %p\n", peer);
//No peer yet
if(peer == NULL)
{
peer = FindEmptyPeerSlot();
printf("Empty peer slot == %p\n", peer);
if(peer != NULL)
{
uint32_t clientFilterUserData;
//Any callback function?
if(this->connectCallback)
clientFilterUserData = connectCallback(addr, container.parsed.connect.userdata, callbackParam);
else
clientFilterUserData = 0; //default to success
//If the client passed the filter
if(clientFilterUserData == 0)
{
printf("New client connected!\n");
ZNetPacket* packet = this->CreatePacket(NULL, sizeof(uint32_t), 0);
if(packet != NULL)
{
memset(packet->GetData(), 0, sizeof(uint32_t));
if(peer->Initialize(this, addr, s, this->channelCount))
{
printf("Queueing ZNETCMD_CONNRESP");
peer->GetPacketChannel(ZNET_SYSCHANNEL)->QueueForSending(packet, ZNETCMD_CONNRESP);
packet->ReleaseReference();
ZNetEvent ev;
ev.packet = NULL;
ev.userdata = container.parsed.connect.userdata;
ev.remote = peer;
ev.type = ZNETEVENT_CONNECT;
AddEvent(&ev);
}
}
}
else //Did not pass the filter. Directly send a response since we won't be using channels.
this->TrySendConnResp(s, addr, clientFilterUserData, 0);
}
else //Server is full
{
//It's non-critical, so if we don't have the BW, ignore it
this->TrySendConnResp(s, addr, 0, ZNETCMDFLAGS_FULL);
}
} // else peer is known
/*
There is no "else{}" after this. If the peer is already known to us, that means
we've already queued a ZNETCMD_CONNRESP command in return. Once the remote host
receives it, they will stop spamming us with CONNECT.
*/
break;
} //case CONNECT
//==============================================================
//Client sent how far it is. We can safely act on this now because it does not generate any events.
//ACKs themselves do not utilize sequence numbers (they always have 0)
case ZNETCMD_ACK:
{
printf("ZNETCMD_ACK: CH %u: highestRecv = %u, highestSent = %u\n", container.channel, container.parsed.ack.highestReceived, container.parsed.ack.highestSent);
//Ensure it is a valid ack
if(container.channel < peer->GetNumberChannels())
{
ZNetPacketChannel* channel = peer->GetPacketChannel(container.channel);
int32_t currentPing = peer->GetPing();
//Update ACKs, calculating an adjusted ping
channel->UpdateRemoteAck(container.parsed.ack.highestReceived, &currentPing);
peer->SetPing(currentPing);
}
else
wasValid = false;
break;
}
default:
SST_OS_DebugError("Not yet implemented!");
break;
}
}
} while(retval > 0);
//If all was valid, update last received time
if(wasValid && peer)
peer->SetLastReceived(now);
}
/*************************************************************************/
void ZNetServer::TrySendConnResp(SST_Socket s, const SST_NetAddress* addr, uint32_t reasonCode, uint32_t flags)
{
if(outBW.TryAllocate(ZNetPrivate::WireSizeForSimpleCommand(ZNETCMD_CONNRESP)))
{
uint8_t data[128];
ZBinaryBufferWriter writer(data, sizeof(data), ZNET_BYTEORDER);
ZNetPrivate::WriteWirePacketHeader(&writer);
ZNetPrivate::WriteWireMessageHeader(&writer, 0, flags, ZNETCMD_CONNRESP, 0); //always ch0seq0
writer.WriteU32(reasonCode);
//Send packet
ZNetPrivate::SendAll(s, addr, data, (uint32_t)writer.GetOffset());
}
}
/*************************************************************************/
ZNetPeer* ZNetServer::PeerForAddress(const SST_NetAddress* addr) const
{
//TODO: At some point (maxClients > K), a hashtable would be faster. Unfortunately, ZHashMap does not have an allocation failure policy
for(uint32_t i=0; i<maxClients; i++)
{
ZNetPeer* peer = &peers[i];
if(peer->GetState() != STATE_UNCONNECTED)
{
if(SST_Net_AddressCompare(peer->GetNetAddress(), addr) == 0)
return peer;
}
}
//Does not match anyone
return NULL;
}
/*************************************************************************/
ZNetPeer* ZNetServer::FindEmptyPeerSlot()
{
for(uint32_t i=0; i<maxClients; i++)
{
if(peers[i].GetState() == STATE_UNCONNECTED)
return &peers[i];
}
return NULL;
}