Open source Star Ruler 2 source code!

This commit is contained in:
Lucas de Vries
2018-07-17 14:15:37 +02:00
commit cc307720ff
4342 changed files with 2365070 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
#Find the correct architecture to build for
ifndef ARCH
ARCH = $(shell getconf LONG_BIT)
endif
#Set correct flags for architecture
ifeq ($(ARCH), 32)
#Global
CXXFLAGS += -m32 -march=pentium4 -mtune=generic
#Arch name
ARCHNAME = x86
else
#Global
CXXFLAGS += -m64 -march=athlon64 -mtune=generic
#Arch name
ARCHNAME = x64
endif
#Global flags
ifeq ($(origin AR), default)
AR = gcc-ar
endif
BIN = libnetwork.a
SRCDIR = source/network/source
UNAME = $(shell uname)
ifeq ($(UNAME), Darwin)
OSNAME = osx
else
OSNAME = lin
endif
ifdef DEBUG
OBJDIR = obj_d/$(OSNAME)$(ARCH)/network
BINDIR = obj_d/$(OSNAME)$(ARCH)
CXXFLAGS += -O0 -g
CXXFLAGS += -D_DEBUG
else
OBJDIR = obj/$(OSNAME)$(ARCH)/network
BINDIR = obj/$(OSNAME)$(ARCH)
CXXFLAGS += -Ofast
CXXFLAGS += -DNDEBUG
ifndef NLTO
CXXFLAGS += -flto
else
CXXFLAGS += -fno-lto
endif
endif
ifndef CC
CC = g++
endif
CXXFLAGS += -std=c++11
CXXFLAGS += -Wall -Wno-invalid-offsetof -Wno-switch -Wno-reorder
CXXFLAGS += -isystem./source/network/include
CXXFLAGS += -I./source/os/include
#Source code files
SOURCES = \
address.cpp \
message.cpp \
time.cpp \
init.cpp \
transport.cpp \
message_handler.cpp \
connection.cpp \
sequence.cpp \
server.cpp \
client.cpp \
lobby.cpp
CPP_FILES = $(addprefix $(SRCDIR)/, $(SOURCES))
OBJ_FILES = $(addprefix $(OBJDIR)/, $(SOURCES:.cpp=.o))
DEP_FILES = $(addprefix $(OBJDIR)/, $(SOURCES:.cpp=.d))
-include $(DEP_FILES)
compile: $(DEP_FILES) $(BINDIR)/$(BIN)
#Dependency files to take care of header changes
$(OBJDIR)/%.d: $(SRCDIR)/%.cpp
@mkdir -p $(dir $@)
@$(CC) $(CXXFLAGS) -MM -MT "$(@:.d=.o)" $< >> $@
#Object files are compiled separately
$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
@mkdir -p $(dir $@)
@echo $<
@$(CC) $(CXXFLAGS) $< -c -o $@
#Complete binary compile
$(BINDIR)/$(BIN): $(OBJ_FILES)
@mkdir -p $(BINDIR)
$(AR) rcs $@ $^
clean:
@rm -rf $(OBJDIR)
@rm -f ./.depend
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <network/address.h>
#include <network/client.h>
#include <network/connection.h>
#include <network/init.h>
#include <network/lobby.h>
#include <network/message.h>
#include <network/message_handler.h>
#include <network/message_types.h>
#include <network/sequence.h>
#include <network/server.h>
#include <network/time.h>
#include <network/transport.h>
+62
View File
@@ -0,0 +1,62 @@
#pragma once
#include <string>
#include <stdint.h>
#include <functional>
#ifdef _MSC_VER
//Yay more shitty compiler implementation
//assumptions because windows sucks.
struct sockaddr;
struct sockaddr_storage;
struct addrinfo;
typedef int socklen_t;
#else
#include <sys/socket.h>
#include <netdb.h>
#endif
namespace net {
enum AddressType {
AT_IPv4,
AT_IPv6,
AT_INVALID,
};
struct Address {
AddressType type;
int port;
union {
int adr4;
uint8_t adr6[16];
size_t adr6Hash;
};
Address();
Address(const std::string& hostname, int port, AddressType type = AT_IPv4);
Address(int ip4, int port);
Address(uint8_t* ip6, int port);
std::string toString(bool showPort = true) const;
void to_sockaddr(sockaddr_storage& adr, socklen_t* size = 0) const;
void from_sockaddr(sockaddr_storage& adr);
bool operator<(const Address& other) const;
bool operator==(const Address& other) const;
bool ipEquals(const Address& other) const;
};
struct addrinfo* lookup(const std::string& hostname, int port = 0, AddressType type = AT_IPv4);
};
namespace std {
template<>
struct hash<net::Address> {
size_t operator() (const net::Address& addr) const {
return addr.type == net::AT_IPv4 ? (size_t)addr.adr4 : addr.adr6Hash;
}
};
};
+97
View File
@@ -0,0 +1,97 @@
#pragma once
#include <network/message.h>
#include <network/message_handler.h>
#include <network/connection.h>
#include <functional>
#include <unordered_map>
namespace net {
struct LobbyHeartbeat;
struct LobbyPunchthrough;
/*
* Client
* ------
* Connects to a server at an address, and handles incoming
* messages from the server to the client.
*/
class Client : public MessageHandler {
public:
typedef std::function<void(Client&,Message&)> clMessageHandler;
Address address;
bool established;
std::string hostname;
bool hasConnection;
bool resolved;
//Connect to the server on address, if makeConnection is true,
//establish a connection, otherwise send only connectionless messages
Client(Address connectTo, bool makeConnection = true);
Client(const std::string& hostname, int port, bool makeConnection = true, AddressType type = AT_IPv4);
~Client();
//Handlers for any messages that are sent to this client
void resolve();
void handle(uint8_t type, clMessageHandler func);
void handleClear(uint8_t type);
//Send and get ping for the connection
void sendPing();
unsigned getLastPing();
//Shortcut for sending messages to the server
Client& operator<<(Message& msg);
Connection* getConnection();
//Overrides to add functionality to MessageHandler
virtual void handleMessage(Transport* transport, Address addr, Message* msg);
virtual bool mainTick();
virtual void stop();
private:
threads::Mutex handlerMutex;
std::unordered_map<uint8_t, clMessageHandler> handlers;
Transport* trans;
Connection* conn;
friend LobbyHeartbeat;
friend LobbyPunchthrough;
};
/*
* BroadcastClient
* ---------------
* Can broadcast messages and interact with replies to broadcasts.
*/
class BroadcastClient : public MessageHandler {
public:
typedef std::function<void(BroadcastClient&,Address,Message&)> bcMessageHandler;
//Connect to the server on address, if makeConnection is true,
//establish a connection, otherwise send only connectionless messages
BroadcastClient(int Port, AddressType Type = AT_IPv4);
~BroadcastClient();
//Handlers for any messages that are sent to this client
void handle(uint8_t type, bcMessageHandler func);
void handleClear(uint8_t type);
//Send messages to an address over the transport
void send(Message& msg, Address addr);
//Broadcast messages on the port
void broadcast(Message& msg);
//Overrides to add functionality to MessageHandler
virtual void handleMessage(Transport* transport, Address addr, Message* msg);
private:
threads::Mutex handlerMutex;
std::unordered_map<uint8_t, bcMessageHandler> handlers;
int port;
Transport trans;
};
};
+164
View File
@@ -0,0 +1,164 @@
#pragma once
#include "threads.h"
#include <network/transport.h>
#include <network/address.h>
#include <network/time.h>
#include <unordered_map>
#include <unordered_set>
#include <list>
#include <deque>
namespace net {
#ifndef NET_RESEND_TIME
#define NET_RESEND_TIME 100
#endif
#ifndef NET_RESEND_RELIABLE_TIME
#define NET_RESEND_RELIABLE_TIME 250
#endif
#ifndef NET_RESEND_PERIOD
#define NET_RESEND_PERIOD 5
#endif
#ifndef NET_SEQUENCE_FUTURE
#define NET_SEQUENCE_FUTURE 100
#endif
#ifndef NET_PING_INTERVAL
#define NET_PING_INTERVAL 1000
#endif
#ifndef NET_PING_TIMEOUT
#define NET_PING_TIMEOUT 5000
#endif
#ifndef NET_FRAGMENT_LIMIT
#define NET_FRAGMENT_LIMIT 1000
#endif
#ifndef NET_FRAGMENT_OVERHEAD
#define NET_FRAGMENT_OVERHEAD 10
#endif
#ifndef NET_FRAGMENT_TIMEOUT
#define NET_FRAGMENT_TIMEOUT 5000
#endif
struct OutSequence;
struct InSequence;
struct SeqAck {
unsigned short seqID;
unsigned short msgID;
};
class Connection {
//Connections can send messages reliable, that is,
//messages with flag MF_Reliable set will be resent until
//acknowledged by the other side
struct MessageAwaitingAck {
Message* msg;
time lastResend;
};
mutable threads::atomic_int references;
threads::Mutex ackMutex;
std::unordered_set<unsigned short> queuedAcks;
std::vector<SeqAck> queuedSeqAcks;
threads::Mutex reliableMutex;
std::list<Message*> queuedReliable;
std::unordered_set<unsigned short> handledMessages;
std::unordered_set<unsigned short> unorderedAcks;
unsigned short nextOutgoingID;
unsigned short nextAck;
unsigned nextReliablePeriod;
void handleAck(unsigned short id);
bool split(Message& msg);
void queueReliable(Message* msg, bool immediate = false);
bool shouldHandleID(unsigned short id);
threads::Mutex msgQueueLock;
std::list<Message*> queuedMessages;
void queue(Message* msg);
struct WindowPacket {
Message* msg;
time added, sent;
};
threads::Mutex windowMutex;
size_t windowBytes, windowUsed;
double windowLength, windowUpdate;
std::deque<WindowPacket> window;
//Connections also have sequences of ordered, reliable messages
threads::Mutex sequenceMutex;
unsigned nextSequenceID;
std::unordered_map<unsigned short, OutSequence*> outSequences;
std::unordered_map<unsigned short, InSequence*> inSequences;
//Connections handle message fragmentation transparently.
//Messages over the fragment limit are sent in multiple parts without
//any application interaction.
threads::Mutex fragmentMutex;
threads::atomic_int nextFragmentID;
struct Fragment {
std::vector<Message*> received;
unsigned fragmentCount;
time lastActivity;
};
std::unordered_map<unsigned, Fragment*> waitingFragments;
void handleFragment(MessageHandler& handler, Message* msg);
//Connections will keep themselves alive by pingponging, and time
//out if we get no messages or responses to our pings for a certain
//amount of time
unsigned pingPongWait;
time lastMessageReceived;
time lastPingSent;
time firstPingSent;
bool pingSent;
time lastProcessTime;
unsigned outBytes, inBytes;
public:
Transport& transport;
bool active;
Address address;
int id;
int availBytes;
void grab() const;
void drop() const;
unsigned ping;
void sendPing();
bool preHandle(MessageHandler& handler, Message* msg);
void postHandle(MessageHandler& handler, Message* msg);
void process(MessageHandler& handler);
//Returns the total amount of traffic in bytes since the last time getTraffic was called
void getTraffic(unsigned& in_bytes, unsigned& out_bytes, unsigned& queuedPackets);
OutSequence* sequence();
void queueSeqAck(unsigned short sequenceID, unsigned short messageID);
Connection(Transport& trans, Address addr);
Connection& operator<<(Message& msg);
//Sends a messsage through the pipe, immediately if possible
// Returns true if the message should be considered sent
bool send(Message& msg, bool isResend);
~Connection();
friend OutSequence;
};
};
+13
View File
@@ -0,0 +1,13 @@
#pragma once
namespace net {
typedef void (*netErrorCallback)(const char* message, int code);
void setErrorCallback(netErrorCallback cb);
void netError(const char* err, int code);
bool prepare();
void clear();
};
+142
View File
@@ -0,0 +1,142 @@
#pragma once
#include <network/message.h>
#include <network/client.h>
#include <network/server.h>
#include <unordered_map>
#include <set>
#include "threads.h"
namespace net {
#ifndef NET_LOBBY_HEARTBEAT_INTERVAL
#define NET_LOBBY_HEARTBEAT_INTERVAL 1000
#endif
#ifndef NET_LOBBY_HEARTBEAT_TIMEOUT
#define NET_LOBBY_HEARTBEAT_TIMEOUT 5000
#endif
#ifndef NET_LOBBY_QUERY_TIMEOUT
#define NET_LOBBY_QUERY_TIMEOUT 3000
#endif
enum LobbyMessage {
LM_Heartbeat = MT_Application,
LM_Query,
LM_Result,
LM_Results_End,
LM_Identify,
};
enum LobbyFilterMode {
LFM_Ignore,
LFM_True,
LFM_False,
};
struct Game {
Address address;
std::string name, mod;
unsigned short players, maxPlayers;
bool started, isLocal, password, listed;
int punchPort, version;
Game() : players(0), maxPlayers(0), started(false), isLocal(false), listed(true), password(false), punchPort(-1), version(0) {
}
void write(Message& msg);
void read(Message& msg);
};
struct LobbyQuery {
typedef std::function<void(Game&)> ResultHandler;
Client client;
BroadcastClient broadcast;
int broadcastPort;
ResultHandler handler;
threads::Signal running;
bool active;
bool doUpdate;
bool queryServer;
bool queryBroadcast;
std::set<Address> handled_lobbies;
unsigned short receivedFromServer;
unsigned short totalFromServer;
LobbyQuery(Address ServerAddress, int BroadcastPort = -1);
LobbyQuery(const std::string& hostname, int port, int BroadcastPort = -1, AddressType type = AT_IPv4);
void bind();
~LobbyQuery();
LobbyFilterMode full, started;
std::string name, mod;
bool updating;
void refresh(bool queryServer = true, bool queryBroadcast = true);
void update(bool clear);
void stop();
};
struct LobbyPunchthrough {
Client* client;
threads::Signal running;
Address lobbyAddress;
bool active;
uint64_t interval;
bool established;
LobbyPunchthrough(Address ServerAddress, Client* client);
void stop();
void heartbeat();
~LobbyPunchthrough();
};
struct LobbyHeartbeat : public Game {
Client client;
Transport* transport;
Server broadcast;
int broadcastPort;
threads::Signal running;
int fullCounter;
bool active;
uint64_t interval;
bool identified;
unsigned externalIP;
unsigned short externalPort;
LobbyHeartbeat(Address ServerAddress, int BroadcastPort = -1);
void enablePunchthrough(Server* server);
void run(bool doQuery = true, bool doBroadcast = true);
void stop();
void heartbeat();
void identRequest();
~LobbyHeartbeat();
};
struct LobbyServer {
struct GameDesc : public Game {
time lastHeartbeat;
};
Server server;
threads::ReadWriteMutex mutex;
threads::Signal running;
std::map<Address, GameDesc> games;
bool active, logging;
LobbyServer(int port, const std::string& address = "", bool logging = true);
void listen(int port, const std::string& address = "");
void runThreads(int workerThreads = 4);
void stop();
~LobbyServer();
};
};
+331
View File
@@ -0,0 +1,331 @@
#pragma once
#include <string>
#include <cstring>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <network/message_types.h>
#include <vector>
#include <list>
#include <map>
#include <unordered_map>
namespace net {
//Message
//======
//Handles memory between the Network and Application
//
//Use 'message >> variable' to read from the Message
//Use 'message << variable' to write to the Message
//
//To send a message,
//Message msg(MessageType);
//msg << data;
class MessageReadError {
};
typedef unsigned msize_t;
struct Message {
//Prepares a message for sending
Message();
Message(uint8_t type, uint8_t flags = 0);
Message(uint8_t* data, msize_t bytes);
Message(Message& other);
void setType(uint8_t type);
uint8_t getType() const;
uint8_t getFlags() const;
void setFlags(uint8_t flags);
bool hasFlags() const;
bool getFlag(uint8_t flag) const;
bool hasID() const;
unsigned short getID() const;
void setID(unsigned short id);
unsigned short getSeqID() const;
void setSeqID(unsigned short id);
msize_t size() const;
//Reserve space on the message for filling in later
msize_t reserve(size_t bytes);
template<class T>
msize_t reserve() {
return reserve(sizeof(T));
}
template<class T>
void fill(msize_t pos, const T& value) {
memcpy(buffer->data() + pos, (void*)&value, sizeof(T));
}
template<class T>
bool canRead() const {
return used_bytes * 8 + used_bits >= (read_bytes + sizeof(T)) * 8 + read_bits;
}
//Easy write functions (msg << data)
template<class T>
void write(T value) {
writeBits((uint8_t*)&value, sizeof(T) * 8);
}
void write(void* ptr, unsigned bytes) {
writeBits((uint8_t*)ptr, bytes * 8);
}
void read(void* ptr, unsigned bytes) {
readBits((uint8_t*)ptr, bytes * 8);
}
template<class T>
Message& operator<<(const T& value) {
writeBits((uint8_t*)&value, sizeof(T) * 8);
return *this;
}
Message& operator<<(bool bit);
Message& operator<<(const char* str);
Message& operator<<(const std::string& str);
//Easy read functions (msg >> data)
template<class T>
Message& operator>>(T& value) {
readBits((uint8_t*)&value, sizeof(T) * 8);
return *this;
}
Message& operator>>(bool& bit);
Message& operator>>(char*& str);
Message& operator>>(std::string& str);
//Writing and reading vectors
template<class T>
Message& operator<<(const std::vector<T>& arr) {
*this << (unsigned short)arr.size();
for(auto it = arr.begin(), end = arr.end(); it != end; ++it)
*this << *it;
return *this;
}
template<class T>
Message& operator>>(const std::vector<T>& arr) {
unsigned short size;
*this >> size;
arr.reserve(size);
for(unsigned short i = 0; i < size; ++i) {
T elem;
*this >> elem;
arr.push_back(elem);
}
return *this;
}
//Writing and reading lists
template<class T>
Message& operator<<(const std::list<T>& arr) {
*this << (unsigned short)arr.size();
for(auto it = arr.begin(), end = arr.end(); it != end; ++it)
*this << *it;
return *this;
}
template<class T>
Message& operator>>(const std::list<T>& arr) {
unsigned short size;
*this >> size;
arr.reserve(size);
for(unsigned short i = 0; i < size; ++i) {
T elem;
*this >> elem;
arr.push_back(elem);
}
return *this;
}
//Writing and reading maps
template<class K, class V>
Message& operator<<(const std::map<K,V>& mp) {
*this << (unsigned short)mp.size();
for(auto it = mp.begin(), end = mp.end(); it != end; ++it) {
*this << it->first;
*this << it->second;
}
return *this;
}
template<class K, class V>
Message& operator>>(const std::map<K,V>& mp) {
unsigned short size;
*this >> size;
for(unsigned short i = 0; i < size; ++i) {
K key; V value;
*this >> key;
*this >> value;
mp[key] = value;
}
return *this;
}
//Writing and reading unordered maps
template<class K, class V>
Message& operator<<(const std::unordered_map<K,V>& mp) {
*this << (unsigned short)mp.size();
for(auto it = mp.begin(), end = mp.end(); it != end; ++it) {
*this << it->first;
*this << it->second;
}
return *this;
}
template<class K, class V>
Message& operator>>(const std::unordered_map<K,V>& mp) {
unsigned short size;
*this >> size;
for(unsigned short i = 0; i < size; ++i) {
K key; V value;
*this >> key;
*this >> value;
mp[key] = value;
}
return *this;
}
template<class T>
T readIn() {
T v;
*this >> v;
return v;
}
void writeSmall(unsigned value);
unsigned readSmall();
void writeSignedSmall(int value);
int readSignedSmall();
void writeBitValue(unsigned value, uint8_t bits);
unsigned readBitValue(uint8_t bits);
void writeLimited(unsigned value, unsigned limit);
void writeLimited(unsigned value, unsigned min, unsigned max);
unsigned readLimited(unsigned limit);
unsigned readLimited(unsigned min, unsigned max);
void writeFixed(double value, double min, double max, uint8_t bits = 16);
double readFixed(double min, double max, uint8_t bits = 16);
//Writes a vec3 to a much smaller version without sacrificing signicant quality
void writeSmallVec3(double x, double y, double z);
void readSmallVec3(double& x, double& y, double& z);
//Writes a vec3 to a smaller version without sacrificing noticeable quality
void writeMedVec3(double x, double y, double z);
void readMedVec3(double& x, double& y, double& z);
//Writes a direction (unit vector) in a very small size
void writeDirection(double x, double y, double z, unsigned acc = 12);
void readDirection(double& x, double& y, double& z, unsigned acc = 12);
//Writes a rotation (unit quaterniond) in a very small size
void writeRotation(double x, double y, double z, double w);
void readRotation(double& x, double& y, double& z, double& w);
void finalize();
void writeAlign();
void writeBits(uint8_t* ptr, unsigned bits);
void readAlign();
void readBits(uint8_t* ptr, unsigned bits);
void writeBit(bool bit);
void write1();
void write0();
bool readBit();
~Message();
bool hasError() const;
void clear();
void dump();
void reset();
void advance(unsigned bits);
void rewind();
void rewind(unsigned bytes, unsigned bits = 0);
struct Position {
msize_t bytes;
uint8_t bits;
};
Position getReadPosition();
Position getWritePosition();
void setReadPosition(Position pos);
void setWritePosition(Position pos);
void copyTo(Message& to, msize_t fromPos = 0, msize_t toPos = 0) const;
void move(Message& other);
void operator=(const Message& other);
uint8_t& operator[](msize_t byte);
const uint8_t& operator[](msize_t byte) const;
void getAsPacket(char*& pBytes, msize_t& bytes);
void setPacket(char* pBytes, msize_t bytes);
private:
void allocate(msize_t size);
//Prepares the write stage for the addition of <bytes> bytes
void _prepwrite(msize_t bytes);
struct Buffer {
//Values in every message
uint8_t type : 8;
//Not all types have flags, but most do
uint8_t flags : 8;
//Optional values, check the flags first
unsigned short id : 16;
unsigned short seqID : 16;
uint8_t* data();
static Buffer* create(msize_t bytes);
static Buffer* copyPacket(uint8_t* pBytes, msize_t bytes);
static void resize(Buffer*& buff, msize_t prevSize, msize_t bytes);
}* buffer;
msize_t allocated;
msize_t used_bytes;
uint8_t used_bits;
msize_t read_bytes;
uint8_t read_bits;
bool HasID, readError;
};
};
@@ -0,0 +1,54 @@
#pragma once
#include "threads.h"
#include <network/message.h>
#include <network/transport.h>
#include <queue>
#include <vector>
#include <functional>
namespace net {
#ifndef NET_SELECT_TIMEOUT
#define NET_SELECT_TIMEOUT 0
#endif
#ifndef NET_IDLE_SLEEP
#define NET_IDLE_SLEEP 1
#endif
class MessageHandler {
struct QueuedMessage {
Transport* transport;
Address addr;
Message* msg;
};
threads::Mutex queueMutex;
threads::Mutex transportMutex;
std::queue<QueuedMessage> messageQueue;
std::vector<Transport*> transports;
public:
threads::Signal threadsRunning;
bool active;
std::function<void(bool)> threadInit;
std::function<void(bool)> threadExit;
MessageHandler();
virtual ~MessageHandler();
void addTransport(Transport* transport);
void clearTransports();
virtual void queueMessage(Transport* transport, Address addr, Message* msg);
virtual void handleMessage(Transport* transport, Address addr, Message* msg);
virtual bool queueTick();
virtual bool mainTick();
virtual void runThreads(int workerThreads = 4);
virtual void stop();
};
};
@@ -0,0 +1,59 @@
#pragma once
#include <limits.h>
namespace net {
enum MessageType : unsigned char {
//Builtin message types
MT_Invalid = 0,
MT_Connect,
MT_Disconnect,
MT_Close_Sequence,
MT_Ping,
MT_Pong,
MT_Ack,
MT_SeqAck,
MT_Fragment,
MT_LastFragment,
MT_Punchthrough,
//The application starts defining message types here
MT_Application = 0x10,
};
enum DisconnectReason : unsigned char {
DR_Timeout,
DR_Error,
DR_Close,
DR_Kick,
DR_Version,
DR_Password,
DR_NULL
};
const bool MessageHasFlags[] = {
false, //MT_Invalid
true, //MT_Connect
true, //MT_Disconnect
true, //MT_Close_Sequence
false, //MT_Ping
false, //MT_Pong
false, //MT_Ack
false, //MT_SeqAck
true, //MT_Fragment
true, //MT_LastFragment
false, //MT_Punchthrough
};
const int MessageFlagsAll = sizeof(MessageHasFlags) / sizeof(bool);
enum MessageFlags : unsigned char {
MF_Reliable = 0x1,
MF_Sequenced = 0x2,
MF_Acknowledged = 0x80,
MF_Managed = MF_Reliable | MF_Sequenced,
};
};
+74
View File
@@ -0,0 +1,74 @@
#pragma once
#include <network/message.h>
#include <network/client.h>
#include <network/message_handler.h>
#include <network/connection.h>
#include <network/time.h>
#include <unordered_map>
#include <unordered_set>
namespace net {
//Handles queueing and resending outgoing messages
struct OutSequence {
Connection& conn;
unsigned short id;
unsigned short nextOutgoingID;
unsigned short nextAck;
unsigned resendPeriod;
bool closed;
struct MessageAwaitingAck {
Message* msg;
time lastResend;
bool sent;
};
void queue(Message* mess);
std::list<Message*> queuedMessages;
std::unordered_set<unsigned short> waitingAcks;
OutSequence(Connection& connection);
void handleAck(unsigned short num);
//Tries to pull a message from the sequence.
// If it succeeds, the requester owns the message
// Otherwise, returns a nullptr
Message* getNextMessage();
OutSequence& operator<<(Message& message);
void close();
};
//Handles receiving and reordering incoming messages
struct InSequence {
Connection& conn;
unsigned short id;
bool closed;
std::unordered_map<unsigned short, Message*> unhandledMessages;
unsigned short nextHandleID;
unsigned short handlingID;
InSequence(Connection& connection, unsigned short id);
bool preHandle(MessageHandler& handler, Message* msg);
void postHandle(MessageHandler& handler, Message* msg);
void process(MessageHandler& handler, time& now);
};
//Wrapper class to easily create new outgoing sequences and
//close them when the wrapper goes out of scope
struct Sequence {
OutSequence* ws;
Sequence(Client& client);
Sequence(Connection& connection);
~Sequence();
unsigned short id();
Sequence& operator<<(Message& message);
};
};
+75
View File
@@ -0,0 +1,75 @@
#pragma once
#include <network/message.h>
#include <network/message_handler.h>
#include <network/transport.h>
#include <network/connection.h>
#include <vector>
#include <map>
#include <unordered_map>
#include <functional>
#include "threads.h"
namespace net {
/*
* Server
* ------
* Handles incoming connections and messages from
* listening on one or multiple addresses/ports.
*/
class Server : public MessageHandler {
public:
typedef std::function<void(Server&,Connection&,Message&)> connMessageHandler;
typedef std::function<void(Server&,Transport&,Address,Message&)> genMessageHandler;
typedef std::function<void(Connection&)> connFunction;
Server();
~Server();
//Listen on everything associated with the passed address and port
Server(int port, const std::string& address = "", bool broadcast = false);
void listen(int port, const std::string& address = "", bool broadcast = false);
//Handlers for messages that arrive on a specific connection
void connHandle(uint8_t type, connMessageHandler func);
void connHandleClear(uint8_t type);
//Handlers for connection-less messages arriving on a transport
void genHandle(uint8_t type, genMessageHandler func);
void genHandleClear(uint8_t type);
//Send to the connection with the given id
void send(int connId, Message& message);
//Send to all active connections
void sendAll(Message& message);
//Send a ping to all connected clients
void pingAll();
//Find the connection with the given id
// NOTE: Grabs the connection before returning it
Connection* getConnectionByID(int id);
void doAll(connFunction func);
//Kick a connection
void kick(Connection& conn, DisconnectReason reason = DR_Kick);
virtual void handleMessage(Transport* transport, Address addr, Message* msg);
virtual bool mainTick();
virtual void stop();
private:
threads::Mutex connMutex;
threads::Mutex handlerMutex;
unsigned nextConnectionID;
std::unordered_map<Address, Connection*> connections;
std::unordered_map<int, Connection*> connectionIDs;
std::unordered_map<uint8_t, connMessageHandler> connHandlers;
std::unordered_map<uint8_t, genMessageHandler> genHandlers;
};
};
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <stdint.h>
#ifdef __GNUC__
#include <sys/time.h>
#endif
namespace net {
#ifdef __GNUC__
typedef unsigned int time;
#else
typedef unsigned int time;
#endif
void time_now(time& time);
uint64_t time_diff(time& from, time& to);
void time_add(time& base, int64_t add_ms);
};
@@ -0,0 +1,44 @@
#pragma once
#include <network/address.h>
#include <network/message.h>
#include "threads.h"
#include <queue>
namespace net {
class MessageHandler;
class Transport {
mutable threads::atomic_int references;
threads::Mutex queueMutex;
std::queue<std::pair<Message*,Address>> queuedSends;
std::queue<std::pair<Message*,int>> queuedBroadcasts;
int sockfd;
public:
bool active;
bool canBroadcast;
AddressType type;
int rate;
Transport(AddressType Type = AT_IPv4);
~Transport();
void grab() const;
void drop() const;
void listen(Address& addr, bool rcvBroadcast = false);
void process();
void close();
bool send(Message& msg, Address& adr, bool queue = true);
bool broadcast(Message& msg, int port, bool queue = true);
bool receive(Message& msg, Address& adr);
static int RATE_LIMIT;
friend class MessageHandler;
};
};
+194
View File
@@ -0,0 +1,194 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{A1E1942C-36D2-4824-9ECB-B4727543E958}</ProjectGuid>
<RootNamespace>network</RootNamespace>
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>MultiByte</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>MultiByte</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<OutDir>$(SolutionDir)..\..\lib\win32\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>$(SolutionDir)..\..\lib\win64\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<TargetExt>.lib</TargetExt>
<TargetName>$(ProjectName)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<TargetExt>.lib</TargetExt>
<TargetName>$(ProjectName)64</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<OutDir>$(SolutionDir)..\..\lib\win32\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>$(SolutionDir)..\..\lib\win64\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<TargetExt>.lib</TargetExt>
<TargetName>$(ProjectName)d</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<TargetExt>.lib</TargetExt>
<TargetName>$(ProjectName)64d</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>..\include\;..\..\os\include\</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
<Lib>
<AdditionalLibraryDirectories>..\..\lib\win32\</AdditionalLibraryDirectories>
<AdditionalDependencies>Winmm.lib;Ws2_32.lib;osd.lib</AdditionalDependencies>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>..\include\;..\..\os\include\</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
<Lib>
<AdditionalLibraryDirectories>..\..\lib\win64\</AdditionalLibraryDirectories>
<AdditionalDependencies>Winmm.lib;Ws2_32.lib;os64d.lib</AdditionalDependencies>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<AdditionalIncludeDirectories>..\include\;..\..\os\include\</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
<Lib>
<AdditionalLibraryDirectories>..\..\lib\win32\</AdditionalLibraryDirectories>
<AdditionalDependencies>Winmm.lib;Ws2_32.lib;os.lib</AdditionalDependencies>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<AdditionalIncludeDirectories>..\include\;..\..\os\include\</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
<Lib>
<AdditionalLibraryDirectories>..\..\lib\win64\</AdditionalLibraryDirectories>
<AdditionalDependencies>Winmm.lib;Ws2_32.lib;os64.lib</AdditionalDependencies>
</Lib>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\include\network.h" />
<ClInclude Include="..\include\network\address.h" />
<ClInclude Include="..\include\network\client.h" />
<ClInclude Include="..\include\network\connection.h" />
<ClInclude Include="..\include\network\init.h" />
<ClInclude Include="..\include\network\lobby.h" />
<ClInclude Include="..\include\network\message.h" />
<ClInclude Include="..\include\network\message_handler.h" />
<ClInclude Include="..\include\network\message_types.h" />
<ClInclude Include="..\include\network\sequence.h" />
<ClInclude Include="..\include\network\server.h" />
<ClInclude Include="..\include\network\time.h" />
<ClInclude Include="..\include\network\transport.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\source\address.cpp" />
<ClCompile Include="..\source\client.cpp" />
<ClCompile Include="..\source\connection.cpp" />
<ClCompile Include="..\source\init.cpp" />
<ClCompile Include="..\source\lobby.cpp" />
<ClCompile Include="..\source\message.cpp" />
<ClCompile Include="..\source\message_handler.cpp" />
<ClCompile Include="..\source\sequence.cpp" />
<ClCompile Include="..\source\server.cpp" />
<ClCompile Include="..\source\time.cpp" />
<ClCompile Include="..\source\transport.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\include\network.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\network\address.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\network\client.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\network\connection.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\network\init.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\network\message.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\network\message_handler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\network\message_types.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\network\sequence.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\network\server.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\network\time.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\network\transport.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\network\lobby.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\source\address.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\source\client.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\source\connection.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\source\init.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\source\message.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\source\message_handler.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\source\sequence.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\source\server.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\source\time.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\source\transport.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\source\lobby.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>
+225
View File
@@ -0,0 +1,225 @@
#include <network/address.h>
#include <cstring>
#include <sstream>
#ifdef _MSC_VER
#include <WinSock2.h>
#include <ws2def.h>
#include <ws2ipdef.h>
#include <WS2tcpip.h>
#elif defined(__GNUC__)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#endif
namespace net {
Address::Address() : type(AT_INVALID) {
}
Address::Address(int ip4, int port)
: type(AT_IPv4), adr4(ip4), port(port) {
}
Address::Address(uint8_t* ip6, int port)
: type(AT_IPv6), port(port) {
memcpy(adr6, ip6, 16);
}
bool Address::operator<(const Address& other) const {
if(type != other.type)
return type < other.type;
if(type == AT_IPv4) {
if(adr4 < other.adr4)
return true;
else if(adr4 == other.adr4)
return port < other.port;
return false;
}
else if(type == AT_IPv6) {
int lt = memcmp(&adr6, &other.adr6, sizeof(adr6));
if(lt < 0)
return true;
if(lt == 0)
return port < other.port;
return false;
}
else { //All invalid addresses are equal
return false;
}
}
bool Address::operator==(const Address& other) const {
if(type != other.type)
return false;
if(type == AT_IPv4) {
return port == other.port && adr4 == other.adr4;
}
else if(type == AT_IPv6) {
return port == other.port && memcmp(&adr6, &other.adr6, sizeof(adr6)) == 0;
}
else { //All invalid addresses are equal
return true;
}
}
bool Address::ipEquals(const Address& other) const {
if(type != other.type)
return false;
if(type == AT_IPv4)
return adr4 == other.adr4;
else if(type == AT_IPv6)
return memcmp(&adr6, &other.adr6, sizeof(adr6)) == 0;
else
return false;
}
struct addrinfo* lookup(const std::string& hostname, int port, AddressType type) {
struct addrinfo* res;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
switch(type) {
case AT_IPv4: hints.ai_family = AF_INET; break;
case AT_IPv6: hints.ai_family = AF_INET6; break;
default: hints.ai_family = AF_UNSPEC; break;
}
char strport[64];
#ifndef _MSC_VER
snprintf(strport, 64, "%d", port);
#else
_snprintf(strport, 64, "%d", port);
#endif
int code = 0;
if(hostname.empty()) {
hints.ai_flags = AI_PASSIVE;
code = getaddrinfo(0, strport, &hints, &res);
}
else {
code = getaddrinfo(hostname.c_str(), strport, &hints, &res);
}
if(code != 0) {
fprintf(stderr, "getaddrinfo failed: %s\n", gai_strerror(code));
return 0;
}
return res;
}
Address::Address(const std::string& Hostname, int Port, AddressType Type) : type(AT_INVALID) {
struct addrinfo* res, *head;
res = head = lookup(Hostname, Port, Type);
if(!res) {
fprintf(stderr, "ERROR: Could not resolve hostname \"%s\".\n", Hostname.c_str());
return;
}
from_sockaddr(*(sockaddr_storage*)res->ai_addr);
freeaddrinfo(head);
}
std::string Address::toString(bool showPort) const {
std::stringstream out;
switch(type) {
case AT_IPv4: {
struct in_addr adr;
adr.s_addr = adr4;
#ifndef _MSC_VER
char buf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &adr, buf, INET_ADDRSTRLEN);
out << buf;
#else
out << inet_ntoa(adr);
#endif
if(showPort) {
out << ":";
out << port;
}
} break;
case AT_IPv6: {
if(showPort)
out << "[";
#ifndef _MSC_VER
char buf[INET6_ADDRSTRLEN];
struct in6_addr adr;
memcpy(&adr, adr6, 16);
inet_ntop(AF_INET6, &adr, buf, INET6_ADDRSTRLEN);
out << buf;
#else
//TODO: Actually support this, inet_ntop not reliable on Windows XP
out << "IPv6";
#endif
if(showPort) {
out << "]:";
out << port;
}
} break;
}
return out.str();
}
void Address::to_sockaddr(sockaddr_storage& adr, socklen_t* size) const {
switch(type) {
case AT_IPv4: {
sockaddr_in* st = (sockaddr_in*)&adr;
st->sin_family = AF_INET;
st->sin_port = htons(port);
st->sin_addr.s_addr = adr4;
if(size)
*size = sizeof(sockaddr_in);
} break;
case AT_IPv6: {
sockaddr_in6* st = (sockaddr_in6*)&adr;
st->sin6_family = AF_INET6;
st->sin6_flowinfo = 0;
st->sin6_port = htons(port);
st->sin6_scope_id = 0;
memcpy(&st->sin6_addr, adr6, 16);
if(size)
*size = sizeof(sockaddr_in6);
} break;
}
}
void Address::from_sockaddr(sockaddr_storage& adr) {
switch(adr.ss_family) {
case AF_INET: {
sockaddr_in* ad = (sockaddr_in*)&adr;
type = AT_IPv4;
adr4 = ad->sin_addr.s_addr;
port = ntohs(ad->sin_port);
} break;
case AF_INET6: {
sockaddr_in6* ad = (sockaddr_in6*)&adr;
type = AT_IPv6;
memcpy(adr6, &(ad->sin6_addr), 16);
port = ntohs(ad->sin6_port);
} break;
default:
fprintf(stderr, "ERROR: Invalid hostname family detected.\n");
break;
}
}
};
+209
View File
@@ -0,0 +1,209 @@
#include <network/client.h>
#include <network/init.h>
namespace net {
Client::Client(Address connectTo, bool makeConnection)
: trans(new Transport(connectTo.type)), conn(0), address(connectTo),
established(false), resolved(true), hasConnection(makeConnection) {
if(makeConnection) {
conn = new Connection(*trans, address);
Message cmsg(MT_Connect, MF_Reliable);
*this << cmsg;
}
if(trans->active)
addTransport(trans);
}
Client::Client(const std::string& hostname, int port, bool makeConnection, AddressType type)
: trans(new Transport(type)), conn(0), hostname(hostname), established(false),
resolved(false), hasConnection(makeConnection) {
address.port = port;
}
void Client::resolve() {
threads::Lock lock(handlerMutex);
if(!resolved) {
address = net::Address(hostname, address.port);
if(trans->active)
addTransport(trans);
if(hasConnection) {
conn = new Connection(*trans, address);
resolved = true;
Message cmsg(MT_Connect, MF_Reliable);
*this << cmsg;
}
else {
resolved = true;
}
}
}
Client::~Client() {
stop();
}
void Client::stop() {
if(active) {
if(conn) {
Message dmsg(MT_Disconnect, MF_Reliable);
dmsg << DR_Close;
conn->send(dmsg, false);
}
MessageHandler::stop();
if(conn) {
conn->drop();
conn = 0;
}
}
}
void Client::handle(uint8_t type, Client::clMessageHandler func) {
threads::Lock lock(handlerMutex);
handlers[type] = func;
}
void Client::handleClear(uint8_t type) {
threads::Lock lock(handlerMutex);
handlers.erase(type);
}
Client& Client::operator<<(Message& msg) {
if(!resolved)
resolve();
if(conn)
*conn << msg;
else
trans->send(msg, address);
return *this;
}
void Client::sendPing() {
if(conn)
conn->sendPing();
}
unsigned Client::getLastPing() {
if(conn)
return conn->ping;
return 0;
}
Connection* Client::getConnection() {
return conn;
}
void Client::handleMessage(Transport* transport, Address addr, Message* msg) {
uint8_t type = msg->getType();
if(type == MT_Disconnect)
active = false;
if(conn) {
if(type == MT_Connect)
established = true;
if(!conn->preHandle(*this, msg)) {
MessageHandler::handleMessage(transport, addr, msg);
return;
}
}
clMessageHandler handler = nullptr;
{
threads::Lock lock(handlerMutex);
auto it = handlers.find(type);
if(it != handlers.end())
handler = it->second;
}
if(handler)
handler(*this, *msg);
if(conn)
conn->postHandle(*this, msg);
MessageHandler::handleMessage(transport, addr, msg);
}
bool Client::mainTick() {
//Resolve the address if we have to
if(!resolved)
resolve();
//Do normal message handler stuff
bool received = MessageHandler::mainTick();
//Let the connection process things
if(conn) {
if(!conn->active) {
{
Message* dmsg = new Message(MT_Disconnect);
if(trans->active)
*dmsg << DR_Timeout;
else
*dmsg << DR_Error;
trans->grab();
handleMessage(trans, address, dmsg);
}
active = false;
}
else {
conn->process(*this);
}
}
return received;
}
BroadcastClient::BroadcastClient(int Port, AddressType Type)
: trans(Type), port(Port) {
if(trans.active)
addTransport(&trans);
}
BroadcastClient::~BroadcastClient() {
stop();
}
void BroadcastClient::handle(uint8_t type, BroadcastClient::bcMessageHandler func) {
threads::Lock lock(handlerMutex);
handlers[type] = func;
}
void BroadcastClient::handleClear(uint8_t type) {
threads::Lock lock(handlerMutex);
handlers.erase(type);
}
void BroadcastClient::handleMessage(Transport* transport, Address addr, Message* msg) {
uint8_t type = msg->getType();
bcMessageHandler handler = nullptr;
{
threads::Lock lock(handlerMutex);
auto it = handlers.find(type);
if(it != handlers.end())
handler = it->second;
}
if(handler)
handler(*this, addr, *msg);
MessageHandler::handleMessage(transport, addr, msg);
}
void BroadcastClient::send(Message& msg, Address addr) {
trans.send(msg, addr);
}
void BroadcastClient::broadcast(Message& msg) {
trans.broadcast(msg, port);
}
};
+726
View File
@@ -0,0 +1,726 @@
#include <network/connection.h>
#include <network/sequence.h>
#include <stdlib.h>
#include <algorithm>
const unsigned UDP_Header_IPv4 = 28;
const unsigned UDP_Header_IPv6 = 48;
namespace net {
Connection::Connection(Transport& trans, Address addr)
: references(1), nextOutgoingID(1), nextAck(1), nextSequenceID(1), nextReliablePeriod(0),
pingPongWait(rand() % NET_PING_INTERVAL), pingSent(false),
transport(trans), active(true), address(addr), id(-1), inBytes(0), outBytes(0), availBytes(0),
windowBytes(500000), windowLength(1.0), windowUpdate(1.0), windowUsed(0)
{
transport.grab();
time_now(lastMessageReceived);
time_now(lastProcessTime);
}
Connection::~Connection() {
transport.drop();
}
void Connection::grab() const {
++references;
}
void Connection::drop() const {
if(--references == 0)
delete this;
}
void Connection::queueReliable(Message* msg, bool immediate) {
threads::Lock lock(reliableMutex);
if(immediate)
queuedReliable.push_front(msg);
else
queuedReliable.push_back(msg);
}
OutSequence* Connection::sequence() {
threads::Lock lock(sequenceMutex);
OutSequence* ws = new OutSequence(*this);
ws->id = nextSequenceID++;
outSequences[ws->id] = ws;
return ws;
}
void Connection::queueSeqAck(unsigned short sequenceID, unsigned short messageID) {
threads::Lock lock(ackMutex);
SeqAck ack = {sequenceID, messageID};
queuedSeqAcks.push_back(ack);
}
void Connection::queue(Message* msg) {
threads::Lock lock(msgQueueLock);
#ifdef _DEBUG
if(msg->getFlag(MF_Reliable) && !msg->hasID())
return;
#endif
queuedMessages.push_back(msg);
}
Connection& Connection::operator<<(Message& msg) {
if(msg.getFlag(MF_Reliable))
queueReliable(new Message(msg));
else
queue(new Message(msg));
return *this;
}
bool Connection::split(Message& msg) {
if(msg.size() <= NET_FRAGMENT_LIMIT)
return false;
//Send the message in fragments
unsigned fragId = nextFragmentID++;
msize_t startAt = 0;
msize_t size = msg.size();
unsigned short index = 0;
msize_t maxPerFrag = (NET_FRAGMENT_LIMIT - NET_FRAGMENT_OVERHEAD);
msize_t frags = (size + maxPerFrag - 1) / maxPerFrag;
msize_t bytesPerFrag = (size / frags) + (frags - 1);
if(bytesPerFrag > maxPerFrag)
bytesPerFrag = maxPerFrag;
while(startAt < size) {
msize_t fragSize = size - startAt;
bool lastFragment = true;
if(fragSize > bytesPerFrag) {
fragSize = bytesPerFrag;
lastFragment = false;
}
Message* fragment = new Message(lastFragment ? MT_LastFragment : MT_Fragment, msg.getFlag(MF_Reliable) ? MF_Reliable : 0);
*fragment << fragId;
*fragment << index;
msg.copyTo(*fragment, startAt, startAt+fragSize);
{
threads::Lock lock(reliableMutex);
queueReliable(fragment);
}
startAt += fragSize;
index += 1;
}
//Fragments are reliable if the message is, so the original message no longer needs acked
if(msg.getFlag(MF_Sequenced)) {
threads::Lock lock(sequenceMutex);
auto seq = outSequences.find(msg.getSeqID());
if(seq != outSequences.end())
seq->second->handleAck(msg.getID());
}
else if(msg.getFlag(MF_Reliable)) {
handleAck(msg.getID());
}
return true;
}
bool Connection::send(Message& msg, bool isResend) {
if((isResend || !msg.getFlag(MF_Reliable)) && availBytes < 0)
return false;
if(msg.getFlag(MF_Reliable) && !msg.hasID()) {
threads::Lock lock(reliableMutex);
queueReliable(new Message(msg));
return false;
}
auto attemptSend = [this](int size) -> bool {
if(availBytes < 0)
return false;
availBytes -= size;
return true;
};
if(attemptSend(UDP_Header_IPv4 + msg.size())) {
outBytes += UDP_Header_IPv4 + msg.size();
transport.send(msg, address);
return true;
}
return false;
}
void Connection::process(MessageHandler& handler) {
time now;
time_now(now);
double time;
{
auto ms = time_diff(lastProcessTime, now);
if(ms > 0) {
time_now(lastProcessTime);
availBytes = std::min(availBytes + (int)((double)transport.rate * (double)ms / 1000.0), (int)transport.rate / 10);
}
time = (double)ms / 1000.0;
}
//Check if our transport is still active
if(!transport.active) {
active = false;
return;
}
//Check if the last received message was long ago
if(time_diff(lastMessageReceived, now) > NET_PING_INTERVAL) {
if(pingSent) {
if(time_diff(firstPingSent, now) > NET_PING_TIMEOUT) {
//Disconnect if we don't hear any messages for a while
if(time_diff(lastMessageReceived, now) > NET_PING_TIMEOUT) {
active = false;
return;
}
}
else if(time_diff(lastPingSent, now) > pingPongWait) {
sendPing();
}
}
else {
sendPing();
}
}
//Network window
//1. Check for out of date messages (messages sent at least windowLength seconds ago)
// If any packet was out of date, reduce window bytes by a %
// If no packets are behind, and our window was full, expand the window by a fixed amount.
//2. Transmit any queued acks as bandwidth is available.
//3. Append and transmit any packet that fits in our network window.
//4. Retransmit each front packet we can, moving it to the end, based on estimated available bandwidth.
windowUpdate -= time;
if(windowUpdate <= 0.0) { //Check for out of date packets
windowUpdate = 1.0;
bool congested = false;
unsigned packets = 0, late = 0;
threads::Lock lock(windowMutex);
uint64_t winLen_ms = (uint64_t)(windowLength * 1000.0);
for(auto i = window.begin(), end = window.end(); i != end; ++i) {
auto& packet = *i;
if(!packet.msg->getFlag(MF_Reliable))
continue;
++packets;
auto ms = time_diff(packet.added, now);
if(ms > winLen_ms) {
++late;
packet.added = now;
}
}
if((double)late / (double)(packets + 8) > 0.2)
congested = true;
//Scale our window based on the response
if(congested) {
windowBytes = std::max<size_t>(1000, (size_t)(double(windowBytes) * 0.8));
windowLength = std::min<double>(windowLength + 0.1, 3.0);
}
else {
if(!queuedMessages.empty()) {
windowBytes += 50000;
windowLength = std::max<double>(windowLength * 0.9, 0.5);
}
}
transport.rate = (int)(1.1 * (double)std::max(windowBytes, windowUsed) / windowLength);
}
//Send any acks queued since the last process tick
if(availBytes > 0) {
std::vector<Message*> acks;
auto transmitAck = [&acks](Message& msg) {
acks.push_back(new Message(msg));
};
//Batch acks into small groups and duplicate the message to improve the chances the acks are received
if(!queuedAcks.empty()) {
std::vector<unsigned short> ackIDs;
threads::Lock lock(ackMutex);
auto it = queuedAcks.begin(), end = queuedAcks.end();
unsigned readied = 0;
Message ack(MT_Ack);
for(; it != end; ++it) {
ack << *it;
ackIDs.push_back(*it);
++readied;
if(readied >= 20) {
transmitAck(ack);
readied = 0;
ack.reset();
}
}
if(readied != 0)
transmitAck(ack);
queuedAcks.clear();
}
if(!queuedSeqAcks.empty()) {
std::vector<unsigned> ackIndexes;
unsigned readied = 0;
threads::Lock lock(ackMutex);
Message ack(MT_SeqAck);
for(unsigned i = 0; i < queuedSeqAcks.size(); ++i) {
auto& seqAck = queuedSeqAcks[i];
ack << seqAck.seqID << seqAck.msgID;
ackIndexes.push_back(i);
++readied;
if(readied >= 10) {
transmitAck(ack);
readied = 0;
ack.reset();
}
}
if(readied != 0)
transmitAck(ack);
queuedSeqAcks.clear();
}
if(!acks.empty()) {
threads::Lock lock(windowMutex);
for(unsigned i = 0; i < acks.size(); ++i) {
WindowPacket packet;
packet.msg = new Message(*acks[i]);
packet.added = now;
packet.sent = now;
time_add(packet.sent, -10000);
window.push_front(packet);
windowUsed += packet.msg->size();
}
for(unsigned i = 0; i < acks.size(); ++i) {
WindowPacket packet;
packet.msg = acks[i];
packet.added = now;
packet.sent = now;
time_add(packet.sent, -10000);
window.push_front(packet);
windowUsed += packet.msg->size();
}
}
}
{ //Ask sequences to be queued
threads::Lock lock(sequenceMutex);
//Check for resending outgoing sequenced messages
{
auto it = outSequences.begin(), end = outSequences.end();
while(it != end) {
OutSequence* seq = it->second;
if(seq->closed) {
delete seq;
it = outSequences.erase(it);
continue;
}
while(Message* msg = seq->getNextMessage()) {
//Split the message if necessary
if(!split(*msg))
queue(msg);
else
delete msg;
}
++it;
}
}
//Check for requeueing ingoing sequenced messages
{
auto it = inSequences.begin(), end = inSequences.end();
while(it != end) {
InSequence* seq = it->second;
if(seq->closed) {
delete seq;
it = inSequences.erase(it);
continue;
}
seq->process(handler, now);
++it;
}
}
}
{ //Queue reliables
std::deque<Message*> addQueue;
{
threads::Lock lock(reliableMutex);
while(!queuedReliable.empty() && (unsigned short)(nextOutgoingID - nextAck) <= (unsigned short)0x7D00) {
auto* msg = queuedReliable.front();
queuedReliable.pop_front();
msg->setID(nextOutgoingID++);
if(!split(*msg))
addQueue.push_back(msg);
else
delete msg;
}
}
if(!addQueue.empty()) {
threads::Lock lock(msgQueueLock);
while(!addQueue.empty()) {
queuedMessages.push_front(addQueue.front());
addQueue.pop_front();
}
}
}
{ //If we have available bytes in our window, queue packets. We also transmit them if we have available bytes.
std::deque<WindowPacket> addQueue;
if(!queuedMessages.empty()) {
threads::Lock lock(msgQueueLock);
size_t addedBytes = 0;
while(windowUsed + addedBytes < windowBytes && !queuedMessages.empty()) {
WindowPacket packet;
packet.added = now;
packet.sent = now;
time_add(packet.sent, -10000);
packet.msg = queuedMessages.front();
queuedMessages.pop_front();
addQueue.push_back(packet);
addedBytes += packet.msg->size();
}
}
if(!addQueue.empty()) {
threads::Lock lock(windowMutex);
while(!addQueue.empty()) {
window.push_back(addQueue.front());
windowUsed += window.back().msg->size();
addQueue.pop_front();
}
}
}
//If we have available transmit bytes, resend anything in the window, cycling packets
if(!window.empty()) {
threads::Lock lock(windowMutex);
unsigned count = window.size();
while(availBytes > 0 && count > 0) {
--count;
WindowPacket packet = window.front();
window.pop_front();
if(time_diff(packet.sent, now) > (unsigned)(windowLength * 0.2 * 1000.0)) {
send(*packet.msg, false);
packet.sent = now;
}
if(packet.msg->getFlag(MF_Reliable)) {
window.push_back(packet);
}
else {
windowUsed -= packet.msg->size();
delete packet.msg;
}
}
}
}
bool Connection::shouldHandleID(unsigned short id) {
//Track the last ~32,000 messages we've handled, and remove very old ones as we reach new ids
threads::Lock lock(reliableMutex);
auto old = handledMessages.find(id - 0x8000);
if(old != handledMessages.end())
handledMessages.erase(old);
if(handledMessages.find(id) != handledMessages.end())
return false;
handledMessages.insert(id);
return true;
}
void Connection::sendPing() {
//Send a ping message
Message msg(MT_Ping);
*this << msg;
time_now(lastPingSent);
if(!pingSent)
firstPingSent = lastPingSent;
pingSent = true;
pingPongWait = rand() % NET_PING_INTERVAL;
}
void Connection::handleAck(unsigned short id) {
threads::Lock lock(ackMutex);
if(id == nextAck) {
unorderedAcks.erase(nextAck);
++nextAck;
while(unorderedAcks.find(nextAck) != unorderedAcks.end())
unorderedAcks.erase(nextAck++);
}
else if((unsigned short)(id - nextAck) < (unsigned short)0x8000) {
unorderedAcks.insert(id);
}
}
bool Connection::preHandle(MessageHandler& handler, Message* msg) {
time_now(lastMessageReceived);
inBytes += UDP_Header_IPv4 + msg->size();
switch(msg->getType()) {
case MT_Ack: {
//Remove the acked message from the queue
std::vector<unsigned short> ids;
{
threads::Lock lock(windowMutex);
while(msg->canRead<unsigned short>()) {
unsigned short id;
*msg >> id;
ids.push_back(id);
for(auto i = window.begin(), end = window.end(); i != end; ++i) {
auto* msg = i->msg;
if(msg->getFlag(MF_Reliable) && !msg->getFlag(MF_Sequenced) && msg->getID() == id) {
windowUsed -= msg->size();
delete msg;
window.erase(i);
break;
}
}
}
}
if(!ids.empty()) {
threads::Lock lock(ackMutex);
for(unsigned i = 0; i < ids.size(); ++i) {
unsigned short id = ids[i];
if(id == nextAck) {
unorderedAcks.erase(id);
++nextAck;
while(unorderedAcks.find(nextAck) != unorderedAcks.end())
unorderedAcks.erase(nextAck++);
}
else if((unsigned short)(id - nextAck) < (unsigned short)0x8000) {
unorderedAcks.insert(id);
}
}
}
} return false;
case MT_Ping: {
Message pong(MT_Pong);
*this << pong;
} return true;
case MT_Pong: {
pingSent = false;
ping = (unsigned)time_diff(lastPingSent, lastMessageReceived);
} return true;
case MT_SeqAck: {
std::unordered_set<unsigned> sacks;
{
threads::Lock lock(sequenceMutex);
while(msg->canRead<unsigned short>()) {
unsigned short seqID, id;
*msg >> seqID;
if(!msg->canRead<unsigned short>())
break;
*msg >> id;
sacks.insert((unsigned)seqID << 16 | (unsigned)id);
auto it = outSequences.find(seqID);
if(it != outSequences.end())
it->second->handleAck(id);
}
}
threads::Lock lock(windowMutex);
for(auto i = window.begin(); i != window.end();) {
auto* msg = i->msg;
if(msg->getFlag(MF_Reliable) && msg->getFlag(MF_Sequenced)) {
if(sacks.find((unsigned)msg->getSeqID() << 16 | (unsigned)msg->getID()) != sacks.end()) {
windowUsed -= msg->size();
delete msg;
i = window.erase(i);
continue;
}
}
++i;
}
} return false;
case MT_LastFragment:
case MT_Fragment: {
{
threads::Lock lock(ackMutex);
queuedAcks.insert(msg->getID());
}
if(shouldHandleID(msg->getID()))
handleFragment(handler, msg);
} return false;
}
if(msg->getFlag(MF_Sequenced)) {
threads::Lock lock(sequenceMutex);
InSequence* seq = 0;
unsigned short seqID = msg->getSeqID();
auto it = inSequences.find(seqID);
if(it == inSequences.end()) {
seq = new InSequence(*this, seqID);
inSequences[seqID] = seq;
}
else {
seq = it->second;
}
if(!seq->preHandle(handler, msg))
return false;
}
else if(msg->getFlag(MF_Reliable)) {
//Queue acks for reliable messages
{
threads::Lock lock(ackMutex);
queuedAcks.insert(msg->getID());
}
//Check if we've already handled this message
if(!shouldHandleID(msg->getID()))
return false;
}
return true;
}
void Connection::handleFragment(MessageHandler& handler, Message* msg) {
unsigned id;
unsigned short fragIndex;
*msg >> id;
*msg >> fragIndex;
{
threads::Lock lock(fragmentMutex);
auto it = waitingFragments.find(id);
//Record this fragmented message
Fragment* frag;
if(it != waitingFragments.end()) {
frag = it->second;
}
else {
frag = new Fragment();
frag->fragmentCount = 0;
waitingFragments[id] = frag;
}
time_now(frag->lastActivity);
//If we received the last fragment, we know
//how many fragments we're waiting for.
if(msg->getType() == MT_LastFragment)
frag->fragmentCount = fragIndex + 1;
//Save the fragment
if(fragIndex >= frag->received.size()) {
size_t prev = frag->received.size();
frag->received.resize(fragIndex + 1);
for(size_t i = prev; i < fragIndex; ++i)
frag->received[i] = 0;
}
frag->received[fragIndex] = new Message(*msg);
//Check if we have every fragment
if(frag->fragmentCount > 0) {
bool completed = true;
for(size_t i = 0, cnt = frag->received.size(); i < cnt; ++i) {
if(!frag->received[i]) {
completed = false;
break;
}
}
if(completed) {
//Reconstruct the message
char* pBytes; msize_t size;
frag->received[0]->getAsPacket(pBytes, size);
Message* recMsg = new Message((uint8_t*)(pBytes+NET_FRAGMENT_OVERHEAD), size-NET_FRAGMENT_OVERHEAD);
for(size_t i = 1, cnt = frag->received.size(); i < cnt; ++i)
frag->received[i]->copyTo(*recMsg, NET_FRAGMENT_OVERHEAD);
//printf("Fragment reconstructed message %d:%d\n", recMsg->getSeqID(), recMsg->getID());
//Send reconstructed message into handler
handler.queueMessage(&transport, address, recMsg);
//Clean up the stored fragment
if(it == waitingFragments.end())
it = waitingFragments.find(id);
waitingFragments.erase(it);
for(size_t i = 0, cnt = frag->received.size(); i < cnt; ++i)
delete frag->received[i];
delete frag;
}
}
}
}
void Connection::postHandle(MessageHandler& handler, Message* msg) {
if(msg->getFlag(MF_Sequenced)) {
threads::Lock lock(sequenceMutex);
auto it = inSequences.find(msg->getSeqID());
if(it != inSequences.end())
it->second->postHandle(handler, msg);
}
}
void Connection::getTraffic(unsigned& in_bytes, unsigned& out_bytes, unsigned& queuedPackets) {
in_bytes = inBytes;
inBytes = 0;
out_bytes = outBytes;
outBytes = 0;
queuedPackets = queuedReliable.size() + queuedAcks.size() + queuedSeqAcks.size() + queuedMessages.size();
threads::Lock lock(sequenceMutex);
for(auto i = outSequences.begin(), end = outSequences.end(); i != end; ++i)
queuedPackets += i->second->queuedMessages.size();
}
};
+56
View File
@@ -0,0 +1,56 @@
#include <network/init.h>
#include "threads.h"
#ifdef _MSC_VER
#include <WinSock2.h>
#include <ws2def.h>
#include <ws2ipdef.h>
#include <WS2tcpip.h>
#endif
namespace net {
netErrorCallback errorCallback = nullptr;
void setErrorCallback(netErrorCallback cb) {
errorCallback = cb;
}
void netError(const char* err, int code) {
if(errorCallback)
errorCallback(err, code);
}
#ifdef _MSC_VER
threads::Mutex ws_mutex;
unsigned ws_usage = 0;
bool prepare() {
threads::Lock lock(ws_mutex);
if(ws_usage == 0) {
WSAData winSockInfo;
int result = WSAStartup(MAKEWORD(2,2), &winSockInfo);
if(result != 0) {
netError("Failed to initialize network", WSAGetLastError());
return false;
}
}
ws_usage += 1;
return true;
}
void clear() {
threads::Lock lock(ws_mutex);
if(--ws_usage == 0)
WSACleanup();
}
#else
bool prepare() {
return true;
}
void clear() {
}
#endif
};
+678
View File
@@ -0,0 +1,678 @@
#include <network/lobby.h>
#include <network/time.h>
#include <time.h>
#include <stdio.h>
#include <stdarg.h>
namespace net {
void log(const char* format, ...) {
#ifdef __GNUC__
va_list ap;
va_start(ap, format);
char line[2048];
vsnprintf(line, 2048, format, ap);
char date[2048];
time_t t;
::time(&t);
struct tm tmp;
localtime_r(&t, &tmp);
strftime(date, 2048, "%d %b %Y %H:%M:%S", &tmp);
printf("[%s] %s\n", date, line);
fflush(stdout);
va_end(ap);
#endif
}
void Game::write(Message& msg) {
msg << name << mod;
msg << players << maxPlayers;
msg << address;
msg << punchPort;
msg.writeSmall(version);
msg.writeBit(started);
msg.writeBit(password);
msg.writeBit(listed);
}
void Game::read(Message& msg) {
msg >> name >> mod;
msg >> players >> maxPlayers;
msg >> address;
msg >> punchPort;
version = msg.readSmall();
started = msg.readBit();
password = msg.readBit();
listed = msg.readBit();
isLocal = false;
}
struct LobbyFilters {
std::string name, mod;
uint8_t full, started;
};
inline bool filterLobby(Game& desc, LobbyFilters& filters) {
if(!filters.name.empty()) {
if(desc.name.find(filters.name) == std::string::npos)
return false;
}
if(!filters.mod.empty()) {
if(desc.mod.find(filters.mod) == std::string::npos)
return false;
}
switch(filters.full) {
case LFM_True:
if(desc.players != desc.maxPlayers)
return false;
break;
case LFM_False:
if(desc.players == desc.maxPlayers)
return false;
break;
}
switch(filters.started) {
case LFM_True:
if(!desc.started)
return false;
break;
case LFM_False:
if(desc.started)
return false;
break;
}
return true;
}
threads::threadreturn threadcall _heartbeatLoop(void* data) {
LobbyHeartbeat* beat = (LobbyHeartbeat*)data;
time timer, now;
time_now(now);
timer = now;
while(beat->active) {
time_now(now);
if(time_diff(timer, now) >= beat->interval) {
beat->heartbeat();
timer = now;
}
threads::sleep(10);
}
beat->running.signalDown();
return 0;
}
threads::threadreturn threadcall _identifyLoop(void* data) {
LobbyHeartbeat* beat = (LobbyHeartbeat*)data;
beat->identRequest();
time timer, now;
time_now(now);
timer = now;
while(beat->active && !beat->identified) {
time_now(now);
if(time_diff(timer, now) >= beat->interval) {
beat->identRequest();
timer = now;
}
threads::sleep(100);
}
beat->running.signalDown();
return 0;
}
LobbyHeartbeat::LobbyHeartbeat(Address serverAddress, int Port)
: client(serverAddress, false), broadcastPort(Port), identified(false), externalIP(0),
externalPort(0), interval(NET_LOBBY_HEARTBEAT_INTERVAL), fullCounter(0), active(true) {
broadcast.genHandle(LM_Query, [this](Server& srv, Transport& trans, Address addr, Message& mess) {
//Read filters
LobbyFilters filters;
mess >> filters.name >> filters.mod;
mess >> filters.full >> filters.started;
//Apply filters
if(!filterLobby(*this, filters))
return;
//Send result to requester
Message msg(LM_Result);
write(msg);
trans.send(msg, addr);
});
client.handle(LM_Identify, [this](net::Client& cl, Message& msg) {
identified = true;
unsigned char type = 0;
msg >> type;
if(type == net::AT_IPv4) {
msg >> externalIP;
msg >> externalPort;
}
if(msg.hasError())
externalIP = 0;
});
}
void LobbyHeartbeat::enablePunchthrough(Server* server) {
if(punchPort < 0)
punchPort = 0;
server->addTransport(client.trans);
client.clearTransports();
//Punchthrough messages should be redirected to open the port
server->genHandle(MT_Punchthrough, [this](Server& srv, Transport& trans, Address adr, Message& msg) {
if(&trans != client.trans)
return;
net::Address punchTo;
msg >> punchTo;
net::Message reply(MT_Punchthrough);
trans.send(reply, punchTo);
});
server->genHandle(LM_Identify, [this](Server& srv, Transport& trans, Address adr, Message& msg) {
identified = true;
unsigned char type = 0;
msg >> type;
if(type == net::AT_IPv4) {
msg >> externalIP;
msg >> externalPort;
}
if(msg.hasError())
externalIP = 0;
});
}
void LobbyHeartbeat::run(bool doHeartbeat, bool doBroadcast) {
if(doHeartbeat) {
heartbeat();
running.signalUp();
threads::createThread(_heartbeatLoop, this);
}
if(doBroadcast && broadcastPort != -1) {
broadcast.listen(broadcastPort, "", true);
broadcast.runThreads(1);
}
running.signalUp();
threads::createThread(_identifyLoop, this);
client.runThreads(1);
}
void LobbyHeartbeat::stop() {
if(active) {
active = false;
running.wait(0);
client.stop();
broadcast.stop();
}
}
void LobbyHeartbeat::heartbeat() {
Message msg(LM_Heartbeat);
//Every 4 messages, send a full heartbeat
if(fullCounter == 0) {
msg.write1();
write(msg);
}
else {
msg.write0();
}
client << msg;
fullCounter = (fullCounter + 1) % 4;
}
void LobbyHeartbeat::identRequest() {
Message msg(LM_Identify);
client << msg;
}
LobbyHeartbeat::~LobbyHeartbeat() {
stop();
}
threads::threadreturn threadcall _punchthroughLoop(void* data) {
LobbyPunchthrough* punch = (LobbyPunchthrough*)data;
time timer, now;
time_now(now);
timer = now;
while(punch->active) {
time_now(now);
if(time_diff(timer, now) >= punch->interval) {
punch->heartbeat();
timer = now;
}
threads::sleep(10);
}
punch->running.signalDown();
return 0;
}
LobbyPunchthrough::LobbyPunchthrough(Address serverAddress, Client* cl)
: interval(250), lobbyAddress(serverAddress),
active(true), client(cl), established(false) {
heartbeat();
running.signalUp();
threads::createThread(_punchthroughLoop, this);
}
void LobbyPunchthrough::stop() {
active = false;
running.wait(0);
}
void LobbyPunchthrough::heartbeat() {
if(client) {
if(!client->established && client->active) {
net::Message req(MT_Punchthrough);
net::Address reqAddr = client->address;
req << reqAddr;
client->trans->send(req, lobbyAddress);
}
else {
active = false;
}
}
}
LobbyPunchthrough::~LobbyPunchthrough() {
stop();
}
threads::threadreturn threadcall _serverLoop(void* data) {
LobbyServer* srv = (LobbyServer*)data;
time now;
while(srv->active) {
time_now(now);
//Deactivate if the server goes down
if(!srv->server.active) {
srv->active = false;
break;
}
//Prune all timed out games
{
threads::WriteLock lock(srv->mutex);
auto it = srv->games.begin(), end = srv->games.end();
while(it != end) {
LobbyServer::GameDesc& desc = it->second;
if(time_diff(desc.lastHeartbeat, now) > NET_LOBBY_HEARTBEAT_TIMEOUT) {
if(srv->logging)
log("[DD] Server \"%s\" timed out from %s. (comm port %d)",
desc.name.c_str(), desc.address.toString().c_str(), it->first.port);
it = srv->games.erase(it);
}
else
++it;
}
}
threads::sleep(500);
}
srv->running.signalDown();
return 0;
}
LobbyServer::LobbyServer(int port, const std::string& address, bool Logging)
: server(port, address), active(true), logging(Logging) {
if(logging)
log("[II] Starting master server on port %d", port);
//Clients are constantly sending heartbeats, we track
//all the active servers. Full heartbeats add timed out servers
//back to the list, normal heartbeats only keep it there
server.genHandle(LM_Heartbeat, [this](Server& srv, Transport& trans, Address addr, Message& msg) {
if(msg.readBit()) {
GameDesc desc;
desc.read(msg);
int port = desc.address.port;
desc.address = addr;
desc.address.port = port;
if(desc.punchPort >= 0)
desc.punchPort = addr.port;
time_now(desc.lastHeartbeat);
bool has = false;
{
threads::ReadLock lock(mutex);
auto it = games.find(addr);
has = (it != games.end());
if(has) {
if(logging) {
if(desc.players != it->second.players)
log(" [PP] Server \"%s\" on %s now has %d players.",
desc.name.c_str(), desc.address.toString().c_str(), desc.players);
}
it->second = desc;
}
}
if(!has) {
threads::WriteLock lock(mutex);
games[addr] = desc;
if(logging)
log("[SS] New server \"%s\" on %s. (comm port %d)",
desc.name.c_str(), desc.address.toString().c_str(), addr.port);
}
}
else {
threads::ReadLock lock(mutex);
auto it = games.find(addr);
if(it != games.end()) {
GameDesc& desc = it->second;
time_now(desc.lastHeartbeat);
if(desc.punchPort >= 0)
desc.punchPort = addr.port;
}
}
});
//Respond to lobby queries from clients
server.genHandle(LM_Query, [this](Server& srv, Transport& trans, Address addr, Message& msg) {
//Read filters
LobbyFilters filters;
msg >> filters.name >> filters.mod;
msg >> filters.full >> filters.started;
Message* mess = 0;
unsigned short num = 0;
msize_t numpos = 0;
unsigned short total = 0;
threads::ReadLock lock(mutex);
auto it = games.begin(), end = games.end();
for(; it != end; ++it) {
LobbyServer::GameDesc& desc = it->second;
//Don't list unlisted games
if(!desc.listed)
continue;
//Apply filters
if(!filterLobby(desc, filters))
continue;
//Create a new message if necessary
if(!mess) {
mess = new Message(LM_Result);
numpos = mess->reserve<unsigned short>();
}
//Send result to client
desc.write(*mess);
++num;
++total;
if(num > 128) {
mess->fill(numpos, num);
trans.send(*mess, addr);
delete mess;
mess = 0;
num = 0;
}
}
if(mess) {
mess->fill(numpos, num);
trans.send(*mess, addr);
delete mess;
}
Message final(LM_Results_End);
final << total;
trans.send(final, addr);
});
//Second stage of punchthrough
server.genHandle(MT_Punchthrough, [this](Server& srv, Transport& trans, Address addr, Message& msg) {
net::Address findAddr;
msg >> findAddr;
{
threads::ReadLock lock(mutex);
auto it = games.find(findAddr);
if(it != games.end()) {
net::Message reply(MT_Punchthrough);
if(it->second.punchPort != -1) {
reply << addr;
net::Address replyAddr = it->first;
trans.send(reply, replyAddr);
}
}
}
});
server.genHandle(LM_Identify, [this](Server& srv, Transport& trans, Address addr, Message& msg) {
Message m(LM_Identify);
m << (unsigned char)addr.type;
if(addr.type == AT_IPv4)
m << (unsigned)addr.adr4;
else
m.writeBits(addr.adr6, 16 * 8);
{
threads::ReadLock lock(mutex);
auto it = games.find(addr);
if(it != games.end()) {
GameDesc& desc = it->second;
if(desc.punchPort == -1)
m << (unsigned short)0;
else
m << (unsigned short)desc.punchPort;
}
else {
m << (unsigned short)0;
}
}
trans.send(m, addr);
});
}
void LobbyServer::listen(int port, const std::string& address) {
server.listen(port, address);
}
void LobbyServer::runThreads(int workerThreads) {
running.signal(1);
threads::createThread(_serverLoop, this);
server.runThreads(workerThreads);
}
void LobbyServer::stop() {
running.wait(0);
server.stop();
}
LobbyServer::~LobbyServer() {
stop();
}
threads::threadreturn threadcall _queryThread(void* data) {
LobbyQuery* query = (LobbyQuery*)data;
time timer, now;
time_now(now);
timer = now;
while(query->active) {
if(query->doUpdate) {
query->update(true);
query->doUpdate = false;
}
time_now(now);
if(time_diff(timer, now) >= NET_LOBBY_QUERY_TIMEOUT) {
if(query->totalFromServer == (unsigned short)-1 || query->receivedFromServer < query->totalFromServer) {
query->update(false);
}
else {
query->updating = false;
}
timer = now;
}
threads::sleep(10);
}
query->running.signalDown();
return 0;
}
LobbyQuery::LobbyQuery(Address addr, int BroadcastPort)
: client(addr, false), broadcast(BroadcastPort, addr.type),
broadcastPort(BroadcastPort), handler(nullptr), receivedFromServer(0), totalFromServer((unsigned short)-1), updating(false), active(true),
queryServer(true), queryBroadcast(BroadcastPort != -1), doUpdate(true) {
bind();
}
LobbyQuery::LobbyQuery(const std::string& hostname, int port, int BroadcastPort, AddressType type)
: client(hostname, port, false, type), broadcast(BroadcastPort, type),
broadcastPort(BroadcastPort), handler(nullptr), receivedFromServer(0), totalFromServer((unsigned short)-1), updating(false), active(true),
queryServer(true), queryBroadcast(BroadcastPort != -1), doUpdate(true), full(LFM_Ignore), started(LFM_Ignore) {
bind();
}
void LobbyQuery::bind() {
client.handle(LM_Result, [this](Client& cl, Message& msg) {
Game game;
unsigned short num;
msg >> num;
receivedFromServer += num;
for(unsigned short i = 0; i < num; ++i) {
game.read(msg);
auto it = handled_lobbies.find(game.address);
if(it == handled_lobbies.end()) {
if(handler)
handler(game);
handled_lobbies.insert(game.address);
}
}
if(receivedFromServer >= totalFromServer)
updating = false;
});
client.handle(LM_Results_End, [this](Client& cl, Message& msg) {
msg >> totalFromServer;
if(receivedFromServer >= totalFromServer)
updating = false;
});
broadcast.handle(LM_Result, [this](BroadcastClient& cl, Address addr, Message& msg) {
Game game;
game.read(msg);
int port = game.address.port;
game.address = addr;
game.address.port = port;
game.punchPort = -1;
game.isLocal = true;
auto it = handled_lobbies.find(game.address);
if(it == handled_lobbies.end()) {
if(handler)
handler(game);
handled_lobbies.insert(game.address);
}
});
client.runThreads(1);
running.signalUp();
threads::createThread(_queryThread, this);
if(broadcastPort != -1)
broadcast.runThreads(1);
}
void LobbyQuery::refresh(bool doQuery, bool doBroadcast) {
queryServer = doQuery;
queryBroadcast = doBroadcast;
doUpdate = true;
updating = true;
totalFromServer = (unsigned short)-1;
}
void LobbyQuery::update(bool clear) {
if(clear)
handled_lobbies.clear();
//Build query
Message msg(LM_Query);
msg << name << mod;
msg << (uint8_t)full;
msg << (uint8_t)started;
//Query the lobby server
if(queryServer) {
client << msg;
receivedFromServer = 0;
totalFromServer = -1;
updating = true;
}
//Query on broadcast
if(queryBroadcast && broadcastPort != -1)
broadcast.broadcast(msg);
}
void LobbyQuery::stop() {
client.stop();
updating = false;
active = false;
running.wait(0);
if(broadcastPort != -1)
broadcast.stop();
}
LobbyQuery::~LobbyQuery() {
stop();
}
};
File diff suppressed because it is too large Load Diff
+219
View File
@@ -0,0 +1,219 @@
#ifdef __GNUC__
#include <sys/socket.h>
#include <sys/select.h>
#elif defined(_MSC_VER)
#include <WS2tcpip.h>
#endif
#include <network/message_handler.h>
#include <network/init.h>
namespace net {
threads::threadreturn threadcall _mainLoop(void* data) {
MessageHandler* handler = (MessageHandler*)data;
if(handler->threadInit)
handler->threadInit(true);
while(handler->active) {
if(!handler->mainTick())
threads::sleep(NET_IDLE_SLEEP);
}
if(handler->threadExit)
handler->threadExit(true);
handler->threadsRunning.signalDown();
return 0;
}
threads::threadreturn threadcall _queueLoop(void* data) {
MessageHandler* handler = (MessageHandler*)data;
if(handler->threadInit)
handler->threadInit(false);
while(handler->active) {
if(!handler->queueTick())
threads::sleep(NET_IDLE_SLEEP);
}
if(handler->threadExit)
handler->threadExit(false);
handler->threadsRunning.signalDown();
return 0;
}
MessageHandler::MessageHandler()
: active(true) {
}
void MessageHandler::queueMessage(Transport* transport, Address addr, Message* msg) {
QueuedMessage q;
q.transport = transport;
q.addr = addr;
q.msg = msg;
transport->grab();
{
threads::Lock lock(queueMutex);
messageQueue.push(q);
}
}
void MessageHandler::handleMessage(Transport* transport, Address addr, Message* msg) {
delete msg;
transport->drop();
}
bool MessageHandler::queueTick() {
if(messageQueue.empty())
return false;
queueMutex.lock();
if(messageQueue.empty()) {
queueMutex.release();
return false;
}
QueuedMessage q = messageQueue.front();
messageQueue.pop();
queueMutex.release();
handleMessage(q.transport, q.addr, q.msg);
return true;
}
void MessageHandler::addTransport(Transport* transport) {
threads::Lock lock(transportMutex);
transport->grab();
transports.push_back(transport);
}
void MessageHandler::clearTransports() {
threads::Lock lock(transportMutex);
for(auto it = transports.begin(); it != transports.end(); ++it)
(*it)->drop();
transports.clear();
}
bool MessageHandler::mainTick() {
struct timeval timeout;
bool received = false;
//Populate the fd_set forselect
fd_set polling;
int nfds = 0;
FD_ZERO(&polling);
{
threads::Lock lock(transportMutex);
if(transports.empty())
return false;
for(auto it = transports.begin(), end = transports.end(); it != end;) {
Transport* trans = *it;
if(!trans->active) {
it = transports.erase(it);
end = transports.end();
trans->drop();
if(transports.empty())
active = false;
}
else {
//Process the transport
trans->process();
//Add the transport to the fd set
int fd = trans->sockfd;
FD_SET(fd, &polling);
if(fd >= nfds)
nfds = fd + 1;
++it;
}
}
}
//Calculate the timeout
timeout.tv_sec = NET_SELECT_TIMEOUT / 1000;
timeout.tv_usec = (NET_SELECT_TIMEOUT % 1000) * 1000;
int ready = select(nfds, &polling, 0, 0, &timeout);
//Intercept errors
if(ready < 0) {
#ifdef __GNUC__
perror("Select error");
#else
netError("Socket polling failed: ", WSAGetLastError());
#endif
active = false;
return true;
}
//Skip the rest if no transports are ready
if(ready == 0)
return false;
//Read all messages that should be received
{
threads::Lock lock(transportMutex);
for(auto it = transports.begin(), end = transports.end(); it != end; ++it) {
Transport* trans = *it;
Message msg;
Address adr;
while(trans->receive(msg, adr)) {
Message* qmsg = new Message();
msg.move(*qmsg);
queueMessage(trans, adr, qmsg);
received = true;
}
}
}
return received;
}
void MessageHandler::runThreads(int workerThreads) {
if(workerThreads <= 0)
throw "Threaded server needs at least one worker thread";
threadsRunning.signal(workerThreads + 1);
//Create a thread for the main connection loop
threads::createThread(_mainLoop, this);
//Create worker threads
for(int i = 0; i < workerThreads; ++i)
threads::createThread(_queueLoop, this);
}
void MessageHandler::stop() {
if(active) {
active = false;
threadsRunning.wait(0);
{
threads::Lock lock(transportMutex);
for(auto it = transports.begin(), end = transports.end(); it != end; ++it) {
(*it)->close();
(*it)->drop();
}
transports.clear();
}
}
}
MessageHandler::~MessageHandler() {
stop();
}
};
+146
View File
@@ -0,0 +1,146 @@
#include <network/sequence.h>
namespace net {
Sequence::Sequence(Connection& conn)
: ws(conn.sequence()) {
}
Sequence::Sequence(Client& client)
: ws(0) {
Connection* conn = client.getConnection();
if(conn)
ws = conn->sequence();
}
Sequence::~Sequence() {
if(ws)
ws->close();
}
Sequence& Sequence::operator<<(Message& msg) {
if(ws)
*ws << msg;
return *this;
}
unsigned short Sequence::id() {
if(!ws)
return -1;
return ws->id;
}
OutSequence::OutSequence(Connection& connection)
: conn(connection), id(-1), nextOutgoingID(1), nextAck(1), resendPeriod(0), closed(false) {
}
OutSequence& OutSequence::operator<<(Message& msg) {
if(!(msg.getFlags() & MF_Sequenced))
throw "Attempted to write non-sequenced message to sequence.";
msg.setSeqID(id);
queue(new Message(msg));
return *this;
}
void OutSequence::queue(Message* msg) {
threads::Lock lock(conn.sequenceMutex);
queuedMessages.push_back(msg);
}
Message* OutSequence::getNextMessage() {
if(queuedMessages.empty())
return nullptr;
if((unsigned short)(nextOutgoingID - nextAck) > (unsigned short)0x7D00)
return nullptr;
threads::Lock lock(conn.sequenceMutex);
auto* msg = queuedMessages.front();
queuedMessages.pop_front();
msg->setID(nextOutgoingID++);
waitingAcks.insert(msg->getID());
return msg;
}
void OutSequence::handleAck(unsigned short num) {
auto it = waitingAcks.find(num);
if(it != waitingAcks.end())
waitingAcks.erase(it);
if(nextAck == num)
++nextAck;
while(nextAck != nextOutgoingID) {
if(waitingAcks.find(nextAck) == waitingAcks.end())
++nextAck;
else
break;
}
}
void OutSequence::close() {
Message msg(MT_Close_Sequence, MF_Sequenced);
msg << id;
*this << msg;
}
InSequence::InSequence(Connection& connection, unsigned short ID)
: conn(connection), id(ID), closed(false), nextHandleID(1), handlingID(0) {
}
bool InSequence::preHandle(MessageHandler& handler, Message* msg) {
unsigned short msgID = msg->getID();
//Acknowledge the message if we haven't done so before
if(!msg->getFlag(MF_Acknowledged)) {
conn.queueSeqAck(id, msgID);
}
//Check if we should handle this message or not
//NOTE: (msgID - nextHandleID) will only work with less than ~SHORT_MAX messages in flight
if((short)(msgID - nextHandleID) < 0|| msgID == handlingID) {
//This message was already handled previously
return false;
}
else if(msgID == nextHandleID) {
//Handle close sequence messages here
if(msg->getType() == MT_Close_Sequence) {
closed = true;
return false;
}
//Yay, we can immediately handle this
handlingID = msgID;
return true;
}
else {
//Boo, we have to queue it
Message* qmsg = new Message();
msg->move(*qmsg);
msg->setFlags(msg->getFlags() | MF_Acknowledged);
unhandledMessages[msgID] = qmsg;
return false;
}
}
void InSequence::postHandle(MessageHandler& handler, Message* msg) {
nextHandleID++;
time now;
time_now(now);
process(handler, now);
}
void InSequence::process(MessageHandler& handler, time& now) {
//Check if we can handle a message from the queue
auto it = unhandledMessages.find(nextHandleID);
if(it != unhandledMessages.end()) {
handler.queueMessage(&conn.transport, conn.address, it->second);
unhandledMessages.erase(it);
}
}
};
+240
View File
@@ -0,0 +1,240 @@
#include <network/server.h>
#ifdef __GNUC__
#include <netdb.h>
#include <sys/socket.h>
#elif defined(_MSC_VER)
#include <WS2tcpip.h>
#endif
namespace net {
Server::Server() : nextConnectionID(1) {
}
Server::Server(int port, const std::string& address, bool broadcast)
: nextConnectionID(1) {
listen(port, address, broadcast);
}
Server::~Server() {
stop();
}
void Server::listen(int port, const std::string& address, bool broadcast) {
struct addrinfo* res, *head;
res = head = lookup(address, port);
if(!res) {
fprintf(stderr, "ERROR: Could not resolve hostname \"%s\".\n", address.c_str());
return;
}
for(; res; res = res->ai_next) {
if(res->ai_family != AF_INET && res->ai_family != AF_INET6)
continue;
Address addr;
addr.from_sockaddr(*(sockaddr_storage*)res->ai_addr);
Transport* transport = new Transport(addr.type);
transport->listen(addr, broadcast);
if(transport->active)
addTransport(transport);
transport->drop();
#ifdef __GNUC__
//TODO: Figure out if this actually works.
break;
#endif
}
freeaddrinfo(head);
}
void Server::connHandle(uint8_t type, Server::connMessageHandler func) {
threads::Lock lock(handlerMutex);
connHandlers[type] = func;
}
void Server::genHandle(uint8_t type, Server::genMessageHandler func) {
threads::Lock lock(handlerMutex);
genHandlers[type] = func;
}
void Server::connHandleClear(uint8_t type) {
threads::Lock lock(handlerMutex);
connHandlers.erase(type);
}
void Server::genHandleClear(uint8_t type) {
threads::Lock lock(handlerMutex);
genHandlers.erase(type);
}
void Server::send(int connId, Message& message) {
if(connId < 0)
sendAll(message);
else
*getConnectionByID(connId) << message;
}
void Server::sendAll(Message& message) {
threads::Lock lock(connMutex);
auto it = connections.begin(), end = connections.end();
for(; it != end; ++it)
*it->second << message;
}
void Server::doAll(connFunction func) {
if(!func)
return;
threads::Lock lock(connMutex);
auto it = connections.begin(), end = connections.end();
for(; it != end; ++it)
func(*it->second);
}
void Server::pingAll() {
threads::Lock lock(connMutex);
auto it = connections.begin(), end = connections.end();
for(; it != end; ++it)
it->second->sendPing();
}
Connection* Server::getConnectionByID(int id) {
threads::Lock lock(connMutex);
auto it = connectionIDs.find(id);
if(it == connectionIDs.end())
return 0;
it->second->grab();
return it->second;
}
void Server::kick(Connection& conn, DisconnectReason reason) {
net::Message msg(MT_Disconnect);
msg << reason;
conn.send(msg, false);
queueMessage(&conn.transport, conn.address, new Message(msg));
}
void Server::handleMessage(Transport* transport, Address addr, Message* msg) {
//Find the connection that this address belongs to
Connection* conn = 0;
{
threads::Lock lock(connMutex);
auto it = connections.find(addr);
if(it != connections.end()) {
conn = it->second;
conn->grab();
}
}
//Handle the message
uint8_t type = msg->getType();
switch(type) {
case MT_Disconnect:
if(conn) {
threads::Lock lock(connMutex);
connections.erase(addr);
connectionIDs.erase(conn->id);
conn->active = false;
conn->drop();
}
break;
case MT_Connect:
if(!conn) {
conn = new Connection(*transport, addr);
conn->id = nextConnectionID++;
conn->grab();
threads::Lock lock(connMutex);
connections[addr] = conn;
connectionIDs[conn->id] = conn;
{
Message response(MT_Connect, MF_Reliable);
*conn << response;
}
}
break;
}
//Send the message to the server handlers
if(conn) {
if(conn->preHandle(*this, msg)) {
connMessageHandler handler = nullptr;
{
threads::Lock lock(handlerMutex);
auto it = connHandlers.find(type);
if(it != connHandlers.end())
handler = it->second;
}
if(handler)
handler(*this, *conn, *msg);
conn->postHandle(*this, msg);
}
conn->drop();
}
else {
genMessageHandler handler = nullptr;
{
threads::Lock lock(handlerMutex);
auto it = genHandlers.find(type);
if(it != genHandlers.end())
handler = it->second;
}
if(handler)
handler(*this, *transport, addr, *msg);
}
MessageHandler::handleMessage(transport, addr, msg);
}
bool Server::mainTick() {
//Do normal message handler stuff
bool received = MessageHandler::mainTick();
//Let the connections process things
{
threads::Lock lock(connMutex);
for(auto it = connections.begin(), end = connections.end(); it != end; ++it) {
Connection* conn = it->second;
if(!conn->active) {
Message* msg = new Message(MT_Disconnect);
if(conn->transport.active)
*msg << DR_Timeout;
else
*msg << DR_Error;
queueMessage(&conn->transport, conn->address, msg);
}
else {
conn->process(*this);
}
}
}
return received;
}
void Server::stop() {
if(active) {
MessageHandler::stop();
{
threads::Lock lock(connMutex);
for(auto it = connections.begin(), end = connections.end(); it != end; ++it)
it->second->drop();
connections.clear();
}
}
}
};
+32
View File
@@ -0,0 +1,32 @@
#include <network/time.h>
#ifdef _MSC_VER
#include <Windows.h>
#endif
namespace net {
#ifdef _MSC_VER
void time_now(time& tm) {
tm = timeGetTime();
}
#elif defined(__GNUC__)
void time_now(time& tm) {
timeval tv;
gettimeofday(&tv, 0);
uint64_t value = 0;
value += tv.tv_sec * 1000;
value += tv.tv_usec / 1000;
tm = (time)value;
}
#endif
uint64_t time_diff(time& from, time& to) {
return to - from;
}
void time_add(time& base, int64_t add_ms) {
base += add_ms;
}
};
+319
View File
@@ -0,0 +1,319 @@
#include <network/transport.h>
#include <network/init.h>
#ifdef _MSC_VER
#include <WinSock2.h>
#include <ws2def.h>
#include <ws2ipdef.h>
#include <WS2tcpip.h>
#include <time.h>
#elif defined(__GNUC__)
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <unistd.h>
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#endif
#ifdef __APPLE__
#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
#endif
namespace net {
#ifdef _MSC_VER
static char zero_arg = 0;
static char true_arg = 1;
#else
static int zero_arg = 0;
static int true_arg = 1;
#endif
uint8_t IPV6_MCAST_ALL_NODES[]
= {0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
int Transport::RATE_LIMIT = 1024*250;
Transport::Transport(AddressType Type)
: references(1), active(true), canBroadcast(false), type(Type), rate(RATE_LIMIT)
{
net::prepare();
int ai_family = (type == AT_IPv4) ? AF_INET : AF_INET6;
sockfd = socket(ai_family, SOCK_DGRAM, IPPROTO_UDP);
#ifdef __GNUC__
ioctl(sockfd, FIONBIO, &true_arg);
#elif defined(_MSC_VER)
if(sockfd == INVALID_SOCKET) {
netError("Failed to bind a socket", WSAGetLastError());
close();
return;
}
int result = ioctlsocket(sockfd, FIONBIO, (u_long*)&true_arg);
if(result != 0) {
netError("Failed to set socket state", WSAGetLastError());
close();
return;
}
#endif
}
void Transport::grab() const {
++references;
}
void Transport::drop() const {
if(--references == 0)
delete this;
}
Transport::~Transport() {
close();
}
void Transport::close() {
if(active) {
if(sockfd != INVALID_SOCKET) {
#ifdef _MSC_VER
closesocket(sockfd);
#else
::close(sockfd);
#endif
sockfd = INVALID_SOCKET;
}
active = false;
net::clear();
}
}
void Transport::listen(Address& address, bool rcvBroadcast) {
if(!active)
return;
canBroadcast = rcvBroadcast;
sockaddr_storage saddr;
socklen_t len;
address.to_sockaddr(saddr, &len);
int result = bind(sockfd, (sockaddr*)&saddr, len);
if(result != 0) {
active = false;
#ifdef _MSC_VER
closesocket(sockfd);
netError("Error binding to socket", WSAGetLastError());
#else
::close(sockfd);
perror("Error binding socket");
#endif
}
if(canBroadcast) {
if(type == AT_IPv4) {
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &true_arg, sizeof(true_arg));
}
else {
//TODO: We gotta test ipv6 somehow
ipv6_mreq mcast;
memcpy(&mcast.ipv6mr_multiaddr, IPV6_MCAST_ALL_NODES, 16);
mcast.ipv6mr_interface = 0;
setsockopt(sockfd, SOL_SOCKET, IPV6_ADD_MEMBERSHIP, (const char*)&mcast, sizeof(mcast));
setsockopt(sockfd, SOL_SOCKET, IPV6_MULTICAST_IF, &zero_arg, sizeof(zero_arg));
}
}
}
void Transport::process() {
if(queuedSends.empty() && queuedBroadcasts.empty())
return;
threads::Lock queueLock(queueMutex);
while(!queuedSends.empty()) {
Message* msg = queuedSends.front().first;
Address& adr = queuedSends.front().second;
if(send(*msg, adr, false)) {
delete msg;
queuedSends.pop();
}
else
break;
}
while(!queuedBroadcasts.empty()) {
Message* msg = queuedBroadcasts.front().first;
int port = queuedBroadcasts.front().second;
if(broadcast(*msg, port, false)) {
delete msg;
queuedBroadcasts.pop();
}
else
break;
}
}
bool Transport::send(Message& msg, Address& address, bool queue) {
if(!active)
return false;
sockaddr_storage saddr;
socklen_t len;
address.to_sockaddr(saddr, &len);
char* pBytes; msize_t byteCount;
msg.finalize();
msg.getAsPacket(pBytes, byteCount);
int bytes = sendto(sockfd, pBytes, byteCount, 0, (sockaddr*)&saddr, len);
if(bytes == SOCKET_ERROR) {
#ifdef _MSC_VER
if(WSAGetLastError() == WSAEWOULDBLOCK) {
#else
if(errno == EAGAIN) {
#endif
if(queue) {
threads::Lock queueLock(queueMutex);
Message* q = new Message(msg);
queuedSends.push(std::pair<Message*,Address>(q, address));
}
return false;
}
#ifdef _MSC_VER
netError("Socket write failed", WSAGetLastError());
#else
perror("Error writing to socket");
#endif
close();
return false;
}
return true;
}
bool Transport::broadcast(Message& msg, int port, bool queue) {
if(!active)
return false;
if(!canBroadcast) {
if(type == AT_IPv4)
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &true_arg, sizeof(true_arg));
else
setsockopt(sockfd, SOL_SOCKET, IPV6_MULTICAST_IF, &zero_arg, sizeof(zero_arg));
canBroadcast = true;
}
sockaddr_storage saddr;
socklen_t len;
switch(type) {
case AT_IPv4: {
sockaddr_in* st = (sockaddr_in*)&saddr;
st->sin_family = AF_INET;
st->sin_port = htons(port);
st->sin_addr.s_addr = INADDR_BROADCAST;
len = sizeof(sockaddr_in);
} break;
case AT_IPv6: {
sockaddr_in6* st = (sockaddr_in6*)&saddr;
st->sin6_family = AF_INET6;
st->sin6_flowinfo = 0;
st->sin6_port = htons(port);
st->sin6_scope_id = 0;
memcpy(&st->sin6_addr, IPV6_MCAST_ALL_NODES, 16);
len = sizeof(sockaddr_in6);
} break;
#ifdef _MSC_VER
default:
__assume(0);
#elif defined(__GNUC__)
default:
__builtin_unreachable();
#endif
}
char* pBytes; msize_t byteCount;
msg.finalize();
msg.getAsPacket(pBytes, byteCount);
int bytes = sendto(sockfd, pBytes, byteCount, 0, (sockaddr*)&saddr, len);
if(bytes == SOCKET_ERROR) {
#ifdef _MSC_VER
if(WSAGetLastError() == WSAEWOULDBLOCK) {
#else
if(errno == EAGAIN) {
#endif
if(queue) {
threads::Lock queueLock(queueMutex);
Message* q = new Message(msg);
queuedBroadcasts.push(std::pair<Message*,int>(q, port));
}
return false;
}
#ifdef _MSC_VER
netError("Socket write failed", WSAGetLastError());
#else
perror("Error writing to socket");
#endif
close();
}
return true;
}
bool Transport::receive(Message& msg, Address& adr) {
if(!active)
return false;
char buffer[USHRT_MAX];
sockaddr_storage saddr;
socklen_t len = sizeof(saddr);
int bytes = recvfrom(sockfd, buffer, USHRT_MAX, 0, (sockaddr*)&saddr, &len);
#ifdef _MSC_VER
int error = (bytes == SOCKET_ERROR ? WSAGetLastError() : -1);
#endif
if(bytes > 0) {
msg.setPacket(buffer, bytes);
adr.from_sockaddr(saddr);
return true;
}
#ifdef _MSC_VER
else if(error == WSAEWOULDBLOCK || error == WSAECONNRESET) {
#else
else if(errno == EAGAIN) {
#endif
return false;
}
else {
close();
#ifdef __GNUC__
perror("Error reading from socket");
#else
netError("Error reading from socket", WSAGetLastError());
#endif
return false;
}
}
};
+99
View File
@@ -0,0 +1,99 @@
#include "../include/network/connection.h"
#include "../include/network/transport.h"
#include "../include/network/client.h"
#include "../include/network/sequence.h"
#include "../include/network/lobby.h"
#include <threads.h>
void test_client() {
net::Address adr("localhost", 2048, net::AT_IPv4);
net::Client cl(adr);
cl.handle(net::MT_Application, [](net::Client& cl, net::Message& mess) {
char* str = 0; char num = 0;
mess >> str;
if(mess.readBit())
mess >> num;
printf("Response (%d): %s - %d\n", mess.getID(), str, num);
});
cl.handle(net::MT_Connect, [](net::Client& cl, net::Message& msg) {
printf("Connection to %s\n", cl.address.toString().c_str());
});
cl.handle(net::MT_Disconnect, [](net::Client& cl, net::Message& msg) {
net::DisconnectReason reason;
msg >> reason;
printf("Disconnection from %s (%d)\n", cl.address.toString().c_str(), reason);
});
net::Message msg1(net::MT_Application, net::MF_Sequenced);
msg1 << "Test Test One";
net::Message msg2(net::MT_Application, net::MF_Sequenced);
msg2 << "Test Test Two";
net::Message msg3(net::MT_Application, net::MF_Sequenced);
msg3 << "Test Test Three";
net::Sequence seq(cl);
seq << msg1;
seq << msg2;
seq << msg3;
cl.runThreads(4);
threads::sleep(25000);
}
void test_lobby_heartbeat() {
net::Address adr("localhost", 2044);
net::LobbyHeartbeat beat(adr, 2012);
beat.name = "Test Server";
beat.mod = "Standard";
beat.players = 1;
beat.maxPlayers = 8;
beat.address.port = 2222;
beat.started = false;
beat.run();
threads::sleep(500);
net::LobbyQuery query(adr, 2012);
query.name = "Test";
query.full = net::LFM_False;
query.started = net::LFM_False;
query.handler = [](net::Game& game) {
printf("%s -- %s\n", game.address.toString().c_str(), game.name.c_str());
};
query.refresh();
while(true)
threads::sleep(1000);
}
void test_broadcast_client() {
net::BroadcastClient cl(2012);
net::Message msg(net::MT_Application);
cl.handle(net::MT_Application + 1, [](net::BroadcastClient& cl, net::Address addr, net::Message& mess) {
printf("Message %d received from %s\n", mess.getType(), addr.toString().c_str());
});
cl.broadcast(msg);
cl.runThreads(4);
while(cl.active)
threads::sleep(1);
}
int main() {
test_client();
return 0;
}
+66
View File
@@ -0,0 +1,66 @@
#include "../include/network/server.h"
#include "../include/network/lobby.h"
#include <threads.h>
void test_server() {
net::Server srv(2048);
srv.connHandle(net::MT_Connect, [](net::Server& srv, net::Connection& conn, net::Message& mess) {
printf("Connection from %s\n", conn.address.toString().c_str());
});
srv.connHandle(net::MT_Disconnect, [](net::Server& srv, net::Connection& conn, net::Message& mess) {
net::DisconnectReason reason;
mess >> reason;
printf("Disconnection from %s (%d)\n", conn.address.toString().c_str(), reason);
});
srv.connHandle(net::MT_Application, [](net::Server& srv, net::Connection& conn, net::Message& mess) {
char* str = 0;
mess >> str;
printf("Message (%d) received: %s\n", mess.getID(), str);
net::Message resp(net::MT_Application, net::MF_Reliable);
resp << "My Reply";
resp.write1();
resp << (char)-18;
conn << resp;
});
srv.runThreads(4);
while(srv.active)
threads::sleep(1);
}
void test_lobby_server() {
net::LobbyServer srv(2044);
srv.runThreads(4);
while(srv.active)
threads::sleep(1);
}
void test_broadcast_server() {
net::Server srv(2012, "", true);
srv.genHandle(net::MT_Application, [](net::Server& srv, net::Transport& trans,
net::Address addr, net::Message& mess) {
printf("Message %d received from %s\n", mess.getType(), addr.toString().c_str());
net::Message resp(net::MT_Application + 1);
trans.send(resp, addr);
});
srv.runThreads(4);
while(srv.active)
threads::sleep(1);
}
int main() {
test_server();
return 0;
}