I'm writing an async client for my server (I'm writing the chat) and I got a problem that my lambda function doesn't get the values I need. In my case, the lambda function doesn't get two values error_code and length. So the lambda function doesn't even work and the program doesn't do its job. The program kinda passes the lambda function and I can't understand the reason. What should I do with this? The part where ther's that problem.
void ReadLoop() {
if(!socket_.is_open())
{
std::cout << "Trimp" << std::endl;
}
std::cout << "Socket: " << socket_.get_executor() << '\n';
std::cout << "Buffer: " << input_buffer_ << '\n';
asio::async_read_until(socket_, asio::dynamic_buffer(input_buffer_), '\n', [&](error_code error, size_t bytes_transferred)
{
if (error == asio::error::eof) {
std::cout << "Connection closed by peer\n";
return;
}
if (error) {
std::cerr << "Error: " << error.message() << " (" << error.value() << " - " << error.category().name() << ")\n";
}
std::cout << "Received message: " << input_buffer_.substr(0, bytes_transferred);
input_buffer_.erase(0, bytes_transferred);
});
}
I also tried to make an ordinary function but the problem is the same. The server works properly and there are not any errors. It gets the values and sends them. So I think the main problem is in this class. Here's the full code:
class AsyncClient
{
public:
AsyncClient(asio::io_context& io_context, const std::string& server_address, const std::string& server_port)
: io_context_(&io_context), socket_(io_context), resolver_(io_context), server_address_(server_address), server_port_(server_port) { }
void Start() {
auto endpoints = resolver_.resolve(server_address_, server_port_);
std::cout << "Socket: " << socket_.get_executor() << '\n';
asio::async_connect(socket_, endpoints, [&](error_code error, const asio::ip::tcp::endpoint&)
{
if (!error) {
std::cout << "Connected to server\n";
StartMain();
}
else {
std::cerr << "Error while connecting to server: " << error.message() << "\n";
}
});
}
void StartMain()
{
while(true)
{
ReadLoop();
WriteLoop();
}
}
void SendMessage(const std::string& message) {
std::cout << "You are here 4" << "\n";
asio::async_write(socket_, asio::buffer(message + "\n"), [&](error_code error, size_t bytes_transferred) {
if (error) {
std::cerr << "Error while writing to server: " << error.message() << "\n";
}
else
{
std::cout << "Yeas" << '\n';
}
});
}
private:
void ReadLoop() {
if(!socket_.is_open())
{
std::cout << "Trimp" << std::endl;
}
std::cout << "Socket: " << socket_.get_executor() << '\n';
std::cout << "Buffer: " << input_buffer_ << '\n';
asio::async_read_until(socket_, asio::dynamic_buffer(input_buffer_), '\n', [&](error_code error, size_t bytes_transferred)
{
if (error == asio::error::eof) {
std::cout << "Connection closed by peer\n";
return;
}
if (error) {
std::cerr << "Error: " << error.message() << " (" << error.value() << " - " << error.category().name() << ")\n";
}
std::cout << "Received message: " << input_buffer_.substr(0, bytes_transferred);
input_buffer_.erase(0, bytes_transferred);
});
}
void WriteLoop() {
std::string message;
std::getline(std::cin, message);
if (message.empty()) {
WriteLoop();
return;
}
else
{
SendMessage(message);
}
}
asio::io_context* io_context_;
asio::ip::tcp::socket socket_;
asio::ip::tcp::resolver resolver_;
std::string server_address_;
std::string server_port_;
std::string input_buffer_;
};
There are several issues here. Two of them are correctly called out in the comments.
io_context
somewhereOthers are:
you need to queue outgoing messages because no more than a single read/write operation maybe in flight at a given time.
you have UB here:
asio::async_write(socket_, asio::buffer(message + "\n")
This passes a reference to a temporary string (message + "\n"
) to an async operation. The temporary string is destructed before the operation completes, hence Undefined Behaviour.
the ReadLoop
doesn't loop - this in part ties into 2.
so might be considered the same issue (not understanding async operations well)
printing an executor is not useful. At all.
You're doing blocking IO from a handler (std::cin
reading blocks, and you call it from inside the completion for async_connect
). Sadly, allowing blocking console input requires a second thread, which implies synchronizing access to the client
On the positive side: kudos for getting the
read_until
correct with consuming the bytes processed, but leaving the remaining input buffer!
Fixing these things:
#include <boost/asio.hpp>
#include <deque>
#include <iomanip>
#include <iostream>
using boost::system::error_code;
namespace asio = boost::asio;
using asio::ip::tcp;
class AsyncClient {
public:
AsyncClient(asio::io_context& io_context, std::string const& server_address,
std::string const& server_port)
: socket_(io_context)
, resolver_(io_context)
, server_address_(server_address)
, server_port_(server_port) {}
void Start() {
auto endpoints = resolver_.resolve(server_address_, server_port_);
asio::async_connect(socket_, endpoints, [&](error_code error, tcp::endpoint const&) {
if (!error) {
std::cout << "Connected to server" << std::endl;
ReadLoop();
} else {
std::cerr << "Error while connecting to server: " << error.message() << std::endl;
}
});
}
void Send(std::string message) {
post(socket_.get_executor(), [this, m = std::move(message)]() mutable {
outbox_.push_back(std::move(m));
if (outbox_.size() == 1)
DoWriteLoop();
});
}
private: // all private handlers assumed to be on the service thread
void DoWriteLoop() {
if (outbox_.empty())
return;
async_write(socket_, asio::buffer(outbox_.front()),
[&](error_code error, size_t /*bytes_transferred*/) {
if (error) {
std::cerr << "Error while writing to server: " << error.message() << std::endl;
} else {
outbox_.pop_front();
DoWriteLoop();
}
});
}
void ReadLoop() {
std::cout << "Buffer: " << quoted(input_buffer_) << std::endl;
asio::async_read_until(
socket_, asio::dynamic_buffer(input_buffer_), '\n',
[&](error_code error, size_t bytes_transferred) {
if (error) {
if (error == asio::error::eof) {
std::cout << "Connection closed by peer" << std::endl;
} else {
std::cerr << "Error: " << error.message() << " (" << error.value() << " - "
<< error.category().name() << ")" << std::endl;
return;
}
}
std::cout << "Received message: " << std::quoted(input_buffer_.substr(0, bytes_transferred)) << std::endl;
input_buffer_.erase(0, bytes_transferred);
if (!error)
ReadLoop();
});
}
tcp::socket socket_;
tcp::resolver resolver_;
std::string server_address_, server_port_, input_buffer_;
std::deque<std::string> outbox_;
};
void InputLoop(AsyncClient& c) {
std::string message;
while (std::getline(std::cin, message)) {
if (!message.empty())
c.Send(message + '\n');
}
}
int main() {
asio::io_context ioc;
AsyncClient c(ioc, "127.0.0.1", "8989");
c.Start();
std::thread ui(InputLoop, std::ref(c));
ioc.run();
ui.join();
}
Local demo: