boost asio для сервера синхронизации, поддерживающего открытую сессию TCP (с протобуферами Google) - PullRequest
6 голосов
/ 12 августа 2011

У меня в настоящее время есть очень простой сервер boost :: asio, который отправляет обновление статуса при подключении (используя прото буферы Google):

try
{
  boost::asio::io_service io_service;
  tcp::acceptor acceptor(io_service,tcp::endpoint(tcp::v4(), 13));
  for (;;)
  {
    tcp::socket socket(io_service);
    acceptor.accept(socket);
    ...
    std::stringstream message;
    protoMsg.SerializeToOstream(&message);
    boost::system::error_code ignored_error;
    boost::asio::write(socket, boost::asio::buffer(message.str()), ignored_error);
  }
}
catch (std::exception& e) { }

Я хотел бы расширить его до первого чтения после принятия новогосоединение, проверьте, какой запрос был получен, и отправьте разные сообщения обратно в зависимости от этого сообщения.Я также хотел бы оставить TCP-соединение открытым, чтобы клиенту не приходилось повторно подключаться, и он хотел бы обрабатывать несколько клиентов (не многих, возможно, 2 или 3).

Я посмотрелна нескольких примерах на Boost Asio, а именно асинхронный tcp-сервер и чат-сервер , но оба они мне не нравятся.Я даже не понимаю, нужен ли мне асинхронный сервер.Думаю, я мог бы просто прочитать после acceptor.accept(socket), но тогда я бы не стал слушать дальнейшие запросы.И если я пойду в цикл, я думаю, это будет означать, что я смогу справиться только с одним клиентом.Итак, я думаю, это означает, что я должен идти асинхронно?Есть более простой пример, может быть, это не 250 строк кода?Или мне просто нужно пробиться сквозь эти примеры?Спасибо

Ответы [ 2 ]

12 голосов
/ 12 августа 2011

Примеры, которые вы упомянули в документации Boost.Asio, на самом деле очень хороши, чтобы увидеть, как все работает. Вы правы, что поначалу это может показаться немного сложным для понимания, особенно если вы новичок в этих понятиях. Однако я бы порекомендовал вам начать с примера с сервером чата и получить его на своей машине. Это позволит вам поближе взглянуть на вещи и начать что-то менять, чтобы узнать, как это работает. Позвольте мне рассказать вам о нескольких вещах, которые я считаю важными для начала.

Из вашего описания того, что вы хотите сделать, кажется, что сервер чата дает вам хорошую отправную точку, поскольку у него уже есть похожие кусочки, которые вам нужны. Асинхронный сервер - это то, что вам нужно, так как вы можете легко обрабатывать несколько клиентов в одном потоке. Ничего сложного с самого начала.

Упрощенный, асинхронный в данном случае означает, что ваш сервер обрабатывает очередь, берет обработчик (задачу) и выполняет его. Если в очереди ничего нет, он просто ждет, когда что-то будет помещено в очередь. В вашем случае это означает, что это может быть соединение от клиента, новое чтение сообщения от клиента или что-то в этом роде. Чтобы это работало, каждый обработчик (функция, обрабатывающая реакцию на конкретное событие) должен быть настроен.

Позвольте мне немного объяснить, используя код из примера сервера чата.

В исходном файле сервера вы видите класс chat_server, который вызывает start_accept в конструкторе. Здесь устанавливается обработчик принятия.

void start_accept()
{
    chat_session_ptr new_session(new chat_session(io_service_, room_)); // 1
    acceptor_.async_accept(new_session->socket(),                       // 2
        boost::bind(&chat_server::handle_accept, this, new_session,     // 3
            boost::asio::placeholders::error));                         // 4
}

Строка 1 : создается объект chat_session, представляющий сеанс между одним клиентом и сервером. Для принятия создается сеанс (еще не подключен клиент).

Строка 2 : асинхронный прием для сокета ...

Строка 3 : ... Обязательно позвоните chat_server::handle_accept, когда это произойдет. Сеанс передается для использования первым клиентом, который подключается.

Теперь, если мы посмотрим на handle_accept, мы увидим, что при подключении клиента для сеанса вызывается start (это просто запускает вещи между сервером и этим клиентом). И, наконец, новый запрос будет принят, если другие клиенты также захотят подключиться.

void handle_accept(chat_session_ptr session, 
                   const boost::system::error_code& error)
{
    if (!error)
    {
        session->start();
    }
    start_accept();
}

Это то, что вы тоже хотите иметь. Выдающийся прием для входящих соединений. И если несколько клиентов могут подключиться, всегда должен быть один из этих невыполненных, чтобы сервер мог обработать принятие.

Как сервер и клиент (ы) взаимодействуют - все в сеансе, и вы можете следовать той же схеме и изменить это, чтобы сделать то, что вы хотите. Вы упоминаете, что сервер должен смотреть на то, что отправлено и делать разные вещи. Взгляните на chat_session и функцию start, которая была вызвана сервером в handle_accept.

void start()
{
    room_.join(shared_from_this());
    boost::asio::async_read(socket_,
        boost::asio::buffer(read_msg_.data(), chat_message::header_length),
        boost::bind(
            &chat_session::handle_read_header, shared_from_this(),
            boost::asio::placeholders::error));
}

Здесь важен звонок на boost::asio::async_read. Это то, что вы тоже хотите. Это помещает выдающееся чтение в сокет, так что сервер может читать то, что отправляет клиент. Есть обработчик (функция), который связан с этим событием chat_session::handle_read_header. Это будет вызываться всякий раз, когда сервер читает что-то в сокете. В этой функции-обработчике вы можете начать вводить свой конкретный код, чтобы определить, что делать, если отправлено конкретное сообщение и т. Д.

Важно знать, что при вызове этих асинхронных функций boost :: asio внутри этого вызова ничего не происходит (т. Е. Сокет не читается, если вы вызываете функцию read). Это асинхронный аспект. Вы просто регистрируете обработчик чего-то, и ваш код вызывается, когда это происходит. Следовательно, когда вызывается это чтение, оно немедленно возвращается, и вы возвращаетесь в handle_accept для сервера (если вы будете следовать тому, как все вызывается). И если вы помните, мы также вызываем start_accept, чтобы настроить другое асинхронное принятие. На этом этапе у вас есть два выдающихся обработчика, ожидающих подключения другого клиента или первого клиента, отправляющего что-либо. В зависимости от того, что произойдет первым, будет вызван этот конкретный обработчик.

Также важно понимать, что всякий раз, когда что-то запускается, оно будет работать непрерывно, пока не будет выполнено все, что ему нужно сделать.Другие обработчики должны ждать, даже если есть ожидающие события, которые их инициируют.

Наконец, для запуска сервера вам понадобится io_service, который является центральным понятием в Asio.

io_service.run();

Это одна строка, которую вы видите в функции main.Это просто говорит о том, что поток (только один в примере) должен запустить io_service, который является очередью, в которой обработчики ставятся в очередь, когда есть работа, которую нужно сделать.Когда ничего не происходит, io_service просто ждет (конечно, блокируя основной поток).

Надеюсь, это поможет вам начать то, что вы хотите сделать.Есть много вещей, которые вы можете сделать и чему поучиться.Я считаю это отличным программным обеспечением!Удачи!

3 голосов
/ 13 августа 2011

В случае, если кто-то еще захочет сделать это, вот минимум, чтобы идти выше: (аналогично учебникам, но немного короче и немного по-другому)

class Session : public boost::enable_shared_from_this<Session>
{
    tcp::socket socket;
    char buf[1000];
public:
    Session(boost::asio::io_service& io_service)
        : socket(io_service) { }
    tcp::socket& SocketRef() { return socket; }
    void Read() {
        boost::asio::async_read( socket,boost::asio::buffer(buf),boost::asio::transfer_at_least(1),boost::bind(&Session::Handle_Read,shared_from_this(),boost::asio::placeholders::error));
    }
    void Handle_Read(const boost::system::error_code& error) {
        if (!error)
        {
            //read from buffer and handle requests
            //if you want to write sth, you can do it sync. here: e.g. boost::asio::write(socket, ..., ignored_error);
            Read();
        }
    }
};

typedef boost::shared_ptr<Session> SessionPtr;

class Server
{
    boost::asio::io_service io_service;
    tcp::acceptor acceptor;
public:
    Server() : acceptor(io_service,tcp::endpoint(tcp::v4(), 13)) { }
    ~Server() { }
    void operator()() { StartAccept(); io_service.run(); }
    void StartAccept() {
        SessionPtr session_ptr(new Session(io_service));
        acceptor.async_accept(session_ptr->SocketRef(),boost::bind(&Server::HandleAccept,this,session_ptr,boost::asio::placeholders::error));
    }
    void HandleAccept(SessionPtr session,const boost::system::error_code& error) {
        if (!error)
          session->Read();
        StartAccept();
    }
};

Из того, что я собрал методом проб и ошибок и чтения: я запускаю его в операторе () (), чтобы вы могли запустить его в фоновом режиме в дополнительной теме. Вы запускаете один экземпляр Сервера. Чтобы обрабатывать несколько клиентов, вам нужен дополнительный класс, я назвал его сессионным классом. Для asio, чтобы убрать мертвые сессии, вам нужен общий указатель, как указано выше. В противном случае код должен помочь вам начать работу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...