我试图在 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额外转换为字符串,尽管我已经传递了字符串常量参数。
您的
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)) {
代码还有许多其他令人担忧的事情。我很快就会在咖啡下起草一份快速审查清单:
所有
[[noreturn]]
属性都是谎言。事实上,Asio异步启动函数all保证立即返回。操作本身将在服务上完成。
create_id_and_port
中缺少返回(确实应该有一个更好的名称,只需返回tcp::endpoint
)。这会调用 UB。
您的消息读取永远不会处理文件结尾,因此它可能会在末尾陷入无限循环
您正在将临时值作为缓冲区传递给
send_message
。缓冲区将在异步操作完成之前被销毁,再次调用 UB。缓冲区需要保持活动状态,直到操作完成。
send_message
还需要防止重叠写入。如果输入实际上来自控制台,则不太可能发生这种情况,但通过输入重定向,我不会依靠机会(和 Nagle 算法)来阻止它。
使用默认参数组合两个构造函数。这减少了代码重复:
MainServer(asio::io_service& service, std::string const& ip_address = "0.0.0.0",
uint16_t port = 8080);
您似乎期望按行输入,但您的
async_read
是无界。将其更改为带有分隔符的 async_read_until
(例如 '\n'
)。然后,您不需要 istream
和 streambuf
复杂性,而只需使用 std::string
或 std::deque<char>
之类的东西(后者对于前端的消费效率更高),例如:
许多函数都需要
socket_ptr
和 streambuf
。这是代码味道。将这些方法分组在自己的类中会更有效(session
)。这也将允许您使用 std::enable_shared_from_this
来避免首先需要 shared_ptr
。
我不会重写全部内容,而是指出最近的一个问题,其中我展示了完全相同的事情(分离
server
和 session
之间的关注点): async_write 仅在服务器关闭后发送
您最终会在 client_session 中执行 BLOCKING I/O:
send_message(sock, util_common::readMessage());
这阻塞了IO服务,反驳了使用Asio的目的。
接受许多(并发)连接存在概念问题。所有这些都来自
std::cin
,最多只会造成混乱,如果您添加多线程,还会产生更多 [UB]。
代码的某些方面似乎“优化”(例如分支预测提示)。这与其他迹象相矛盾,例如冗余动态分配。
无用的
static_cast
表达已经提到了
坦率地说,代码中最终存在太多无法真正解决的问题(不改变行为,例如从
std::cin
读取),我会考虑采用非异步(阻塞函数而不是async_XXXX)。
您仍然可以从 Asio 中的所有网络原语中受益。
事实上,您只需几行即可获得极其简单、非常相似的服务,例如使用评论中的示例这里:
#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` 的数据竞争?。然而,这个答案也说:不要这样做。如果您需要全双工,请硬着头皮选择异步。但是,只有当您有一些要求可以首先证明这一点时,我才能提供帮助。