596 lines
16 KiB
C++
596 lines
16 KiB
C++
// Copyright (C) 2003 Davis E. King (davis@dlib.net)
|
|
// License: Boost Software License See LICENSE.txt for the full license.
|
|
#ifndef DLIB_SERVER_KERNEL_CPp_
|
|
#define DLIB_SERVER_KERNEL_CPp_
|
|
|
|
#include "server_kernel.h"
|
|
#include "../string.h"
|
|
|
|
namespace dlib
|
|
{
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
server::
|
|
server (
|
|
) :
|
|
listening_port(0),
|
|
running(false),
|
|
shutting_down(false),
|
|
running_signaler(running_mutex),
|
|
thread_count(0),
|
|
thread_count_signaler(thread_count_mutex),
|
|
max_connections(1000),
|
|
thread_count_zero(thread_count_mutex),
|
|
graceful_close_timeout(500)
|
|
{
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
server::
|
|
~server (
|
|
)
|
|
{
|
|
clear();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
unsigned long server::
|
|
get_graceful_close_timeout (
|
|
) const
|
|
{
|
|
auto_mutex lock(max_connections_mutex);
|
|
return graceful_close_timeout;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
void server::
|
|
set_graceful_close_timeout (
|
|
unsigned long timeout
|
|
)
|
|
{
|
|
auto_mutex lock(max_connections_mutex);
|
|
graceful_close_timeout = timeout;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
|
|
int server::
|
|
get_max_connections (
|
|
) const
|
|
{
|
|
max_connections_mutex.lock();
|
|
int temp = max_connections;
|
|
max_connections_mutex.unlock();
|
|
return temp;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
void server::
|
|
set_max_connections (
|
|
int max
|
|
)
|
|
{
|
|
// make sure requires clause is not broken
|
|
DLIB_CASSERT(
|
|
max >= 0 ,
|
|
"\tvoid server::set_max_connections"
|
|
<< "\n\tmax == " << max
|
|
<< "\n\tthis: " << this
|
|
);
|
|
|
|
max_connections_mutex.lock();
|
|
max_connections = max;
|
|
max_connections_mutex.unlock();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
void server::
|
|
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 = 1000;
|
|
graceful_close_timeout = 500;
|
|
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();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
void server::
|
|
start_async_helper (
|
|
)
|
|
{
|
|
try
|
|
{
|
|
start_accepting_connections();
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
sdlog << LERROR << e.what();
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
void server::
|
|
start_async (
|
|
)
|
|
{
|
|
auto_mutex lock(running_mutex);
|
|
if (running)
|
|
return;
|
|
|
|
// Any exceptions likely to be thrown by the server are going to be
|
|
// thrown when trying to bind the port. So calling this here rather
|
|
// than in the thread we are about to make will cause start_async()
|
|
// to report errors back to the user in a very straight forward way.
|
|
open_listening_socket();
|
|
|
|
async_start_thread.reset(new thread_function(make_mfp(*this,&server::start_async_helper)));
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
void server::
|
|
open_listening_socket (
|
|
)
|
|
{
|
|
if (!sock)
|
|
{
|
|
int status = create_listener(sock,listening_port,listening_ip);
|
|
const int port_used = listening_port;
|
|
|
|
// 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 = 1000;
|
|
graceful_close_timeout = 500;
|
|
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::start()\nport " + cast_to_string(port_used) + " already in use"
|
|
);
|
|
}
|
|
else if (status == OTHER_ERROR)
|
|
{
|
|
throw dlib::socket_error(
|
|
"error occurred in server::start()\nunable to create listener"
|
|
);
|
|
}
|
|
}
|
|
|
|
running_mutex.lock();
|
|
running = true;
|
|
running_mutex.unlock();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
void server::
|
|
start (
|
|
)
|
|
{
|
|
// make sure requires clause is not broken
|
|
DLIB_CASSERT(
|
|
this->is_running() == false,
|
|
"\tvoid server::start"
|
|
<< "\n\tis_running() == " << this->is_running()
|
|
<< "\n\tthis: " << this
|
|
);
|
|
|
|
start_accepting_connections();
|
|
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
void server::
|
|
start_accepting_connections (
|
|
)
|
|
{
|
|
open_listening_socket();
|
|
|
|
// 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();
|
|
|
|
|
|
|
|
int status = 0;
|
|
|
|
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(...)
|
|
{
|
|
sock.reset();
|
|
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,
|
|
get_graceful_close_timeout()
|
|
);
|
|
} catch (...)
|
|
{
|
|
sock.reset();
|
|
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
|
|
sock.reset();
|
|
|
|
// 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::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
|
|
sock.reset();
|
|
|
|
// 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::start()\nlistening socket returned error"
|
|
);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
bool server::
|
|
is_running (
|
|
) const
|
|
{
|
|
running_mutex.lock();
|
|
bool temp = running;
|
|
running_mutex.unlock();
|
|
return temp;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
const std::string server::
|
|
get_listening_ip (
|
|
) const
|
|
{
|
|
listening_ip_mutex.lock();
|
|
std::string ip(listening_ip);
|
|
listening_ip_mutex.unlock();
|
|
return ip;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
int server::
|
|
get_listening_port (
|
|
) const
|
|
{
|
|
listening_port_mutex.lock();
|
|
int port = listening_port;
|
|
listening_port_mutex.unlock();
|
|
return port;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
void server::
|
|
set_listening_port (
|
|
int port
|
|
)
|
|
{
|
|
// make sure requires clause is not broken
|
|
DLIB_CASSERT(
|
|
( port >= 0 &&
|
|
this->is_running() == false ),
|
|
"\tvoid server::set_listening_port"
|
|
<< "\n\tport == " << port
|
|
<< "\n\tis_running() == " << this->is_running()
|
|
<< "\n\tthis: " << this
|
|
);
|
|
|
|
listening_port_mutex.lock();
|
|
listening_port = port;
|
|
listening_port_mutex.unlock();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
void server::
|
|
set_listening_ip (
|
|
const std::string& ip
|
|
)
|
|
{
|
|
// make sure requires clause is not broken
|
|
DLIB_CASSERT(
|
|
( ( is_ip_address(ip) || ip == "" ) &&
|
|
this->is_running() == false ),
|
|
"\tvoid server::set_listening_ip"
|
|
<< "\n\tip == " << ip
|
|
<< "\n\tis_running() == " << this->is_running()
|
|
<< "\n\tthis: " << this
|
|
);
|
|
|
|
listening_ip_mutex.lock();
|
|
listening_ip = ip;
|
|
listening_ip_mutex.unlock();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
// static member function definitions
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
const logger server::sdlog("dlib.server");
|
|
|
|
void server::
|
|
service_connection(
|
|
void* item
|
|
)
|
|
{
|
|
param& p = *static_cast<param*>(item);
|
|
|
|
|
|
p.the_server.on_connect(p.new_connection);
|
|
|
|
|
|
// remove this connection from cons and close it
|
|
p.the_server.cons_mutex.lock();
|
|
connection* temp;
|
|
if (p.the_server.cons.is_member(&p.new_connection))
|
|
p.the_server.cons.remove(&p.new_connection,temp);
|
|
p.the_server.cons_mutex.unlock();
|
|
|
|
try{ close_gracefully(&p.new_connection, p.graceful_close_timeout); }
|
|
catch (...) { sdlog << LERROR << "close_gracefully() threw"; }
|
|
|
|
// decrement the thread count and signal if it is now zero
|
|
p.the_server.thread_count_mutex.lock();
|
|
--p.the_server.thread_count;
|
|
p.the_server.thread_count_signaler.broadcast();
|
|
if (p.the_server.thread_count == 0)
|
|
p.the_server.thread_count_zero.broadcast();
|
|
p.the_server.thread_count_mutex.unlock();
|
|
|
|
delete &p;
|
|
|
|
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
}
|
|
|
|
#endif // DLIB_SERVER_KERNEL_CPp_
|
|
|