/* * netbase.h by Matze Braun * * Copyright (C) 2001 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation (version 2 of the License) * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * This file defines some inline functions which are used in client and server * network functions, the client/server interface classes should be derived from * this base class */ #ifndef __NETBASE_H__ #define __NETBASE_H__ #include "net/pstypes.h" #include "net/netinfos.h" #include "util/prb.h" #include "util/genqueue.h" #include #include #include #include "util/growarray.h" #include "netprofile.h" #define NUM_BROADCAST 0xffffffff #define MAXQUEUESIZE 2000 #define MAXPACKETHISTORY 200 // The number of times the SendTo function will retry on a EAGAIN or EWOULDBLOCK #define SENDTO_MAX_RETRIES 200 // The whole seconds that the SendTo function will block each cycle waiting for the write state to change on the socket #define SENDTO_SELECT_TIMEOUT_SEC 0 // The microseconds (1/10^6 s) that the SendTo function will block each cycle waiting for the write state to change on the socket #define SENDTO_SELECT_TIMEOUT_USEC 10000 /* Include platform specific socket settings */ #ifdef USE_WINSOCK # include "net/sockwin.h" #endif #ifdef USE_UNISOCK # include "net/sockuni.h" #endif // Jorrit: hack for mingw. #ifdef SendMessage #undef SendMessage #endif class psNetPacketEntry; class MsgEntry; class NetPacketQueueRefCount; class csRandomGen; class csStringHash; struct iEngine; typedef GenericQueue MsgQueue; typedef GenericQueue NetPacketQueue; struct PublishDestination { int client; void *object; float dist; float min_dist; PublishDestination(int client, void* object, float dist, float min_dist) : client(client), object(object), dist(dist), min_dist(min_dist) {} }; /** * This class acts as a base for client/server net classes. It tries to define * as much common used code as possible while not trying to slow things down * because of the generalisation */ class NetBase { public: /** * you can specify how much messages the ouptput queue can contain before * being full. 100 should be enough for the Client, but the server should * increase this number */ NetBase(int outqueuelen=100); virtual ~NetBase(); /** * This adds another message queue (for other threads) * These queues are for reading off. * Selection of the messages is done by a minimum and maximum ObjID */ bool AddMsgQueue (MsgQueue *,objID minID=0,objID maxID=0xffffffff); /** this removes a queue */ void RemoveMsgQueue(MsgQueue *); /** * Put a message into the outgoing queue */ virtual bool SendMessage (MsgEntry* me); virtual bool SendMessage (MsgEntry* me,NetPacketQueueRefCount *queue); /** * Broadcast a message, DON'T USE this function, it's only for MsgHandler! */ virtual void Broadcast (MsgEntry* me, int scope, int guildID) = 0; /** * Mulitcast a message, to all client nums */ virtual void Multicast (MsgEntry* me, const csArray& multi, int except, float range) = 0; /** * Is this connection ready to use? */ bool IsReady() { return ready; } /** * This adds a completed message to any queues that are signed up. */ void QueueMessage(MsgEntry *me); /** * This function is the heart of NetBase - it look for new incoming packets * and sends packets in the outgoing queue. This is thought of being called * from a thread in the server or regularely (between frames?) in the client * It loops until both send and receive are done with all their queues or until * the current time has passed timeout, then returns to caller. */ void ProcessNetwork (csTicks timeout); /** sendOut sends the next packet in the outgoing message queue */ bool SendOut(void); /** this receives an Incoming Packet and analyses it */ bool CheckIn(void); /** * Flush all messages in given queue. * * @param queue The queue to flush * @return True if all messages flushed. */ bool Flush(MsgQueue * queue); /** Binds the socket to the specified address (only needed on server */ bool Bind(const char* addr, int port); bool Bind(int addr, int port); /** * This class describes connections to other computers - so it contains * TCP/IP address, a buffer for splitted packets, msgnum and others, but not * things like name of the client or character type */ class Connection; enum broadcasttype { BC_EVERYONEBUTSELF = 1, BC_GROUP = 2, BC_GUILD = 3, BC_SECTOR = 4, BC_EVERYONE = 5, BC_FINALPACKET = 6 }; csTicks GetPing(void) { return netInfos.GetAveragePingTicks();} psNetMsgProfiles * GetProfs() { return profs; } /// The timeout to use when waiting for new incoming packets. struct timeval timeout; /// Set the MsgString Hash void SetMsgStrings(csStringHash* msgstrings) { this->msgstrings = msgstrings; } /// Get the MsgString Hash csStringHash* GetMsgStrings() { return msgstrings; } /// Set the Engine void SetEngine(iEngine* engine) { this->engine = engine; } /// Get the Engine iEngine* GetEngine() { return engine; } /** * Log the message to LOG_MESSAGE. * * @param dir Should be R for received messages, S for sent messages and I for internal. */ void LogMessages(char * dir, MsgEntry* me); /** * Pars and configure user filter settings. * * @param arg User command argumets */ csString LogMessageFilter(char *arg); /** * Add a new message type to the LogMessage message filter list * * @param type The type of message to filter when loging */ void AddFilterLogMessage(int type); /** * Remove a message type from the LogMessage message filter list * * @param type The type of message to not filter. */ void RemoveFilterLogMessage(int type); /** * Clear all message type from the LogMessage message filter list * */ void LogMessageFilterClear(); /** * Invert the LogMessage filter */ void InvertLogMessageFilter() { logmsgfiltersetting.invert = !logmsgfiltersetting.invert; } /** * Set the filter hex messages flag. */ void SetLogMessageFilterHex(bool filterhex) { logmsgfiltersetting.filterhex = filterhex; } /** * Dump the current filter settings. */ void LogMessageFilterDump(); protected: /** * Check if the given message type should be loged or not. * */ bool FilterLogMessage(int type); protected: /* the following protected stuff is thought of being used in the inherited * network classes */ /** Wrapper to encapsulate the sendto call and provide for retry if the buffer is full. * * */ int SendTo (LPSOCKADDR_IN addr, const void *data, unsigned int size) { struct timeval timeout; int sentbytes; int retries=0; fd_set wfds; #ifdef DEBUG if (!addr || !data) Error1("wrong args"); #endif // #define ENDIAN_DEBUG #ifdef ENDIAN_DEBUG FILE *f = fopen("hexdump.txt","a"); int i; int len; char c; const char *ptr; ptr = (const char *)data; len = ((400) { totaltransferout += size; totalcountout++; } else Error1("NetBase::SendTo() gave up trying to send a packet."); return sentbytes; } /** * small inliner for receiving packets... This just * encapsulates the lowlevel socket funcs */ int RecvFrom (LPSOCKADDR_IN addr, socklen_t *socklen, void *buf, unsigned int maxsize) { #ifdef DEBUG if (!addr || !buf) Error1("wrong args"); #endif fd_set set; /* Initialize the file descriptor set. */ FD_ZERO (&set); FD_SET (mysocket, &set); // Backup the timeval struct in case select changes it as on Linux struct timeval prevTimeout = timeout; /* select returns 0 if timeout, 1 if input available, -1 if error. */ if (SOCK_SELECT(FD_SETSIZE, &set, NULL, NULL, &timeout) != 1) { timeout = prevTimeout; return 0; } timeout = prevTimeout; int err = SOCK_RECVFROM (mysocket, buf, maxsize, 0, (LPSOCKADDR) addr, socklen); if (err>=0) { totaltransferin += err; totalcountin++; } return err; } /** * some helper functions... the getConnBy functions should be reimplemented * in the client/server classes. */ int GetIPByName (LPSOCKADDR_IN addr, const char *name); virtual Connection *GetConnByIP (LPSOCKADDR_IN addr) = 0; virtual Connection *GetConnByNum (uint32_t clientnum) = 0; /** * This function is called when we receive packets from an unknown client. * This is usefull for the server to handle clients that are just connecting */ virtual bool HandleUnknownClient (LPSOCKADDR_IN addr, MsgEntry *data) = 0; /** * This initialises the socket lib and creates a listening UDP socket, if * you're the client you should set port to zero, so a random free port is * user */ bool Init(bool autobind = true); void Close(bool force = true); /** * This takes incoming packets and examines them for priority. * If pkt is ACK, it finds the awaiting ack pkt and removes it. * If pkt is HIGH priority, it creates an ack pkt and queues it to send back. */ bool HandleAck(psNetPacketEntry* pkt, Connection* connection, LPSOCKADDR_IN addr); /** * This cycles through set of pkts awaiting ack and resends old ones. * This function must be called periodically by an outside agent, such * as NetManager. */ void CheckResendPkts(void); /** * This takes incoming packets and rebuilds psMessages from them. If/when a * complete message is reassembled, it calls HandleCompletedMessage(). */ bool BuildMessage(psNetPacketEntry* pkt,Connection* &connection,LPSOCKADDR_IN addr); /** * This checks the list of packets waiting to be assembled into complete messages. * It forms a list of up to 10 (may be changed, check code) packets which are older * than 10 seconds of age. These packets are removed from the list. * This should usually be called with the same frequency as CheckResendPkts() though * their functionality is not related. */ void CheckFragmentTimeouts(void); /** * This adds the incoming packet to the pending packets tree, and builds * the psMessageBytes struct and MsgEntry struct if complete. */ csPtr CheckCompleteMessage(uint32_t client,uint32_t id); /** * This receives only fully reassembled messages and adds to appropriate * queues. */ void HandleCompletedMessage(MsgEntry *me, Connection* &connection, LPSOCKADDR_IN addr, psNetPacketEntry* pkt); /** * This tries to drop packets that received doubled */ bool CheckDoublePackets (Connection* connection, psNetPacketEntry* pkt); /** * This attempts to merge as many packets as possible into one before * sending. It empties the passed queue. */ bool SendMergedPackets(NetPacketQueue *q); /** * This does the sending and puts the packet in "awaiting ack" if necessary. */ bool SendSinglePacket(psNetPacketEntry* pkt); /** * Send packet to the clientnum given by clientnum in psNetPacketEntry */ bool SendFinalPacket(psNetPacketEntry* pkt); /** * This only sends out a packet */ bool SendFinalPacket(psNetPacketEntry* pkt, LPSOCKADDR_IN addr); /** Outgoing message queue */ NetPacketQueueRefCount *NetworkQueue; /** list of outbound queues with waiting data */ GenericQueue senders; /** Incoming message queue vector */ csArray inqueues; /** Packets Awaiting Ack pool */ BinaryRBTree awaitingack; /** System Socket lib initialized? */ static int socklibrefcount; /** is the connection ready? */ bool ready; /** total bytes transferred by this object */ int totaltransferin, totaltransferout; /** total packages transferred by this object */ int totalcountin, totalcountout; /** return a random ID that can be used for messages */ uint32_t GetRandomID(); psNetMsgProfiles * profs; private: /** my socket */ SOCKET mysocket; /** tree holding the outgoing packets */ BinaryRBTree packets; /** for generating random values (unfortunately the msvc rand() function * is not good at all */ csRandomGen* randomgen; /** mutex used for the random generator */ csRef mutex; /** network information layer */ psNetInfos netInfos; /** MsgString Hash */ csStringHash * msgstrings; /** Engine */ iEngine * engine; /** LogMessage filter setting */ csArray logmessagefilter; typedef struct { bool invert; bool filterhex; } LogMsgFilterSetting_t; LogMsgFilterSetting_t logmsgfiltersetting; char* input_buffer; }; /** This class holds data for a connection */ class NetBase::Connection { public: /** buffer for split up packets, allocated when needed */ void *buf; /** The INet Adress of the client */ SOCKADDR_IN addr; /** The Number of the last incoming packet */ int pcknumin; /** The Number of the last outgoing packet */ int pcknumout; /** Is this already a valid connection? */ bool valid; /** the client num */ uint32_t clientnum; /** last time packet was received from this connection */ csTicks lastRecvPacketTime; /** number of attempts to keep alive connection without ack response */ int heartbeat; /** keeps track of received packets to drop doubled packets */ uint32_t packethistoryid[MAXPACKETHISTORY]; uint32_t packethistoryoffset[MAXPACKETHISTORY]; int historypos; Connection(uint32_t num=0); ~Connection(); bool isValid() const { return valid; } }; class NetPacketQueueRefCount : public NetPacketQueue, public csRefCount { private: bool pending; public: NetPacketQueueRefCount(int qlen) : NetPacketQueue(qlen) { pending=false; } virtual ~NetPacketQueueRefCount() {} /// This flag ensures the same object is not queued twice. /// Doesn't have to mutexed here because these are already called from within a queue mutex. void SetPending(bool flag) { pending = flag; } bool GetPending() { return pending; } }; #endif