-
-
Save ramntry/3827445 to your computer and use it in GitHub Desktop.
#include <ctime> | |
#include <string> | |
#include <iostream> | |
#include <boost/bind.hpp> | |
#include <boost/asio.hpp> | |
#include <boost/thread.hpp> | |
#include <boost/format.hpp> | |
#include <boost/smart_ptr.hpp> | |
using boost::asio::ip::tcp; | |
class client_session | |
{ | |
public: | |
typedef boost::shared_ptr<client_session> pointer; | |
static pointer create(boost::asio::io_service &io) { | |
return pointer(new client_session(io)); | |
} | |
~client_session() { | |
log("Connection closed"); | |
} | |
tcp::socket &socket() { return socket_; } | |
void start() { | |
log("Connection established"); | |
std::string time_string; | |
for (int i = 0; i < 5; ++i) { | |
time_string = make_time_string(); | |
boost::asio::write(socket_, boost::asio::buffer(time_string)); | |
sleep(1); | |
} | |
} | |
protected: | |
client_session(boost::asio::io_service &io) | |
: socket_(io) { | |
} | |
std::string make_time_string() { | |
char buf[32]; | |
time_t now = time(0); | |
return ctime_r(&now, buf); | |
} | |
void log(std::string const &message) { | |
std::clog << boost::format("%|-25| [client address: %|15|]\n") | |
% message % socket_.remote_endpoint().address().to_string(); | |
} | |
private: | |
tcp::socket socket_; | |
}; | |
int main() | |
{ | |
boost::asio::io_service io; | |
tcp::acceptor acceptor(io, tcp::endpoint(tcp::v4(), 13)); | |
for (;;) { | |
client_session::pointer new_client = client_session::create(io); | |
acceptor.accept(new_client->socket()); | |
boost::thread(boost::bind(&client_session::start, new_client)).detach(); | |
} | |
} |
CONFIG -= qt | |
CONFIG += debug | |
CONFIG += thread | |
boost_path = /home/ramntry/boost/boost_1_50_0 | |
INCLUDEPATH += \ | |
$${boost_path} \ | |
LIBS += \ | |
$${boost_path}/stage/lib/libboost_system.a \ | |
$${boost_path}/stage/lib/libboost_thread.a \ | |
SOURCES = \ | |
simple_daytime_server.cpp \ |
13: Класс client_session - это своеобразный хранитель (существует одноименный паттерн, хотя, надо признать, наш случай лишь общеидеологически с ним перекликается) части внутреннего состояния сервера, индивидуальной для каждого клиента. Экземпляр этого класса создается на каждого клиента в количестве одной штуки, живет столько же, сколько соединение, его породившее. Парадигма не меняется при переходе к асинхронной модели, скорее даже напротив, становится очевиднее ее значение: сервер, выполняясь в одном (для простоты) потоке постоянно "бегает" по установившим соединение клиетам, меж делом устанавливает новые или завершает существующие соединения, и в каждом случае ему нужно "вспомнить", что уже было сделано для клиента, что еще нет - нужно уметь восстанавливать контекст работы. Класс client_session и является таким контекстом.
16: Даже для очень простых серверов актуальна проблема - не совсем очевидно, когда, как и кто будет уничтожать экземпляр класса client_session. Некоторая специфика работы классов Boost.Thread позволяет удобно использовать тот факт, что поток (thread), закончив всю полезную работу, уничтожается самостоятельно - а с уничтожением своего стека вызовет деструкторы всех объектов, созданных на нем. Маленькая трудность состоит в том, что экземпляр класса client_session намного удобнее создавать еще в родительском потоке, тогда, когда стека дочернего потока, как и самого потока, еще не существует. Перенос объекта со стека на стек - нетривиальная задача, адекватно решаемая только по схеме копирование - уничтожение оригинала, которая не всегда подходит (например, копирование сокета как уникального системного идентификатора (дескриптора) может не пройти гладко) потому проще поступить иначе - создать экземпляр класса client_session в динамической памяти, общей для всех потоков, а на стек дочернего потока скопировать умный указатель на созданный экземпляр. На стеке родительского потока этот указатель уничтожается, так что счетчик ссылок этого указателя меняется так: 1 - 2 - 1 (очевидно). Объект остается жить ровно столько, сколько живет поток - со смертью потока умирает умный указатель (см. выше), счетчик ссылок обнуляется и экземпляр client_session уничтожается. Это нам и нужно. typedef же крайне удобен и улучшает инкапсуляцию - клиентскому коду не нужно знать, какова реализация умного указателя (да и умен ли он вовсе :-))
18: Удобно защитить самого себя от ошибок: согласно описанной выше идеоме корректен лишь один способ создания экземпляра - на куче с обязательным оборачиванием в умный указатель. Потому все "альтернативные" способы, по возможности, следует запрещать (см. 40: - конструктор защищен и не может быть использован вне класса). Для простоты не запрещались копирующий конструктор и оператор присваивания, что стоит, вообще говоря, сделать (не обязательно "руками", к примеру в boost есть маленький класс-примесь noncopyable, достаточно от него отнаследоваться)
26: Сокет на момент создания экземпляра не ассоциирован ни с каким соединением - это должен сделать главный цикл сервера в методе accept - потому необходимо дать доступ к сокету.
28: Главный метод класса, осуществляющий всю работу с клиентом - код именно этого метода будет выполняться в отдельном потоке, стек именно этого метода - корневой стек дочернего потока. С завершением этого метода разворачиваются события, описанные в 16:
32: Сервер пятикратно отправляет клиенту актуальные дату и время с задержкой в секунду. Так не нужно, так удобно - можно успеть запустить одновременно несколько клиентов и убедиться, что сервер действительно обслуживает их одновременно.
34: Глобальная функция boost::asio::write осуществляет блокирующую запись в сокет всего содержимого переданного буфера (в отличие от методов сокетов вида "write_some", способных завершиться не закончив передачу всех данных). Концепция "буфера" нетривиальна, и следует посмотреть документацию, чтобы прояснить ее себе.
61: Все потенциально асинхронные сервисы в Boost.Asio создаются поверх экземпляра io_service. Подробности в документации Boost.Asio.
63: acceptor - всего лишь специализация сокета (прослушивающая), в данном случае он связывается (bind) с любым адресом IPv4 и портом 13 (порт сервиса даты и времени)
64: Режим работы большинства реальных серверов - бесконечный. Большая часть серверов запускается как демоны (daemon) в терминах Unix или службы - в терминах Windows. Останов хорошо спроектированного сервера выполняется подачей ему сигнала (в linux традиционно SIGUSR1), для которого существует специальный обработчик. Регистрация обработчика осуществляется системным вызовом, а управление ему впоследствии передается по аппаратному прерыванию ядром ОС, потому главный поток сервера действительно выйдет из этого якобы неразрывного цикла.
65: "Правильно" (см. 16:) создаем экземпляр client_session для будущего клиента.
64: Осуществляем блокирующее ожидание нового подключения в основном потоке на сокете заранее подготовленного контекста (см. 13:) подключения (потому сервер и синхронный - все операции в рамках одного конкретно взятого потока блокирующие)
65: Создаем новый поток, захватываем с помощью boost::bind умный указатель на client_session в функтор (теперь он "удерживает" счетчик ссылок от падения в ноль), попутно превращая метод start класса client_session в нульарную функцию, которую и требует конструктор boost::thread, запускаем в этом потоке таким полученную функцию, а сам поток отсоединяем (detach) от основного - теперь со смертью экземпляра thread (а она наступает незамедлительно) описываемый им поток продолжает жить самостоятельно, уже не имея в основном потоке никаких средств обращения к себе.
13: Класс client_session - это своеобразный хранитель (существует одноименный паттерн, хотя, надо признать, наш случай лишь общеидеологически с ним перекликается) части внутреннего состояния сервера, индивидуальной для каждого клиента. Экземпляр этого класса создается на каждого клиента в количестве одной штуки, живет столько же, сколько соединение, его породившее. Парадигма не меняется при переходе к асинхронной модели, скорее даже напротив, становится очевиднее ее значение: сервер, выполняясь в одном (для простоты) потоке постоянно "бегает" по установившим соединение клиетам, меж делом устанавливает новые или завершает существующие соединения, и в каждом случае ему нужно "вспомнить", что уже было сделано для клиента, что еще нет - нужно уметь восстанавливать контекст работы. Класс client_session и является таким контекстом.
16: Даже для очень простых серверов актуальна проблема - не совсем очевидно, когда, как и кто будет уничтожать экземпляр класса client_session. Некоторая специфика работы классов Boost.Thread позволяет удобно использовать тот факт, что поток (thread), закончив всю полезную работу, уничтожается самостоятельно - а с уничтожением своего стека вызовет деструкторы всех объектов, созданных на нем. Маленькая трудность состоит в том, что экземпляр класса client_session намного удобнее создавать еще в родительском потоке, тогда, когда стека дочернего потока, как и самого потока, еще не существует. Перенос объекта со стека на стек - нетривиальная задача, адекватно решаемая только по схеме копирование - уничтожение оригинала, которая не всегда подходит (например, копирование сокета как уникального системного идентификатора (дескриптора) может не пройти гладко) потому проще поступить иначе - создать экземпляр класса client_session в динамической памяти, общей для всех потоков, а на стек дочернего потока скопировать умный указатель на созданный экземпляр. На стеке родительского потока этот указатель уничтожается, так что счетчик ссылок этого указателя меняется так: 1 - 2 - 1 (очевидно). Объект остается жить ровно столько, сколько живет поток - со смертью потока умирает умный указатель (см. выше), счетчик ссылок обнуляется и экземпляр client_session уничтожается. Это нам и нужно. typedef же крайне удобен и улучшает инкапсуляцию - клиентскому коду не нужно знать, какова реализация умного указателя (да и умен ли он вовсе :-))
18: Удобно защитить самого себя от ошибок: согласно описанной выше идеоме корректен лишь один способ создания экземпляра - на куче с обязательным оборачиванием в умный указатель. Потому все "альтернативные" способы, по возможности, следует запрещать (см. 40: - конструктор защищен и не может быть использован вне класса). Для простоты не запрещались копирующий конструктор и оператор присваивания, что стоит, вообще говоря, сделать (не обязательно "руками", к примеру в boost есть маленький класс-примесь noncopyable, достаточно от него отнаследоваться)
26: Сокет на момент создания экземпляра не ассоциирован ни с каким соединением - это должен сделать главный цикл сервера в методе accept - потому необходимо дать доступ к сокету.
28: Главный метод класса, осуществляющий всю работу с клиентом - код именно этого метода будет выполняться в отдельном потоке, стек именно этого метода - корневой стек дочернего потока. С завершением этого метода разворачиваются события, описанные в 16:
32: Сервер пятикратно отправляет клиенту актуальные дату и время с задержкой в секунду. Так не нужно, так удобно - можно успеть запустить одновременно несколько клиентов и убедиться, что сервер действительно обслуживает их одновременно.
34: Глобальная функция boost::asio::write осуществляет блокирующую запись в сокет всего содержимого переданного буфера (в отличие от методов сокетов вида "write_some", способных завершиться не закончив передачу всех данных). Концепция "буфера" нетривиальна, и следует посмотреть документацию, чтобы прояснить ее себе.
61: Все потенциально асинхронные сервисы в Boost.Asio создаются поверх экземпляра io_service. Подробности в документации Boost.Asio.
63: acceptor - всего лишь специализация сокета (прослушивающая), в данном случае он связывается (bind) с любым адресом IPv4 и портом 13 (порт сервиса даты и времени)
64: Режим работы большинства реальных серверов - бесконечный. Большая часть серверов запускается как демоны (daemon) в терминах Unix или службы - в терминах Windows. Останов хорошо спроектированного сервера выполняется подачей ему сигнала (в linux традиционно SIGUSR1), для которого существует специальный обработчик. Регистрация обработчика осуществляется системным вызовом, а управление ему впоследствии передается по аппаратному прерыванию ядром ОС, потому главный поток сервера действительно выйдет из этого якобы неразрывного цикла.
65: "Правильно" (см. 16:) создаем экземпляр client_session для будущего клиента.
64: Осуществляем блокирующее ожидание нового подключения в основном потоке на сокете заранее подготовленного контекста (см. 13:) подключения (потому сервер и синхронный - все операции в рамках одного конкретно взятого потока блокирующие)
65: Создаем новый поток, захватываем с помощью boost::bind умный указатель на client_session в функтор (теперь он "удерживает" счетчик ссылок от падения в ноль), попутно превращая метод start класса client_session в нульарную функцию, которую и требует конструктор boost::thread, запускаем в этом потоке таким полученную функцию, а сам поток отсоединяем (detach) от основного - теперь со смертью экземпляра thread (а она наступает незамедлительно) описываемый им поток продолжает жить самостоятельно, уже не имея в основном потоке никаких средств обращения к себе.