Простой сервер, использующий Boost.Asio, выдает исключение

Я пытаюсь написать простой сервер с использованием библиотеки Boost.Asio. Я хочу, чтобы мой сервер получил сообщение от клиента и распечатал это сообщение на консоли. Вот код моей серверной программы:

#include <iostream>
#include <string>
#include <memory>

#include <boost/asio.hpp>

using namespace boost::asio;
using namespace boost::system;
using boost::asio::ip::tcp;

class Session : public std::enable_shared_from_this<Session> {
public:
    Session(tcp::socket socket);

    void start();
private:
    tcp::socket socket_;
    std::string data_;
};

Session::Session(tcp::socket socket) : socket_(std::move(socket))
{}

void Session::start()
{
    socket_.async_read_some(buffer(data_), [this](error_code errorCode, size_t length) {
        if (!errorCode) {
            std::cout << "received: " << data_ << std::endl;
        }
        start();
    });
}

class Server {
public:
    Server(io_context& context);
private:
    tcp::acceptor acceptor_;

    void accept();
};

Server::Server(io_context& context) : acceptor_(context, tcp::endpoint(tcp::v4(), 8888))
{
    accept();
}

void Server::accept()
{
    acceptor_.async_accept([this](error_code errorCode, tcp::socket socket) {
        if (!errorCode) {
            std::make_unique<Session>(std::move(socket))->start();
        }
        accept();
    });
}

int main()
{
    boost::asio::io_context context;
    Server server(context);
    context.run();
    return 0;
}

А вот код моей клиентской программы:

#include <iostream>
#include <string>

#include <boost/asio.hpp>

using namespace boost::asio;
using boost::asio::ip::tcp;

int main()
{
    io_context context;
    tcp::socket socket(context);
    tcp::resolver resolver(context);
    connect(socket, resolver.resolve("127.0.0.1", "8888"));
    while (true) {
        try {
            std::string data;
            std::cin >> data;
            write(socket, buffer(data));
        } catch (const std::exception& exception) {
            std::cerr << exception.what() << std::endl;
        }
    }
    return 0;
}

Но когда я запускаю клиента, сервер выдает исключение «нарушение доступа для чтения». Что я делаю не так?

Часть этого сообщения в вашем отладчике будет включать трассировку стека. Посмотрите на это, и он точно скажет вам, что происходит. (Если бы я был букмекером, он бы вращался вокруг вызова boost :: buffer (string) - и он пытался бы записать в недопустимую память. stackoverflow.com/questions/4068249/…

UKMonkey 25.06.2018 19:17

Исключение генерируется функцией buffer(), которая вызывается из моего start().

Count Zero 25.06.2018 19:20

Перечитайте ссылку .... вы найдете ее дубликат (вы обнаружите, что создание изменяемого буфера с помощью std :: string на самом деле не работает - вместо этого используйте std :: vector)

UKMonkey 25.06.2018 19:21

Спасибо за ваш комментарий! Я изменил std::string data_; на std::vector<char> data_ = std::vector<char>(1024); и buffer(data_) на buffer(data_, 1024), но теперь он терпит неудачу при отладочном утверждении с сообщением «векторный итератор не может быть разыменован».

Count Zero 25.06.2018 19:37

«Часть этого сообщения в вашем отладчике будет включать трассировку стека. Посмотрите на это, и он точно расскажет, что происходит» Другая ошибка - другая трассировка стека ... дайте полную информацию в первый раз, и вы получите более точные ответы время ... но на самом деле вам следует потренироваться смотреть на это и понимать, что идет не так. Я предполагаю, что теперь он, вероятно, терпит неудачу в другом месте; возможно потому, что данные имеют размер 1024, но были записаны только байты длины.

UKMonkey 25.06.2018 19:42

Сейчас это от io_context::run().

Count Zero 25.06.2018 19:49

@UKMonkey Я думаю, ты здесь совсем не в теме. Конечно, буфер имеет нулевую емкость (это бесполезно, но и не вызывает беспокойства). Проблема в том, что после запуска сеанс не существует.

sehe 25.06.2018 20:42

@sehe хороший улов - радость от попытки ответить на пару вопросов перед тем, как отправиться домой на ночь;) Я только проверил, попал ли еще буфер в прицел!

UKMonkey 25.06.2018 21:22
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
8
505
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы используете enable_shared_from_this, но ничто не поддерживает вашу сессию, потому что вы используете только unique_ptr<Session>.

Это означает, что ваша сессия прекращается во время выполнения операций.

Почини это:

std::make_shared<Session>(std::move(socket))->start();

Затем удерживайте общий указатель в обработчике завершения:

void Session::start()
{
    auto self = shared_from_this();
    socket_.async_read_some(buffer(data_), [this, self](error_code errorCode, size_t /*length*/) {
        if (!errorCode) {
            std::cout << "received: " << data_ << std::endl;
        }
        start();
    });
}

Затем ВЫРЕЗИТЕ асинхронный цикл, если есть ошибка (или ваш сеанс будет зацикливаться бесконечно):

socket_.async_read_some(buffer(data_), [this, self](error_code errorCode, size_t length) {
    if (!errorCode && length) {
        std::cout << "received: " << data_ << std::endl;
        start();
    }
});

Наконец, измените размер буфера, чтобы вы действительно могли получать данные (!):

data_.resize(32);
socket_.async_read_some(buffer(data_), [this, self](error_code errorCode, size_t length) {
    if (!errorCode) {
        data_.resize(length);
        std::cout << "received: '" << data_ << "'" << std::endl;
        start();
    }
});

Есть еще некоторые проблемы, но, эй, программа не выйдет из строя сразу, и у вас есть результаты некоторый.

Обновлять

Добавлена ​​живая демонстрация, показывающая еще несколько предложений Live On Coliru

#include <iostream>
#include <string>
#include <memory>

#include <boost/asio.hpp>

using namespace boost::asio;
using namespace boost::system;
using boost::asio::ip::tcp;

class Session : public std::enable_shared_from_this<Session> {
public:
    Session(tcp::socket socket);

    void start();
private:
    tcp::socket socket_;
    boost::asio::streambuf _sb;
};

Session::Session(tcp::socket socket) : socket_(std::move(socket))
{}

void Session::start()
{
    auto self = shared_from_this();
    async_read_until(socket_, _sb, '\n', [this, self](error_code errorCode, size_t /*length*/) {
        std::cout << "completion " << errorCode.message() << "\n";
        if (!errorCode) {
            std::string line;
            {
                std::istream is(&_sb);
                if (getline(is, line)) {
                    std::cout << "received: '" << line << "'" << std::endl;
                }
                start();
            }
        }
    });
}

class Server {
public:
    Server(io_context& context);
private:
    tcp::acceptor acceptor_;

    void accept();
};

Server::Server(io_context& context) : acceptor_(context, tcp::endpoint(tcp::v4(), 8888))
{
    accept();
}

void Server::accept()
{
    acceptor_.async_accept([this](error_code errorCode, tcp::socket socket) {
        if (!errorCode) {
            std::make_shared<Session>(std::move(socket))->start();
        }
        accept();
    });
}

int main(int argc, char**) {
    if (argc>1) {
        io_context context;
        tcp::socket socket(context);
        tcp::resolver resolver(context);
        connect(socket, resolver.resolve("127.0.0.1", "8888"));
        std::string data;
        while (getline(std::cin, data)) {
            try {
                data += '\n';
                write(socket, buffer(data));
            } catch (const std::exception& exception) {
                std::cerr << exception.what() << std::endl;
            }
        }
    } else {
        boost::asio::io_context context;
        Server server(context);
        context.run();
    }
}

Большое спасибо! Но у меня остались вопросы. Во-первых, зачем нам этот указатель self в функции start()? Как я вижу, мы не используем его в закрытии.

Count Zero 25.06.2018 20:46

Это требует от вас понимания как общих указателей, так и асинхронных операций.

sehe 25.06.2018 21:15

async_* всегда возвращается немедленно (то есть до завершения операции или даже до ее запуска). Это означает, что после выхода из start() вам все равно необходимо убедиться, что экземпляр Session «остается». В противном случае использование this в обработчике завершения (что вы делаете) незаконно.

sehe 25.06.2018 21:16

Принцип работы shared_ptr заключается в том, что он поддерживает указатель в живых до тех пор, пока последний указывающий на него shared_ptr не исчезнет. Захватив этот указатель shared_from_this() (который представляет собой такой shared_ptr<SessIon>), вы убедитесь, что, пока обработчик завершения находится где-то в очереди, соответствующий экземпляр Session не может быть уничтожен, так что вы можете законно использовать this в лямбда («закрытие»).

sehe 25.06.2018 21:18

Хорошо, теперь я понял! Не могли бы вы указать на другие вопросы, о которых вы рассказывали?

Count Zero 25.06.2018 22:21

Я не думаю, что указание на них здесь очень полезно. Я предлагаю вам прочитать некоторые другие ответы, например stackoverflow.com/questions/40561097/… или stackoverflow.com/a/8377771/85371 - если у вас есть что-то, что не делает то, что вы хотите, вы должны увидеть это во время тестирования. Если вы не можете решить эту проблему, вы всегда можете поискать / опубликовать другой вопрос об этой задаче конкретный

sehe 25.06.2018 23:18

Добавлена ​​живая демонстрация, показывающая еще несколько предложений Live On Coliru

sehe 25.06.2018 23:39

Спасибо вам за ваши предложения!

Count Zero 27.06.2018 19:40

Другие вопросы по теме