Click here to Skip to main content
15,891,976 members
Articles / Programming Languages / C++

A Universal TCP Socket Class for Non-blocking Server/Clients

Rate me:
Please Sign up or sign in to vote.
4.66/5 (121 votes)
26 Jun 2012CPOL13 min read 852.3K   20.4K   383  
A universal class for bidirectional TCP communication
#pragma once

#include <winsock2.h>
#pragma comment(lib, "ws2_32")
#pragma warning(disable: 4996) // _vsnwprintf is deprecated

// Enable Trace output in DebugView from www.sysinternals.com
#define TRACE_EVENTS  _DEBUG && FALSE  // Trace Socket Events and Request/Lock Timer
#define TRACE_LOCK    _DEBUG && FALSE  // Trace Lock Mutex and LoopEvent

// With this buffer cSocket reads data from Winsock
// This buffer should have a similar size as the internal Winsock buffer (which has 64 kB)
// There is no reason why you should modify this value! (except for testing)
#define READ_BUFFER_SIZE   64*1024

// With this size cMemory is initialized.
// If the value is too big you waste memory. (If 62 sockets are open you occupy at least 62 * MEMORY_INITIAL_SIZE Byte)
// If the value is too small there are multiple re-allocations necessary which are slow.
// This value depends on the size of the datablocks that you want to receive (e.g. when using length-prefixed mode)
// This value should be READ_BUFFER_SIZE or more.
#define MEMORY_INITIAL_SIZE  64*1024


// for older Visual Studio versions. (DWORD_PTR is required to compile correctly as 64 Bit)
#ifndef DWORD_PTR
#define DWORD_PTR DWORD
#endif

// This flag is used to notify the caller that the socket has been closed due to a timeout (idle for too long)
// This flag is always signaled in combination with FD_CLOSE
#define FD_TIMEOUT  (1 << 20)

namespace TCP
{
	class cSocket
	{
	public:
		enum eState
		{
			E_Disconnected = 0,
			E_Connected    = 1,
			E_Server       = 2,
			E_Client       = 4,
		};

		class cMemory
		{
		public:
			 cMemory(DWORD u32_InitialSize);
			~cMemory();
			char* GetBuffer();
			DWORD GetLength();
			void  Append(char* s8_Data, DWORD u32_Count);
			void  DeleteLeft(DWORD u32_Count);

		private:
			char*  ms8_Mem;
			DWORD mu32_Size;
			DWORD mu32_Len;
		};

		// Template classes must always be declared in the header file.
		// Example: tValue = DWORD, SOCKET, ULONGLONG,...
		template <class tKey, class tValue>
		class cHash
		{
		public:
			cHash(DWORD u32_InitialCount=10) 
				: mi_Keys(u32_InitialCount * sizeof(tKey)),
				  mi_Vals(u32_InitialCount * sizeof(tValue))
			{
			}
			void Append(tKey t_Key, tValue t_Value)
			{ 
				mi_Keys.Append((char*)&t_Key,   sizeof(tKey));
				mi_Vals.Append((char*)&t_Value, sizeof(tValue));
			}
			DWORD GetCount()
			{ 
				return mi_Keys.GetLength() / sizeof(tKey); 
			}
			void Clear()
			{
				mi_Keys.DeleteLeft(0xFFFFFFFF);
				mi_Vals.DeleteLeft(0xFFFFFFFF);
			}
			tKey GetKeyByIndex(DWORD u32_Index)
			{
				if (u32_Index >= GetCount()) return NULL;
				tKey* p_Key = (tKey*)mi_Keys.GetBuffer();
				return p_Key[u32_Index]; 
			}
			tValue GetValueByIndex(DWORD u32_Index)
			{
				if (u32_Index >= GetCount()) return NULL;
				tValue* p_Val = (tValue*)mi_Vals.GetBuffer();
				return p_Val[u32_Index]; 
			}
			tValue GetValueByKey(tKey t_Key)
			{
				tKey*   p_Key = (tKey*)   mi_Keys.GetBuffer();
				tValue* p_Val = (tValue*) mi_Vals.GetBuffer();
				for (DWORD i=0; i<GetCount(); i++)
				{
					if (p_Key[i] == t_Key) return p_Val[i]; 
				}
				return NULL;
			}

		private:
			cMemory mi_Keys;
			cMemory mi_Vals;
		};

	protected:
		struct kData
		{
			SOCKET      h_Socket;
			DWORD     u32_IP;
			char*      s8_SendBuf;
			DWORD     u32_SendLen;
			DWORD     u32_SendPos;
			LONGLONG  s64_IdleSince;
			BOOL        b_Closed;
			BOOL        b_Timeout;
			BOOL        b_Shutdown;
			cMemory*   pi_RecvMem;
		};

		class cList
		{
		public:
			 cList();
			~cList();
			void     RemoveAll();
			void     RemoveClosed();
			kData*   Add(SOCKET h_Sock, HANDLE h_Event);
			BOOL     Remove(DWORD u32_Index);
			int      FindSocket(SOCKET h_Socket);

			DWORD    mu32_Count;
			eState   me_State;

			// The first event is used for the lock. It is not associated with a socket.
			HANDLE  mh_Events[WSA_MAXIMUM_WAIT_EVENTS];
			kData   mk_Data  [WSA_MAXIMUM_WAIT_EVENTS-1];
		};

		struct kLock
		{
		public:
			 kLock();
			~kLock();
			DWORD  Init();

			HANDLE h_Mutex;
			HANDLE h_ExitTimer;  // set to escape from WSAWaitForMultipleEvents
			HANDLE h_LoopEvent;  // blocks the endless loop ProcessEvents()
		};

		class cLock
		{
		public:
			 cLock();
			~cLock();
			DWORD Request(kLock* pk_Lock);
			DWORD Loop   (kLock* pk_Lock);

		private:
			HANDLE mh_Mutex;
		};

	public:
		 cSocket();
		~cSocket();

		DWORD  Close();
		DWORD  GetSocketCount();
		DWORD  GetAllConnectedSockets(cHash<SOCKET,DWORD>* pi_SockList);
		DWORD  Listen   (DWORD u32_BindIP, USHORT u16_Port, DWORD u32_EventTimeout, DWORD u32_MaxIdleTime=0);
		DWORD  ConnectTo(DWORD u32_ServIP, USHORT u16_Port, DWORD u32_EventTimeout, DWORD u32_MaxIdleTime=0);
		DWORD  DisconnectClient(SOCKET h_Socket);
		DWORD  ProcessEvents(DWORD* pu32_Events, DWORD* pu32_IP, SOCKET* ph_Socket, cMemory** ppi_RecvMem, DWORD* pu32_Read, DWORD* pu32_Sent);
		DWORD  SendTo(SOCKET h_Socket, char* s8_SendBuf, DWORD u32_Len);
		DWORD  GetLocalIPs(cHash<DWORD,DWORD>* pi_IpList);
		eState GetState();
		void   FormatEvents(DWORD u32_Events, char* s8_Buf);

		static void TraceA(const char* s8_Format, ...);
		
	protected:
		DWORD    CreateSocket();
		DWORD    Initialize();
		DWORD    SendDataBlock(SOCKET h_Socket, char* s8_Buf, DWORD* pu32_Pos, DWORD u32_Len);
		DWORD    WSAWaitForMultipleEventsEx(DWORD u32_Count, DWORD* pu32_Index, WSAEVENT* ph_Events, DWORD u32_Timeout);
		DWORD    ProcessIdleSockets(char* s8_Caller);
		LONGLONG GetTickCount64();
		
		static int  WINAPI AcceptCondition(WSABUF* pk_CallerId, WSABUF* pk_CallerData, QOS* pk_SQOS, QOS* pk_GQOS, WSABUF* pk_CalleeId, WSABUF* pk_CalleeData, UINT* pu32_Group, DWORD_PTR p_Param);

		BOOL       mb_Initialized;
		DWORD    mu32_WaitIndex;
		cList      mi_List;
		kLock      mk_Lock;
		char*     ms8_ReadBuffer;
		LONGLONG ms64_MaxIdleTime;
		DWORD    mu32_EventTimeout; // UNSIGNED!!
		DWORD    mu32_Tick64Lo;     // UNSIGNED!!
		DWORD    mu32_Tick64Hi;
	};
};

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) ElmüSoft
Chile Chile
Software Engineer since 40 years.

Comments and Discussions