// Copyright (C) 2003 Davis E. King (davis@dlib.net)
// License: Boost Software License See LICENSE.txt for the full license.
#ifndef DLIB_SERVER_KERNEL_1_
#define DLIB_SERVER_KERNEL_1_
#include "server_kernel_abstract.h"
// non-templatable dependencies
#include "../threads.h"
#include "../sockets.h"
#include <string>
#include "../algs.h"
#include "../logger.h"
namespace dlib
{
template <
typename set_of_connections
>
class server_kernel_1
{
/*!
REQUIREMENTS ON set_of_connections
implements set/set_kernel_abstract.h or hash_set/hash_set_kernel_abstract.h
and is a set/hash_set of dlib::connection*
INITIAL VALUE
listening_port == 0
listening_ip == ""
running == false
shutting_down == false
cons.size() == 0
listening_port_mutex == a mutex
listening_ip_mutex == a mutex
running_mutex == a mutex
running_signaler == a signaler associated with running_mutex
shutting_down_mutex == a mutex
cons_mutex == a mutex
thread_count == 0
thread_count_mutex == a mutex
thread_count_signaler == a signaler associated with thread_count_mutex
thread_count_zero == a signaler associated with thread_count_mutex
max_connections == 0
max_connections_mutex == a mutex for max_connections
CONVENTION
listening_port == get_listening_port()
listening_ip == get_listening_ip()
running == is_running()
shutting_down == true while clear() is running. this
bool is used to tell the thread blocked on
accept that it should terminate
cons == a set containing all open connections
listening_port_mutex == a mutex for listening_port
listening_ip_mutex == a mutex for listening_ip
running_mutex == a mutex for running
running_signaler == a signaler for running and
is associated with running_mutex. it is
used to signal when running is false
shutting_down_mutex == a mutex for shutting_down
cons_mutex == a mutex for cons
thread_count == the number of threads currently running
thread_count_mutex == a mutex for thread_count
thread_count_signaler == a signaler for thread_count and
is associated with thread_count_mutex. it
is used to signal when thread_count is
decremented
thread_count_zero == a signaler for thread_count and
is associated with thread_count_mutex. it
is used to signal when thread_count becomes
zero
max_connections == get_max_connections()
max_connections_mutex == a mutex for max_connections
!*/
// this structure is used to pass parameters to new threads
struct param
{
param (
server_kernel_1<set_of_connections>& server_,
connection& new_connection_
) :
server(server_),
new_connection(new_connection_)
{}
server_kernel_1<set_of_connections>& server;
connection& new_connection;
};
public:
server_kernel_1(
);
virtual ~server_kernel_1(
);
void clear(
);
void start (
);
bool is_running (
) const;
const std::string get_listening_ip (
) const;
int get_listening_port (
) const;
void set_listening_port (
int port
);
void set_listening_ip (
const std::string& ip
);
void set_max_connections (
int max
);
int get_max_connections (
) const;
private:
virtual void on_connect (
connection& new_connection
)=0;
virtual void on_listening_port_assigned (
) {}
const static logger sdlog;
static void service_connection(
void* item
);
/*!
requires
item is a pointer to a param struct
ensures
services the new connection
will take care of closing the connection and
adding the connection to cons when it first starts and
remove the connection from cons and signal that it has
done so when it ends
!*/
// data members
int listening_port;
std::string listening_ip;
bool running;
bool shutting_down;
set_of_connections cons;
mutex listening_port_mutex;
mutex listening_ip_mutex;
mutex running_mutex;
signaler running_signaler;
mutex shutting_down_mutex;
mutex cons_mutex;
int thread_count;
mutex thread_count_mutex;
signaler thread_count_signaler;
int max_connections;
mutex max_connections_mutex;
signaler thread_count_zero;
// restricted functions
server_kernel_1(server_kernel_1<set_of_connections>&);
server_kernel_1<set_of_connections>& operator= (
server_kernel_1<set_of_connections>&
);
};
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// member function definitions
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
template <
typename set_of_connections
>
server_kernel_1<set_of_connections>::
server_kernel_1 (
) :
listening_port(0),
running(false),
shutting_down(false),
running_signaler(running_mutex),
thread_count(0),
thread_count_signaler(thread_count_mutex),
max_connections(0),
thread_count_zero(thread_count_mutex)
{
}
// ----------------------------------------------------------------------------------------
template <
typename set_of_connections
>
server_kernel_1<set_of_connections>::
~server_kernel_1 (
)
{
clear();
}
// ----------------------------------------------------------------------------------------
template <
typename set_of_connections
>
int server_kernel_1<set_of_connections>::
get_max_connections (
) const
{
max_connections_mutex.lock();
int temp = max_connections;
max_connections_mutex.unlock();
return temp;
}
// ----------------------------------------------------------------------------------------
template <
typename set_of_connections
>
void server_kernel_1<set_of_connections>::
set_max_connections (
int max
)
{
max_connections_mutex.lock();
max_connections = max;
max_connections_mutex.unlock();
}
// ----------------------------------------------------------------------------------------
template <
typename set_of_connections
>
void server_kernel_1<set_of_connections>::
clear (
)
{
// signal that we are shutting down
shutting_down_mutex.lock();
shutting_down = true;
shutting_down_mutex.unlock();
max_connections_mutex.lock();
listening_port_mutex.lock();
listening_ip_mutex.lock();
listening_ip = "";
listening_port = 0;
max_connections = 0;
listening_port_mutex.unlock();
listening_ip_mutex.unlock();
max_connections_mutex.unlock();
// tell all the connections to shut down
cons_mutex.lock();
connection* temp;
while (cons.size() > 0)
{
cons.remove_any(temp);
temp->shutdown();
}
cons_mutex.unlock();
// wait for all the connections to shut down
thread_count_mutex.lock();
while (thread_count > 0)
{
thread_count_zero.wait();
}
thread_count_mutex.unlock();
// wait for the listener to close
running_mutex.lock();
while (running == true)
{
running_signaler.wait();
}
running_mutex.unlock();
// signal that the shutdown is complete
shutting_down_mutex.lock();
shutting_down = false;
shutting_down_mutex.unlock();
}
// ----------------------------------------------------------------------------------------
template <
typename set_of_connections
>
void server_kernel_1<set_of_connections>::
start (
)
{
listener* sock;
int status = create_listener(sock,listening_port,listening_ip);
// if there was an error then clear this object
if (status < 0)
{
max_connections_mutex.lock();
listening_port_mutex.lock();
listening_ip_mutex.lock();
listening_ip = "";
listening_port = 0;
max_connections = 0;
listening_port_mutex.unlock();
listening_ip_mutex.unlock();
max_connections_mutex.unlock();
}
// throw an exception for the error
if (status == PORTINUSE)
{
throw dlib::socket_error(
EPORT_IN_USE,
"error occurred in server_kernel_1::start()\nport already in use"
);
}
else if (status == OTHER_ERROR)
{
throw dlib::socket_error(
"error occurred in server_kernel_1::start()\nunable to crate listener"
);
}
running_mutex.lock();
running = true;
running_mutex.unlock();
// determine the listening port
bool port_assigned = false;
listening_port_mutex.lock();
if (listening_port == 0)
{
port_assigned = true;
listening_port = sock->get_listening_port();
}
listening_port_mutex.unlock();
if (port_assigned)
on_listening_port_assigned();
connection* client;
bool exit = false;
while ( true )
{
// accept the next connection
status = sock->accept(client,1000);
// if there was an error then quit the loop
if (status == OTHER_ERROR)
{
break;
}
shutting_down_mutex.lock();
// if we are shutting down then signal that we should quit the loop
exit = shutting_down;
shutting_down_mutex.unlock();
// if we should be shutting down
if (exit)
{
// if a connection was opened then close it
if (status == 0)
delete client;
break;
}
// if the accept timed out
if (status == TIMEOUT)
{
continue;
}
// add this new connection to cons
cons_mutex.lock();
connection* client_temp = client;
try{cons.add(client_temp);}
catch(...)
{
delete sock;
delete client;
cons_mutex.unlock();
// signal that we are not running start() anymore
running_mutex.lock();
running = false;
running_signaler.broadcast();
running_mutex.unlock();
clear();
throw;
}
cons_mutex.unlock();
// make a param structure
param* temp = 0;
try{
temp = new param (
*this,
*client
);
} catch (...)
{
delete sock;
delete client;
running_mutex.lock();
running = false;
running_signaler.broadcast();
running_mutex.unlock();
clear();
throw;
}
// if create_new_thread failed
if (!create_new_thread(service_connection,temp))
{
delete temp;
// close the listening socket
delete sock;
// close the new connection and remove it from cons
cons_mutex.lock();
connection* ctemp;
if (cons.is_member(client))
{
cons.remove(client,ctemp);
}
delete client;
cons_mutex.unlock();
// signal that the listener has closed
running_mutex.lock();
running = false;
running_signaler.broadcast();
running_mutex.unlock();
// make sure the object is cleared
clear();
// throw the exception
throw dlib::thread_error(
ECREATE_THREAD,
"error occurred in server_kernel_1::start()\nunable to start thread"
);
}
// if we made the new thread then update thread_count
else
{
// increment the thread count
thread_count_mutex.lock();
++thread_count;
if (thread_count == 0)
thread_count_zero.broadcast();
thread_count_mutex.unlock();
}
// check if we have hit the maximum allowed number of connections
max_connections_mutex.lock();
// if max_connections is zero or the loop is ending then skip this
if (max_connections != 0)
{
// wait for thread_count to be less than max_connections
thread_count_mutex.lock();
while (thread_count >= max_connections)
{
max_connections_mutex.unlock();
thread_count_signaler.wait();
max_connections_mutex.lock();
// if we are shutting down the quit the loop
shutting_down_mutex.lock();
exit = shutting_down;
shutting_down_mutex.unlock();
if (exit)
break;
}
thread_count_mutex.unlock();
}
max_connections_mutex.unlock();
if (exit)
{
break;
}
} //while ( true )
// close the socket
delete sock;
// signal that the listener has closed
running_mutex.lock();
running = false;
running_signaler.broadcast();
running_mutex.unlock();
// if there was an error with accept then throw an exception
if (status == OTHER_ERROR)
{
// make sure the object is cleared
clear();
// throw the exception
throw dlib::socket_error(
"error occurred in server_kernel_1::start()\nlistening socket returned error"
);
}
}
// ----------------------------------------------------------------------------------------
template <
typename set_of_connections
>
bool server_kernel_1<set_of_connections>::
is_running (
) const
{
running_mutex.lock();
bool temp = running;
running_mutex.unlock();
return temp;
}
// ----------------------------------------------------------------------------------------
template <
typename set_of_connections
>
const std::string server_kernel_1<set_of_connections>::
get_listening_ip (
) const
{
listening_ip_mutex.lock();
std::string ip(listening_ip);
listening_ip_mutex.unlock();
return ip;
}
// ----------------------------------------------------------------------------------------
template <
typename set_of_connections
>
int server_kernel_1<set_of_connections>::
get_listening_port (
) const
{
listening_port_mutex.lock();
int port = listening_port;
listening_port_mutex.unlock();
return port;
}
// ----------------------------------------------------------------------------------------
template <
typename set_of_connections
>
void server_kernel_1<set_of_connections>::
set_listening_port (
int port
)
{
listening_port_mutex.lock();
listening_port = port;
listening_port_mutex.unlock();
}
// ----------------------------------------------------------------------------------------
template <
typename set_of_connections
>
void server_kernel_1<set_of_connections>::
set_listening_ip (
const std::string& ip
)
{
listening_ip_mutex.lock();
listening_ip = ip;
listening_ip_mutex.unlock();
}
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// static member function definitions
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
template <
typename soc
>
const logger server_kernel_1<soc>::sdlog("dlib.server");
template <
typename soc
>
void server_kernel_1<soc>::
service_connection(
void* item
)
{
param& p = *reinterpret_cast<param*>(item);
p.server.on_connect(p.new_connection);
// remove this connection from cons and close it
p.server.cons_mutex.lock();
connection* temp;
if (p.server.cons.is_member(&p.new_connection))
p.server.cons.remove(&p.new_connection,temp);
try{ close_gracefully(&p.new_connection); }
catch (...) { sdlog << LERROR << "close_gracefully() threw"; }
p.server.cons_mutex.unlock();
// decrement the thread count and signal if it is now zero
p.server.thread_count_mutex.lock();
--p.server.thread_count;
p.server.thread_count_signaler.broadcast();
if (p.server.thread_count == 0)
p.server.thread_count_zero.broadcast();
p.server.thread_count_mutex.unlock();
delete &p;
}
// ----------------------------------------------------------------------------------------
}
#endif // DLIB_SERVER_KERNEL_1_