无法使用boost/asio在C++中启动服务器

问题描述 投票:0回答:1

我试图在 Windows 上使用 boost/asio 编写一个简单的服务器,但编译器会抛出错误 “在 Server.exe 中的 0x00007FF90EE05B0C 处抛出异常:Microsoft C++ 异常:boost::wrap exceptboost::asio::ip:: bad_address_cast 位于内存位置 0x0000002A5AF2EFF8。 调试错误!”输入端口和IP后,我不明白可能出了什么问题,因为我将参数传递给了该函数的实现中编写的函数。 当我使用带有一个参数的构造函数时,错误不会立即出现,但是当我从键盘输入某些内容时,它会立即出现

我会很高兴接受任何公正的批评

服务器.h 简单的头文件。

#pragma once

#include <iostream>
#include <boost/asio.hpp>
#include <thread>

#include "utillity.hpp"

#ifndef _WIN32_WINNT

#define _WIN32_WINNT 0x0601

#endif


using namespace boost::asio;
using socket_ptr = boost::shared_ptr<ip::tcp::socket>;

class MainServer
{
private:

    ip::tcp::endpoint       ep;
    ip::tcp::acceptor       acc;
    socket_ptr              sock;
    ip::address             ipAddr;

    streambuf               buffer;

public:

    MainServer(boost::asio::io_service& service);
    MainServer(boost::asio::io_service& service, const std::string& ip_address, uint16_t port);
    ~MainServer();

    streambuf& getBuffer() { return buffer; }

    [[noreturn]] void Handler(boost::asio::io_service& service);
    [[noreturn]] void client_session(socket_ptr sock);
    [[noreturn]] void Receive_message(socket_ptr sock, streambuf& buffe) const;
    [[noreturn]] void send_message(socket_ptr sock, std::string mess) ;
    
};

namespace util_common
{

    std::string create_message()
    {
        std::string message;
        std::getline(std::cin, message);
        return message;
    }
}




namespace util_server
{
    std::pair<std::string, uint16_t> create_ip_and_port()
    {
        try
        {
            uint16_t port;
            std::string server_ip_address;

            std::cout << "IP: ";
            std::getline(std::cin, server_ip_address);

            std::cout << "Port: ";
            std::cin >> port;
            std::cin.ignore();
            static_cast<uint16_t>(port);
            static_cast<std::string>(server_ip_address);
            return std::make_pair(server_ip_address, port);
        }
        catch (const std::exception& e)
        {
            std::cerr << "Error create_ip_and_port(): " << e.what() << std::endl;
        }

    }
}

服务器.cpp 实现

#include "../headers/Server.h"

MainServer::MainServer(boost::asio::io_service& service, const std::string& ip_address, uint16_t port) :
    ipAddr  (ip::make_address(ip_address)),
    ep      (ipAddr, port),
    acc     (service, ep),
    sock    (new ip::tcp::socket(service))
{
    try
    {
        std::cout << "Server starting in ip: " << ip_address << "and port:" << port << std::endl;
        Handler(service);
    }
    catch (const std::exception& e)
    {
        std::cerr << "Error constructor MainServer(): " << e.what() << std::endl;
    }
}

MainServer::MainServer(boost::asio::io_service& service) :
    ipAddr(ip::make_address("0.0.0.0")),
    ep(ipAddr, 8080),
    acc(service, ep),
    sock(new ip::tcp::socket(service))
{
        try
    {
        std::cout << "Server starting on \"0.0.0.0\" ip and port: 8080\n";
        Handler(service);
    }
    catch (const std::exception& e)
    {
        std::cerr << "Error constructor MainServer(): " << e.what() << std::endl;
    }
}

MainServer::~MainServer() = default;


[[noreturn]] void MainServer::Handler(boost::asio::io_service& service)
{
    acc.async_accept
    (
        *sock, 
        [this, &service](const boost::system::error_code& error)
        {
            if (!error) _LIKELY
            {
                std::cout << "Client Connected!" << std::endl;
                client_session(sock);
            }
            else _UNLIKELY
            {
                std::cout << "Error Handler(): " << error.message() << std::endl;
            }

            Handler(service);
        }
    );

    service.run();
}

[[noreturn]] void MainServer::client_session(socket_ptr sock) 
{
    try
    {
        Receive_message(sock, getBuffer());
        send_message(sock, util_common::create_message());
    }
    catch (const std::exception& e)
    {
        std::cerr << "Error client_session(socket_ptr sock): " << e.what() << std::endl;
    }
}

[[noreturn]] void MainServer::Receive_message(socket_ptr sock, streambuf& buffer) const
{
    async_read(*sock, buffer, [this, sock, &buffer](const boost::system::error_code& error, std::size_t bytes_transferred)
    {
        if (!error) _LIKELY
        {
            std::istream is(&buffer);
            std::string message;
            std::getline(is, message);

            std::cout << "Client message: " << message << std::endl;

            // Consume the data that was read
            buffer.consume(bytes_transferred);

            // Handle the next message
            Receive_message(sock, buffer);
        }
        else _UNLIKELY
        {
            std::cout << "Error Receive_message(socket_ptr sock, streambuf& buffer): " << error.message() << std::endl;
        }
    });
}

[[noreturn]] void MainServer::send_message(socket_ptr sock, std::string mess) 
{
    async_write
    (
        *sock, 
        boost::asio::buffer(mess),
        [this, sock, mess](const boost::system::error_code& error, std::size_t bytes_transferred)
        {
            if (!error) _LIKELY
            {
                std::cout << "Send message: " << mess << std::endl;
                client_session(sock);
            }
            else _UNLIKELY
            {
                std::cout << "Error send_message(socket_ptr sock, std::string mess): " << error.message() << std::endl;
            }
        }   
    );
}

int main()
{
    setlocale(LC_ALL, "Rus");
    auto result = util_server::create_ip_and_port();
    try
    {
        io_service service;
        MainServer server(service, result.first, result.second);
        service.run();
    }
    catch (const std::exception& e)
    {
        std::cerr << "Exception in main(): " << e.what() << std::endl;
    }
    return 0;
}

控制台中的完整消息

'Server.exe' (Win32): Loaded 'D:\C++\Server\out\build\x64-debug\Server.exe'. Symbols loaded.
'Server.exe' (Win32): Loaded 'C:\Windows\System32\ntdll.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\kernel32.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\KernelBase.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\ws2_32.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\rpcrt4.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\msvcp140d.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\mswsock.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\vcruntime140d.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\vcruntime140_1d.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\ucrtbased.dll'. 
The thread 16212 has exited with code 0 (0x0).
Exception thrown at 0x00007FF90EE05B0C in Server.exe: Microsoft C++ exception: boost::wrapexcept<boost::asio::ip::bad_address_cast> at memory location 0x000000F986B7F298.
Debug Error!

Program: D:\C++\Server\out\build\x64-debug\Server.exe

abort() has been called

(Press Retry to debug the application)
'Server.exe' (Win32): Loaded 'C:\Windows\System32\kernel.appcore.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\msvcrt.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\user32.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\win32u.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\gdi32.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\gdi32full.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\msvcp_win.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\ucrtbase.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\imm32.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\TextShaping.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\uxtheme.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\combase.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\msctf.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\bcryptprimitives.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\sechost.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\bcrypt.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\TextInputFramework.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\oleaut32.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\CoreMessaging.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\CoreUIComponents.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\WinTypes.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\advapi32.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\cryptbase.dll'. 
'Server.exe' (Win32): Loaded 'C:\Windows\System32\ole32.dll'. 
The thread 36256 has exited with code 3 (0x3).
The thread 15152 has exited with code 3 (0x3).
The thread 19884 has exited with code 3 (0x3).
The thread 32800 has exited with code 3 (0x3).
The thread 4196 has exited with code 3 (0x3).
The program '[25276] Server.exe' has exited with code 3 (0x3).

由于错误表明问题出在转换上,因此我尝试了各种可能的方式将IP额外转换为字符串,尽管我已经传递了字符串常量参数。

c++ boost server network-programming boost-asio
1个回答
2
投票

您的

ipAddr
在您的
ep
之后初始化,这取决于它(在两个构造函数中)。

这意味着

ep
永远无效。因此,至少要翻转成员声明的顺序,例如

tcp::endpoint ep;
tcp::acceptor acc;
socket_ptr        sock;
asio::ip::address ipAddr;

asio::ip::address ipAddr;
tcp::endpoint ep;
tcp::acceptor acc;
socket_ptr        sock;

更好的是,快速扫描显示,除了初始化

ipAddr
之外,实际上从未使用过
ep
,所以,我会完全放弃它:

MainServer::MainServer(asio::io_service& service, std::string const& ip_address,
                       uint16_t port)
    : ep(asio::ip::make_address(ip_address), port)
    , acc(service, ep)
    , sock(new tcp::socket(service)) {

评论

代码还有许多其他令人担忧的事情。我很快就会在咖啡下起草一份快速审查清单:

  1. 所有

    [[noreturn]]
    属性都是谎言。事实上,Asio异步启动函数all保证立即返回。操作本身将在服务上完成。

  2. create_id_and_port
    中缺少返回(确实应该有一个更好的名称,只需返回
    tcp::endpoint
    )。这会调用 UB

  3. 您的消息读取永远不会处理文件结尾,因此它可能会在末尾陷入无限循环

  4. 您正在将临时值作为缓冲区传递给

    send_message
    。缓冲区将在异步操作完成之前被销毁,再次调用 UB。缓冲区需要保持活动状态,直到操作完成。

  5. send_message
    还需要防止重叠写入。如果输入实际上来自控制台,则不太可能发生这种情况,但通过输入重定向,我不会依靠机会(和 Nagle 算法)来阻止它。

  6. 使用默认参数组合两个构造函数。这减少了代码重复:

    MainServer(asio::io_service& service, std::string const& ip_address = "0.0.0.0",
               uint16_t port = 8080);
    
  7. 您似乎期望按行输入,但您的

    async_read
    无界。将其更改为带有分隔符的
    async_read_until
    (例如
    '\n'
    )。然后,您不需要
    istream
    streambuf
    复杂性,而只需使用
    std::string
    std::deque<char>
    之类的东西(后者对于前端的消费效率更高),例如:

  8. 许多函数都需要

    socket_ptr
    streambuf
    。这是代码味道。将这些方法分组在自己的类中会更有效(
    session
    )。这也将允许您使用
    std::enable_shared_from_this
    来避免首先需要
    shared_ptr

    我不会重写全部内容,而是指出最近的一个问题,其中我展示了完全相同的事情(分离

    server
    session
    之间的关注点): async_write 仅在服务器关闭后发送

  9. 您最终会在 client_session 中执行 BLOCKING I/O:

    send_message(sock, util_common::readMessage());
    

    这阻塞了IO服务,反驳了使用Asio的目的。

  10. 接受许多(并发)连接存在概念问题。所有这些都来自

    std::cin
    ,最多只会造成混乱,如果您添加多线程,还会产生更多 [UB]。

  11. 代码的某些方面似乎“优化”(例如分支预测提示)。这与其他迹象相矛盾,例如冗余动态分配。

  12. 无用的

    static_cast
    表达已经提到了

简化您的生活

坦率地说,代码中最终存在太多无法真正解决的问题(不改变行为,例如从

std::cin
读取),我会考虑采用非异步(阻塞函数而不是async_XXXX)。 您仍然可以从 Asio 中的所有网络原语中受益。

事实上,您只需几行即可获得极其简单、非常相似的服务,例如使用评论中的示例这里

住在Coliru

#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using boost::system::error_code;
using boost::asio::ip::tcp;

void ReverseEcho(tcp::iostream conn) {
    std::cout << "ReverseEcho session from " << conn.socket().remote_endpoint() << std::endl;
    conn << "Hello from server\n";

    for (std::string line; getline(conn, line);) {
        reverse(begin(line), end(line));
        conn << "Echo reversed: " << quoted(line) << std::endl;
    }
    std::cout << "ReverseEcho session closed" << std::endl;
}

struct listener {
    using Handler = std::function<void(tcp::iostream)>;
    listener(tcp::endpoint ep, Handler handler) : acc_(ioc_, ep), handler_(std::move(handler)) {
        accept_loop();
    }

    void cancel() {
        post(acc_.get_executor(), [this] { acc_.cancel(); });
    }

    ~listener() { ioc_.join(); }

  private:
    asio::thread_pool ioc_{1};
    tcp::acceptor     acc_;
    Handler           handler_;

    void accept_loop() {
        acc_.async_accept(make_strand(acc_.get_executor()), [this](error_code ec, tcp::socket s) {
            if (!ec) {
                std::thread([s = std::move(s), h = handler_]() mutable {
                    h(tcp::iostream(std::move(s)));
                }).detach();
                accept_loop();
            } else
                std::cout << std::endl;
        });
    }
};

int main() {
    listener server({{}, 1900}, ReverseEcho);
}

一些演示用于说明目的:

当然,

tcp::iostream
不允许Full Duplex IO(Boost asio ip tcp iostream支持异步吗?)。

您可以非常小心地使用一些线程破解它:如何避免与 `asio::ip::tcp::iostream` 的数据竞争?。然而,这个答案也说:不要这样做。如果您需要全双工,请硬着头皮选择异步。但是,只有当您有一些要求可以首先证明这一点时,我才能提供帮助。

© www.soinside.com 2019 - 2024. All rights reserved.