|
I've been teaching myself how to use the asynchronous I/O capabilities
in Boost asio. I took the chat client example code that came with Boost and modified it to act as a more general telnet client. Hopefully, this may be of use to others trying to get to grips with the basics of this way of working. More experienced developers can feel free to suggest better ways of doing things here. This is just a demonstration - it does not do all the niceties of a full application, but hopefully covers the basics. I wrote this for a Linux system, but the only system specific thing is the code that enables single keypress input for cin.get(). I'm sure you can easily find alternatives for your system to achieve the same result. Enjoy, Jeff /* telnet.cpp A simple demonstration telnet client with Boost asio Parameters: hostname or address port - typically 23 for telnet service To end the application, send Ctrl-C on standard input */ #include <deque> #include <iostream> #include <boost/bind.hpp> #include <boost/asio.hpp> #include <boost/thread.hpp> #ifdef POSIX #include <termios.h> #endif using boost::asio::ip::tcp; using namespace std; class telnet_client { public: enum { max_read_length = 512 }; telnet_client(boost::asio::io_service& io_service, tcp::resolver::iterator endpoint_iterator) : io_service_(io_service), socket_(io_service) { connect_start(endpoint_iterator); } void write(const char msg) // pass the write data to the do_write function via the io service in the other thread { io_service_.post(boost::bind(&telnet_client::do_write, this, msg)); } void close() // call the do_close function via the io service in the other thread { io_service_.post(boost::bind(&telnet_client::do_close, this)); } private: void connect_start(tcp::resolver::iterator endpoint_iterator) { // asynchronously connect a socket to the specified remote endpoint and call connect_complete when it completes or fails tcp::endpoint endpoint = *endpoint_iterator; socket_.async_connect(endpoint, boost::bind(&telnet_client::connect_complete, this, boost::asio::placeholders::error, ++endpoint_iterator)); } void connect_complete(const boost::system::error_code& error, tcp::resolver::iterator endpoint_iterator) { // the connection to the server has now completed or failed and returned an error if (!error) // success, so start waiting for read data read_start(); else if (endpoint_iterator != tcp::resolver::iterator()) { // failed, so wait for another connection event socket_.close(); connect_start(endpoint_iterator); } } void read_start(void) { // Start an asynchronous read and call read_complete when it completes or fails socket_.async_read_some(boost::asio::buffer(read_msg_, max_read_length), boost::bind(&telnet_client::read_complete, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } void read_complete(const boost::system::error_code& error, size_t bytes_transferred) { // the asynchronous read operation has now completed or failed and returned an error if (!error) { // read completed, so process the data cout.write(read_msg_, bytes_transferred); // echo to standard output //cout << "\n"; read_start(); // start waiting for another asynchronous read again } else do_close(); } void do_write(const char msg) { // callback to handle write call from outside this class bool write_in_progress = !write_msgs_.empty(); // is there anything currently being written? write_msgs_.push_back(msg); // store in write buffer if (!write_in_progress) // if nothing is currently being written, then start write_start(); } void write_start(void) { // Start an asynchronous write and call write_complete when it completes or fails boost::asio::async_write(socket_, boost::asio::buffer(&write_msgs_.front(), 1), boost::bind(&telnet_client::write_complete, this, boost::asio::placeholders::error)); } void write_complete(const boost::system::error_code& error) { // the asynchronous read operation has now completed or failed and returned an error if (!error) { // write completed, so send next write data write_msgs_.pop_front(); // remove the completed data if (!write_msgs_.empty()) // if there is anthing left to be written write_start(); // then start sending the next item in the buffer } else do_close(); } void do_close() { socket_.close(); } private: boost::asio::io_service& io_service_; // the main IO service that runs this connection tcp::socket socket_; // the socket this instance is connected to char read_msg_[max_read_length]; // data read from the socket deque<char> write_msgs_; // buffered write data }; int main(int argc, char* argv[]) { // on Unix POXIS based systems, turn off line buffering of input, so cin.get() returns after every keypress // On other systems, you'll need to look for an equivalent #ifdef POSIX termios stored_settings; tcgetattr(0, &stored_settings); termios new_settings = stored_settings; new_settings.c_lflag &= (~ICANON); new_settings.c_lflag &= (~ISIG); // don't automatically handle control-C tcsetattr(0, TCSANOW, &new_settings); #endif try { if (argc != 3) { cerr << "Usage: telnet <host> <port>\n"; return 1; } boost::asio::io_service io_service; // resolve the host name and port number to an iterator that can be used to connect to the server tcp::resolver resolver(io_service); tcp::resolver::query query(argv[1], argv[2]); tcp::resolver::iterator iterator = resolver.resolve(query); // define an instance of the main class of this program telnet_client c(io_service, iterator); // run the IO service as a separate thread, so the main thread can block on standard input boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service)); while (1) { char ch; cin.get(ch); // blocking wait for standard input if (ch == 3) // ctrl-C to end program break; c.write(ch); } c.close(); // close the telnet client connection t.join(); // wait for the IO service thread to close } catch (exception& e) { cerr << "Exception: " << e.what() << "\n"; } #ifdef POSIX // restore default buffering of standard input tcsetattr(0, TCSANOW, &stored_settings); #endif return 0; } _______________________________________________ Boost-users mailing list [hidden email] http://lists.boost.org/mailman/listinfo.cgi/boost-users |
|
Hi Jeff,
thanks, I am going to dive into Boost::Asio myself, soon, and will come back to your example then :-) Just wondering (while being aware, that this is not a fully fledged telnet client): I tried to connect to localhost, port 23. There is no service running, so I would have expected the program to terminate (it terminates when I specify a host which does not exist). I even sent 800MB of XML data into the program. It did not complain and wrote it to Nirvana, I guess :-) Regards, Roland Jeff Gray wrote: > I've been teaching myself how to use the asynchronous I/O capabilities > in Boost asio. I took the chat client example code that came with Boost > and modified it to act as a more general telnet client. > > Hopefully, this may be of use to others trying to get to grips with the > basics of this way of working. More experienced developers can feel free > to suggest better ways of doing things here. > > This is just a demonstration - it does not do all the niceties of a full > application, but hopefully covers the basics. > > I wrote this for a Linux system, but the only system specific thing is > the code that enables single keypress input for cin.get(). I'm sure you > can easily find alternatives for your system to achieve the same result. > > Enjoy, > Jeff > > > ------------------------------------------------------------------------ > > _______________________________________________ > Boost-users mailing list > [hidden email] > http://lists.boost.org/mailman/listinfo.cgi/boost-users _______________________________________________ Boost-users mailing list [hidden email] http://lists.boost.org/mailman/listinfo.cgi/boost-users |
|
Roland Bock wrote:
> thanks, I am going to dive into Boost::Asio myself, soon, and will come > back to your example then :-) > > Just wondering (while being aware, that this is not a fully fledged > telnet client): > I tried to connect to localhost, port 23. There is no service running, > so I would have expected the program to terminate (it terminates when I > specify a host which does not exist). I even sent 800MB of XML data into > the program. It did not complain and wrote it to Nirvana, I guess :-) You're quite right - I haven't tested this code for failure like this. My initial thought is that it probably silently calls the close function and exits the communication thread without exiting the application. I'll look at adding this resubmitting the code - it should be a straightforward change. Jeff ______________________________________________________________________ This email has been scanned by the MessageLabs Email Security System. For more information please visit http://www.messagelabs.com/email ______________________________________________________________________ _______________________________________________ Boost-users mailing list [hidden email] http://lists.boost.org/mailman/listinfo.cgi/boost-users |
|
Here is a CMakeLists.txt file that will let you use CMake
(http://www.cmake.org) to create either a visual studio solution or a kdevelop/unix makefile solution. Your code builds with no errors using Visual Studio 2008 and (of course) Linux (Debian), I haven't tried a mac. Andrerw On Tue, Sep 30, 2008 at 10:51 AM, Jeff Gray <[hidden email]> wrote: > Roland Bock wrote: >> thanks, I am going to dive into Boost::Asio myself, soon, and will come >> back to your example then :-) >> >> Just wondering (while being aware, that this is not a fully fledged >> telnet client): >> I tried to connect to localhost, port 23. There is no service running, >> so I would have expected the program to terminate (it terminates when I >> specify a host which does not exist). I even sent 800MB of XML data into >> the program. It did not complain and wrote it to Nirvana, I guess :-) > > You're quite right - I haven't tested this code for failure like this. > My initial thought is that it probably silently calls the close function > and exits the communication thread without exiting the application. > > I'll look at adding this resubmitting the code - it should be a > straightforward change. > > Jeff > > ______________________________________________________________________ > This email has been scanned by the MessageLabs Email Security System. > For more information please visit http://www.messagelabs.com/email > ______________________________________________________________________ > _______________________________________________ > Boost-users mailing list > [hidden email] > http://lists.boost.org/mailman/listinfo.cgi/boost-users > -- ___________________________________________ Andrew J. P. Maclean Centre for Autonomous Systems The Rose Street Building J04 The University of Sydney 2006 NSW AUSTRALIA Ph: +61 2 9351 3283 Fax: +61 2 9351 7474 URL: http://www.acfr.usyd.edu.au/ ___________________________________________ _______________________________________________ Boost-users mailing list [hidden email] http://lists.boost.org/mailman/listinfo.cgi/boost-users |
|
Andrew Maclean wrote:
> Here is a CMakeLists.txt file that will let you use CMake > (http://www.cmake.org) to create either a visual studio solution or a > kdevelop/unix makefile solution. Your code builds with no errors using > Visual Studio 2008 and (of course) Linux (Debian), I haven't tried a > mac. Thanks for that Andrew. Nice to see an easy portable build system. I've worked on Roland Bock's suggestion of timeouts to handle failure to connect & attached the working code. When an error occurs in the class, in my example code, it sets a variable which is checked by the main thread. I'm sure there are better built in ways of dealing with this for Boost asio or threads. Presumably, when the socket is closed, this means the io_service.run() function returns and the thread is terminated. If anyone has any suggestions how to detect any of this from the main thread to determine when to exit, I'd like to hear them. Regards, Jeff /* telnet.cpp A simple demonstration telnet client with Boost asio Parameters: hostname or address port - typically 23 for telnet service To end the application, send Ctrl-C on standard input */ #include <deque> #include <iostream> #include <boost/bind.hpp> #include <boost/asio.hpp> #include <boost/thread.hpp> #include <boost/date_time/posix_time/posix_time_types.hpp> #ifdef POSIX #include <termios.h> #endif using boost::asio::ip::tcp; using namespace std; class telnet_client { public: telnet_client(boost::asio::io_service& io_service, tcp::resolver::iterator endpoint_iterator) : active_(true), io_service_(io_service), socket_(io_service), connect_timer_(io_service), connection_timeout(boost::posix_time::seconds(3)) { connect_start(endpoint_iterator); } void write(const char msg) // pass the write data to the do_write function via the io service in the other thread { io_service_.post(boost::bind(&telnet_client::do_write, this, msg)); } void close() // call the do_close function via the io service in the other thread { io_service_.post(boost::bind(&telnet_client::do_close, this, boost::system::error_code())); } bool active() // return true if the socket is still active { return active_; } private: static const int max_read_length = 512; // maximum amount of data to read in one operation void connect_start(tcp::resolver::iterator endpoint_iterator) { // asynchronously connect a socket to the specified remote endpoint and call connect_complete when it completes or fails tcp::endpoint endpoint = *endpoint_iterator; socket_.async_connect(endpoint, boost::bind(&telnet_client::connect_complete, this, boost::asio::placeholders::error, ++endpoint_iterator)); // start a timer that will expire and close the connection if the connection cannot connect within a certain time connect_timer_.expires_from_now(connection_timeout); //boost::posix_time::seconds(connection_timeout)); connect_timer_.async_wait(boost::bind(&telnet_client::do_close, this, boost::asio::placeholders::error)); } void connect_complete(const boost::system::error_code& error, tcp::resolver::iterator endpoint_iterator) { // the connection to the server has now completed or failed and returned an error if (!error) // success, so start waiting for read data { connect_timer_.cancel(); // the connection was successful, so cancel the timeout read_start(); } else do_close(error); } void read_start(void) { // Start an asynchronous read and call read_complete when it completes or fails socket_.async_read_some(boost::asio::buffer(read_msg_, max_read_length), boost::bind(&telnet_client::read_complete, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } void read_complete(const boost::system::error_code& error, size_t bytes_transferred) { // the asynchronous read operation has now completed or failed and returned an error if (!error) { // read completed, so process the data cout.write(read_msg_, bytes_transferred); // echo to standard output read_start(); // start waiting for another asynchronous read again } else do_close(error); } void do_write(const char msg) { // callback to handle write call from outside this class bool write_in_progress = !write_msgs_.empty(); // is there anything currently being written? write_msgs_.push_back(msg); // store in write buffer if (!write_in_progress) // if nothing is currently being written, then start write_start(); } void write_start(void) { // Start an asynchronous write and call write_complete when it completes or fails boost::asio::async_write(socket_, boost::asio::buffer(&write_msgs_.front(), 1), boost::bind(&telnet_client::write_complete, this, boost::asio::placeholders::error)); } void write_complete(const boost::system::error_code& error) { // the asynchronous read operation has now completed or failed and returned an error if (!error) { // write completed, so send next write data write_msgs_.pop_front(); // remove the completed data if (!write_msgs_.empty()) // if there is anthing left to be written write_start(); // then start sending the next item in the buffer } else do_close(error); } void do_close(const boost::system::error_code& error) { // something has gone wrong, so close the socket & make this object inactive if (error == boost::asio::error::operation_aborted) // if this call is the result of a timer cancel() return; // ignore it because the connection cancelled the timer if (error) cerr << "Error: " << error.message() << endl; // show the error message else cout << "Error: Connection did not succeed.\n"; cout << "Press Enter to exit\n"; socket_.close(); active_ = false; } private: bool active_; // remains true while this object is still operating boost::asio::io_service& io_service_; // the main IO service that runs this connection tcp::socket socket_; // the socket this instance is connected to boost::asio::deadline_timer connect_timer_; boost::posix_time::time_duration connection_timeout; // time to wait for the connection to succeed char read_msg_[max_read_length]; // data read from the socket deque<char> write_msgs_; // buffered write data }; int main(int argc, char* argv[]) { // on Unix POSIX based systems, turn off line buffering of input, so cin.get() returns after every keypress // On other systems, you'll need to look for an equivalent #ifdef POSIX termios stored_settings; tcgetattr(0, &stored_settings); termios new_settings = stored_settings; new_settings.c_lflag &= (~ICANON); new_settings.c_lflag &= (~ISIG); // don't automatically handle control-C tcsetattr(0, TCSANOW, &new_settings); #endif try { if (argc != 3) { cerr << "Usage: telnet <host> <port>\n"; return 1; } boost::asio::io_service io_service; // resolve the host name and port number to an iterator that can be used to connect to the server tcp::resolver resolver(io_service); tcp::resolver::query query(argv[1], argv[2]); tcp::resolver::iterator iterator = resolver.resolve(query); // define an instance of the main class of this program telnet_client c(io_service, iterator); // run the IO service as a separate thread, so the main thread can block on standard input boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service)); while (c.active()) // check the internal state of the connection to make sure it's still running { char ch; cin.get(ch); // blocking wait for standard input if (ch == 3) // ctrl-C to end program break; c.write(ch); } c.close(); // close the telnet client connection t.join(); // wait for the IO service thread to close } catch (exception& e) { cerr << "Exception: " << e.what() << "\n"; } #ifdef POSIX // restore default buffering of standard input tcsetattr(0, TCSANOW, &stored_settings); #endif return 0; } _______________________________________________ Boost-users mailing list [hidden email] http://lists.boost.org/mailman/listinfo.cgi/boost-users |
|
It works nicely, this is a good example of using ASIO.
The CMakeLists.txt file could have been made more minimal but I hope people find the extra bits useful as templates. Regards Andrew On Tue, Sep 30, 2008 at 7:49 PM, Jeff Gray <[hidden email]> wrote: > Andrew Maclean wrote: >> Here is a CMakeLists.txt file that will let you use CMake >> (http://www.cmake.org) to create either a visual studio solution or a >> kdevelop/unix makefile solution. Your code builds with no errors using >> Visual Studio 2008 and (of course) Linux (Debian), I haven't tried a >> mac. > > Thanks for that Andrew. Nice to see an easy portable build system. > > I've worked on Roland Bock's suggestion of timeouts to handle failure to > connect & attached the working code. > > When an error occurs in the class, in my example code, it sets a > variable which is checked by the main thread. I'm sure there are better > built in ways of dealing with this for Boost asio or threads. > > Presumably, when the socket is closed, this means the io_service.run() > function returns and the thread is terminated. If anyone has any > suggestions how to detect any of this from the main thread to determine > when to exit, I'd like to hear them. > > Regards, > Jeff > > _______________________________________________ > Boost-users mailing list > [hidden email] > http://lists.boost.org/mailman/listinfo.cgi/boost-users > -- ___________________________________________ Andrew J. P. Maclean Centre for Autonomous Systems The Rose Street Building J04 The University of Sydney 2006 NSW AUSTRALIA Ph: +61 2 9351 3283 Fax: +61 2 9351 7474 URL: http://www.acfr.usyd.edu.au/ ___________________________________________ _______________________________________________ Boost-users mailing list [hidden email] http://lists.boost.org/mailman/listinfo.cgi/boost-users |
| Powered by Nabble | Edit this page |
