Related
I'm trying to implement a simple IPC protocol for a project that will be built using Boost ASIO. The idea is to have the communication be done through IP/TCP, with a server with the backend and a client that will be using the data received from the server to build the frontend. The whole session would go like this:
The connection is established
The client sends a 2 byte packet with some information that will be used by the server to build its response (this is stored as the struct propertiesPacket)
The server processes the data received and stores the output in a struct of variable size called processedData
The server sends a 2 byte unsigned integer that will indicate the client what size the struct it will receive has (let's say the struct is of size n bytes)
The server sends the struct data as a n byte packet
The connection is ended
I tried implementing this by myself, following the great tutorial available in Boost ASIO's documentation, as well as the examples included in the library and some repos I found on Github, but as this is my first hand working with networking and IPC, I couldn't make it work, my client returns an exception saying the connection was reset by the peer.
What I have right now is this:
// File client.cpp
int main(int argc, char *argv[])
{
try {
propertiesPacket properties;
// ...
// We set the data inside the properties struct
// ...
boost::asio::io_context io;
boost::asio::ip::tcp::socket socket(io);
boost::asio::ip::tcp::resolver resolver(io);
boost::asio::connect(socket, resolver.resolve(argv[1], argv[2]));
boost::asio::write(socket, boost::asio::buffer(&properties, sizeof(propertiesPacket)));
unsigned short responseSize {};
boost::asio::read(socket, boost::asio::buffer(&responseSize, sizeof(short)));
processedData* response = reinterpret_cast<processedData*>(malloc(responseSize));
boost::asio::read(socket, boost::asio::buffer(response, responseSize));
// ...
// The client handles the data
// ...
return 0;
} catch (std::exception &e) {
std::cerr << e.what() << std::endl;
}
}
// File server.cpp
class ServerConnection
: public std::enable_shared_from_this<ServerConnection>
{
public:
using TCPSocket = boost::asio::ip::tcp::socket;
ServerConnection::ServerConnection(TCPSocket socket)
: socket_(std::move(socket)),
properties_(nullptr),
filePacket_(nullptr),
filePacketSize_(0)
{
}
void start() { doRead(); }
private:
void doRead()
{
auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(properties_, sizeof(propertiesPacket)),
[this, self](boost::system::error_code ec, std::size_t /*length*/)
{
if (!ec) {
processData();
doWrite(&filePacketSize_, sizeof(short));
doWrite(filePacket_, sizeof(*filePacket_));
}
});
}
void doWrite(void* data, size_t length)
{
auto self(shared_from_this());
boost::asio::async_write(socket_, boost::asio::buffer(data, length),
[this, self](boost::system::error_code ec, std::size_t /*length*/)
{
if (!ec) { doRead(); }
});
}
void processData()
{ /* Data is processed */ }
TCPSocket socket_;
propertiesPacket* properties_;
processedData* filePacket_;
short filePacketSize_;
};
class Server
{
public:
using IOContext = boost::asio::io_context;
using TCPSocket = boost::asio::ip::tcp::socket;
using TCPAcceptor = boost::asio::ip::tcp::acceptor;
Server::Server(IOContext& io, short port)
: socket_(io),
acceptor_(io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
{
doAccept();
}
private:
void doAccept()
{
acceptor_.async_accept(socket_,
[this](boost::system::error_code ec)
{
if (!ec) {
std::make_shared<ServerConnection>(std::move(socket_))->start();
}
doAccept();
});
}
TCPSocket socket_;
TCPAcceptor acceptor_;
};
What did I do wrong? My guess is that inside the doRead function, calling multiple times the doWrite function, when that function then also calls doRead is in part what's causing problems, but I don't know what the correct way of writing data asynchronously multiple times is. But I'm also sure that isn't the only part of my code that isn't behaving as I think it should.
Besides the problems with the code shown that I mentioned in the comments, there is indeed the problem that you suspected:
My guess is that inside the doRead function, calling multiple times the doWrite function, when that function then also calls doRead is in part what's causing problems
The fact that "doRead" is in the same function isn't necessarily a problem (that's just full-duplex socket IO). However "calling multiple times" is. See the docs:
This operation is implemented in terms of zero or more calls to the stream's async_write_some function, and is known as a composed operation. The program must ensure that the stream performs no other write operations (such as async_write, the stream's async_write_some function, or any other composed operations that perform writes) until this operation completes.
The usual way is to put the whole message in a single buffer, but if that would be "expensive" to copy, you can use a BufferSequence, which is known as scatter/gather buffers.
Specifically, you would replace
doWrite(&filePacketSize_, sizeof(short));
doWrite(filePacket_, sizeof(*filePacket_));
with something like
std::vector<boost::asio::const_buffer> msg{
boost::asio::buffer(&filePacketSize_, sizeof(short)),
boost::asio::buffer(filePacket_, sizeof(*filePacket_)),
};
doWrite(msg);
Note that this assumes that filePacketSize and filePacket have been assigned proper values!
You could of course modify do_write to accept the buffer sequence:
template <typename Buffers> void doWrite(Buffers msg)
{
auto self(shared_from_this());
boost::asio::async_write(
socket_, msg,
[this, self](boost::system::error_code ec, std::size_t /*length*/) {
if (!ec) {
doRead();
}
});
}
But in your case I'd simplify by inlining the body (now that you don't call it more than once anyway).
SIDE NOTES
Don't use new or delete. NEVER use malloc in C++. Never use reinterpret_cast<> (except in the very rarest of exceptions that the standard allows!). Instead of
processedData* response = reinterpret_cast<processedData*>(malloc(responseSize));
Just use
processedData response;
(optionally add {} for value-initialization of aggregates). If you need variable-length messages, consider to put a vector or a array<char, MAXLEN> inside the message. Of course, array is fixed length but it preserves POD-ness, so it might be easier to work with. If you use vector, you'd want a scatter/gather read into a buffer sequence like I showed above for the write side.
Instead of reinterpreting between inconsistent short and unsigned short types, perhaps just spell the type with the standard sizes: std::uint16_t everywhere.
Keep in mind that you are not taking into account byte order so your protocol will NOT be portable across compilers/architectures.
Provisional Fixes
This is the listing I ended up with after reviewing the code you shared.
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
namespace ba = boost::asio;
using boost::asio::ip::tcp;
using boost::system::error_code;
using TCPSocket = tcp::socket;
struct processedData { };
struct propertiesPacket { };
// File server.cpp
class ServerConnection : public std::enable_shared_from_this<ServerConnection> {
public:
ServerConnection(TCPSocket socket) : socket_(std::move(socket))
{ }
void start() {
std::clog << __PRETTY_FUNCTION__ << std::endl;
doRead();
}
private:
void doRead()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
auto self(shared_from_this());
socket_.async_read_some(
ba::buffer(&properties_, sizeof(properties_)),
[this, self](error_code ec, std::size_t length) {
std::clog << "received: " << length << std::endl;
if (!ec) {
processData();
std::vector<ba::const_buffer> msg{
ba::buffer(&filePacketSize_, sizeof(uint16_t)),
ba::buffer(&filePacket_, filePacketSize_),
};
ba::async_write(socket_, msg,
[this, self = shared_from_this()](
error_code ec, std::size_t length) {
std::clog << " written: " << length
<< std::endl;
if (!ec) {
doRead();
}
});
}
});
}
void processData() {
std::clog << __PRETTY_FUNCTION__ << std::endl;
/* Data is processed */
}
TCPSocket socket_;
propertiesPacket properties_{};
processedData filePacket_{};
uint16_t filePacketSize_ = sizeof(filePacket_);
};
class Server
{
public:
using IOContext = ba::io_context;
using TCPAcceptor = tcp::acceptor;
Server(IOContext& io, uint16_t port)
: socket_(io)
, acceptor_(io, {tcp::v4(), port})
{
doAccept();
}
private:
void doAccept()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
acceptor_.async_accept(socket_, [this](error_code ec) {
if (!ec) {
std::clog << "Accepted " << socket_.remote_endpoint()
<< std::endl;
std::make_shared<ServerConnection>(std::move(socket_))->start();
doAccept();
} else {
std::clog << "Accept " << ec.message() << std::endl;
}
});
}
TCPSocket socket_;
TCPAcceptor acceptor_;
};
// File client.cpp
int main(int argc, char *argv[])
{
ba::io_context io;
Server s{io, 6869};
std::thread server_thread{[&io] {
io.run();
}};
// always check argc!
std::vector<std::string> args(argv, argv + argc);
if (args.size() == 1)
args = {"demo", "127.0.0.1", "6869"};
// avoid race with server accept thread
post(io, [&io, args] {
try {
propertiesPacket properties;
// ...
// We set the data inside the properties struct
// ...
tcp::socket socket(io);
tcp::resolver resolver(io);
connect(socket, resolver.resolve(args.at(1), args.at(2)));
write(socket, ba::buffer(&properties, sizeof(properties)));
uint16_t responseSize{};
ba::read(socket, ba::buffer(&responseSize, sizeof(uint16_t)));
std::clog << "Client responseSize: " << responseSize << std::endl;
processedData response{};
assert(responseSize <= sizeof(response));
ba::read(socket, ba::buffer(&response, responseSize));
// ...
// The client handles the data
// ...
// for online demo:
io.stop();
} catch (std::exception const& e) {
std::clog << e.what() << std::endl;
}
});
io.run_one();
server_thread.join();
}
Printing something similar to
void Server::doAccept()
Server::doAccept()::<lambda(boost::system::error_code)> Success
void ServerConnection::start()
void ServerConnection::doRead()
void Server::doAccept()
received: 1
void ServerConnection::processData()
written: 3
void ServerConnection::doRead()
Client responseSize: 1
Here is my server class, which renders an async event to send a string to my client, when connected.
The message is definitely dispatched to the client, as the writehandler is invoked successfully without any errors:
class Server {
private:
void writeHandler(ServerConnection connection, const boost::system::error_code &error_code,
std::size_t bytes_transferred) {
if (!(error_code)) {
std::cout << "SENT "<<bytes_transferred <<" BYTES"<< std::endl;
}
}
void renderWriteEvent(ServerConnection connection, const std::string& str) {
std::cout << "RENDERING WRITE EVENT" << std::endl;
connection->write = str;
boost::asio::async_write(connection->socket, boost::asio::buffer(connection->write),
boost::bind(&Server::writeHandler, this, connection,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
};
Now on the client side, after successfully connecting to the server, I call
void renderRead(){
std::cout<<"Available Bytes: "<<socket.available()<<std::endl;
std::string foo;
boost::system::error_code error_code;
std::size_t x = socket.read_some(boost::asio::buffer(foo), error_code);
std::cout<<error_code.message()<<std::endl;
std::cout<<"Bytes read: "<<x<<std::endl;
std::cout<<"Available Bytes: "<<socket.available()<<std::endl;
std::cout<<foo<<std::endl;
//boost::asio::async_read(socket, boost::asio::buffer(read_string), boost::bind(&Client::readHandler, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
which outputs "Available Bytes: 12"
Then, in calling boost::asio::read, I get 0 bytes read, and no error. I don't understand what's wrong. After the read, the number of bytes available for reading in the socket stream is still printed to be 12
A key point here is that read_some() doesn't allocate any memory, it fills memory that is provided to it. For your code, this means ASIO will only replace the data already existing inside of foo, and it will never exceed these bounds.
But you have std::string foo;, which is a default-constructed string, aka an empty string.
So ASIO is populating the buffer you are passing just fine. However, you are passing it a buffer with no room in it. ASIO fills it as much as possible: 0 bytes.
You can test this for yourself by adding the following to your code:
std::string foo;
std::cout << "Available room in buffer: "<< foo.size() << std::endl;
The fix would be to pass a buffer with memory already allocated. You could initialize the string with a length, but using a raw block of bytes that you interpret later as a string_view is more explicit.
constexpr std::size_t buffer_size = 32;
std::array<char, buffer_size> foo;
std::size_t x = socket.read_some(boost::asio::buffer(foo), error_code);
//...
std::string_view message(foo.data(), x);
std::cout << message << std::endl;
int main(){
boost::asio::io_context io_context;
Server server(io_context, SOCKET_ADDRESS, SOCKET_PORT);
std::thread thread_server([&]() {
server.start();
io_context.run();
});
std::thread thread_client([&]() {
Client &client = Client::create(SOCKET_ADDRESS, SOCKET_PORT);
client.start();
done = true; // <-----atomic
});
std::thread thread_stop([&]() {
while (done == false) {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
server.stop();
});
thread_server.join();
thread_client.join();
thread_stop.join();
}
I am experimenting with boost::asio and encountered problem which I am unable to solve. When I run program(simpler example above) on Linux(compiled with gcc) everything is fine. Same when I run it on Release in VS2017CE. However when I run it on Debug(VS2017CE as well) it crash with and exception:
cannot dereference string iterator because string iterator was invalidated
It crash on _endthreadx either when exiting thread_stop or thread_server(most likely second one). Here are my questions then:
What are the differences between Release and Debug basic configurations which might affect code execution and point me where should I look.(I am aware of some but couldn't find anything connected with this particular problem.)
What mistakes were made by me which affects code execution.
I've made some classes so I will provide more code if neccessary, but code basically works so I am starting with just a piece of it.
The code shown doesn't do anything with strings. Also you don't show what io_context is being used in Client instances.
As given, everything is a giant race-condition, because none of the client work might ever get run, but you always set done = true immediately after posting the (supposedly) asynchronous operation of Client::start.
(The potentially sane interpretation here would be if Client::start() were actually completely synchronous, but that really would make the whole existence of static Client& Client::create(...) pretty strange and useless?).
Using a thread to sleep is an anti-pattern, and doubly so in asynchronous code.
cannot dereference string iterator because string iterator was invalidated
This is a clear sign that MSVC's Iterator Debugging is working. It just tells you you have a programming error.
Your error causes string iterators to be used when they're no longer valid. I can't see it, but 99% of the time this would be caused by asynchronous operations using a buffer that gets destroyed (or modified) before the async operation completes. In a nutshell:
void foo() {
std::string msg = "message";
boost::asio::async_write(_socket, boost::asio::buffer(msg), /*...*/);
}
Suggested Code
Simplifying from your code, and showing some hints:
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
#include <iomanip>
using boost::asio::ip::tcp;
using boost::system::error_code;
static std::string const SOCKET_ADDRESS = "127.0.0.1";
static unsigned short const SOCKET_PORT = 6767;
bool check(error_code const& ec, char const* message) {
std::cout << message << " (" << ec.message() << ")\n";
return !ec;
}
struct Server {
boost::asio::io_context& io_;
Server(boost::asio::io_context& io, std::string host, unsigned short port) : io_(io), host_(host), port_(port) {}
void start() {
acc_.set_option(tcp::acceptor::reuse_address(true));
acc_.listen(5);
accept_loop();
}
void stop() {
io_.post([this] { // thread safety
acc_.cancel();
acc_.close();
});
}
private:
void accept_loop() {
acc_.async_accept(sock_, [this](error_code ec) {
if (check(ec, "accepted")) {
std::make_shared<Connection>(std::move(sock_))->start();
accept_loop();
}
});
}
struct Connection : std::enable_shared_from_this<Connection> {
tcp::socket sock_;
std::string buffer_;
Connection(tcp::socket&& sock) : sock_(std::move(sock)) {}
~Connection() {
error_code ec;
std::cout << "Disconnected " << sock_.remote_endpoint(ec) << "\n";
}
void start() {
auto self = shared_from_this();
async_read_until(sock_, boost::asio::dynamic_buffer(buffer_), "\n", [self,this](error_code ec, size_t bytes) {
if (check(ec, "received request")) {
std::cout << "Request: " << std::quoted(buffer_.substr(0, bytes), '\'') << "\n";
if (bytes > 0)
std::reverse(buffer_.begin(), buffer_.begin() + bytes - 1); // reverse the request for the response
async_write(sock_, boost::asio::buffer(buffer_, bytes), [self,this](error_code ec, size_t bytes) {
if (check(ec, "response sent")) {
buffer_.erase(0, bytes);
start(); // handle more requests, if any
}
});
}
});
}
};
std::string host_;
unsigned short port_;
tcp::acceptor acc_{io_, {boost::asio::ip::address_v4::from_string(host_), port_}};
tcp::socket sock_{io_};
};
struct Client {
Client(std::string host, std::string port) : host_(host), port_(port) {}
void start() {
boost::asio::io_context io;
tcp::socket s(io);
tcp::resolver r(io);
connect(s, r.resolve(host_, port_));
send_request(s, "hello world\n");
send_request(s, "bye world\n");
}
private:
void send_request(tcp::socket& s, std::string const& request) {
write(s, boost::asio::buffer(request));
boost::asio::streambuf sb;
read_until(s, sb, "\n");
std::cout << "Received server response: '" << &sb << "'\n";
}
std::string host_;
std::string port_;
};
int main(){
boost::asio::io_context io_context;
Server server(io_context, SOCKET_ADDRESS, SOCKET_PORT);
server.start();
std::thread thread_server([&]() { io_context.run(); });
{
Client client {SOCKET_ADDRESS, std::to_string(SOCKET_PORT)};
client.start();
}
{
Client client {SOCKET_ADDRESS, std::to_string(SOCKET_PORT)};
client.start();
}
server.stop();
thread_server.join();
}
Prints
accepted (Success)
received request (Success)
Request: 'hello world
'
response sent (Success)
Received server response: 'dlrow olleh
'
received request (Success)
Request: 'bye world
'
response sent (Success)
Received server response: 'dlrow eyb
'
received request (End of file)
Disconnected 127.0.0.1:49778
accepted (Success)
received request (Success)
Request: 'hello world
'
response sent (Success)
Received server response: 'dlrow olleh
'
received request (Success)
Request: 'bye world
'
response sent (Success)
Received server response: 'dlrow eyb
'
received request (End of file)
Disconnected 127.0.0.1:49780
accepted (Operation canceled)
Note There's a startup race. Depending on your luck, the first Client might try to connect before the Server has started listening. I'm assuming this is not your worst worry, and I'll leave it as an exercise for the reader.
I make an asynchronous chat server in C++ using the boost library. Almost everything works fine.
There are two ways for a client to disconnect:
by pressing Ctrl + C (killing the client process)
by entering "exit".
The former is OK. However, the latter has a problem. If a client disconnects with "exit", the next message, sent by another client, appears without the first several characters. After that it's OK.
For example: Several clients chat. One of them disconnects with "exit". After that, another client sends "0123456789abcdefghijk" and all clients receive only: "abcdefghijk". I don't know where's the problem, I guess it's something about streambuf. I found similar problem (almost the same) but in C#.
Here's the code:
#include<iostream>
#include<list>
#include<map>
#include<queue>
#include<vector>
#include<cstdlib>
#include<ctime>
#include<boost/thread.hpp>
#include<boost/bind.hpp>
#include<boost/asio.hpp>
#include<boost/asio/ip/tcp.hpp>
using namespace std;
using namespace boost::asio;
using namespace boost::asio::ip;
typedef boost::shared_ptr<tcp::socket> socket_ptr;
typedef boost::shared_ptr<string> string_ptr;
typedef boost::shared_ptr< list<socket_ptr> > clientList_ptr;
typedef boost::shared_ptr< list<string> > nameList_ptr;
const string waitingMsg("Waiting for clients...\n");
const string totalClientsMsg("Total clients: ");
const string errorReadingMsg("Error on reading: ");
const string errorWritingMsg("Error on writing: ");
const int EOF_ERROR_CODE = 2;
const int THREADS = 1;
io_service service;
tcp::acceptor acceptor(service, tcp::endpoint(tcp::v4(), 30001));
boost::mutex mtx;
clientList_ptr clientList(new list<socket_ptr>);
nameList_ptr nameList(new list<string>);
boost::asio::streambuf buff;
time_t timer;
void ready();
void accepting();
void askForName(socket_ptr clientSock, const boost::system::error_code& error);
void receiveName(socket_ptr clientSock, const boost::system::error_code& error,
std::size_t bytes_transferred);
void identify(socket_ptr clientSock, const boost::system::error_code& error, std::size_t bytes_transferred);
void accepted(socket_ptr clientSock, string_ptr name);
void receiveMessage(socket_ptr clientSock, string_ptr name);
void received(socket_ptr clientSock, string_ptr name, const boost::system::error_code& error,
std::size_t bytes_transferred);
bool extract(string_ptr message, std::size_t bytes_transferred);
bool clientSentExit(string_ptr clientSock);
void disconnectClient(socket_ptr clientSock, string_ptr name, const boost::system::error_code& error);
void writeMessage(socket_ptr clientSock, string_ptr message);
void responseSent(const boost::system::error_code& error);
void notification(socket_ptr sock, string_ptr name, const string headOfMsg, const string tailOfMsg);
int main(int argc, char* argv[])
{
try
{
vector<boost::shared_ptr<boost::thread> > threads;
ready();
for (int i = 0; i < THREADS; i++)
{
boost::shared_ptr <boost::thread> t(new boost::thread(boost::bind(&io_service::run, &service)));
threads.push_back(t);
}
for (int i = 0; i < THREADS; i++)
{
threads[i]->join();
}
}
catch (std::exception& error)
{
cerr << error.what() << endl;
}
return 0;
}
void ready()
{
cout << waitingMsg;
accepting();
}
void accepting()
{
socket_ptr clientSock(new tcp::socket(service));
acceptor.async_accept(*clientSock, boost::bind(&askForName, clientSock, boost::asio::placeholders::error));
}
void askForName(socket_ptr sock, const boost::system::error_code& error)
{
if (error)
{
cerr << "Error on accepting: " << error.message() << endl;
}
boost::asio::async_write(*sock, buffer("Please, enter your name:\n"),
boost::bind(&receiveName, sock, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
accepting();
}
void receiveName(socket_ptr sock, const boost::system::error_code& error,
std::size_t bytes_transferred)
{
if (error)
{
cerr << errorWritingMsg << error.message() << endl;
}
boost::asio::async_read_until(*sock, buff, '\n',
boost::bind(&identify, sock, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void identify(socket_ptr sock, const boost::system::error_code& error,
std::size_t bytes_transferred)
{
if(error)
{
if (error.value() != EOF_ERROR_CODE)
{
cerr << errorReadingMsg << error.message() << endl;
}
return;
}
string_ptr name(new string(""));
if (!extract(name, bytes_transferred))
{
return;
}
if (find(nameList->begin(), nameList->end(), *name) != nameList->end())
{
boost::asio::async_write(*sock, buffer("This name is already in use! Please, select another name:\n"),
boost::bind(&receiveName, sock, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
return;
}
nameList->emplace_back(*name);
accepted(sock, name);
}
void accepted(socket_ptr sock, string_ptr name)
{
mtx.lock();
clientList->emplace_back(sock);
mtx.unlock();
notification(sock, name, "New client: ", " joined. ");
receiveMessage(sock, name);
}
void receiveMessage(socket_ptr sock, string_ptr name)
{
boost::asio::async_read_until(*sock, buff, '\n', boost::bind(&received, sock, name, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void received(socket_ptr sock, string_ptr name, const boost::system::error_code& error,
std::size_t bytes_transferred)
{
if(error)
{
if (error.value() != EOF_ERROR_CODE)
{
cerr << errorReadingMsg << error.message() << endl;
}
disconnectClient(sock, name, error);
return;
}
if(!clientList->empty())
{
//mtx.lock();
string_ptr message(new string(""));
if(!extract(message, bytes_transferred))
{
//mtx.unlock();
disconnectClient(sock, name, error);
return;
}
*message = *name + ": " + *message + "\n";
cout << "ChatLog: " << *message << endl;
writeMessage(sock, message);
receiveMessage(sock, name);
//mtx.unlock();
}
}
bool extract(string_ptr message, std::size_t bytes_transferred)
{
mtx.lock();
buff.commit(bytes_transferred);
std::istream istrm(&buff);
//mtx.unlock();
std::getline(istrm, *message);
buff.consume(buff.size());
string_ptr msgEndl(new string(*message + "\n"));
mtx.unlock();
if(clientSentExit(msgEndl))
{
return false;
}
return true;
}
bool clientSentExit(string_ptr message)
{
return message->compare(0, 5, "exit\n") == 0;
}
void disconnectClient(socket_ptr sock, string_ptr name, const boost::system::error_code& error)
{
boost::system::error_code ec = error;
auto position = find(clientList->begin(), clientList->end(), sock);
auto namePos = find(nameList->begin(), nameList->end(), *name);
sock->shutdown(tcp::socket::shutdown_both, ec);
if (ec)
{
cerr << "Error on shutting: " << ec.message() << endl;
}
sock->close(ec);
if(ec)
{
cerr << "Error on closing: " << ec.message() << endl;
}
clientList->erase(position);
nameList->erase(namePos);
notification(sock, name, "", " disconnected. ");
}
void writeMessage(socket_ptr sock, string_ptr message)
{
for(auto& cliSock : *clientList)
{
if (cliSock->is_open() && cliSock != sock)
{
boost::asio::async_write(*cliSock, buffer(*message),
boost::bind(&responseSent, boost::asio::placeholders::error));
}
}
}
void responseSent(const boost::system::error_code& error)
{
if (error)
{
cerr << "Error on writing: " << error.message() << endl;
}
}
void notification(socket_ptr sock, string_ptr name, const string headOfMsg, const string tailOfMsg)
{
string_ptr serviceMsg (new string (headOfMsg + *name + tailOfMsg));
cout << *serviceMsg << totalClientsMsg << clientList->size() << endl;
*serviceMsg = *serviceMsg + "\n";
writeMessage(sock, serviceMsg);
cout << waitingMsg;
}
It's interesting that I have similar synchronous server with the same way of using of streambuf, but there are no such problems.
boost::asio::async_read_until() can read any amount of characters to streambuf after \n. It then gives you bytes_transferred, which is count of characters in the first line (not necessarily the count of characters that were read to the buffer). See documentation.
As long as you keep your buffer variable intact, next boost::asio::async_read_until() will read characters first from the buffer and then from the socket.
It seems to me that you read a line from the buffer using getline(), which is correct. After that, you call
buff.consume(buff.size());
which clears the buffer, removing all information about the partial lines you may have received. The first complete line that you read has already been removed from the buffer by getline(), so the consume() call is unnecessary in any case.
Just removing the consume() call would not solve your problem, because you seem to use one buffer that is shared between all clients, and you would not know what partial data was from which client. A possible solution could be creating a list of buffers (one for each client), just like you have a list of sockets. Then boost::asio::async_read_until() and getline() would take care of handling the partial data, and you wouldn't have to think about that.
The other answer explains what went wrong.
However it can be a bit tricky to find out how to fix it.
Maybe you can find inspiration from a similar question I handled here:
Boost asio: Unable to read URL Body (JSON)
Here, the OP was having trouble with basically the same thing: after reading his HTTP headers he would have "dropped" part of the body. So I added logic:
NOTE Again, it's important not to assume that the end-of-headers coincides with the packet boundary. Therefore, start read_body() with draining the available input already received.
std::shared_ptr<std::vector<char> > bufptr = std::make_shared<std::vector<char> >(nbuffer);
auto partial = std::copy(
std::istreambuf_iterator<char>(&pThis->buff), {},
bufptr->begin());
std::size_t already_received = std::distance(bufptr->begin(), partial);
assert(nbuffer >= already_received);
nbuffer -= already_received;
I think I fixed the issue. Just created a list of streambufs - a streambuf for each client. But I had to keep the consume() function, because otherwise the check if a given name already exists failed resulting in possibility to have several clients sharing the same name. Then messaging stopped working but I removed the locks in extract() and now everything appears to be all right.
I have a c++ windows app which works fine. I use Boost::asio. When I tried porting on linux, the app did not work as intended at all. After getting discouraged by valgrind errors, I decided to run DrMemory on windows and fix the errors that would show up first. One error I have not been able to fix is an error I have deducted to be about my socket. Is it not possible to use scoped_ptr with a socket? Anyways, here are the errors DrMemory records, followed by some relevant code. The code does not compile if I change the logic for the socket from a smart pointer to either references or a bare pointer. (Some error about the socket, I can't recall exactly what it was, but it was the same for reference and pointer)
What have I done wrong with this?
DrMemory : http://pastebin.com/gHQYrCjA
NetworkEntity.cpp
#include "boost/asio.hpp"
#include "NetworkEntity.h"
#include "boost/bind.hpp"
NetworkEntity::NetworkEntity(std::string address, std::string port)
: address_(address),
port_(port)
{
}
void NetworkEntity::stop()
{
if (socket_)
{
socket_->close();
}
socket_.reset();
timeout_->cancel();
}
void NetworkEntity::check_timeout(const boost::system::error_code& error)
{
if (error != boost::asio::error::operation_aborted)
{
stop();
errorbuf_ << "timed out after " << TIMEOUT_ << " seconds\n";
}
}
std::vector<std::string> NetworkEntity::tcpPoll(const char* message, const char endOfMessage)
{
boost::asio::io_service io_service;
try{
//initialize timeout timer.
timeout_.reset(new boost::asio::deadline_timer(io_service));
timeout_->expires_from_now(boost::posix_time::seconds(TIMEOUT_));
timeout_->async_wait(boost::bind(&NetworkEntity::check_timeout, this, boost::asio::placeholders::error));
//initialize connection, which writes then reads.
tcp::resolver resolver(io_service);
start_connect(&io_service, resolver.resolve(tcp::resolver::query(address_, port_)), message, endOfMessage);
//run async operations, wait for their completion.
io_service.run();
//retrieve answer
std::vector<std::string> lines;
std::string line;
std::istream is(&answer_);
int i = 0;
while (std::getline(is, line)){
lines.push_back(line);
}
//reset answer to nothing (not needed but is a security)
answer_.consume(answer_.size());
request_.consume(request_.size());
setError(errorbuf_.str());
errorbuf_.str(""); // clear the contents
errorbuf_.clear();
return lines;
}
catch (std::exception& e){
errorbuf_ << "An exception has occured : " << e.what() << "\n";
return std::vector<std::string>{};
}
}
void NetworkEntity::start_connect(boost::asio::io_service* io_service, tcp::resolver::iterator endpoint_iterator, const std::string message, const char endOfMessage)
{
// Start the asynchronous connect operation.
socket_.reset(new tcp::socket(*io_service));
socket_->async_connect(endpoint_iterator->endpoint(),
boost::bind(&NetworkEntity::handle_connect, this, io_service, boost::asio::placeholders::error, message, endOfMessage));
}
void NetworkEntity::handle_connect(boost::asio::io_service* io_service, const boost::system::error_code& err, const std::string message, const char endOfMessage)
{
if (err)
{
stop();
errorbuf_ << "Connect error : " << err.message() << "\n";
}
else
{
start_write(message, endOfMessage);
}
}
void NetworkEntity::start_write(const std::string message, const char endOfMessage)
{
//convert message from string to streambuf
std::ostream request_stream(&request_);
request_stream << message;
//end of convertion
boost::asio::async_write(*socket_, request_,
boost::bind(&NetworkEntity::handle_write, this, boost::asio::placeholders::error, endOfMessage));
}
void NetworkEntity::handle_write(const boost::system::error_code& error, const char endOfMessage)
{
if (!error)
{
boost::asio::io_service io;
boost::asio::deadline_timer wait(io);
wait.expires_from_now(boost::posix_time::milliseconds(500));
wait.wait();
start_read(endOfMessage);
}
else
{
stop();
errorbuf_ << "Write error : " << error.message() << "\n";
}
}
void NetworkEntity::start_read(const char endOfMessage)
{
boost::asio::async_read_until(*socket_, answer_, endOfMessage,
boost::bind(&NetworkEntity::handle_read, this, boost::asio::placeholders::error));
}
void NetworkEntity::handle_read(const boost::system::error_code& error)
{
if (error)
{
errorbuf_ << "read error : " << error.message() << "\n";
}
stop();
}
NetworkEntity.h
#inclued "boost/asio.hpp"
#include "boost/array.hpp"
#include "boost/scoped_ptr.hpp"
#include "boost/shared_ptr.hpp"
#include "Error.h"
#ifndef NETWORK_ENTITY_H
#define NETWORK_ENTITY_H
using boost::asio::ip::tcp;
class NetworkEntity : private boost::noncopyable
{
public:
NetworkEntity(std::string address, std::string port);
std::vector<std::string> tcpPoll(const char* message, const char endOfMessage);
private:
void stop();
void check_timeout(const boost::system::error_code& error);
void start_write(const std::string message, const char endOfMessage);
void handle_write(const boost::system::error_code& error, const char endOfMessage);
void start_read(const char endOfMessage);
void start_connect(boost::asio::io_service* io_service, tcp::resolver::iterator endpoint_iterator, const std::string message, const char endOfMessage);
void handle_connect(boost::asio::io_service* io_service, const boost::system::error_code& error, const std::string message, const char endOfMessage);
void handle_read(const boost::system::error_code& error);
void timeoutHandler(const boost::system::error_code& error);
boost::scoped_ptr<tcp::socket> socket_;
boost::scoped_ptr<boost::asio::deadline_timer> timeout_;
std::string address_;
std::string port_;
boost::asio::streambuf answer_;
boost::asio::streambuf request_;
static const int TIMEOUT_ = 5;
std::stringstream errorbuf_; //loggable by functions I removed for the sake of simplicity
boost::shared_ptr<Error> error_;
boost::scoped_ptr<boost::asio::deadline_timer> logTimer_;
NetworkEntity(const NetworkEntity& e) = delete;
NetworkEntity & operator=(const NetworkEntity& e) = delete;
};
#endif
main.cpp (I did not try to compile this one, sorry if there are any errors)
#include "NetworkEntity.h"
#include <iostream>
int main()
{
NetworkEntity n("192.168.0.36", "10001");
while (true){
std::string mes;
std::cin >> mes;
std::vector<std::string> ans = n.tcpPoll(mes.c_str(), '\n'); //message to send, last character expected to recieve (read until)
for (int i = 0; i < ans.size(); i++)
std::cout << ans[i] << "\n";
}
}
Some tips to help you to find the problem, hopefully:
move boost::asio::io_service io_service from NetworkEntity::tcpPoll to class member variable.
I don't undertsand why you create another ioservice in NetworkEntity::handle_write, use the member variable.
In NetworkEntity::tcpPoll, 'timeout_.reset(new boost::asio::deadline_timer(io_service))' will create a memory leak, use std::make_shared
I do not understand the error handling in NetworkEntity::handle_write as you 'reset' the socket, and continue the io_service.run() in NetworkEntity::tcpPoll without even ckecking kind of error.
And last but not least, about your question 'Is it not possible to use scoped_ptr with a socket?', socket is one thing, pointer, scoped or not another thing. You should read and learn about smart pointer, nothing magic behind that term, only a reference count, and delete when that count reach 0.
And as bonus a free advice: forget DrMemory and understand exactly each line, each statement of your code, if function has a returned value, get it and check it ...
Good luck !
In the end, I made it work on linux and run valgrind-clean. My problem wasn't even in what was shown here, it was that I was passing "10001\r" instead of "10001" as the port, which made the resolver fail. That \r came from a getline (read from file) which deleted only the \n in "10001\r\n"
socket_.reset();
should be inside the prior 'if' block.