Quantcast

Simple telnet client demonstration with boost asio asynchronous I/O

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Simple telnet client demonstration with boost asio asynchronous I/O

Jeff Gray-3
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
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Simple telnet client demonstration with boost asio asynchronous I/O

Roland Bock-2
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
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Simple telnet client demonstration with boost asio asynchronous I/O

Jeff Gray-3
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
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Simple telnet client demonstration with boost asio asynchronous I/O

Andrew Maclean-2
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

CMakeLists.txt (5K) Download Attachment
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Simple telnet client demonstration with boost asio asynchronous I/O

Jeff Gray-3
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
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Simple telnet client demonstration with boost asio asynchronous I/O

Andrew Maclean-2
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
Loading...