Introduction to C#A console file server

In this example we are going to create a basic file server. For this we will actually need two console applications, one acting as a client and the other acting as the server. The client will be started with a parameter i that will act as its unique identifier: $ client -i 1.

The client asks the user to type one of these commands: S, H or L. With this information, the client will generate a "message", that is, a file named ID_X_00000.txt, where ID is the unique identifier of the client, X is the command (S, H or L), and 00000 is a counter for the generated files. That file is stored in a predefined folder that can be accessed by the server as well as by all clients

The server will be listening for a file to appear in that folder. When this happens, it will process the file, breaking down the file name and displaying in the console:

  • A square with the ID in the middle, if the command is S,
  • An hexagon with the ID in the middle, if the command is H,
  • A line, but only if the ID equals 1, if the command is L.
  • Any other messages will be discarded.

When the server is done processing a file, it is deleted from the folder.

A possible solution, in C++ first

Since C++ is an old cousin of C#, let's do a simple implementation in Linux first, and then we'll go to the example. This solution uses inotify. Prepare for a lot of ASCII art.


		         YE OLDE PIRATE' SERVER!


		                 uuuuuuu
		             uu$$$$$$$$$$$uu
		          uu$$$$$$$$$$$$$$$$$uu
		         u$$$$$$$$$$$$$$$$$$$$$u
		        u$$$$$$$$$$$$$$$$$$$$$$$u
		       u$$$$$$$$$$$$$$$$$$$$$$$$$u
		       u$$$$$$$$$$$$$$$$$$$$$$$$$u
		       u$$$$$$"   "$$$"   "$$$$$$u
		       "$$$$"      u$u       $$$$"
		        $$$u       u$u       u$$$
		        $$$u      u$$$u      u$$$
		         "$$$$uu$$$   $$$uu$$$$"
		          "$$$$$$$"   "$$$$$$$"
		            u$$$$$$$u$$$$$$$u
		             u$"$"$"$"$"$"$u
		  uuu        $$u$ $ $ $ $u$$       uuu
		 u$$$$        $$$$$u$u$u$$$       u$$$$
		  $$$$$uu      "$$$$$$$$$"     uu$$$$$$      
		u$$$$$$$$$$$uu    """""    uuuu$$$$$$$$$$
		$$$$"""$$$$$$$$$$uuu   uu$$$$$$$$$"""$$$"
		 """      ""$$$$$$$$$$$uu ""$"""
		           uuuu ""$$$$$$$$$$uuu
		  u$$$uuu$$$$$$$$$uu ""$$$$$$$$$$$uuu$$$
		  $$$$$$$$$$""""           ""$$$$$$$$$$$"
		   "$$$$$"                      ""$$$$""
		     $$$"                         $$$$"

			Arrr! I be watchin' you, ./queue
						

First, we create a server.cpp and a client.cpp programs. Optionally, we could create a csmanager.cpp too, as we will see later. The files generated by the client are saved in the queue folder, which is in the same directory as the programs.

Download the example!

The manager program

Let's see the list of files we have and explain all of them:


$ tree -L 1 -F --dirsfirst
.
├── queue/
├── client*
├── client.cpp
├── csmanager*
├── csmanager.cpp
├── inotify-cxx.cpp
├── inotify-cxx.h
├── server*
└── server.cpp
1
						

The three programs can be compiled in Ubuntu (for example), just typing this in the console:


$ g++ csmanager.cpp -o csmanager
$ g++ client.cpp -o client
$ g++ server.cpp inotify-cxx.cpp -o server
						

To run our server, we can launch the server and the client separately, each in a different console, or we can use the controler, csmanager.cpp. This program starts the server so that it listens to the queue folder, and opens another terminal to run the client. The code of csmanager.cpp is:


/***************************************
* Simple CLI Client-Server Manager
***************************************/
#include <cstdlib>

int main(int argc, char* argv[]) {
	system("gnome-terminal &"); // Client
	system("./server");         // Server
	return 0;
}
						

To run it, type:


$ ./csmanager
						

As we said, csmanager.cpp is optional, we can run the server whenever we want with:


$ ./server
						

and we can have as many clients as we want to, writing to the same folder, running in another terminal:


$ ./client -i 1
...
$ ./client -i 2
...
						

The server program

The server program uses a little help from the inotify-cxx libraries –http://inotify.aiken.cz– which, at the same time, are a wrapper of inotifyhttp://linux.die.net/man/7/inotify–. These libraries allow us to use the inotify functionality in a C++ program. We can use them to "listen" to changes in folders or files in a Linux system.

To be able to use inotify-cxx, we should have the headers of inotify-cxx in our distribution.

The server program has a group of functions and a main() method. Once started, it keeps listening until CTRL + C is pressed.

Functions of the server program

string decode(const string& filename, string &id, string &command, string &counter, int digits)
Obtains the ID, command and counter from the filename.
void draw()
Decides what to draw depending on the file name.
void keelhaul(string directory, string filename)
Deletes the file from the server once it is processed.

The main() method implements some of the inotify functions:


/***************************************
 * MAIN
 ***************************************/
int main(int argc, char *argv[]) {
	welcome();

	string directory = "./queue";
	try {
		Inotify notify;

		// Only check for creation and deletion
		InotifyWatch watch(directory, IN_CREATE | IN_DELETE);
		notify.Add(watch);

		// SUPER LEGAL INFINITE LOOP. Exit with CTRL + C
		cout << "\nArrr! I be watchin' you, " << directory << endl << endl;
		for (;;) {
			notify.WaitForEvents();

			size_t count = notify.GetEventCount();
			while (count > 0) {
				InotifyEvent event;
				bool got_event = notify.GetEvent(&event);

				if (got_event) {
					string eventype;
					event.DumpTypes(eventype);

					string filename = event.GetName();

					cout << "Avast, "          << directory << "!... ";
					cout << "Me eaye see: \""  << eventype  << "\". ";
					cout << "Ship o' name: \"" << filename  << "\"" << endl << endl;

					// Here's the parrrty
					if (eventype.compare("IN_CREATE") == 0) {
						draw(filename);
						keelhaul(directory, filename);
					}
				}

				count--;
			}
		}
	} catch (InotifyException &e) {
		cerr << "Inotify exception occured: " << e.GetMessage() << endl;
	} catch (exception &e) {
		cerr << "STL exception occured: "     << e.what() << endl;
	} catch (...) {
		cerr << "unknown exception occured"   << endl;
	}

	return 0;
}
						

Client program

The client program also has a bunch of methods and a main(). To end the execution, we can write "nope" or "aye" to continue.

Functions of the client program

bool check(int argc, char *argv[])
Checks the input parameters.
string readCommand(void)
Reads a command entered by the user through the console and checks that it is S, H or L.
string getFilename(const string& filename)
Strips the filename from the path to the file.

And this is the main() of the client program:


/***************************************
 * MAIN
 * Where all the bad things happen
 ***************************************/
int main(int argc, char *argv[]) {

	if ( !check(argc, argv) ) 
		return 1;

	char * c;
	long i = strtol(argv[2], &c, 10);
	int counter = 0;
	string directory = "./queue";
	string command, filename, answer;

	do {
		command = readCommand();

		// If the ID is not 1 and the command is S, discard message.
		if ( i != 1 && command.compare("S") == 0 )
			cout << "Swim with the fishes, ye scurvy dog, mwahuahuahaha..." << endl;

		// Else, create message.
		else {
			// Build filename. If file exists, regenerate (not very elegant, BUT)
			do {
				char counter_str[10];
				sprintf(counter_str, "%05d", counter);

				filename = directory + "/" + argv[2] + "_" + command + "_" + string( counter_str ) + ".txt";

				counter++;
				if(counter > 99999)
					counter = 0;
			} while ( ifstream(filename.c_str()) );
	
			// Now that filename doesn't exist, create file
			ofstream file(filename.c_str());
			if (!file)
				cout << "Thar be nothin' in the sea! Unable to create ship" << endl;
			else {
				cout << "Ship ahoy! It's called '" << getFilename(filename) << "'." << endl;
				file.close();
			}
		}

		cout << "\nWrite 'aye' to drink moar rum or 'nope' to leave me ship: ";
		cin.clear();
		getline (cin, answer);

	} while(answer.compare("aye") == 0);

	return 0;
}
						

Go on and launch csmanager in an Ubuntu terminal. Below you will find a screenshot showing all the possible cases!

Download the example!

A C# solution

How would the previous program be translated into C#? Want to give it a try? See what you can do and then come back to check the example!.

In Visual Studio we can not launch two consoles as we did before with the Linux solution, so we have to create two separate programs, Client and Server.

We also can not use inotify anymore, but we can use the FileSystemWatcher class and its methods.

To launch the client program passing it the arguments as we did in Linux, we have to go to the Solution Explorer, right click on our project name and select Properties. Then, go to the Debug tab on the left and enter your parameters in the Command Line Arguments textbox. In this case, that would be client.exe -i 1

To run the programs, you have to open both and run them from the GUI, this will open the two MS-DOS consoles. Just remember to run the server first!

If after working with the smoother Linux terminal you feel awkward working in MS-DOS, don't worry. We have just one example more to go, and we will be working with Windows Forms. Or you can go to the home page and select what you want to learn next =)

Download the example!

Comments

Have any questions? Spotted any typos? Want to showcase what you did? Found a better solution? Your feedback and suggestions are welcome! And don't forget to share =)