Click here to Skip to main content
15,896,118 members
Articles / Desktop Programming / MFC

.NET Dynamic Software Load Balancing

Rate me:
Please Sign up or sign in to vote.
4.96/5 (111 votes)
9 Dec 200271 min read 511.7K   4.5K   242  
A Draft Implementation of an Idea for .NET Dynamic Software Load Balancing
//////////////////////////////////////////////////////////////////////////////
// .NET Dynamic Software Load Balancing
// A Draft Implementation of an Idea for .NET Dynamic Software Load Balancing
// (c) 2002, Stoyan Damov (stoyan_damov@hotmail.com)
// The software comes �AS IS�, with all faults and no warranties.
//////////////////////////////////////////////////////////////////////////////

#pragma once
#if !defined (Common_H)
#define Common_H

// .NET method access modifiers
// C# access term:)	assembly	class
//
#define PUBLIC		public		public
#define PRIVATE		private		private
#define PROTECTED	public		protected
#define INTERNAL	private		public

// tired of writing "unsigned char buf __gc [] = new unsigned char __gc[...]"
//
typedef unsigned char byte;

// configuration file name -- same for MLMS and MLRS
//
#define CONFIG_FILE_NAME				S"LoadBalancingConfiguration.xml"

// Configuration Sections (CS)
//
#define CS_LMS							S"LoadMonitoringServer"
#define CS_LRS							S"LoadReportingServer"

// Configuration Keys (CK)
//

// for the Load Reporting Server (LRS)
//
// the interval in milliseconds at which the MLRS reports the machine
// load to MLMS
//
#define CK_LRS_INTERVAL					S"ReportingInterval"
//
// the IP address of the multicast group, which MLRS joins in order
// to report machine loads to all "listening" MLMS; if multicasting
// is disabled, it is the IP address of a single MLMS server, to which
// MLRS sends machine loads
//
#define CK_LRS_IP_ADDRESS				S"IpAddress"
//
// the port, MLRS sends machine loads to, no matter whether it is using
// multicasts, UDP-only or TCP
//
#define CK_LRS_PORT						S"Port"

// for the Load Monitoring Server (LMS)
//
// the IP address of the multicast group, MLMS joins in order to receive
// machine loads; if multicasting is disabled, it is not used, as MLMS
// binds to any available IP address on the local machine
//
#define CK_LMS_IP_ADDRESS				S"IpAddress"
//
// the port of the multicast group, and the port MLMS listens to if
// multicasting and UDP are disabled
//
#define CK_LMS_COLLECTOR_PORT			S"CollectorPort"
//
// the backlog (the maximum number of sockets), MLMS uses if it is
// using TCP (no multicasting and UDP)
//
#define CK_LMS_COLLECTOR_BACKLOG		S"CollectorBacklog"
//
// the port on which MLMS listens and accepts TCP queries for the
// least loaded machine; I used it in the first version of the LML
// library, but since I'm using Remoting to publish this information
// you may forget "ReporterPort" and "ReporterBacklog", unless you
// wish to extend the LML library to report machine loads via Remoting
// and by answering TCP queries
//
#define CK_LMS_REPORTER_PORT			S"ReporterPort"
//
// the backlog of the ReporterWorker's listener, accepting TCP
// requests
//
#define CK_LMS_REPORTER_BACKLOG			S"ReporterBacklog"
//
// the maximum time in milliseconds, at which a machine should report
// a fresh load; i.e. if a machine reports a load now, and it does not
// report a new load in "MachineReportTimeout" milliseconds, the machine
// is considered dead, and is removed from the load balancing, until the
// next time it reports a new load
//
#define CK_LMS_MACHINE_REPORT_TIMEOUT	S"MachineReportTimeout"
//
// initially, I thought to register the load balancing object with the
// Remoting runtime, only on a TCP channel; however, later I decided to
// support both TCP and HTTP; so the "RemotingProtocol" is a string, 
// which is either "HTTP" or "TCP"
//
#define CK_LMS_REMOTING_PROTOCOL		S"RemotingProtocol"
//
// the port, on which the Remoting runtime accepts requests for 
// the creation of the load balancing object
//
#define CK_LMS_REMOTING_CHANNEL_PORT	S"RemotingChannelPort"

// Remoting channels (protocols)
//
#define	CHANNEL_TCP						S"tcp"
#define	CHANNEL_HTTP					S"http"

// the alias of the load balancing object, used by the Remoting runtime
// and the client library LBL
//
#define REMOTED_OBJECT_NAME				S"LoadBalancer"

// message parts defined to avoid hardcoding of strings
//
#define MSG_PART_OK						S"+OK"
#define MSG_PART_MACH					S"MACH"
#define MSG_PART_LOAD					S"LOAD"

// I love C++'s macro support! And I really don't like people who
// preach that macros are bad. Macros are good, save a whole lot
// of typing and errors, resulting from Object-Oriented Copy/Pasting:)


// Here's just a simple example of a good macro: the Assert method of
// the Debug class accepts a boolean expression to evaluate, and a
// string which describes the test, so if the test fails, the string
// is output in the assert message box. Well, imagine if I had to write
// every time the same expression in quotes, in order to see it in the
// message box, i.e. Debug::Assert (0 != someObject, S"0 != someObject");
// Now look at the super simple macro, that does the same thing for me
// automatically.
//
#define	DEBUG_ASSERT(x)	Debug::Assert (x, S#x)

// Here's another good macro, I use for exception handling:
// Rule of a thumb for exception handling: catch only the exceptions
// you can handle, and no more. This means that if you expect the method
// you're calling to throw "ArgumentNullException" and/or 
// "ArgumentOutOfRangeException", you should write two catch clauses and
// catch only these exceptions. Another rule is to NEVER swallow an exception
// you caught, but cannot handle. You MUST re-throw it, so the caller of
// YOUR method knows why it failed. There are 2 exceptions you can do nothing
// about but report them to the user and die: these are "OutOfMemoryException",
// and "ExecutionEngineException". I don't know which one is worse -- probably
// the latter, though if you're out of memory, there's almost nothing you can
// do about it. Now, because I'm not writing production code here, I allowed
// myself to catch ALL possible exceptions, when I don't need to handle them
// except to know something wrong has happened. So I catch the base class
// "Exception". This violates both rules, I've written about exception 
// handling, but I wrote some code to fit into the second one -- if I catch
// OutOfMemoryException or ExecutionEngineException, I re-throw them 
// immediately. Here's the macro I call, after I catch the generic Exception
// class:
//
#define TRACE_EXCEPTION_AND_RETHROW_IF_NEEDED(e)		\
	System::Type __gc* exType = e->GetType ();			\
	if (exType == __typeof (OutOfMemoryException) ||	\
		exType == __typeof (ExecutionEngineException))	\
		throw;											\
	Console::WriteLine (								\
		S"\n{0}\n{1} ({2}/{3}): {4}\n{0}",				\
		new String (L'-', 79),							\
		new String ((char *) __FUNCTION__),				\
		new String ((char *) __FILE__),					\
		__box (__LINE__),								\
		e->Message);

// forward class declaration macro, that "remembers" to wrap the class
// in the namespace I use throughout the solution;
//
#define FORWARD_DECLARE(className)				\
	namespace SoftwareLoadBalancing				\
	{											\
		public __gc class className;			\
	}
	
// I think the macro is self-explanatory, but I need to say, that the
// interval is measured in microseconds, not milliseconds
//
#define SOCKET_POLL_INTERVAL	100

// the time, a "listener" thread waits, before it checks for an arrived
// network requests (in milliseconds)
//
#define SOCKET_WAIT_INTERVAL	50

// long live macros!:)
//
#define SOCKET_CLOSE(sock)							\
	try												\
	{												\
		if (0 != sock)								\
		{											\
			sock->Shutdown (SocketShutdown::Both);	\
			sock->Close ();							\
			sock = 0;								\
		}											\
	}												\
	catch (SocketException __gc*) {}				\
	catch (ObjectDisposedException __gc*) {}
	
// undefine the USING_UDP macro to make LRS report to LMS via TCP
//
#define USING_UDP			1

// undefine the USING_MULTICASTS macro to disable multicasting
//
#define USING_MULTICASTS	1

// helper macros for protocol-independent coding
//
#define SOCKET_CREATE_TCP(sock)					\
	sock = new Socket (							\
		AddressFamily::InterNetwork,			\
		SocketType::Stream,						\
		ProtocolType::Tcp)
		
#define SOCKET_CREATE_UDP(sock)					\
	sock = new Socket (							\
		AddressFamily::InterNetwork,			\
		SocketType::Dgram,						\
		ProtocolType::Udp)

#define QUEUE_TCP_REQUEST(cls,svr,clientSock)	\
	ThreadPool::QueueUserWorkItem (				\
		new WaitCallback (						\
			this, 								\
			&cls::HandleTcpRequest),			\
		new WorkerTcpState (svr, clientSock))

// there's no need to queue a user work item in the ThreadPool
// in case we use UDP and multicasting, so we will call the method
// synchronously
//
#define QUEUE_UDP_REQUEST(svr,clientSock,listenSock)		\
	HandleUdpRequest (new WorkerUdpState (svr, listenSock))
		
#define SOCKET_SEND_TCP(sock,endPoint,buffer)	\
	sock->Connect (endPoint);					\
	sock->Send (buffer)

#define SOCKET_SEND_UDP(sock,endPoint,buffer)	\
	sock->SendTo (buffer, endPoint)

// preprocessor code to enable TCP instead of UDP or disable multicasting
//
#if !defined(USING_UDP) // using TCP
#	define SOCKET_CREATE(sock) SOCKET_CREATE_TCP(sock)
#	define SOCKET_LISTEN(sock,backlog) sock->Listen (backlog)
#	define SOCKET_ACCEPT(sock,clientSock)		\
		Socket __gc* clientSock = sock->Accept ()
#	define QUEUE_SOCKET_REQUEST(cls,svr,clientSock,listenSock) \
		QUEUE_TCP_REQUEST (cls, svr, clientSock)
#	define SOCKET_SEND(sock,endPoint,buffer) \
		SOCKET_SEND_TCP (sock, endPoint, buffer)
#	define SOCKET_ADD_MEMBERSHIP(sock,multicastIpAddress) \
		(void) 0
#	define SOCKET_DROP_MEMBERSHIP(sock,multicastIpAddress) \
		(void) 0
#else  // using UDP
#	define SOCKET_CREATE(sock) SOCKET_CREATE_UDP(sock)
#	define SOCKET_LISTEN(sock,backlog) (void) 0
#	define SOCKET_ACCEPT(sock,clientSock) (void) 0
#	define QUEUE_SOCKET_REQUEST(cls,svr,clientSock,listenSock) \
		QUEUE_UDP_REQUEST (svr, clientSock, listenSock)
#	define SOCKET_SEND(sock,endPoint,buffer) \
		SOCKET_SEND_UDP (sock, endPoint, buffer)
#	if !defined (USING_MULTICASTS)
#		define SOCKET_ADD_MEMBERSHIP(sock,multicastIpAddress) (void) 0
#		define SOCKET_DROP_MEMBERSHIP(sock,multicastIpAddress) (void) 0
#	else // multicasting enabled
#		define SOCKET_ADD_MEMBERSHIP(sock,multicastIpAddress)	\
			try													\
			{													\
				sock->SetSocketOption (							\
					SocketOptionLevel::IP,						\
					SocketOptionName::AddMembership,			\
					new MulticastOption (						\
						multicastIpAddress,						\
						IPAddress::Any));						\
			}													\
			catch (SocketException __gc*)						\
			{													\
			}
#		define SOCKET_DROP_MEMBERSHIP(sock,multicastIpAddress)	\
			try													\
			{													\
				sock->SetSocketOption (							\
					SocketOptionLevel::IP,						\
					SocketOptionName::DropMembership,			\
					new MulticastOption (						\
						multicastIpAddress,						\
						IPAddress::Any));						\
			}													\
			catch (SocketException __gc*)						\
			{													\
			}
#	endif // !USING_MULTICASTS
#endif

#endif // Common_H

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Bulgaria Bulgaria
I'm crazy about programming, bleeding-edge technologies and my wife, Irina. Thinking seriously to start living in Centurian time.

The image shows me, happy :P

My blog

Comments and Discussions