this happens when constructing a structure, my code:
http://wklej.org/hash/c42680a7f9d/txt/ at this line: http://wklej.org/hash/5fefcecc371/txt/ backtrace: http://wklej.org/id/451070/txt/
any help is apperciated
Sorry, i fail to copy the code here so i post it at any other site ;(
Use a debugger and get a stack trace.
The problem is almost certainly passing a bad C string into the std::string constructor. Perhaps the pointer is invalid or the C string does not terminate and the constructor reads off into protected memory.
But without more information I can't tell what the bug is. The debugger should point it out immediately.
Also, your Socket holds a pointer but only defines a constructor and destructor. You also need a copy constructor and an assignment operator. If those two operations aren't supposed to happen then define them as private with no implementation.
Also, I see from your backtrace that this is an old version of GCC. It's possible that this version does not have the fixes that make std::string safe to use in multi-threaded programs. I don't know when it was fixed but some older versions of the libstdc++ library didn't lock the reference counts on the string and could crash when different threads would free the string memory while also writing to it.
I've placed your code here in order to be able to edit it:
#ifdef _WIN32
#define _WIN32_WINNT 0x0501
#endif
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/noncopyable.hpp>
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <vector>
#include <sstream>
#include <string>
#ifdef assert
#undef assert
#endif
#define assert(x)\
if (!x) { \
std::clog << "[Error - __FUNCTION__] Asseration to: " << #x << " failed. line: " << __LINE__ << ", file: " << __FILE__ << "." << std::endl;\
std::exit(100);\
}
template<typename _Tp>
inline std::string toString(_Tp __p)
{
std::stringstream ss;
ss << __p;
std::string ret;
ss >> ret;
return ret;
}
struct Proxy;
typedef std::vector<Proxy*> ProxyVec;
struct Proxy
{
std::string name;
uint32_t port;
};
ProxyVec loadProxies(const std::string& fileName)
{
std::FILE *f = fopen(fileName.c_str(), "r");
if (!f) {
std::clog << "[Error - loadProxies] Cannot open: " << fileName << "." << std::endl;
delete f;
f = NULL;
return ProxyVec();
}
char buffer[1024];
ProxyVec ret;
int32_t __n = 0;
while (fgets(buffer, sizeof(buffer), f)) {
++__n;
std::string str(buffer);
if (str.find("\n") != std::string::npos)
str = str.substr(0, str.length()-1);
if (str.find("\r") != std::string::npos)
str = str.substr(0, str.length()-1);
size_t sep = str.rfind(":");
if (sep == std::string::npos) {
std::clog << "[Error - loadProxies] Cannot load proxy #" << __n << "." << std::endl;
continue;
}
std::string hostname = str.substr(0, sep);
uint32_t port = static_cast<uint32_t>(std::atoi(str.substr(sep+1, str.length()-sep).c_str()));
std::clog << "Loading proxy: " << hostname << ":" << port << "." << std::endl;
Proxy proxy;
proxy.name = hostname;
proxy.port = port;
ret.push_back(&proxy);
}
std::clog << "Loaded: " << __n << " proxies." << std::endl;
return ret;
}
class Socket
{
public:
Socket(boost::asio::io_service& service, const std::string& host,
const std::string& port, const std::string& targetHost, const std::string& targetPort);
~Socket();
void write(const std::string& message);
std::string receivedData() const;
bool connected() const;
void receive();
void handle();
private:
struct SocketData
{
SocketData(boost::asio::io_service& service, const std::string& _host, const std::string& _port,
const std::string& _targetHost, const std::string& _targetPort):
socket(service),
write(service),
read(service),
resolver(service),
buffers(),
host(_host),
port(_port),
targetHost(_targetHost),
targetPort(_targetPort),
connected(false)
{
}
boost::asio::ip::tcp::socket socket;
boost::asio::io_service::strand write, read;
boost::asio::ip::tcp::resolver resolver;
boost::array<char, 1024> buffers;
std::string host, port;
std::string targetHost, targetPort;
bool connected;
};
// FIXME: Use shared_ptr instead.
SocketData* d;
protected:
//handle resolve func
void handle_resolve(const boost::system::error_code&,
boost::asio::ip::tcp::resolver::iterator);
//handle connection func
void handle_connect(const boost::system::error_code&,
boost::asio::ip::tcp::resolver::iterator);
//handle write func
void handle_write(const boost::system::error_code&, size_t);
//handle read func
void handle_read(const boost::system::error_code&, size_t);
private:
void connectionThread();
};
Socket::Socket(boost::asio::io_service& service, const std::string& host, const std::string& port,
const std::string& targetHost, const std::string& targetPort)
: d(new SocketData(service, host, port, targetHost, targetPort))
{
boost::thread thread(boost::bind(&Socket::connectionThread, this));
// FIXME: This function is blocking. The constructur will never exit.
// Use thread_group.join_all() as last line in main() instead.
thread.join();
}
Socket::~Socket()
{
d->socket.close();
delete d;
d = NULL;
}
void Socket::connectionThread()
{
if (!d)
return;
if (d->connected)
return;
boost::asio::ip::tcp::resolver::query query(d->host, d->port);
d->resolver.async_resolve(query,
boost::bind(&Socket::handle_resolve, this,
boost::asio::placeholders::error,
boost::asio::placeholders::iterator));
}
void Socket::handle_resolve(const boost::system::error_code& e,
boost::asio::ip::tcp::resolver::iterator ep_iter)
{
if (!e) {
boost::asio::ip::tcp::endpoint iter = *ep_iter;
d->socket.async_connect(iter,
boost::bind(&Socket::handle_connect, this,
boost::asio::placeholders::error, ++ep_iter));
} else {
std::clog << "[Error - Socket::handle_resolve] " << e.message() << "." << std::endl;
}
}
void Socket::handle_connect(const boost::system::error_code& e,
boost::asio::ip::tcp::resolver::iterator ep_iter)
{
if (!e) {
std::cout << "[Notice - Socket::handle_connect] Connected to host." << std::endl;
d->connected = true;
write("CONNECT " + d->targetHost + ":" + d->targetPort + " HTTP/1.1\r\n\r\n");
receive();
} else if (ep_iter != boost::asio::ip::tcp::resolver::iterator()) {
d->socket.close();
boost::asio::ip::tcp::endpoint ep = *ep_iter;
d->socket.async_connect(ep,
boost::bind(&Socket::handle_connect, this,
boost::asio::placeholders::error, ++ep_iter));
} else {
std::clog << "[Error - Server::handle_connect] " << e.message() << "." << std::endl;
}
}
void Socket::handle_write(const boost::system::error_code& e,
size_t bytes)
{
assert(!e || bytes < 0);
}
void Socket::handle_read(const boost::system::error_code& e,
size_t bytes)
{
assert(!e || bytes < 0);
std::cout << receivedData() << std::endl;
receive();
}
void Socket::write(const std::string& message)
{
boost::asio::async_write(d->socket, boost::asio::buffer(message),
d->write.wrap(
boost::bind(&Socket::handle_write, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
std::string Socket::receivedData() const
{
return std::string(d->buffers.data());
}
void Socket::receive()
{
d->socket.async_read_some(boost::asio::buffer(d->buffers),
d->read.wrap(
boost::bind(&Socket::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
void Socket::handle()
{
assert(!d->targetHost.empty());
assert(!d->targetPort.empty());
std::string str(d->buffers.data());
std::clog << "Received: " << str << "." << std::endl;
if (str.substr(0, 4) == "PING")
write("PO" + str.substr(2) + "\r\n");
else if (str.find("MODE") != std::string::npos)
write("JOIN #OTland\r\n");
else if (str.find("JOIN") != std::string::npos)
write("PRIVMSG #OTland :Hello\r\n");
}
bool Socket::connected() const
{
return d->connected;
}
void handler(const std::string& fileName, const std::string& host, const std::string& port, uint32_t threads);
int main(int argc, char **argv)
{
if (argc < 5) {
std::clog << "[Error - main] Usage: " << argv[0] << " <proxy_file> <host> <port> <threads>" << std::endl;
return 1;
}
std::string file(argv[1]);
std::string host(argv[2]);
std::string port(argv[3]);
uint32_t threads = static_cast<uint32_t>(std::atoi(argv[4]));
if (!threads)
threads = 1;
for (uint32_t __i = 0; __i < threads; ++__i)
// FIXME: Use the thread.join() there.
handler(file, host, port, threads);
}
typedef std::vector<Socket*> SocketVec;
void handler(const std::string& fileName, const std::string& host,
const std::string& port, uint32_t threads)
{
assert(!fileName.empty());
assert(!host.empty());
assert(!port.empty());
ProxyVec proxies = loadProxies(fileName);
assert(proxies.size());
SocketVec sockets;
for (ProxyVec::const_iterator it = proxies.begin(); it != proxies.end(); ++it) {
boost::asio::io_service io;
Socket socket(io, (*it)->name, toString((*it)->port), host, port);
// FIXME: socket is a local variable and it's address is invalid outside
// this loop -> memory leak -> seg-fault.
sockets.push_back(&socket);
}
for (SocketVec::const_iterator it = sockets.begin(); it != sockets.end(); ++it) {
if (!(*it)->connected())
continue;
(*it)->handle();
}
// FIXME: I'm not sure if I understand this architecture. A new thread is
// started with this function as handler ? Who waits until this new created
// thread is finished ? Where is the join() ?
(void) new boost::thread(boost::bind(handler, fileName, host, port, threads));
}
I don't think this is your only problem, but in this snippet in handler, you're creating your Socket objects on the stack. Each Socket object that you create will be destroyed at the end of the for loop. This means that the objects in the sockets vector are invalid objects. Doing this may also corrupt the memory heap enough to produce the error that you're seeing.
SocketVec sockets;
for (ProxyVec::const_iterator it = proxies.begin(); it != proxies.end(); ++it) {
boost::asio::io_service io;
Socket socket(io, (*it)->name, toString((*it)->port), host, port);
sockets.push_back(&socket);
}
Change this to:
SocketVec sockets;
for (ProxyVec::const_iterator it = proxies.begin(); it != proxies.end(); ++it) {
boost::asio::io_service io;
Socket* socket = new Socket(io, (*it)->name, toString((*it)->port), host, port);
sockets.push_back(socket);
}
This answer was originally a comment to Dan's answer, but after looking at your code I felt compelled to give a full answer. You really need to take a closer look at the Boost.Asio examples and understand how they work. Pay special attention to the asynchronous examples, it doesn't look like you've grasped the concepts of object lifetime and how the handlers work. Particularly, you should master single threaded programs before jumping into multiple threads. When you've conquered that, you should use a thread pool invoking io_service::run instead of an io_service per thread. It will ultimately make your program's logic easier to understand.
You also should look into valgrind, there's a slew of errors in your code like this one:
==19853== Invalid read of size 4
==19853== at 0x10000D0E4: handler(std::string const&, std::string const&, std::string const&, unsigned int) (in ./a.out)
==19853== by 0x10000D5E6: main (in ./a.out)
==19853== Address 0x7fff5fbff398 is not stack'd, malloc'd or (recently) free'd
Related
I have a client which read file and sends the data to server line by line. Server must count the number of lines sent. I'm using boost::asio::async_read_until to reach this result. But I'm getting the garbage (like this: Line: �68�) when trying to read data from buffer. Client sends the data only in ASCII encoding.
Client code fragment:
std::ifstream infile(argv[1]);
std::string line;
while (std::getline(infile, line)) {
boost::asio::write(s, boost::asio::buffer(line + '\n'));
std::cout << line << std::endl;
Server read function:
void handle_read(const boost::system::error_code& error, size_t bytes_trasferred, boost::asio::streambuf& buf)
{
if (!error)
{
if (!bytes_trasferred)
{
std::cout << "0 bytes trasferred\n";
return;
}
std::string data = boost::asio::buffer_cast<const char*>(buf.data());
std::istringstream is(data);
std::string line;
std::getline(is, line);
std::cout << "Line: " << line << std::endl;
}
else
std::cerr << "Error: " << error.message() << std::endl;
}
void do_read()
{
std::cout << "do_read\n";
auto self(shared_from_this());
boost::asio::streambuf buf;
buf.prepare(1048576);
std::cout << "ASYNC\n";
boost::asio::async_read_until(socket_, buf, "\n",
boost::bind(&TcpConnection::handle_read, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred, boost::ref(buf)));
}
How to resolve it? Help me, please!
I've tried to use 'self' 'this' instead. It follows to memory leak.
I've tried to add null terminator after getting data in handler function. ChatGPT said me about this way, but behavior still same.
Here:
std::string data = boost::asio::buffer_cast<const char*>(buf.data());
This is Undefined Behaviour as your are assuming that buf.data() points to a character sequence that is properly terminated with a NUL character, where you have absolutely no reason to assume this.
Besides you have UB because you pass a reference to buf, a local variable, which will by definition no longer be valid after do_read returns.
Thirdly, as Alan pointed out, you failed to copy the shared pointer (self) into the bound handler.
You're mixing many different ways to attack the problem of dealing with line-wise input. By far the easiest approach is to
use a dynamic buffer (like indeed, streambuf)
make it a member, because that's why you have shared_from_this in the first place
do not use prepare() because async_read_until knows how to do that (just like it does commit() for you)
do use consume() instead of doing getline /again/, even though async_read_until already tells you where the newline is.
Combining that:
struct TcpConnection : std::enable_shared_from_this<TcpConnection> {
TcpConnection(tcp::socket s) : socket_(std::move(s)) {}
void run() {
do_read();
}
private:
tcp::socket socket_;
asio::streambuf buf;
void handle_read(error_code ec, size_t n) {
std::cerr << "handle_read: " << ec.message() << std::endl;
if (!ec) {
std::string const line(boost::asio::buffer_cast<char const*>(buf.data()), n);
buf.consume(n);
std::cout << "Line: " << quoted(line) << std::endl;
do_read();
}
}
void do_read() {
async_read_until( //
socket_, buf, "\n",
boost::bind(&TcpConnection::handle_read, shared_from_this(), asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
};
I'd probably simplify, avoiding copying and potentially allocations:
struct TcpConnection : std::enable_shared_from_this<TcpConnection> {
TcpConnection(tcp::socket s) : socket_(std::move(s)) {}
void run() {
buf_.reserve(8192); // optional, tune to taste
do_read();
}
private:
tcp::socket socket_;
std::string buf_;
void handle_read(error_code ec, size_t n) {
std::cerr << "handle_read: " << ec.message() << std::endl;
if (!ec) {
auto line = std::string_view(buf_).substr(0, n);
std::cout << "Line: " << quoted(line) << std::endl;
buf_.erase(0, n);
do_read();
}
}
void do_read() {
async_read_until( //
socket_, asio::dynamic_buffer(buf_), "\n",
boost::bind(&TcpConnection::handle_read, shared_from_this(), ph::error,
ph::bytes_transferred));
}
};
This still uses a dynamic buffer, but you don't have to do the extra hoops with istream which you apparently really don't need. For illustration purposes only, you could make the dynamic_string_buffer explicit:
void run() {
backing_.reserve(8192); // optional, tune to taste
do_read();
}
private:
tcp::socket socket_;
std::string backing_;
asio::dynamic_string_buffer<char, std::char_traits<char>, std::allocator<char>> buf_{backing_};
void handle_read(error_code ec, size_t n) {
std::cerr << "handle_read: " << ec.message() << std::endl;
if (!ec) {
auto line = std::string_view(backing_).substr(0, n);
std::cout << "Line: " << quoted(line) << std::endl;
buf_.consume(n); // look mom, it's a DynamiceBuffer all the same!
do_read();
}
}
void do_read() {
async_read_until( //
socket_, buf_, "\n",
boost::bind(&TcpConnection::handle_read, shared_from_this(), ph::error,
ph::bytes_transferred));
}
Note, I wouldn't do that, but it demonstrates you how both approaches use the same DynamicBuffer concept as you did with streambuf.
Live Demo
Live On Coliru
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <iomanip>
#include <iostream>
namespace asio = boost::asio;
namespace ph = asio::placeholders;
using asio::ip::tcp;
using boost::system::error_code;
struct TcpConnection : std::enable_shared_from_this<TcpConnection> {
TcpConnection(tcp::socket s) : socket_(std::move(s)) {}
void run() {
buf_.reserve(8192); // optional, tune to taste
do_read();
}
private:
tcp::socket socket_;
std::string buf_;
void handle_read(error_code ec, size_t n) {
if (n) {
auto line = std::string_view(buf_).substr(0, n - 1); // exclude '\n'
std::cout << socket_.remote_endpoint() << " Line: " << quoted(line) << std::endl;
buf_.erase(0, n);
}
if (!ec)
do_read();
else
std::cerr << "handle_read: n:" << n << " " << ec.message() << std::endl;
}
void do_read() {
async_read_until( //
socket_, asio::dynamic_buffer(buf_), "\n",
boost::bind(&TcpConnection::handle_read, shared_from_this(), ph::error,
ph::bytes_transferred));
}
};
struct Server {
Server(asio::any_io_executor ex) : acc(ex, {{}, 7878}) {}
void start() {
do_accept();
}
private:
tcp::acceptor acc;
void do_accept() {
acc.async_accept(make_strand(acc.get_executor()), [this](error_code ec, tcp::socket s) {
if (!ec) {
std::make_shared<TcpConnection>(std::move(s))->run();
do_accept();
}
});
}
};
int main() {
asio::io_context ioc;
Server srv(ioc.get_executor());
srv.start();
ioc.run();
}
With the client emulated by a very similar oneliner:
netcat 127.0.0.1 7878 -w0 < main.cpp
Prints:
127.0.0.1:54860 Line: "#include <boost/asio.hpp>"
127.0.0.1:54860 Line: "#include <boost/bind/bind.hpp>"
127.0.0.1:54860 Line: "#include <iomanip>"
127.0.0.1:54860 Line: "#include <iostream>"
127.0.0.1:54860 Line: "namespace asio = boost::asio;"
...
...
127.0.0.1:54860 Line: "int main() {"
127.0.0.1:54860 Line: " asio::io_context ioc;"
127.0.0.1:54860 Line: ""
127.0.0.1:54860 Line: " Server srv(ioc.get_executor());"
127.0.0.1:54860 Line: " srv.start();"
127.0.0.1:54860 Line: ""
127.0.0.1:54860 Line: " ioc.run();"
127.0.0.1:54860 Line: "}"
handle_read: n:0 End of file
Local demo showing multiple concurrent clients:
I created the main cpp file and three classes to create an asynchronous server.
Server, Service, and Acceptor respectively.
However, they caused errors in the build process, even though there were no errors in the visual studio 2019 environment.
I tried to fix the error, but most of the errors occurred in other files, so I couldn't even think of it myself.
main
#include "Server.h"
#include <iostream>
#include <boost/asio.hpp>
#include <thread>
#include <atomic>
#include <memory>
#define DEFAULT_THREAD_SIZE 2;
using namespace boost;
int main()
{
unsigned short port_num;
std::cin >> port_num;
try {
Server srv;
unsigned int threads = std::thread::hardware_concurrency() * 2;
if (threads == 0) {
threads = DEFAULT_THREAD_SIZE;
}
std::cout << "\nPort - " << port_num << "\nServer start\n";
srv.Start(port_num, threads);
while (1) {
std::cin >> srv;
}
}
catch (system::system_error& e) {
std::cout << "\nError code: " << e.code() << "\nError Message\n" << e.what();
}
return 0;
}
This includes Server.h, which defines Server class.
#include "Acceptor.h"
#include <boost/asio.hpp>
#include <thread>
#include <atomic>
#include <memory>
using namespace boost;
class Server
{
public:
Server();
void Start(unsigned short port_num, unsigned int threads);
void Stop();
int Command(std::string& str);
private:
asio::io_service mios;
std::unique_ptr<asio::io_service::work> mWork;
std::unique_ptr<Acceptor> mAcceptor;
std::vector <std::unique_ptr<std::thread>> mThreads;
};
std::istream& operator>>(std::istream& is, Server& srv);
Here's the implementation, Server.cpp.
#include "Server.h"
Server::Server() {
mWork.reset(new asio::io_service::work(mios));
}
void Server::Start(unsigned short port_num, unsigned int threads) {
assert(thread > 0);
mAcceptor.reset(new Acceptor(mios, port_num));
mAcceptor->Start();
for (int i = 0; i < threads; i++) {
std::unique_ptr<std::thread> th(new std::thread([this]() {mios.run(); }));
mThreads.push_back(std::move(th));
}
}
void Server::Stop() {
mAcceptor->Stop();
mios.stop();
for (auto& th : mThreads) {
th->join();
}
}
int Server::Command(std::string& str) {
return 0;
}
std::istream& operator>>(std::istream& is, Server& srv) {
std::string str;
is >> str;
srv.Command(str);
return is;
}
This is Acceptor class.
#include "Service.h"
#include <boost/asio.hpp>
#include <thread>
#include <atomic>
#include <memory>
using namespace boost;
class Acceptor
{
public:
Acceptor(asio::io_service& ios, unsigned short port_num);
void Start();
void Stop();
private:
std::shared_ptr<asio::io_service> mios;
std::shared_ptr<asio::ip::tcp::acceptor> mAcceptor;
std::atomic<bool> mIsStopped;
void InitAccept();
void OnAccept(const system::error_code ec, std::shared_ptr<asio::ip::tcp::socket> sock);
};
#include "Acceptor.h"
Acceptor::Acceptor(asio::io_service& ios, unsigned short port_num) {
mios = std::make_shared<asio::io_service>(ios);
mAcceptor = std::make_shared<asio::ip::tcp::acceptor>(mios, asio::ip::tcp::endpoint(asio::ip::address_v4::any(), port_num));
mIsStopped = false;
}
void Acceptor::Start() {
mAcceptor->listen();
InitAccept();
}
void Acceptor::Stop() {
mIsStopped.store(true);
}
void Acceptor::InitAccept() {
std::shared_ptr<asio::ip::tcp::socket> sock(new asio::ip::tcp::socket(mios));
mAcceptor->async_accept(*sock, [this, sock](const system::error_code& error) {OnAccept(error, sock);});
}
void Acceptor::OnAccept(const system::error_code ec, std::shared_ptr<asio::ip::tcp::socket> sock) {
if (ec.value() == 0 || ER) {
(new Service(sock))->StartHandling();
}
else{
std::cout << "Error code:" << ec.value() << "error " << "Error message: " << ec.message() << "\n";
}
if (!mIsStopped.load()) {
InitAccept();
}
else {
mAcceptor->close();
}
}
Service class
#define ER true
#include <iostream>
#include <boost/asio.hpp>
#include <thread>
#include <atomic>
#include <memory>
using namespace boost;
class Service
{
public:
Service(std::shared_ptr<asio::ip::tcp::socket> sock);
void StartHandling();
private:
void OnRequestReceived(const boost::system::error_code& ec, std::size_t bytes_transferred);
std::string mReponse;
std::shared_ptr<asio::ip::tcp::socket> mSock;
asio::streambuf mRequest;
void OnReponseSent(const system::error_code& ec, std::size_t bytes_transferred);
void OnFinish();
std::string ProcessRequest(asio::streambuf& request);
};
#include "Service.h"
Service::Service(std::shared_ptr<asio::ip::tcp::socket> sock){
mSock = sock;
}
void Service::StartHandling() {
asio::async_read_until(mSock, mRequest, '\n', [this](const system::error_code ec, std::size_t bytes_transferred) {OnRequestReceived(ec, bytes_transferred); });
}
void Service::OnRequestReceived(const system::error_code& ec, std::size_t bytes_transferred) {
if (ec.value() != 0 || ER) {
std::cout << "Error code:" << ec.value() << "Error message: " << ec.message() << "\n";
OnFinish();
return;
}
mReponse = ProcessRequest(mRequest);
asio::async_write(mSock, asio::buffer(mReponse), [this](const system::error_code& ec, std::size_t bytes_transferred) {OnReponseSent(ec, bytes_transferred); });
}
void Service::OnReponseSent(const system::error_code& ec, std::size_t bytes_transferred) {
if (ec.value() != 0 || ER) {
std::cout << "Error code:" << ec.value() << "Error message: " << ec.message() << "\n";
}
OnFinish();
}
void Service::OnFinish() {
delete this;
}
std::string Service::ProcessRequest(asio::streambuf& request) {
std::string reponse;
std::istream input(&request);
std::getline(input, reponse);
assert(reponse.back() == '\n');
return reponse;
}
I have no idea what to do. I wanted to do it myself, but I couldn't even debug because I couldn't figure out where the problem was going and it wasn't built.
It simply doesn't compile. I genuinely wonder how people can come up with /so much/ code before noticing that stuff doesn't compile.
Rule #1: Baby Steps (this goes for the professionals just as much, only they have it internalized).
You're doing stuff like:
mios = std::make_shared<asio::io_service>(ios);
This requires io_service to be copyable (which it isn't). You would probably make mios a reference:
asio::io_service& mios;
There seems to be a lot of "superstitious" use of shared_ptr all around.
The fact that
assert(thread > 0);
misspelled threads indicates that you may have been building Release-only builds.
Read the compiler messages:
void Service::StartHandling() {
asio::async_read_until(mSock, mRequest, '\n', [this](const system::error_code ec, std::size_t bytes_transferred) {OnRequestReceived(ec, bytes_transferred); });
}
This triggers the error:
/home/sehe/custom/boost_1_73_0/boost/asio/impl/read_until.hpp|959 col 53| error: no type named ‘executor_type’ in ‘class std::shared_ptr<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >’
Obviously you meant *mSock. Same later:
asio::async_write(*mSock, asio::buffer(mReponse), [this](const system::error_code& ec, std::size_t bytes_transferred) {OnReponseSent(ec, bytes_transferred); });
A pointer is not the object it points to - not even smart pointers. The point [sic] of smart pointers is not to make C++ equal to (say) Java - you should use Java if you wanted that.
With these it compiles: Live ON Wandbox
More Review
top-level const makes no difference in value arguments
Don't use new or delete:
mWork.reset(new asio::io_service::work(mios));
use make_unique instead
mWork = std::make_unique<asio::io_service::work>(mios);
// ...
mAcceptor = std::make_unique<Acceptor>(mios, port_num);
Use header guards (or #pragma once)
Do not use namespace using-directives; use using-declarations instead
Especially don't use namespace using-directives in header files (you make it
impossible for your users to prevent/fix name collisions, which may lead to compile error or silent change of behaviour)
Use constructor initializer lists (and move-semantics):
Service::Service(std::shared_ptr<asio::ip::tcp::socket> sock){
mSock = sock;
}
Becomes
Service::Service(std::shared_ptr<asio::ip::tcp::socket> sock)
: mSock(std::move(sock))
{ }
Here:
(new Service(std::move(sock)))->StartHandling();
Don't use new, don't superstitious-use shared pointer, and, ironically, in
the case of Service consider using enable_shared_from_this so you do
use shared_ptr instead of the delete this; anti-pattern.
Initialize your primitive class members1
std::atomic<bool> mIsStopped{};
Without, it will have indeterminate value, which usually leads to UB when used
Don't ignore errors:
if (ec.value() == 0 || ER) {
(new Service(std::move(sock)))->StartHandling();
}
Instead, report / log. Also, detect errors portably:
if (!ec) {
Or
if (!ec.failed()) {
generally, handle errors (cin >> port_num e.g.),
catch by const&
Intermediate result (still compiles): Live on Wandbox
BONUS
Simplify, use asio::thread_pool, uniform initalization
USE bytes_transferred! read_until does not guarantee it stops on the
delimiter, because that's not how TCP works. Trailing data can be present
in the buffer. This means that in DEBUG builds this assert would sometimes fail:
assert(request.back() == '\n');
Actually the code read response.back() which is guaranteed to fail because getline doesn't include it ¯\(ツ)/¯
You might use boost::iostreams::restrict or instead
asio::dynamic_buffer() on a std::string and pass a string_view into
the handler (ProcessRequest):
mReponse = ProcessRequest(std::string_view(mRequest).substr(0, bytes_transferred));
And later
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/stream_buffer.hpp>
std::string Service::ProcessRequest(std::string_view request) {
assert(request.back() == '\n');
boost::iostreams::stream_buffer<boost::iostreams::array_source> buf(
request.data(), request.size());
std::istream input(&buf);
std::string reponse;
std::getline(input, reponse);
return reponse;
}
Get rid of all the redundant shared pointers. If Acceptor is already
dynamically allocated managed by a shared-pointer, there is really no need
to also make it own its tcp::acceptor instance by shared_ptr. In general
all the members could just be by value in your code. As long as the
surrounding object stays around (as you do with Service) the members are
guaranteed to stay alive as well.
mIsStopped can be eliminated by simply cancel()-ing the acceptor instead. To get thread-safety, simply post to the relevant executor.
If you wanted the server to actually exit when the stop command is executed, you need to make the while(true) loop have a stop condition, e.g.
int Server::Command(std::string const& cmd) {
std::cout << "Command: " << std::quoted(cmd) << "\n";
if (cmd == "quit") {
Stop();
return 1;
}
std::cerr << "Unknown command (\"quit\" to exit)" << std::endl;
return 0;
}
std::istream& operator>>(std::istream& is, Server& srv) {
std::string str;
is >> str;
if (srv.Command(str)) {
is.setstate(std::ios::badbit);
}
return is;
}
And in main:
while (std::cin >> srv) { }
FULL DEMO:
Live On Wandbox
File Acceptor.h
#ifndef _HOME_SEHE_PROJECTS_STACKOVERFLOW_ASIO_ACCEPTOR_H
#define _HOME_SEHE_PROJECTS_STACKOVERFLOW_ASIO_ACCEPTOR_H
#include "Service.h"
class Acceptor {
public:
template <typename Executor>
Acceptor(Executor ex, unsigned short port_num) : mAcceptor(make_strand(ex), {{}, port_num}) {}
void Start();
void Stop();
private:
tcp::acceptor mAcceptor;
void InitAccept();
void OnAccept(error_code ec, tcp::socket&& sock);
};
#endif
File Common.h
#pragma once
#include <boost/asio.hpp>
#include <memory>
#include <thread>
#include <atomic>
namespace asio = boost::asio;
using boost::system::error_code;
using asio::ip::tcp;
File Server.h
#ifndef _HOME_SEHE_PROJECTS_STACKOVERFLOW_ASIO_SERVER_H
#define _HOME_SEHE_PROJECTS_STACKOVERFLOW_ASIO_SERVER_H
#include "Acceptor.h"
class Server {
public:
explicit Server(unsigned short port_num);
void Start();
void Stop();
int Command(std::string const& str);
private:
asio::thread_pool mio;
Acceptor mAcceptor;
};
std::istream& operator>>(std::istream& is, Server& srv);
#endif
File Service.h
#ifndef _HOME_SEHE_PROJECTS_STACKOVERFLOW_ASIO_SERVICE_H
#define _HOME_SEHE_PROJECTS_STACKOVERFLOW_ASIO_SERVICE_H
#include "Common.h"
#include <iostream>
class Service : public std::enable_shared_from_this<Service> {
public:
explicit Service(tcp::socket&& sock);
void StartHandling();
private:
void OnRequestReceived(error_code ec, std::size_t bytes_transferred);
std::string mRequest, mReponse;
tcp::socket mSock;
void OnReponseSent(error_code ec, size_t bytes_transferred);
std::string ProcessRequest(std::string_view request);
};
#endif
File Acceptor.cpp
#include "Acceptor.h"
#include <utility>
void Acceptor::Start() {
mAcceptor.listen();
InitAccept();
}
void Acceptor::Stop() {
// be thread safe
post(mAcceptor.get_executor(), [this] { mAcceptor.cancel(); });
}
void Acceptor::InitAccept() {
mAcceptor.async_accept(
make_strand(mAcceptor.get_executor()),
[this](error_code error, tcp::socket&& sock) { OnAccept(error, std::move(sock)); });
}
void Acceptor::OnAccept(error_code ec, tcp::socket&& sock) {
if (!ec.failed()) {
std::make_shared<Service>(std::move(sock))->StartHandling();
InitAccept();
} else {
std::cout << "OnAccept: " << ec.message() << "\n";
}
}
File main.cpp
#include "Server.h"
#include <iostream>
int main() {
if (uint16_t port_num; std::cin >> port_num) {
try {
Server srv(port_num);
std::cout << "Port - " << port_num << "\nServer start\n";
srv.Start();
while (std::cin >> srv) { }
} catch (boost::system::system_error const& e) {
std::cout << "Error " << e.code().message() << "\n";
}
} else {
std::cerr << "Invalid input (port number required)\n";
}
}
File Server.cpp
#include "Server.h"
#include <iomanip>
Server::Server(unsigned short port_num)
: mAcceptor(make_strand(mio), port_num) {}
void Server::Start() { mAcceptor.Start(); }
void Server::Stop() { mAcceptor.Stop(); }
int Server::Command(std::string const& cmd) {
std::cout << "Command: " << std::quoted(cmd) << "\n";
if (cmd == "quit") {
Stop();
return 1;
}
std::cerr << "Unknown command (\"quit\" to exit)" << std::endl;
return 0;
}
std::istream& operator>>(std::istream& is, Server& srv) {
std::string str;
is >> str;
if (srv.Command(str)) {
is.setstate(std::ios::badbit);
}
return is;
}
File Service.cpp
#include "Service.h"
#include <utility>
#include <iomanip>
Service::Service(tcp::socket&& sock)
: mSock(std::move(sock)) {}
void Service::StartHandling() {
asio::async_read_until(
mSock, asio::dynamic_buffer(mRequest), '\n',
[this, self = shared_from_this()](error_code ec, std::size_t bytes_transferred) {
OnRequestReceived(ec, bytes_transferred);
});
}
void Service::OnRequestReceived(error_code ec, std::size_t bytes_transferred) {
if (ec) {
std::cout << "OnRequestReceived: " << ec.message() << "\n";
return;
}
std::string_view view = mRequest;
mReponse = ProcessRequest(view.substr(0, bytes_transferred));
asio::async_write(
mSock, asio::buffer(mReponse),
[this, self = shared_from_this()](error_code ec, std::size_t bytes_transferred) {
OnReponseSent(ec, bytes_transferred);
});
}
void Service::OnReponseSent(error_code ec, std::size_t /*bytes_transferred*/) {
if (ec) {
std::cout << "OnReponseSent: " << ec.message() << "\n";
}
}
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/stream_buffer.hpp>
std::string Service::ProcessRequest(std::string_view request) {
//std::cerr << "TRACE: " << std::quoted(request) << "\n";
assert(request.back() == '\n');
boost::iostreams::stream_buffer<boost::iostreams::array_source> buf(
request.data(), request.size());
std::istream input(&buf);
std::string reponse;
std::getline(input, reponse);
return reponse + '\n';
}
E.g. when running with 2323 and later quit command:
# (echo 2323; sleep 30; echo quit) | ./sotest
Port - 2323
Server start
Command: "quit"
OnAccept: Operation canceled
It does correctly accept multiple connections:
# for a in {1..10}; do printf "Message with random data $RANDOM\n" | nc localhost 2323; done
Message with random data 8002
Message with random data 28046
Message with random data 17943
Message with random data 17845
Message with random data 10832
Message with random data 20049
Message with random data 27593
Message with random data 18979
Message with random data 2773
Message with random data 31159
I am writing a very simple HTTP server based on: http://www.boost.org/doc/libs/1_62_0/doc/html/boost_asio/example/cpp11/echo/async_tcp_echo_server.cpp
I have tried numerous techniques to extract the data from a boost::asio::streambuf in order to parse HTTP headers. The streambuf object does not appear to manage it's memory properly (or, more likely, I am mis-using it) & I end up getting a seg fault.
As you can see from the code, none of the techniques suggested here or here work. I suspect this is because I'm using boost::asio::async_read_until() to read all the headers, rather than just a single header at a time as most other coders appear to be doing.
Any advice/pointers would be appreciated.
/*
g++ -D TRY1 -ggdb3 -I $e/boost-1.62/include /tmp/streambuf.bug.cc $e/boost-1.62/lib/libboost_system.a -D TRY1
or
g++ -D TRY2 -ggdb3 -I $e/boost-1.62/include /tmp/streambuf.bug.cc $e/boost-1.62/lib/libboost_system.a -D TRY2
or
g++ -D TRY3 -ggdb3 -I $e/boost-1.62/include /tmp/streambuf.bug.cc $e/boost-1.62/lib/libboost_system.a -D TRY3
*/
#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
using boost::asio::ip::tcp;
class session : public std::enable_shared_from_this<session>
{
public:
session(tcp::socket socket) : socket_(std::move(socket)), dbg(true) {
assert(headers.empty());
memset(padding, 0, 10000);
std::cout << "buffer size: " << buffer.max_size() << '\n';
}
void start() { readHeaders(); }
private:
void readHeaders() {
if (dbg)
std::cout << "readHeaders() start\n";
//auto self(shared_from_this());
boost::asio::async_read_until(socket_, buffer, "\r\n\r\n", [this](const boost::system::error_code &ec, std::size_t length) {
if (dbg)
std::cout << "Read " << length << " bytes of headers in async_read_until()\n";
if (ec)
throw std::runtime_error("Error code in readHeaders()");
#ifdef TRY1
std::istream stream(&buffer);
std::string str;
assert(!corrupted("A1"));
while (std::getline(stream, str)) { // seg fault! (on 3rd invocation)
assert(!corrupted("A2"));
std::cout << "str=" << str << '\n';
assert(!corrupted("A3"));
}
#endif
#if 0
std::string str;
boost::asio::buffer_copy(boost::asio::buffer(str), buffer); // ugh, won't compile
#endif
#if 0
std::vector<unsigned char> v(buffer.size());
boost::asio::buffer_copy(boost::asio::buffer(v), buffer); // ugh, won't compile
const std::string str(v.begin(), v.end());
#endif
#ifdef TRY2
std::string str;
auto data = buffer.data();
assert(!corrupted("B1"));
for (auto it = data.begin(); it != data.end(); ++it) {
const auto buf = *it;
std::cout << "buf_size=" << boost::asio::buffer_size(buf) << '\n';
assert(!corrupted("B2"));
const char *tmp = boost::asio::buffer_cast<const char *>(buf);
assert(!corrupted("B3"));
str.append(tmp); // BUG!!
assert(!corrupted("B4")); // fails
}
#endif
#ifdef TRY3
auto data = buffer.data();
auto end = data.end();
std::string str;
assert(!corrupted("C1"));
for (auto it = data.begin(); it != end; ++it) {
assert(!corrupted("C2"));
std::vector<unsigned char> v(boost::asio::buffer_size(*it));
assert(!corrupted("C3"));
boost::asio::buffer_copy(boost::asio::buffer(v), *it); // BUG!!
assert(!corrupted("C4")); // fails
str.append(v.begin(), v.end());
assert(!corrupted("C5"));
}
#endif
#ifdef TRY4
assert(!corrupted("D1"));
const std::string str(boost::asio::buffers_begin(buffer.data()), boost::asio::buffers_end(buffer.data())); // BUG!!
assert(!corrupted("D2")); // fails
#endif
#ifdef TRY5
assert(!corrupted("E1"));
const std::string str((std::istreambuf_iterator<char>(&buffer)), std::istreambuf_iterator<char>()); // seg faults!
assert(!corrupted("E2"));
#endif
#ifdef TRY6
boost::asio::streambuf::const_buffers_type bufs = buffer.data();
assert(!corrupted("F1"));
std::string str(boost::asio::buffers_begin(bufs), boost::asio::buffers_begin(bufs) + buffer.size()); // BUG!!
assert(!corrupted("F2")); // fails
#endif
assert(!corrupted("Z1"));
std::cout << "str=" << str << "end of data\n";
std::istringstream input(str);
std::string line;
while (std::getline(input, line)) {
assert(!corrupted("Z2"));
if (line.size() == 1)
continue; // blank line
if (line.substr(0, 3) == "GET")
continue; // TODO: handle properly
const auto idx = line.find(':');
assert(idx != std::string::npos);
const std::string key(line.begin(), line.begin() + idx);
const std::string val(line.begin() + idx + 2, line.end());
// std::cout << "key=" << key << " val=" << val << '\n';
assert(!corrupted("Z3"));
headers[key] = val;
assert(!corrupted("Z4"));
}
assert(!corrupted("Z5"));
for (auto it3 : headers) {
std::cout << it3.first << '=' << it3.second << '\n';
}
const auto it2 = headers.find("Content Length");
contentLength = (it2 == headers.end() ? 0 : atoi(it2->second.c_str()));
if (contentLength > 0) {
const boost::system::error_code ec; // (boost::system::errc::success);
readBody(ec);
}
});
if (dbg)
std::cout << "readHeaders() end\n";
}
void readBody (const boost::system::error_code &ec) {
if (dbg)
std::cout << "readBody()\n";
if (ec)
throw std::runtime_error("Error code in readBody()");
boost::asio::streambuf::const_buffers_type bufs = buffer.data();
body.append(boost::asio::buffers_begin(bufs), boost::asio::buffers_begin(bufs) + buffer.size());
if (dbg)
std::cout << "body.size=" << body.size() << " content length=" << contentLength << '\n';
boost::asio::async_read(socket_,
buffer,
boost::asio::transfer_at_least(1),
boost::bind(&session::readBody, this, boost::asio::placeholders::error));
}
bool corrupted (const std::string s) const {
bool b = false;
if (strlen(padding) > 0) {
std::cout << "buffer overflow detected # " << s << "! padding is: " << padding << '\n';
std::cout.flush();
b = true;
}
if (headers.size() > 1000) {
std::cout << headers.size() << " headers!!\n";
b = true;
}
return b;
}
tcp::socket socket_;
boost::asio::streambuf buffer;
char padding[10000]; // $buffer appears not to manage it's memory properly. Add some padding to detect overflows.
std::map<std::string, std::string> headers;
uint contentLength;
std::string body;
const bool dbg;
};
class server
{
public:
server(boost::asio::io_service& io_service, short port) : acceptor_(io_service, tcp::endpoint(tcp::v4(), port)), socket_(io_service) {
do_accept();
}
private:
void do_accept() {
acceptor_.async_accept(socket_, [this](boost::system::error_code ec) {
if (!ec) {
std::cout << "Connection accepted\n";
std::make_shared<session>(std::move(socket_))->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
tcp::socket socket_;
};
int main(int argc, char* argv[])
{
try {
if (argc != 2) {
std::cerr << "Usage: async_tcp_echo_server <port>\n";
return 1;
}
boost::asio::io_service io_service;
server s(io_service, std::atoi(argv[1]));
io_service.run();
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
I am using boost v1.62, gcc v6.1 on Linux (Ubuntu 12.04).
You can read from the streambuf
manually (see documentation)
using std::istream:
boost::asio::streambuf buf;
std::istream is(&buf);
// usual extraction:
int i;
if (is >> i) {
// use `i`
}
// or usual line-wise extraction:
std::string line;
while (std::getline(is, line)) {
// do something with `line`
}
alternative use boost::asio::buffer_* functions (buffer_begin(), buffer_end() and buffer_copy) - How copy or reuse boost::asio::streambuf?
Copying the contents of a boost::asio::streambuf as a string has been answered in both the Copy a streambuf's contents to a string and How copy or reuse boost::asio::streambuf questions:
boost::asio::streambuf source;
...
std::string target{buffers_begin(source.data()), buffers_end(source.data())};
The problems being observed are the result of undefined behavior. The program fails to meet the lifetime requirement for async_read_until()'s b parameter, as the streambuf are being destroyed before the completion handler is invoked:
[...] Ownership of the streambuf is retained by the caller, which must guarantee that it remains valid until the handler is called.
In this case, streambuf is a data member of session, and session objects are managed by a shared pointer. The only shared pointer managing session is both created and destroyed in the following expression:
std::make_shared<session>(std::move(socket_))->start();
Within start(), an async_read_until() operation is initiated. However, upon returning form start(), the session's buffer is destroyed before the async_read_until()'s completion handler is invoked, violating the lifetime requirement.
The idiomatic solution used by the official Asio examples is to capture the results of shared_from_this() in the completion handler's lambda capture. This guarantees that the lifetime of the session will be at least as long as the completion handler.
auto self(shared_from_this());
async_read_until(socket_, buffer_, ...,
[this, self](boost::system::error_code& ec, std::size_t length)
{
// `self` keeps the `session` alive for the lifetime of the
// handler. If more async operations are initiated from within
// this handler, then the completion handlers should capture
// `self` as well.
...
});
The exact answer to your question gave sehe. Below is some pseudocode I am using currently for parsing headers.
// This is where to store headers.
_STL::map<_STL::string, _STL::string> m_headers;
// buffer is of type asio::streambuf and contains response from asio::async_read_until
_STL::istream response_stream_headers(&buffer);
// Some helper variables.
_STL::string header, header_name, header_value;
while (true) {
_STL::getline(response_stream_headers, header, '\r');
// Remove \n symbol from the stream.
response_stream_headers.get();
if (header == "") {
// We reached end of headers, there might be still some more data!!
break;
}
// Parse header to key->value
size_t separator_pos = header.find(':');
if (separator_pos != _STL::string::npos) {
header_name = header.substr(0, separator_pos);
if (separator_pos < header.length() - 1) {
header_value = header.substr(separator_pos + 1);
}
else {
header_value = "";
}
boost::trim_left(header_value);
m_headers[name] = value;
}
}
// Parsing is done, but some of the request response could have been reed by
// asio::async_read_until, so whe read response_stream_headers untill end.
// You should use body_response_start as the begining of your response.
std::string body_response_start(std::istreambuf_iterator<char>(response_stream_headers), {});
I'm usig boost asio for an IRC bot, and one of my async operation results in a bad file descriptor. I tried to put the socket in a shared_ptr, but I still got the "Bad File Descriptor" error. I don't know whats wrong in it.
Here are the files, I omitted some of the functions from the cpp file. But I you want to read the full file, it's here on my Github.
The error happends in the _read function.
Thanks!
irc.hpp
#ifndef H_IRC
#define H_IRC
#include <vector>
#include <boost/asio.hpp>
#include <boost/tokenizer.hpp>
#include <boost/shared_ptr.hpp>
class Irc
{
public:
Irc(const std::string &server, const std::string &port, const std::function<void()> onConnect);
void connect();
void close();
void user(const std::string &username);
void user(const std::string &username, const std::string &hostname, const std::string &server, const std::string &realname);
void nick(std::string &nickname);
void join(const std::string &chan);
void part(const std::string &chan);
void privmsg(const std::string &to, const std::string &msg);
void command(const std::string &cmd, const std::string &msg);
void command(const std::string &cmd, const std::string &to, const std::string &msg);
void run();
private:
void _read(const boost::system::error_code &error);
void _send(std::string &message);
void _readHandler(const boost::tokenizer<boost::char_separator<char> > &tokenizer);
void _connectHandler(const boost::system::error_code &error);
void _pong(const std::string &ping);
std::string _server;
std::string _port;
std::string _chan;
std::vector<std::function<void (const boost::tokenizer<boost::char_separator<char> >&)>> _readHandlers;
std::function<void()> _onConnect;
boost::asio::streambuf _buffer;
boost::asio::io_service _ios;
boost::shared_ptr<boost::asio::ip::tcp::socket> _socket;
};
#endif
irc.cpp
#include "irc.hpp"
#include <iostream>
#include <boost/bind.hpp>
#include <boost/make_shared.hpp>
Irc::Irc(const std::string &server, const std::string &port, const std::function<void()> onConnect)
: _server(server), _port(port), _onConnect(onConnect),
_socket(boost::make_shared<boost::asio::ip::tcp::socket>(boost::ref(_ios)))
{
// Ping back handler
_readHandlers.push_back([this](const boost::tokenizer<boost::char_separator<char> > &tokenizer) {
std::vector<std::string> tokens(begin(tokenizer), end(tokenizer));
if(tokens[0].compare("PING") == 0)
_pong(tokens[1]);
});
}
void Irc::connect()
{
boost::asio::ip::tcp::resolver resolver(_ios);
boost::asio::ip::tcp::resolver::query query(_server, _port);
boost::asio::ip::tcp::resolver::iterator it = resolver.resolve(query);
boost::asio::ip::tcp::resolver::iterator end;
boost::system::error_code error = boost::asio::error::host_not_found;
while(it != end)
{
if(!error)
break;
std::cout << "Connecting to " << _server << " " << _port << std::endl;
boost::asio::async_connect(*_socket, it,
boost::bind(&Irc::_connectHandler, this, error)
);
it++;
if(error)
std::cout << "Error : " << error.message() << std::endl;
}
if(error)
std::cout << "Error connectinf to " << _server << " " << error.message() << std::endl;
else
std::cout << "Connection success" << std::endl;
}
void Irc::close()
{
_socket->close();
_ios.stop();
}
void Irc::run()
{
boost::asio::async_read_until(*_socket, _buffer, "\r\n",
boost::bind(&Irc::_read, this,
boost::asio::placeholders::error
)
);
_ios.run();
}
/*
* Private
*/
void Irc::_read(const boost::system::error_code &error)
{
if(error)
{
std::cerr << "Error in read : " << error.message() << std::endl;
}
else
{
std::string data(buffers_begin(_buffer.data()), buffers_begin(_buffer.data()) + _buffer.size());
std::cout << data << std::endl;
boost::char_separator<char> sep("!#:; ");
boost::tokenizer<boost::char_separator<char> > tokenizer(data, sep);
_readHandler(tokenizer);
boost::asio::async_read_until(*_socket, _buffer, "\r\n",
boost::bind(&Irc::_read, this,
boost::asio::placeholders::error
)
);
}
}
inline void Irc::_send(std::string &message)
{
boost::asio::write(*_socket, boost::asio::buffer(message + "\r\n"));
}
void Irc::_readHandler(const boost::tokenizer<boost::char_separator<char> > &tokenizer)
{
for(auto it : _readHandlers)
it(tokenizer);
}
void Irc::_connectHandler(const boost::system::error_code &error)
{
if(!error)
{
_onConnect();
}
}
connect is never called.
This causes the "bad file handle" error
Further Notes
Suddenly, _send uses synchronous asio::write. Why?
Error handling should probably be added there, too (catch or pass error_code& argument).
There's only one socket which never gets re-initialized or assigned. Embedding it into a shared pointer isn't changing anything¹.
This however is strange:
std::cout << "Connecting to " << _server << " " << _port << std::endl;
boost::asio::async_connect(*_socket, it,
boost::bind(&Irc::_connectHandler, this, error)
);
This does potentially many asynchronous connect operations on the same socket simultaneously. This is a data race and therefore Undefined Behaviour, see: documentation.
So you need to fix it to use several sockets or sequential.
Turns out, this is very simple: you're already using the free function version of async_connect:
http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/async_connect/overload1.html
This function attempts to connect a socket to one of a sequence of endpoints. It does this by repeated calls to the socket's async_connect member function, once for each endpoint in the sequence, until a connection is successfully established.
So the fix is to just call it once.
Your bind doesn't use a placeholder, instead uses a hardcoded error!
boost::system::error_code error = boost::asio::error::host_not_found;
boost::asio::async_connect(_socket, it, boost::bind(&Irc::_connectHandler, this, error));
Needs to be more like
boost::asio::async_connect(_socket, it, boost::bind(&Irc::_connectHandler, this, boost::asio::placeholders::error()));
after handling incoming traffic, you need to consume the buffer contents, or it will infinitely repeat the same:
_readHandler(tokenizer);
_buffer.consume(_buffer.size());
Pull Request
https://github.com/Bl4ckb0ne/irc-boost/pull/1
Adds:
869c225 Use shared_ptr
9042c6d Add function level trace
50dee1b Revert shared_ptr and rename _onConnect(ed)
20475b9 Fixing the async_connect debacle
c6d8a2e Fixed channel handling and consistency join/part
6fd9242 Initiate `connect()` instead of read from `run()`
06a6c06 Do **not** assume contiguous buffer storage (UB)
090fe8c Consume handled input
68e5e8a Comment
All the above changed, I have successfully connected to an IRC channel and receiving mesages.
¹ (especially not unless you make sure something hangs on to an instance of the shared_ptr)
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.