Created
October 3, 2012 15:07
-
-
Save ramntry/3827445 to your computer and use it in GitHub Desktop.
Simple Boost.Asio based synchronous multithreaded server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 \ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 (а она наступает незамедлительно) описываемый им поток продолжает жить самостоятельно, уже не имея в основном потоке никаких средств обращения к себе.