// 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_