Open source Star Ruler 2 source code!
This commit is contained in:
@@ -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
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
};
|
||||
@@ -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();
|
||||
|
||||
};
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
};
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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
|
||||
|
||||
};
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user