Initial commit
This commit is contained in:
403
ZNet/ZNetServer.cpp
Normal file
403
ZNet/ZNetServer.cpp
Normal 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, ¤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;
|
||||
}
|
||||
Reference in New Issue
Block a user