External Exam Download Resources Web Applications Games Recycle Bin

TcpServer.cpp

-----------------------------------
TcpServer Class Implementation File
-----------------------------------*/

#include "stdafx.h"
#include "TcpServer.h"

/***********
constructor:
************/
TcpServer::TcpServer(){christmaslunch = gcnew Restaurant();};

/**********
destructor:
***********/
TcpServer::~TcpServer(){delete christmaslunch;};

/***********************************************************
dissected: accepts an argument String^ and separates it into
its respective values ready for reservation processing. It
returns this final dissected value (done).
************************************************************/
TcpServer::dissected TcpServer::dissect_msg_string(String^ undissected){
	dissected done;
	//Number of seats:
	done.seats = System::Convert::ToInt32(undissected->Substring(undissected->LastIndexOf(",")+1));
	undissected=undissected->Remove(undissected->LastIndexOf(","));
	//Under what name:
	done.who = undissected->Substring(undissected->LastIndexOf(",")+1);
	undissected=undissected->Remove(undissected->LastIndexOf(","));
	//For which sitting:
	done.sitting = System::Convert::ToInt32(undissected->Substring(undissected->LastIndexOf(",")+1));
	return done;
};

/*********************************************************
redisplayBookingsEverySixtySeconds: a thread that displays
in the server console window the booking information that
is refreshed every sixty seconds.
**********************************************************/
void TcpServer::redisplayBookingsEverySixtySeconds(){
	for(;;){
		for(int timeCounter = 4; timeCounter >= 1; timeCounter--){
			Console::WriteLine("Next booking report appears in "+System::Convert::ToString(timeCounter*15)+" seconds...");
			Thread::Sleep(15000);  //wait 15 seconds (15000ms)
		}
		Console::WriteLine("\nBookings as of "+DateTime::Now+":");
		christmaslunch->generateConsoleBookingReport();
	}
};

/**************************************************************
requestPlaces: takes the current stream writer as the argument
and sends the client the seats available via this stream writer
***************************************************************/
void TcpServer::requestPlaces(StreamWriter^ w){
	w->Write(this->christmaslunch->seatsAvailableMessage());
	w->Flush();
};

/************************************************************
makeBooking: a three step function, taking the current stream
writer and message string as arguments. The steps are -
Step 1: Create New A Booking Thread;
Step 2: Feedback on Processing Time -
	Step 2.1: Thread States;
	Step 2.2: Thread Timeout;
Step 3: Success.
**************************************************************/
void TcpServer::makeBooking(StreamWriter^ w, String^ m){
	/*---------------------------------
	Step 1: Create A New Booking Thread
	-----------------------------------*/
	/*create a delegate to the method that is the starting point of the thread (parameterized so an object can be passed to it):
	note - &Restaurant::addBooking is fully qualified - it generates a pointer to the object method name using & */
	ParameterizedThreadStart ^pts1 = gcnew ParameterizedThreadStart(this->christmaslunch,&Restaurant::addBooking);	
	//create the actual thread:
	Thread ^t1 = gcnew Thread(pts1);
	//dissect m (raw message from the client):
	dissected message = this->dissect_msg_string(m);
	//package dissected information into a new reservation structure:
	Reservation^ newres = gcnew Reservation(message.sitting,message.who,message.seats);
	//start the thread using the new reservation argument (newres):
	t1->Start(newres);
	/*-------------------------------------------------------------------------------------
	Step 2: Feedback on Processing Time (Step 2.1: Thread States; Step 2.2: Thread Timeout)
	---------------------------------------------------------------------------------------*/
	/*the following Booleans are used to prevent repeated thread state MESSAGES being displayed
	(i.e. they are used ONLY for user interface feedback): */
	bool waitsleepjoin, running, other, unsuccessful = false;
	DateTime dt1 = DateTime::Now;
	//continue feedback until Thread t1 has stopped:	
	while(t1->ThreadState!=ThreadState::Stopped){
		DateTime dt2 = DateTime::Now;
		/*---------------------
		Step 2.1: Thread States
		-----------------------*/
		//if the thread is blocked (by sleep or by lock) AND the feedback is yet displayed:
		if(t1->ThreadState==ThreadState::WaitSleepJoin&&!waitsleepjoin){
			waitsleepjoin=true;
			running=false;
			w->WriteLine("Waiting for reservation server...");
			w->Flush();
		}
		//thread is running:
		if(t1->ThreadState==ThreadState::Running&&!running){
			running=true;
			waitsleepjoin=false;
			w->WriteLine("Trying to book now...");
			w->Flush();
		}
		/*----------------------
		Step 2.2: Thread Timeout
		------------------------*/
		//determine milliseconds since beginning:
		Int64 t = (dt2.Ticks - dt1.Ticks)/10000;
		/*1000 milliseconds in a second therefore 10000ms = 10secs
		if thread has taken longer than 10 seconds AND (other) slow time message is yet displayed: */
		if(t>10000&&!other){
			other=true;
			running = waitsleepjoin = false;
			w->WriteLine("It is possible there were not enough seats available.");
			w->WriteLine("The process will continue for another 10 seconds.");
			w->Flush();
		};
		//TIMEOUT after 20 secs && this unsuccessful message is yet to be displayed:
		if(t>20000&&!unsuccessful){
			t1->Abort(newres);
			//(other) should already be true - and so no other messages appear - but just for safety:
			unsuccessful = running = waitsleepjoin = other = true;
			w->WriteLine("Sorry - your booking could not be confirmed.");
			w->WriteLine("Perhaps another booking came in before yours?");
			w->WriteLine("Please check the number of seats available and try again.");
			w->WriteLine("EOM");
			w->Flush();
		}
	}//the above is repeated while ThreadState of t1 is not Stopped
	/*-------------
	Step 3: Success
	---------------*/
	if(!unsuccessful){
		w->WriteLine("Accepted.  To cancel use your UNIQUE RESERVATION KEY: ");
		w->WriteLine("EXPECT_KEY");
		w->WriteLine(this->christmaslunch->feedback_reservation_key(newres));
		w->WriteLine("EOM");
		w->Flush();
	}
};

/****************************************************************************
cancelBooking: removes an old booking from the reservation storage structure
The author recommends future revision of this method - it currently does not
provide adequate feedback during the search and removal of the booking to the
client.  It also does not contain a provision for incorrect / non-existant
keys.
*****************************************************************************/
void TcpServer::cancelBooking(StreamWriter^ w, String^ m){
	// extract the reservation key from the request:
	String^ reservation_key = m->Substring(m->LastIndexOf(",")+1);
	// create the delegate:
	ParameterizedThreadStart ^pts2 = gcnew ParameterizedThreadStart(this->christmaslunch,&Restaurant::deleteBooking);	
	// create the thread:
	Thread ^t2 = gcnew Thread(pts2);
	// invoke the thread with the reservation key argument:
	t2->Start(reservation_key);
	/*until the thread has stopped (which could be better implemented
	in the future using the feedback system above for booking): */
	while(t2->ThreadState!=ThreadState::Stopped){}//a feedback system must be utilised {here} in future
	//currently no provision for non-existant keys - another mandatory future revision required:
	w->WriteLine("Complete. "+reservation_key->ToUpper()+" is gone.");
	w->Flush();
};

/****************************************************************************
ProcessThread: thread that handles the connection of the client, and 
*****************************************************************************/
void TcpServer::ProcessThread(Object^ clientObj)
{
	/*Provide a client connection for reservation service, and enable stream communication with this connection.
	Note in this instance, the TcpClient information is taken from the invoked thread argument - this enables multiple concurrency:*/
	TcpClient^ client = (TcpClient^) clientObj;
	//"Bind" a socket to the client IP End Point (the client's IPEndPoint is the combination of the client's IP Address and port):
	IPEndPoint^ clientEP = (IPEndPoint^)client->Client->RemoteEndPoint;
	//For testing using client address 127.0.0.1 - this is loopback IP address which will hit this server on same machine:
	Console::WriteLine("Connected on IP: {0} Port: {1}",clientEP->Address, clientEP->Port);
	//Create stream reader and writer from a client's Networkstream object to handle outgoing (writer) and incoming (reader) streams:
	StreamWriter^ writer = gcnew StreamWriter(client->GetStream());
	StreamReader^ reader = gcnew StreamReader(client->GetStream());
	//feedback using stream - let client know which port they are connected using:
	writer->WriteLine("Successful connection to the server on port {0}",clientEP->Port);
	writer->WriteLine(this->christmaslunch->welcomeMessage());
	writer->Flush();
	String^ msg;
	while (true) //while connected:
		{
			try //incase the connection is lost:
			{
				//get client request:
				msg = reader->ReadLine();
				reader->DiscardBufferedData();
				Console::WriteLine("Port [{0}] {1}",clientEP->Port, msg);

				//1 SECOND DELAY CORRESPONDING TO THE VALIDATION OF ALL REQUESTS:
				Thread::Sleep(1000);

				if(msg->Equals("DISCONNECT"))break; //Quit
				if(msg->IndexOf("1")==0)requestPlaces(writer); //Information (request 1)
				if(msg->IndexOf("2")==0)makeBooking(writer, msg); //Book (request 2)
				if(msg->IndexOf("3")==0)cancelBooking(writer, msg); //Cancel (request 3)
		    }
			catch(IOException^){break;} //connection is suddently lost, exit the connection loop.
		}//end of loop that continues while still connected
	//Mark the TcpClient instance as disposed, and request the socket to close the TCP connection:
	client->Close();
	Console::WriteLine("Connection to IP: {0} Port {1} closed.",clientEP->Address, clientEP->Port);
}