【问题标题】:Simple server using Boost.Asio throws an exception使用 Boost.Asio 的简单服务器抛出异常
【发布时间】:2018-06-25 17:13:59
【问题描述】:

我正在尝试使用 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/…
  • 从我的start() 调用的buffer() 函数引发了异常。
  • 重新阅读链接 .... 你会发现它是重复的(你会发现用 std::string 创建可变缓冲区并没有真正起作用 - 使用 std::向量代替)
  • 感谢您的评论!我已将std::string data_; 更改为std::vector&lt;char&gt; data_ = std::vector&lt;char&gt;(1024);buffer(data_) 更改为buffer(data_, 1024),但现在它在调试断言上失败,显示消息“vector iterator not dereferencable”。
  • “调试器中该消息的一部分将包括堆栈跟踪。看看它,它会告诉你到底发生了什么”不同的错误 - 不同的堆栈跟踪......第一次给出完整的细节你会在更好的时间得到答案......但实际上,你应该练习查看它并了解出了什么问题。我猜它现在可能在不同的地方失败了。可能是因为数据的大小为 1024,但只写入了长度字节。

标签: c++ client-server boost-asio


【解决方案1】:

您正在使用enable_shared_from_this,但没有什么可以使您的会话保持活跃,因为您只使用unique_ptr&lt;Session&gt;

这意味着您的 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();
    }
}

【讨论】:

  • 非常感谢!但我还有一些问题。首先,为什么我们需要start() 函数中的self 指针?如我所见,我们不在闭包中使用它。
  • 这需要你理解共享指针和异步操作。
  • async_* 总是立即返回(所以在操作完成之前,甚至可能已经开始)。这意味着在退出start() 之后,您仍然需要确保Session 实例“保留”。否则,在完成处理程序中使用this(你这样做)是非法的。
  • shared_ptr 的工作方式是让指针保持活动状态,直到最后一个指向它的 shared_ptr 消失。通过捕获shared_from_this() 指针(就是这样一个shared_ptr&lt;SessIon&gt;),您可以确保只要完成处理程序在某处排队,相应的Session 实例就不会被销毁,这样您就可以在其中合法使用this lambda(“闭包”)。
  • 好的,我明白了!能否请您指出您所说的其他问题?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-11
  • 2010-09-16
相关资源
最近更新 更多