Page 1 of 1

[Part 1] Server From Scratch Rate Topic: -----

#1 BetaWar  Icon User is offline

  • #include "soul.h"
  • member icon

Reputation: 1171
  • View blog
  • Posts: 7,219
  • Joined: 07-September 06

Posted 18 March 2011 - 03:39 PM

NOTE - The server created herein may seg-fault from time to time. It will be updated when I get a chance.

In this tutorial we will go through the basics of setting up a minimalist VM (virtual machine) using VMware Workstation (there is a free version so don’t worry) and then create a custom file echoer (it will return the contents of a file when requested through a web browser).

NOTE – The web server will be built in C and C++ and will not use Apache or other premade server software out there. That said we will not (at least at this point) be supporting server-side languages such as PHP.

GOAL – Make a quick, light-weight server which is capable of receiving incoming HTTP requests and replying with a simple message.

Starting off:
Download VMware Player (or VMware workstation if you have some money burning a hole in your pocket) from here: http://www.vmware.com/ (you will probably have to create an account with their site)
Install it and once it is finished start downloading Ubuntu 10.10 netbook from here: http://www.ubuntu.co...ubuntu/download (while you aren’t required to use the netbook edition it is light weight and that is the point of this tutorial – I will also be using the netbook version so if you are starting off you may want to download it just so you can follow along exactly with that I do, well almost… There may be some differences when using VMware Player…).

Setting up the VM:
Open up VMware Player/Workstation and go to File -> New -> Virtual Machine
A dialogue box will pop up and talk about options on how to create the VM, select standard (not advanced) then click next and use a file as the iso (second option) then click browse and find the downloaded iso (select it).
VMware will recognize that it is Ubuntu 10.10 and will say it is going to use easy install, this is fine. Follow the steps provided and you can use the defaults (the only thing I changed was I told it to allocate 4 GB instead of the 8 it recommends).
Once you have finished all the options it will start installing Ubuntu on a VM for you. Wait for this to finish (shouldn’t take too long).

Minimizing:
Alright, now that we have Ubuntu all set up and running it is time to get rid of some of the additional crap that was installed with the OS.
To accomplish this go to System -> Administration -> Synaptic Package Manager. Wait for it to finish creating its search database (it will be easier to remove the packages that way).
Once it is completed go ahead and search for all the applications they have installed and mark them for complete removal. I left the following applications installed: Terminal and Gedit. I also market Chromium for installation (we really just need a browser, so Firefox would have been fine, I just like Chromium better). Once you have everything marked appropriately apply the changes.

NOTE – You could go ahead and use the terminal for all of this, I just find that people enjoy using GUIs better.

Good, now the annoying set up is all completed. Time to install some additional software.

Since this is a C++ tutorial I figure it is only proper that we install a compiler.

Installing things:
Now, without closing out of the package manager we want to check for a few packages.
Search for g++ and mark it for installation (it will have additional packages). Once it is installed it is time to start doing some fun stuff (programming). You will also need to search for and install the pthread libc library.

Our socket connection will be obtained through a posix compliant library, as will the threads, this means that this application should be able to run on all Unix systems (though apparently out version of Ubuntu doesn’t have it installed either), Mac, and Windows (though you may need to install the posix library on Windows to get it working).

Some code to get us off the ground:
First thing’s first, we need to set up a project folder. I placed this directly on my desktop and called it serverFromScratch (I will be calling it SFS from here on out).
Go into your project folder and create the following files with the following code within each.

NOTE – I won’t be explaining this code as it would be a rather length tutorial otherwise. Most of it is pretty self-explanatory.

Socket.h:
 // James Blades (BetaWar)
// dreamincode.net
 // Definition of the Socket class
#ifndef Socket_class
#define Socket_class

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <string>
#include <arpa/inet.h>

const int MAXHOSTNAME = 200;
const int MAXCONNECTIONS = 5;
const int MAXRECV = 500;
//const int MSG_NOSIGNAL = 0; // defined by dgame

class Socket{
private:
  int m_sock;
  sockaddr_in m_addr;
public:
  Socket();
  virtual ~Socket();
  // Server initialization
  bool create();
  bool bind(const int port);
  bool listen(void) const;
  bool listen(const int connections) const;
  bool accept(Socket&) const;
  // Client initialization
  bool connect(const std::string hostname, const int port);
  std::string getIP() const;
  unsigned short getPort() const;
  // Data Transimission
  bool send(const std::string) const;
  int recv(std::string&) const;
  
  void set_non_blocking(const bool);
  bool is_valid() const;
};
#endif


Socket.cpp:
 // James Blades (BetaWar)
// dreamincode.net
 #include "Socket.h"
#include <string>
#include <iostream>
#include <errno.h>
#include <fcntl.h>
#include <string.h>

Socket::Socket() : m_sock(-1){
  memset(&m_addr, 0, sizeof(m_addr));
}

Socket::~Socket(){
  if(is_valid()){
    ::close(m_sock);
  }
}

bool Socket::create(){
  m_sock = socket(AF_INET, SOCK_STREAM, 0);
  if(!is_valid()){
    return false;
  }
  // TIME_WAIT - argh
  int on = 1;
  return setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on)) != -1;
}

bool Socket::bind(const int port){
  if(!is_valid()){
    return false;
  }
  m_addr.sin_family = AF_INET;
  m_addr.sin_addr.s_addr = INADDR_ANY;
  m_addr.sin_port = htons(port);
  int bind_return = ::bind(m_sock, (struct sockaddr*)&m_addr, sizeof(m_addr));
  return bind_return != -1;
}

bool Socket::listen(void) const{
  return listen(MAXCONNECTIONS);
}

bool Socket::listen(const int connections) const{
  if(!is_valid()){
    return false;
  }
  int listen_return = ::listen(m_sock, connections);
  return listen_return != -1;
}

bool Socket::accept(Socket& new_socket) const{
  int addr_length = sizeof(m_addr);
  new_socket.m_sock = ::accept(m_sock, (sockaddr*)&m_addr, (socklen_t*)&addr_length);
  return !(new_socket.m_sock <= 0);
}

bool Socket::send(const std::string s) const{
  int status = ::send(m_sock, s.c_str(), s.size(), MSG_NOSIGNAL);
  return !(status == -1);
}

int Socket::recv(std::string& s) const{
  char buf[MAXRECV + 1];
  s = "";
  memset(buf, 0, MAXRECV + 1);
  int status = ::recv(m_sock, buf, MAXRECV, 0);
  if(status == -1){
    std::cout << ">>> ERROR - status == -1   errno == " << errno << "  in Socket::recv\n";
    return 0;
  }
  else if(status == 0){
    return 0;
  }
  else{
    s = buf;
    return status;
  }
}

bool Socket::connect(const std::string hostname, const int port){
  if(!is_valid()){
    return false;
  }
  hostent* record = gethostbyname(hostname.c_str());
  in_addr* addr = (in_addr*)record->h_addr;
  std::string ip = inet_ntoa(*addr);
  m_addr.sin_family = AF_INET;
  m_addr.sin_port = htons(port);
  int status = inet_pton(AF_INET, ip.c_str(), &m_addr.sin_addr);
  if(errno == EAFNOSUPPORT){
    return false;
  }
  status = ::connect(m_sock, (sockaddr*)&m_addr, sizeof(m_addr));
  return status == 0;
}

std::string Socket::getIP() const{
  struct sockaddr_in peer_addr;
  socklen_t len = sizeof peer_addr;
  getpeername(m_sock, (struct sockaddr*)&peer_addr, &len);

  return inet_ntoa(peer_addr.sin_addr);
}
unsigned short Socket::getPort() const{
  struct sockaddr_in peer_addr;
  socklen_t len = sizeof peer_addr;
  getpeername(m_sock, (struct sockaddr*)&peer_addr, &len);

  return peer_addr.sin_port;
}

void Socket::set_non_blocking(const bool B)/>{
  int opts;
  opts = fcntl(m_sock, F_GETFL);
  if(opts < 0){
    return;
  }
  if(B)/>{
    opts = (opts | O_NONBLOCK);
  }
  else{
    opts = (opts & ~O_NONBLOCK);
  }
  fcntl(m_sock, F_SETFL,opts);
}

bool Socket::is_valid() const{
  return m_sock != -1;
}


Thread.h:
 // James Blades (BetaWar)
// dreamincode.net
 #ifndef THREAD_H
#define	THREAD_H

#include "Runnable.h"
#include <pthread.h>

class Thread{
private:
  volatile bool m_stoprequested;
  volatile bool m_running;
  pthread_t m_thread;
  Runnable* workBot;
  static void* start_thread(void* obj){
    ((Thread*)obj)->run();
    return NULL;
  }
  void run();
public:
  Thread(Runnable* bot);
  ~Thread();
  void start();
  void stop(bool kill);
};
#endif	/* THREAD_H */



Thread.cpp:
 // James Blades (BetaWar)
// dreamincode.net
 #include "Thread.h"
#include <iostream>

void Thread::run(){
  while(!m_stoprequested){
    if(workBot->ceaseThread){
      stop(workBot->killThread);
      return;
    }
    try{
      workBot->tick();
    }
    catch(...){ // Some error occurred, let the bot try to repair itself.
      std::cout << "An error occurred - run()\n";
      workBot->tack();
    }
  }
}
Thread::Thread(Runnable* bot) : m_stoprequested(false), m_running(false){
//  pthread_mutex_init(&m_mutex, NULL);
  workBot = bot;
  workBot->ceaseThread = workBot->killThread = false;
}
Thread::~Thread(){
//  pthread_mutex_destroy(&m_mutex);
}
void Thread::start(){
  if(m_running){
    return;
  }
  m_running = true;
  pthread_create(&m_thread, NULL, Thread::start_thread, this);
}
void Thread::stop(bool kill){
  if(!m_running){
    return;
  }
  m_running = false;
  m_stoprequested = true;
  pthread_cancel(m_thread);
  pthread_join(m_thread, NULL);
  if(kill){
    delete this;
  }
}



Mutex.h:
 // James Blades (BetaWar)
// dreamincode.net
 #ifndef Mutex_H
#define Mutex_H

#include <map>
#include <pthread.h>

class Mutex{
private:
	typedef std::map<void*, pthread_mutex_t> items_t;
	static items_t& get_items(){
		static items_t* items = new items_t;
		return *items;
	}
public:
	static void lock(void* addr){
		items_t& items = get_items();
		if(&items[addr] == NULL){
		  pthread_mutex_init(&items[addr], NULL);
		}
		pthread_mutex_lock(&items[addr]);
	}
	static void unlock(void* addr){
	  items_t& items = get_items();
	  pthread_mutex_unlock(&items[addr]);
	}
};

#endif



Mutex.cpp:
 // James Blades (BetaWar)
// dreamincode.net
 #include "Mutex.h"

// empty file



Runnable.h:
// James Blades (BetaWar)
// dreamincode.net
#ifndef RUNNABLE_H
#define	RUNNABLE_H

// just an interface
class Runnable{
protected:
public:
  bool ceaseThread;
  bool killThread;
  virtual void tick(void) = 0;
  virtual void tack(void) = 0;
};


#endif	/* RUNNABLE_H */




Runnable.cpp:
 // James Blades (BetaWar)
// dreamincode.net
 #include "Runnable.h"

// empty file


Great… I know that is a TON of code, but we aren’t going to be going over it in this tutorial (if you want to have me explain it I will, just comment stating such).

Creating the Make file:
You may know how this is done and be able to skip this part, but I find normally that people don’t set up makefiles efficiently, or they add a whole bunch of additional crap which isn’t needed. Luckily it is easy to set one up so it only compiles things that are different.

To accomplish this we will use the following code:
Makefile:
.cpp.o:
	g++ -c -0 -Wall $<
.c.o:
	g++ -c -0 -Wall $<
main: Socket.o Runnable.o Thread.o Mutex.o main.o
	g++ -Wall -o $@ $^ -lpthread
clean:
	rm -f main *.o *~


Now, the first rule we set up is for any C++ file that we want to create an object file from, it uses Makefile variables to allow us to make a general rule instead of one which is made specifically for a given file. The second rule does the same thing, but is for C files.
The third rule is the general one (what will happen if you just type “make” into the terminal), and will make an executable called “main” which depends on “Socket.o”, “Runnable.o”, “Thread.o”, “Mutex.o” and “main.o” (each of these files is compiled by the general rules above). This rule also links in the pthread library, which we need linked for our application to run.
The fourth, and final rule is the clean. This allows you to type “make clean” into the terminal and it will remove the main executable, all object files, and all backup files. It is a nice little way to clean up the directory.

Programming:
At this point we have fewer than 100 lines of code to write before this tutorial will be over (that is good because this is getting fairly long…).

Starting off we need a main file so go ahead and create the file “main.cpp” and open it up on your favorite text editor (I will be using Gedit).

Includes:
We will need to have quite a few included files for this server to work (though we aren’t actually using some of them at this point), we will also likely be adding additional ones in the future, but for the purposes of this tutorial we just need the following:

iostream, list, string, Runnable.h, Socket.h, Thread.h and Mutex.h

Include using the following code:
#include <iostream>
#include <list>
#include <string>
#include “Runnable.h”
#include “Socket.h”
#include “Thread.h”
#include “Mutex.h”



And we will want to be using the standard namespace in this file, so you might as well say you are using it:
using namespace std;


Listener:
We now need to create something responsible for handling the incoming client socket connections. This will take the form of a Runnable class derivative (which is required if we are to use the Thread class I defined above).

Here is the code:
class Listener: public Runnable{
private:
  Socket* client;
public:
  Listener(Socket* cli){
    client = cli;
  }
  ~Listener(){
    ceaseThread = true;
    delete client;
    killThread = true;
  }
  void tick(void){
    string in;
    if(!client->recv(in)){
      delete this;
    }
    if(!in.empty()){
      cout << in << endl;
      client->send(“Hello Server\r\n”);
      delete this;
    }
  }
  void tack(void){
    // does nothing.
  }
};


Now, the Runnable interface has 2 variables (ceaseThread and killThead) both of which are Booleans. If you set the ceaseThread to true it will stop the thread, if you set killThread to true it will deallocate the thread memory. Runnable also requires 2 functions (tick and tack), tick is run each time the thread is called, and tack it called if tick throws an error (to allow you to attempt to recover).

In our tick function we see if anything has been sent from the client, if we error (the recv function returns false) we just delete the thread and client socket (this will change later). Then, if the input string isn’t empty we output the request, send a small reply, and then destroy the thread – as we said in the beginning this is just a very simple server at this point.

Connector:
The next class we need to create is the server connector class, which will be responsible for listening to port 80 (standard HTTP request port).

Here is the code:
 class Connector: public Runnable{
private:
  Socket* server;
public:
  Connector(Socket* s){
    server = s;
  }
  ~Connector(){
    ceaseThread = true;
    delete server;
    killThread = true;
  }
  void tick(void){
    Socket* client = new Socket();
    server->accept(*client);
    Runnable* listen = new Listener(client);
    Thread* listener = new Thread(listen);
    listener->start(); // at this point the server orphans the thread so it has to be able to clean up after itself.
  }
  void tack(void){
    // empty again
  }
};


Since this class is very similar to the Listener class I am just going to explain the tick function. It creates a new client socket and then has the server accept it. Then is creates a listener for the client, and a thread for the listener. The last thing it does is starts the listener thread.

Main:
That’s correct it is finally time for the main function. After this we will have a nice, small, simple, server which is capable of taking incoming requests and replying “Hello Server”. When you look at it like that it isn’t all that impressive, but the job as a whole was quite a bit of work.

The code:
int main(void){
  Socket* server = new Socket();
  if(!server->create()){
    cout << “Error – unable to create socket.” << endl;
  }
  if(!server->bind(80)){
    cout << “Error – unable to bind port 80, please make sure you are running the server as root.” << endl;
  }
  if(!server->listen()){
    cout << “Error – unable to listen.” << endl;
  }
  Runnable* serverListener = new Connector(server);
  Thread listenerThread(serverListener);
  listenerThread.start();
  while(1){
    // keep the program up and running
  }
  return 0;
}


Save the file, open a terminal and go to the directory you are using to house this project (cd ~/Desktop/SFS/ on my setup) and make the project (make).

Once it has completed you shouldn’t have any errors or warnings, and it is time to run your new server. Go ahead and type sudo ./main, the program will seem to just hang. That is fine (remember we don’t have it outputting anything other than errors and the HTTP requests it receives), open up your web browser and go to localhost, you should have a bit of output in the terminal now, and the web page should say “Hello Server”. Congrats you have completed this portion of the tutorial!

If you want to look at the request structure that is fine, we will have to respond in a similar fashion for the browser to know what we are talking about and how to deal with the content we give it.

Hopefully you enjoyed this tutorial. Stay tuned for the next one in the series.

Is This A Good Question/Topic? 2
  • +

Replies To: [Part 1] Server From Scratch

#2 mttcttrll  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 02-July 11

Posted 02 July 2011 - 11:39 AM

That was a great tutorial, thank you.

I tried to see if you had done this already, but I couldn't find anything.

Any chance you could do a tut (even a simple one) on those first 5-6 files breaking it down.

Thanks for the great work.
Was This Post Helpful? 0
  • +
  • -

#3 BetaWar  Icon User is offline

  • #include "soul.h"
  • member icon

Reputation: 1171
  • View blog
  • Posts: 7,219
  • Joined: 07-September 06

Posted 28 July 2011 - 01:51 PM

Sure thing, I'll write a tutorial on the supplied files when I get a chance (have the time to do so).

As things are currently I have found a few errors which cause segmentation faults in the code (mostly due to flaws in my Thread and Socket classes). I will be updating those files and as a result the tutorial here. I have part of the next tutorial in the series done, but I will probably just start over once I get this one updated.

Glad you enjoyed the tutorial.
Was This Post Helpful? 0
  • +
  • -

#4 Witnes  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 09-October 13

Posted 09 October 2013 - 02:10 PM

In the beginning sorry for bumping topic, but I wanted to say it's really good and interesting tutorial.

I have a question, is there anywhere part 2 of this tutorial, becouse i can't find it. If not, is there any chance u can update this part or just write a short part 2 and explain code in first files (sockets, threads and mutex)? It would really help especially for people that didn't use this things before. I think it's same thing that mttcttrll was asking about, even if it was some time ago this information will probably help other users in future.

Thanks for helping and for great tutorial.
Was This Post Helpful? 0
  • +
  • -

#5 BetaWar  Icon User is offline

  • #include "soul.h"
  • member icon

Reputation: 1171
  • View blog
  • Posts: 7,219
  • Joined: 07-September 06

Posted 09 October 2013 - 02:24 PM

At the moment there is no part 2 written for this tutorial. I am planning on writing it at some point (and actually had all the code for it done at one point, but the harddrive it was on crashed, hence the reason I never got around to actually writing the tutorial).

Sure, I can write up an explanation about the threads and socket, I will hopefully have time this weekend.
Was This Post Helpful? 0
  • +
  • -

#6 spartan322  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 33
  • Joined: 28-October 13

Posted 29 October 2013 - 10:46 AM

This seems useful so interesting
Was This Post Helpful? 0
  • +
  • -

#7 Gabi9208  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 2
  • Joined: 19-April 14

Posted 27 April 2014 - 08:02 AM

Very interesting....
I have a question....do you have for windows too?
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1