I'm trying to build a thread that checks for user input and if the input equals "exit", it turns off all other threads.
The way that I use cin seems to stop the thread. The thread should run, check for user input, and if there is any and it equals "exit", turn runProcesses off.
This is my code that doesn't work as expected as "newline stopped" never is printed, "running" is printed only once:
void check_for_cin() {
while ( runProcesses ) {
cout << "running";
string input;
std::getline( std::cin, input );
//while ( std::getline( std::cin, input ) ) {
if ( !input.empty() ) {
if ( input == "exit" ) {
runProcesses = false;
cout << "exiting" << ", run processes: " << runProcesses;
}
}
cout << "newline stopped";
boost::this_thread::sleep( boost::posix_time::seconds( 1 ) );
}
cout << "no longer checking for input";
}
How can my intent be done?
Look at Asio's file descriptor service objects.
Posix has a 'reactor' style asynchrony, so you don't actually need threads to achieve asynchronicity.
My example shows a reading loop that exits when 'exit' is typed /or/ when a timeout expires (10s).
#include <boost/asio.hpp>
#include <boost/asio/posix/stream_descriptor.hpp>
boost::asio::io_service my_io_service;
boost::asio::posix::stream_descriptor in(my_io_service, ::dup(STDIN_FILENO));
boost::asio::deadline_timer timer(my_io_service);
// handle timeout
void timeout_expired(boost::system::error_code ec) {
if (!ec)
std::cerr << "Timeout expired\n";
else if (ec == boost::asio::error::operation_aborted) // this error is reported on timer.cancel()
std::cerr << "Leaving early :)\n";
else
std::cerr << "Exiting for another reason: " << ec.message() << "\n";
// stop the reading loop
in.cancel();
in.close();
}
// set timeout timer
void arm_timeout()
{
timer.expires_from_now(boost::posix_time::seconds(10));
timer.async_wait(timeout_expired);
}
// perform reading loop
void reading_loop()
{
std::cerr << "(continueing input...)\n";
static boost::asio::streambuf buffer; // todo some encapsulation :)
async_read_until(in, buffer, '\n', [&](boost::system::error_code ec, size_t bytes_transferred) {
if (!ec)
{
std::string line;
std::istream is(&buffer);
if (std::getline(is, line) && line == "exit")
ec = boost::asio::error::operation_aborted;
else
reading_loop(); // continue
}
if (ec)
{
std::cerr << "Exiting due to: " << ec.message() << "\n";
// in this case, we don't want to wait until the timeout expires
timer.cancel();
}
});
}
int main() {
arm_timeout();
reading_loop();
my_io_service.run();
}
On windows, you can use the equivalent Windows Stream Handle
You could trivially add threads by running my_io_service.run() on more than one thread.
Related
I'm new to the boost::asio, and boost::process libraries and I've come across a problem which I'm struggling to find a solution for...
Consider that I have a small toy program that does the following:
Firstly, fork()s itself into a parent-branch and a child-branch.
The child-branch then uses the boost::process::child class to invoke the unix command ls in an asynchronous context.
The child-branch supplies the boost::process::child class with a boost::process::async_pipe to direct std_out to.
The parent-branch wishes to read what has been written to the pipe, line by line, and process it further.
Currently, my implementation of this works up to a point. However, the read_loop() call in the parent-branch does not terminate. It is almost as if it never reaches EOF, or is blocked. Why is this?
Here is my MWE:
#include <boost/process.hpp>
#include <boost/asio.hpp>
#include <iostream>
#include <string>
#include <unistd.h>
void read_loop(boost::process::async_pipe& pipe)
{
static boost::asio::streambuf buffer;
boost::asio::async_read_until(
pipe,
buffer,
'\n',
[&](boost::system::error_code error_code, std::size_t bytes) {
if (!error_code) {
std::istream is(&buffer);
if (std::string line; std::getline(is, line)) {
std::cout << "Read Line: " << line << "\n";
}
read_loop(pipe);
}
else {
std::cout << "Error in read_loop()!\n";
pipe.close();
}
}
);
}
int main(int argc, char* argv[])
{
boost::asio::io_context io_context{};
boost::process::async_pipe pipe{ io_context };
io_context.notify_fork(boost::asio::io_context::fork_prepare);
pid_t pid{ fork() };
if (pid == 0) {
io_context.notify_fork(boost::asio::io_context::fork_child);
boost::process::child child(
boost::process::args({ "/usr/bin/ls", "/etc/" }),
boost::process::std_out > pipe,
boost::process::on_exit([&](int exit, std::error_code error_code) { std::cout << "[Exited with code " << exit << " (" << error_code.message() << ")]\n"; }),
io_context
);
io_context.run();
}
else {
io_context.notify_fork(boost::asio::io_context::fork_parent);
read_loop(pipe);
io_context.run();
}
return 0;
}
Which will successfully give the (abridged) output, as expected:
Read Line: adduser.conf
...
[Exited with code 0 (Success)]
...
Read Line: zsh_command_not_found
but will then just hang until it is forcibly killed.
Which leaves the main question, why does my read_loop() function end up blocking/not exiting correctly?
Thanks in advance!
Chasing The Symptom
The process not "seeing" EOF makes me think you have to close either end of the pipe. This is somewhat hacky, but works:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/process.hpp>
#include <iostream>
namespace bp = boost::process;
void read_loop(bp::async_pipe& pipe) {
static boost::asio::streambuf buffer;
using boost::system::error_code;
async_read_until( //
pipe, buffer, '\n', [&](error_code ec, [[maybe_unused]] size_t bytes) {
// std::cout << "Handler " << ec.message() << " bytes:" << bytes << " (" <<
// buffer.size() << ")" << std::endl;
if (!ec) {
std::istream is(&buffer);
if (std::string line; std::getline(is, line)) {
std::cout << "Read Line: " << line << "\n";
}
read_loop(pipe);
} else {
std::cout << "Loop exit (" << ec.message() << ")" << std::endl;
pipe.close();
}
});
}
int main() {
boost::asio::io_context ioc{};
bp::async_pipe pipe{ioc};
ioc.notify_fork(boost::asio::io_context::fork_prepare);
pid_t pid{fork()};
if (pid == 0) {
ioc.notify_fork(boost::asio::io_context::fork_child);
bp::child child( //
bp::args({"/usr/bin/ls", "/etc/"}), bp::std_out > pipe, bp::std_in.close(),
bp::on_exit([&](int exit, std::error_code ec) {
std::cout << "[Exited with code " << exit << " (" << ec.message() << ")]\n";
pipe.close();
}),
ioc);
ioc.run();
} else {
ioc.notify_fork(boost::asio::io_context::fork_parent);
std::move(pipe).sink().close();
read_loop(pipe);
ioc.run();
}
}
Side note: I guess it would be nice to have a more unhacky way to specify this, like (bp::std_in < pipe).close() or so.
Fixing The Root Cause
When using Boost Process, the fork is completely redundant. Boost Process literally does the fork for you, complete with correct service notification and file descriptor handling.
You'll find the code becomes a lot simpler and also handles the closing correctly (likely because some assumptions within Boost Process implementation details):
Live On Coliru
#include <boost/asio.hpp>
#include <boost/process.hpp>
#include <iostream>
namespace bp = boost::process;
void read_loop(bp::async_pipe& pipe) {
static boost::asio::streambuf buffer;
static std::string line; // re-used because we can
async_read_until( //
pipe, buffer, '\n',
[&](boost::system::error_code ec, size_t /*bytes*/) {
if (ec) {
std::cout << "Loop exit (" << ec.message() << ")" << std::endl;
return;
}
if (getline(std::istream(&buffer), line))
std::cout << "Read Line: " << line << "\n";
read_loop(pipe);
});
}
int main() {
boost::asio::io_context ioc{};
bp::async_pipe pipe{ioc};
bp::child child( //
bp::args({"/bin/ls", "/etc/"}), bp::std_out > pipe,
bp::on_exit([&](int exit, std::error_code ec) {
std::cout << "[Exited with " << exit << " (" << ec.message()
<< ")]\n";
}));
read_loop(pipe);
ioc.run();
}
I am building an networking application, and being a newbie to Boost asio and networking as a whole had this doubt which might be trivial. I have this application which reads from a file and calls apis accordingly. I am reading json (example):
test.json
{
"commands":
[
{
"type":"login",
"Username": 0,
"Password": "kk"
}
]
}
My main program looks like this :
int main() {
ba::io_service ios;
tcp::socket s(ios);
s.connect({{},8080});
IO io;
io.start_read(s);
io.interact(s);
ios.run();
}
void start_read(tcp::socket& socket) {
char buffer_[MAX_LEN];
socket.async_receive(boost::asio::null_buffers(),
[&](const boost::system::error_code& ec, std::size_t bytes_read) {
(void)bytes_read;
if (likely(!ec)) {
boost::system::error_code errc;
int br = 0;
do {
br = socket.receive(boost::asio::buffer(buffer_, MAX_LEN), 0, errc);
if (unlikely(errc)) {
if (unlikely(errc != boost::asio::error::would_block)) {
if (errc != boost::asio::error::eof)
std::cerr << "asio async_receive: error " << errc.value() << " ("
<< errc.message() << ")" << std::endl;
interpret_read(socket,nullptr, -1);
//close(as);
return;
}
break; // EAGAIN
}
if (unlikely(br <= 0)) {
std::cerr << "asio async_receive: error, read " << br << " bytes" << std::endl;
interpret_read(socket,nullptr, br);
//close(as);
return;
}
interpret_read(socket,buffer_, br);
} while (br == (int)MAX_LEN);
} else {
if (socket.is_open())
std::cerr << "asio async_receive: error " << ec.value() << " (" << ec.message() << ")"
<< std::endl;
interpret_read(socket,nullptr, -1);
//close(as);
return;
}
start_read(socket);
});
}
void interpret_read(tcp::socket& s,const char* buf, int len) {
if(len<0)
{
std::cout<<"some error occured in reading"<<"\n";
}
const MessageHeaderOutComp *obj = reinterpret_cast<const MessageHeaderOutComp *>(buf);
int tempId = obj->TemplateID;
//std::cout<<tempId<<"\n";
switch(tempId)
{
case 10019: //login
{
//const UserLoginResponse *obj = reinterpret_cast<const UserLoginResponse *>(buf);
std::cout<<"*********[SERVER]: LOGIN ACKNOWLEDGEMENT RECEIVED************* "<<"\n";
break;
}
}
std::cout << "RX: " << len << " bytes\n";
if(this->input_type==2)
interact(s);
}
void interact(tcp::socket& s)
{
if(this->input_type == -1){
std::cout<<"what type of input you want ? option 1 : test.json / option 2 : manually through command line :";
int temp;
std::cin>>temp;
this->input_type = temp;
}
if(this->input_type==1)
{
//std::cout<<"reading from file\n";
std::ifstream input_file("test.json");
Json::Reader reader;
Json::Value input;
reader.parse(input_file, input);
for(auto i: input["commands"])
{
std::string str = i["type"].asString();
if(str=="login")
this->login_request(s,i);
}
std::cout<<"File read completely!! \n Do you want to continue or exit?: ";
}
}
The sending works fine, the message is sent and the server responds in a correct manner, but what I need to understand is why is the control not going to on_send_completed (which prints sent x bytes). Neither it prints the message [SERVER]: LOGIN ACKNOWLEDGEMENT RECEIVED, I know I am missing something basic or am doing something wrong, please correct me.
login_request function:
void login_request(tcp::socket& socket,Json::Value o) {
/*Some buffer being filled*/
async_write(socket, boost::asio::buffer(&info, sizeof(info)), on_send_completed);
}
Thanks in advance!!
From a cursory scan it looks like you redefined buffer_ that was already a class member (of IO, presumably).
It's hidden by the local in start_read, which is both UB (because the lifetime ends before the async read operation completes) and also makes it so the member _buffer isn't used.
I see a LOT of confusing code though. Why are you doing synchronous reads from within completion handlers?
I think you might be looking for the composed-ooperation reads (boost::asio::async_read and boost::asio::async_until)
i have a client program that connects to a server via a TCP socket, below:
int main ( )
{
std::cout << "HunterChat client starting up" << std::endl;
std::string cmd;
std::string reply;
bool cont = true;
ClientSocket client_socket ( "localhost", PORT );
try {
while(cont) {
try {
std::cout << ">> ";
// std::getline(std::cin,cmd);
gets(cmd);
if(cmd.compare("logout") == 0) {
cont = false;
break;
}
client_socket << cmd;
client_socket >> reply;
std::cout << reply << std::endl;
}
catch ( SocketException& e) {
std::cout << "Exception was caught:" << e.description() << "\n";
}
}
}
catch ( SocketException& e ) {
std::cout << "Exception was caught:" << e.description() << "\n";
}
return 0;
}
ClientSocket is a custom class that lets me set up and use the TCP connection; the stream operator is overloaded with, the following code:
int status = ::send ( m_sock, s.c_str(), s.size(), MSG_NOSIGNAL );
if ( status == -1 )
{
return false;
}
else
{
return true;
}
The TCP connection itself is working fine, so I won't clutter the post up with more of it. The problem is that one of the available commands involves sending input to a client instance while said client is still waiting for cin input. This means that the server messages only get read and written when I type something into cin. I'm trying to avoid using multithreading, so is there any way to allow cin to be interrupted without it?
Well, you could use a loop and the function kbhit() to check for user input if you really want to. However, threading seems to me such a better solution.
#include <conio.h>
#include <iostream>
using namespace std;
int main()
{
while(1)
{
if(kbhit())
{
char x = getch();
// ...
}
// check messages asynchronously here
}
}
I'm reading stdin using Boost.ASIO, but when I pipe into it I would expect that the pipe would close when the input has been fully consumed. I.e. I'm doing this at the commmand line:
cat somefile.txt | myprog
And I'd expect that myprog will see the file close. Instead it waits forever.
The code looks like this:
boost::asio::posix::stream_descriptor as_stdin(ios);
{
boost::system::error_code error;
as_stdin.assign(dup(STDIN_FILENO), error);
if ( error ) {
exit(2);
}
}
auto proc = [&as_stdinr](auto yield) {
boost::asio::streambuf buffer;
while ( as_stdin.is_open() ) {
auto bytes = boost::asio::async_read_until(as_stdin, buffer, '\n', yield);
if ( bytes ) {
buffer.commit(bytes);
std::istream in(&buffer);
std::string line;
std::getline(in, line);
std::cerr << line << std::endl;
} else {
std::cerr << "No bytes read" << std::endl;
}
}
std::cerr << "Done" << std::endl;
};
boost::asio::spawn(ios, proc);
All of the file content is properly echoed, so reading from the pipe works fine, but neither of the "No bytes read" or "Done" messages are ever printed. I've tried both with and without the dup system call.
Am I misunderstanding how the pipe works, or am I doing something wrong or missing something else?
I think this comes down to "How do I detect EOF when using coroutines?"
You could catch the exception from async_read_until
size_t bytes = 0;
bool eof = false;
try {
bytes = boost::asio::async_read_until(as_stdin, buffer, '\n', yield);
} catch(std::exception const& e) {
std::cerr << "Exception: " << e.what() << "\n";
bytes = 0;
eof = true;
}
// ...
if (eof) break;
Or use the error_code:
boost::system::error_code ec;
auto bytes = boost::asio::async_read_until(as_stdin, buffer, '\n', yield[ec]);
// ...
if (ec) {
std::cerr << "Error: " << ec.message() << "\n";
break;
}
Output is very similar in both cases
Exception: End of file
No bytes read
Done
Or
No bytes read
Error: End of file
Done
Limitations
Regular files cannot be used with POSIX stream_descriptor, see https://stackoverflow.com/a/23631715/85371
I have the following code, trying to code an asynchronous client.
The problem is that in main(), the Client gets deleted in the try-catch block, because execution leaves the scope.
I've tried to come up with a solution to this problem, like adding a while(true), but I don't like this approach. Also, I don't prefer a getchar().
Due to the asynchronous nature of the calls, both connect() and loop() returns immediately.
How can I fix this?
#include <iostream>
#include <thread>
#include <string>
#include <boost\asio.hpp>
#include <Windows.h>
#define DELIM "\r\n"
using namespace boost;
class Client {
public:
Client(const std::string& raw_ip_address, unsigned short port_num) :
m_ep(asio::ip::address::from_string(raw_ip_address), port_num), m_sock(m_ios)
{
m_work.reset(new asio::io_service::work(m_ios));
m_thread.reset(new std::thread([this]() {
m_ios.run();
}));
m_sock.open(m_ep.protocol());
}
void connect()
{
m_sock.async_connect(m_ep, [this](const system::error_code& ec)
{
if (ec != 0) {
std::cout << "async_connect() error: " << ec.message() << " (" << ec.value() << ") " << std::endl;
return;
}
std::cout << "Connection to server has been established." << std::endl;
});
}
void loop()
{
std::thread t = std::thread([this]()
{
recv();
});
t.join();
}
void recv()
{
asio::async_read_until(m_sock, buf, DELIM, [this](const system::error_code& ec, std::size_t bytes_transferred)
{
if (ec != 0) {
std::cout << "async_read_until() error: " << ec.message() << " (" << ec.value() << ") " << std::endl;
return;
}
std::istream is(&buf);
std::string req;
std::getline(is, req, '\r');
is.get(); // discard newline
std::cout << "Received: " << req << std::endl;
if (req == "alive") {
recv();
}
else if (req == "close") {
close();
return;
}
else {
send(req + DELIM);
}
});
}
void send(std::string resp)
{
auto s = std::make_shared<std::string>(resp);
asio::async_write(m_sock, asio::buffer(*s), [this, s](const system::error_code& ec, std::size_t bytes_transferred)
{
if (ec != 0) {
std::cout << "async_write() error: " << ec.message() << " (" << ec.value() << ") " << std::endl;
return;
}
else {
recv();
}
});
}
void close()
{
m_sock.close();
m_work.reset();
m_thread->join();
}
private:
asio::io_service m_ios;
asio::ip::tcp::endpoint m_ep;
asio::ip::tcp::socket m_sock;
std::unique_ptr<asio::io_service::work> m_work;
std::unique_ptr<std::thread> m_thread;
asio::streambuf buf;
};
int main()
{
const std::string raw_ip_address = "127.0.0.1";
const unsigned short port_num = 8001;
try {
Client client(raw_ip_address, port_num);
client.connect();
client.loop();
}
catch (system::system_error &err) {
std::cout << "main() error: " << err.what() << " (" << err.code() << ") " << std::endl;
return err.code().value();
}
return 0;
}
You've not really understood how asio works. Typically in the main thread(s) you will call io_service::run() (which will handle all the asynchronous events.)
To ensure the lifetime of the Client, use a shared_ptr<> and ensure this shared pointer is used in the handlers. For example..
io_service service;
{
// Create the client - outside of this scope, asio will manage
// the life time of the client
auto client = make_shared<Client>(service);
client->connect(); // setup the connect operation..
}
// Now run the io service event loop - this will block until there are no more
// events to handle
service.run();
Now you need to refactor your Client code:
class Client : public std::enable_shared_from_this<Client> {
Client(io_service& service): socket_(service) ...
{ }
void connect() {
// By copying the shared ptr to the lambda, the life time of
// Client is guaranteed
socket_.async_connect(endpoint_, [self = this->shared_from_this()](auto ec)
{
if (ec) {
return;
}
// Read
self->read(self);
});
}
void read(shared_ptr<Client> self) {
// By copying the shared ptr to the lambda, the life time of
// Client is guaranteed
asio::async_read_until(socket_, buffer_, DELIM, [self](auto ec, auto size)
{
if (ec) {
return;
}
// Handle the data
// Setup the next read operation
self->read(self)
});
}
};
You have a thread for the read operation - which is not necessary. That will register one async read operation and return immediately. You need to register a new read operation to continue reading the socket (as I've sketched out..)
You can post any function to io_service via post(Handler)
http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/io_service/post.html
Then in the main() do something like:
while (!exit) {
io_service.run_one();
}
Or call io_service::run_one or io_service::run in the main()