403 lines
10 KiB
C++
403 lines
10 KiB
C++
/*
|
|
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, ¤tPing);
|
|
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;
|
|
} |