英文:
BOOST async_receive doesn't return when socket is closed
问题
我正在尝试使用 *boost.asio* 编写一个简单的客户端/服务器程序。
我的问题是,当客户端关闭其套接字并退出时,“stream.async_receive()”会阻塞服务器端并且不返回。
这是我的代码:
英文:
I'm trying to write a simple client/server program using boost.asio.
My problem is that when the client closes it's socket and exits, "stream.async_receive()" blocks the server side and doesn't return.
Here's my code:
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio.hpp>
#include <string>
#include <iostream>
using namespace std;
namespace asio = boost::asio;
using namespace boost::asio::ip;
asio::awaitable<void> handle_connection(tcp::socket&& stream)
{
std::cout << "new connection\n";
std::string buffer;
uint64_t buffer_size;
while(stream.is_open())
{
co_await stream.async_receive(asio::buffer(&buffer_size, 8), asio::use_awaitable);
buffer = std::string(buffer_size, '#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio.hpp>
#include <string>
#include <iostream>
using namespace std;
namespace asio = boost::asio;
using namespace boost::asio::ip;
asio::awaitable<void> handle_connection(tcp::socket&& stream)
{
std::cout << "new connection\n";
std::string buffer;
uint64_t buffer_size;
while(stream.is_open())
{
co_await stream.async_receive(asio::buffer(&buffer_size, 8), asio::use_awaitable);
buffer = std::string(buffer_size, '\0');
std::cout << "message size: " << buffer_size << "\n";
co_await stream.async_receive(asio::buffer(buffer, buffer_size), asio::use_awaitable);
std::cout << "new message: " << buffer << "\n";
}
// The program never reached this point.
cout << "socket closed\n";
co_return;
}
asio::awaitable<void> accept_connection()
{
auto ex = co_await asio::this_coro::executor;
boost::asio::ip::tcp::acceptor acceptor(ex, {{}, 8000});
tcp::socket sock = co_await acceptor.async_accept(asio::use_awaitable);
while(true)
{
co_spawn(ex, handle_connection(std::move(sock)), asio::detached);
sock = co_await acceptor.async_accept(asio::use_awaitable);
}
co_return;
}
int main()
{
asio::io_context io_ctx(2);
co_spawn(io_ctx, accept_connection(), asio::detached);
io_ctx.run();
}
');
std::cout << "message size: " << buffer_size << "\n";
co_await stream.async_receive(asio::buffer(buffer, buffer_size), asio::use_awaitable);
std::cout << "new message: " << buffer << "\n";
}
// The program never reached this point.
cout << "socket closed\n";
co_return;
}
asio::awaitable<void> accept_connection()
{
auto ex = co_await asio::this_coro::executor;
boost::asio::ip::tcp::acceptor acceptor(ex, {{}, 8000});
tcp::socket sock = co_await acceptor.async_accept(asio::use_awaitable);
while(true)
{
co_spawn(ex, handle_connection(std::move(sock)), asio::detached);
sock = co_await acceptor.async_accept(asio::use_awaitable);
}
co_return;
}
int main()
{
asio::io_context io_ctx(2);
co_spawn(io_ctx, accept_connection(), asio::detached);
io_ctx.run();
}
Any help would be much appreciated.
答案1
得分: 3
以下是您要求翻译的代码部分:
Your loop exits. By exception. Because you you don't handle the exception you don't see it.
To show you three ways in which to handle the errors (including Eof):
asio::awaitable<void> handle_connection(tcp::socket stream) {
std::cout << "new connection" << std::endl;
for (std::string buffer; true;)
try {
boost::endian::big_uint64_t buffer_size[1];
co_await stream.async_receive(asio::buffer(buffer_size), use_awaitable);
buffer.assign(*buffer_size, 'Your loop exits. By exception. Because you you don't handle the exception you don't see it.
To show you three ways in which to handle the errors (including Eof):
asio::awaitable<void> handle_connection(tcp::socket stream) {
std::cout << "new connection" << std::endl;
for (std::string buffer; true;)
try {
boost::endian::big_uint64_t buffer_size[1];
co_await stream.async_receive(asio::buffer(buffer_size), use_awaitable);
buffer.assign(*buffer_size, '\0');
std::cout << "message size: " << *buffer_size << "" << std::endl;
auto [ec, n] =
co_await stream.async_receive(asio::buffer(buffer), as_tuple(use_awaitable));
if (n) {
buffer.resize(n);
std::cout << "new message: " << buffer << "" << std::endl;
}
if (ec) {
std::cout << "socket closed (ec: " << ec.message() << ")" << std::endl;
break;
}
} catch (boost::system::system_error const& se) {
std::cout << "socket closed (thrown ec: " << se.code().message() << ")" << std::endl;
break;
} catch (std::exception const& e) {
std::cout << "socket closed (exception: " << e.what() << ")" << std::endl;
break;
}
}
');
std::cout << "message size: " << *buffer_size << "" << std::endl;
auto [ec, n] =
co_await stream.async_receive(asio::buffer(buffer), as_tuple(use_awaitable));
if (n) {
buffer.resize(n);
std::cout << "new message: " << buffer << "" << std::endl;
}
if (ec) {
std::cout << "socket closed (ec: " << ec.message() << ")" << std::endl;
break;
}
} catch (boost::system::system_error const& se) {
std::cout << "socket closed (thrown ec: " << se.code().message() << ")" << std::endl;
break;
} catch (std::exception const& e) {
std::cout << "socket closed (exception: " << e.what() << ")" << std::endl;
break;
}
}
I'd suggest sticking to the `error_code` approach, as it is the only approach (with C++20 coro's) that allows you to gracefully handle partial success (e.g. when the connection is lost half-way receiving the expected bytes).
> _Whether partial success makes sense depends on your application. Note that if handled without caution this (dealing with unintended partial messages) may lead to security issues._
#include <boost/asio.hpp>
#include <boost/endian/arithmetic.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;
using asio::use_awaitable;
asio::awaitable<void> handle_connection(tcp::socket stream) {
auto tok = as_tuple(use_awaitable);
std::cout << "new connection" << std::endl;
boost::system::error_code ec;
for (std::string buffer;;) {
boost::endian::big_uint64_t buffer_size[1];
size_t n = 0;
tie(ec, std::ignore) = co_await stream.async_receive(asio::buffer(buffer_size), tok);
if (ec) break;
std::cout << "message size: " << *buffer_size << std::endl;
buffer.assign(*buffer_size, '#include <boost/asio.hpp>
#include <boost/endian/arithmetic.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;
using asio::use_awaitable;
asio::awaitable<void> handle_connection(tcp::socket stream) {
auto tok = as_tuple(use_awaitable);
std::cout << "new connection" << std::endl;
boost::system::error_code ec;
for (std::string buffer;;) {
boost::endian::big_uint64_t buffer_size[1];
size_t n = 0;
tie(ec, std::ignore) = co_await stream.async_receive(asio::buffer(buffer_size), tok);
if (ec) break;
std::cout << "message size: " << *buffer_size << std::endl;
buffer.assign(*buffer_size, '\0');
std::tie(ec, n) = co_await stream.async_receive(asio::buffer(buffer), tok);
if (n) {
buffer.resize(n);
std::cout << "new message: " << buffer << std::endl;
}
if (ec) break;
}
std::cout << "socket closed (" << ec.message() << ")" << std::endl;
}
asio::awaitable<void> accept_connection() {
auto ex = co_await asio::this_coro::executor;
tcp::acceptor acceptor(ex, {{}, 8989});
while (true) {
co_spawn(
ex,
handle_connection(co_await acceptor.async_accept(make_strand(ex), use_awaitable)),
asio::detached);
}
}
int main() {
asio::io_context io_ctx;
co_spawn(io_ctx, accept_connection(), asio::detached);
io_ctx.run();
}
');
std::tie(ec, n) = co_await stream.async_receive(asio::buffer(buffer), tok);
if (n) {
buffer.resize(n);
std::cout << "new message: " << buffer << std::endl;
}
if (ec) break;
}
std::cout << "socket closed (" << ec.message() << ")" << std::endl;
}
asio::awaitable<void> accept_connection() {
auto ex = co_await asio::this_coro::executor;
tcp::acceptor acceptor(ex, {{}, 8989});
while (true) {
co_spawn(
ex,
handle_connection(co_await acceptor.async_accept(make_strand(ex), use_awaitable)),
asio::detached);
}
}
int main() {
asio::io_context io_ctx;
co_spawn(io_ctx, accept_connection(), asio::detached);
io_ctx.run();
}
希望这对您有所帮助。如果您需要任何进一步的帮助,请随时提出。
英文:
Your loop exits. By exception. Because you you don't handle the exception you don't see it.
To show you three ways in which to handle the errors (including Eof):
asio::awaitable<void> handle_connection(tcp::socket stream) {
std::cout << "new connection" << std::endl;
for (std::string buffer; true;)
try {
boost::endian::big_uint64_t buffer_size[1];
co_await stream.async_receive(asio::buffer(buffer_size), use_awaitable);
buffer.assign(*buffer_size, 'asio::awaitable<void> handle_connection(tcp::socket stream) {
std::cout << "new connection" << std::endl;
for (std::string buffer; true;)
try {
boost::endian::big_uint64_t buffer_size[1];
co_await stream.async_receive(asio::buffer(buffer_size), use_awaitable);
buffer.assign(*buffer_size, '\0');
std::cout << "message size: " << *buffer_size << "" << std::endl;
auto [ec, n] =
co_await stream.async_receive(asio::buffer(buffer), as_tuple(use_awaitable));
if (n) {
buffer.resize(n);
std::cout << "new message: " << buffer << "" << std::endl;
}
if (ec) {
std::cout << "socket closed (ec: " << ec.message() << ")" << std::endl;
break;
}
} catch (boost::system::system_error const& se) {
std::cout << "socket closed (thrown ec: " << se.code().message() << ")" << std::endl;
break;
} catch (std::exception const& e) {
std::cout << "socket closed (exception: " << e.what() << ")" << std::endl;
break;
}
}
');
std::cout << "message size: " << *buffer_size << "" << std::endl;
auto [ec, n] =
co_await stream.async_receive(asio::buffer(buffer), as_tuple(use_awaitable));
if (n) {
buffer.resize(n);
std::cout << "new message: " << buffer << "" << std::endl;
}
if (ec) {
std::cout << "socket closed (ec: " << ec.message() << ")" << std::endl;
break;
}
} catch (boost::system::system_error const& se) {
std::cout << "socket closed (thrown ec: " << se.code().message() << ")" << std::endl;
break;
} catch (std::exception const& e) {
std::cout << "socket closed (exception: " << e.what() << ")" << std::endl;
break;
}
}
I'd suggest sticking to the error_code
approach, as it is the only approach (with C++20 coro's) that allows you to gracefully handle partial success (e.g. when the connection is lost half-way receiving the expected bytes).
> Whether partial success makes sense depends on your application. Note that if handled without caution this (dealing with unintended partial messages) may lead to security issues.
#include <boost/asio.hpp>
#include <boost/endian/arithmetic.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;
using asio::use_awaitable;
asio::awaitable<void> handle_connection(tcp::socket stream) {
auto tok = as_tuple(use_awaitable);
std::cout << "new connection" << std::endl;
boost::system::error_code ec;
for (std::string buffer;;) {
boost::endian::big_uint64_t buffer_size[1];
size_t n = 0;
tie(ec, std::ignore) = co_await stream.async_receive(asio::buffer(buffer_size), tok);
if (ec) break;
std::cout << "message size: " << *buffer_size << std::endl;
buffer.assign(*buffer_size, '#include <boost/asio.hpp>
#include <boost/endian/arithmetic.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;
using asio::use_awaitable;
asio::awaitable<void> handle_connection(tcp::socket stream) {
auto tok = as_tuple(use_awaitable);
std::cout << "new connection" << std::endl;
boost::system::error_code ec;
for (std::string buffer;;) {
boost::endian::big_uint64_t buffer_size[1];
size_t n = 0;
tie(ec, std::ignore) = co_await stream.async_receive(asio::buffer(buffer_size), tok);
if (ec) break;
std::cout << "message size: " << *buffer_size << std::endl;
buffer.assign(*buffer_size, '\0');
std::tie(ec, n) = co_await stream.async_receive(asio::buffer(buffer), tok);
if (n) {
buffer.resize(n);
std::cout << "new message: " << buffer << std::endl;
}
if (ec) break;
}
std::cout << "socket closed (" << ec.message() << ")" << std::endl;
}
asio::awaitable<void> accept_connection() {
auto ex = co_await asio::this_coro::executor;
tcp::acceptor acceptor(ex, {{}, 8989});
while (true) {
co_spawn(
ex,
handle_connection(co_await acceptor.async_accept(make_strand(ex), use_awaitable)),
asio::detached);
}
}
int main() {
asio::io_context io_ctx;
co_spawn(io_ctx, accept_connection(), asio::detached);
io_ctx.run();
}
');
std::tie(ec, n) = co_await stream.async_receive(asio::buffer(buffer), tok);
if (n) {
buffer.resize(n);
std::cout << "new message: " << buffer << std::endl;
}
if (ec) break;
}
std::cout << "socket closed (" << ec.message() << ")" << std::endl;
}
asio::awaitable<void> accept_connection() {
auto ex = co_await asio::this_coro::executor;
tcp::acceptor acceptor(ex, {{}, 8989});
while (true) {
co_spawn(
ex,
handle_connection(co_await acceptor.async_accept(make_strand(ex), use_awaitable)),
asio::detached);
}
}
int main() {
asio::io_context io_ctx;
co_spawn(io_ctx, accept_connection(), asio::detached);
io_ctx.run();
}
Testing with
g++ -std=c++20 -Os -Wall -pedantic -pthread main.cpp;
./a.out& sleep 1;
printf '\x00\x00\x00\x00\x00\x00\x00\x0cHello world!\x00\x00\x00\x00\x00\x00\x00\x03Bye' | nc 127.0.0.1 8000 -w 1
Prints
new connection
message size: 12
new message: Hello world!
message size: 3
new message: Bye
socket closed (End of file)
Interactive:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论