// // blocking_tcp_client.cpp // ~~~~~~~~~~~~~~~~~~~~~~~ // // Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include #include #include #include #include #include #include #include #include #include #include #include using boost::asio::deadline_timer; using boost::asio::ip::tcp; using boost::lambda::bind; using boost::lambda::var; using boost::lambda::_1; //---------------------------------------------------------------------- // // This class manages socket timeouts by applying the concept of a deadline. // Each asynchronous operation is given a deadline by which it must complete. // Deadlines are enforced by an "actor" that persists for the lifetime of the // client object: // // +----------------+ // | | // | check_deadline |<---+ // | | | // +----------------+ | async_wait() // | | // +---------+ // // If the actor determines that the deadline has expired, the socket is closed // and any outstanding operations are consequently cancelled. The socket // operations themselves use boost::lambda function objects as completion // handlers. For a given socket operation, the client object runs the // io_service to block thread execution until the actor completes. // class client { public: client() : socket_(io_service_), deadline_(io_service_) { // No deadline is required until the first socket operation is started. We // set the deadline to positive infinity so that the actor takes no action // until a specific deadline is set. deadline_.expires_at(boost::posix_time::pos_infin); // Start the persistent actor that checks for deadline expiry. check_deadline(); } void connect(const std::string& host, const std::string& service, boost::posix_time::time_duration timeout) { // Resolve the host name and service to a list of endpoints. tcp::resolver::query query(host, service); tcp::resolver::iterator iter = tcp::resolver(io_service_).resolve(query); // Set a deadline for the asynchronous operation. The host name may resolve // to multiple endpoints, and this function tries to connect to each one in // turn. Setting the deadline here means it applies to the entire sequence. deadline_.expires_from_now(timeout); boost::system::error_code ec; for (; iter != tcp::resolver::iterator(); ++iter) { // We may have an open socket from a previous connection attempt. This // socket cannot be reused, so we must close it before trying to connect // again. socket_.close(); // Set up the variable that receives the result of the asynchronous // operation. The error code is set to would_block to signal that the // operation is incomplete. Asio guarantees that its asynchronous // operations will never fail with would_block, so any other value in // ec indicates completion. ec = boost::asio::error::would_block; // Start the asynchronous operation itself. The boost::lambda function // object is used as a callback and will update the ec variable when the // operation completes. The blocking_udp_client.cpp example shows how you // can use boost::bind rather than boost::lambda. socket_.async_connect(iter->endpoint(), var(ec) = _1); // Block until the asynchronous operation has completed. do io_service_.run_one(); while (ec == boost::asio::error::would_block); // Determine whether a connection was successfully established. The // deadline actor may have had a chance to run and close our socket, even // though the connect operation notionally succeeded. Therefore we must // check whether the socket is still open before deciding that the we // were successful. if (!ec && socket_.is_open()) return; } throw boost::system::system_error( ec ? ec : boost::asio::error::host_not_found); } std::string read_line(boost::posix_time::time_duration timeout) { // Set a deadline for the asynchronous operation. Since this function uses // a composed operation (async_read_until), the deadline applies to the // entire operation, rather than individual reads from the socket. deadline_.expires_from_now(timeout); // Set up the variable that receives the result of the asynchronous // operation. The error code is set to would_block to signal that the // operation is incomplete. Asio guarantees that its asynchronous // operations will never fail with would_block, so any other value in // ec indicates completion. boost::system::error_code ec = boost::asio::error::would_block; // Start the asynchronous operation itself. The boost::lambda function // object is used as a callback and will update the ec variable when the // operation completes. The blocking_udp_client.cpp example shows how you // can use boost::bind rather than boost::lambda. boost::asio::async_read_until(socket_, input_buffer_, '\n', var(ec) = _1); // Block until the asynchronous operation has completed. do io_service_.run_one(); while (ec == boost::asio::error::would_block); if (ec) throw boost::system::system_error(ec); std::string line; std::istream is(&input_buffer_); std::getline(is, line); return line; } void write_line(const std::string& line, boost::posix_time::time_duration timeout) { std::string data = line + "\n"; // Set a deadline for the asynchronous operation. Since this function uses // a composed operation (async_write), the deadline applies to the entire // operation, rather than individual writes to the socket. deadline_.expires_from_now(timeout); // Set up the variable that receives the result of the asynchronous // operation. The error code is set to would_block to signal that the // operation is incomplete. Asio guarantees that its asynchronous // operations will never fail with would_block, so any other value in // ec indicates completion. boost::system::error_code ec = boost::asio::error::would_block; // Start the asynchronous operation itself. The boost::lambda function // object is used as a callback and will update the ec variable when the // operation completes. The blocking_udp_client.cpp example shows how you // can use boost::bind rather than boost::lambda. boost::asio::async_write(socket_, boost::asio::buffer(data), var(ec) = _1); // Block until the asynchronous operation has completed. do io_service_.run_one(); while (ec == boost::asio::error::would_block); if (ec) throw boost::system::system_error(ec); } private: void check_deadline() { // Check whether the deadline has passed. We compare the deadline against // the current time since a new asynchronous operation may have moved the // deadline before this actor had a chance to run. if (deadline_.expires_at() <= deadline_timer::traits_type::now()) { // The deadline has passed. The socket is closed so that any outstanding // asynchronous operations are cancelled. This allows the blocked // connect(), read_line() or write_line() functions to return. socket_.close(); // There is no longer an active deadline. The expiry is set to positive // infinity so that the actor takes no action until a new deadline is set. deadline_.expires_at(boost::posix_time::pos_infin); } // Put the actor back to sleep. deadline_.async_wait(bind(&client::check_deadline, this)); } boost::asio::io_service io_service_; tcp::socket socket_; deadline_timer deadline_; boost::asio::streambuf input_buffer_; }; //---------------------------------------------------------------------- int main(int argc, char* argv[]) { try { if (argc != 4) { std::cerr << "Usage: blocking_tcp \n"; return 1; } client c; c.connect(argv[1], argv[2], boost::posix_time::seconds(10)); boost::posix_time::ptime time_sent = boost::posix_time::microsec_clock::universal_time(); c.write_line(argv[3], boost::posix_time::seconds(10)); for (;;) { std::string line = c.read_line(boost::posix_time::seconds(10)); // Keep going until we get back the line that was sent. if (line == argv[3]) break; } boost::posix_time::ptime time_received = boost::posix_time::microsec_clock::universal_time(); std::cout << "Round trip time: "; std::cout << (time_received - time_sent).total_microseconds(); std::cout << " microseconds\n"; } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << "\n"; } return 0; }