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);
}