| // |
| // 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 <boost/asio.hpp> |
| #include <boost/lambda/lambda.hpp> |
| #include <boost/lambda/bind.hpp> |
| #include <boost/lambda/if.hpp> |
| #include <boost/shared_ptr.hpp> |
| #include <algorithm> |
| #include <cstdlib> |
| #include <exception> |
| #include <iostream> |
| #include <string> |
| #include "protocol.hpp" |
| |
| using namespace boost; |
| using boost::asio::ip::tcp; |
| using boost::asio::ip::udp; |
| |
| int main(int argc, char* argv[]) |
| { |
| try |
| { |
| if (argc != 3) |
| { |
| std::cerr << "Usage: client <host> <port>\n"; |
| return 1; |
| } |
| using namespace std; // For atoi. |
| std::string host_name = argv[1]; |
| std::string port = argv[2]; |
| |
| boost::asio::io_service io_service; |
| |
| // Determine the location of the server. |
| tcp::resolver resolver(io_service); |
| tcp::resolver::query query(host_name, port); |
| tcp::endpoint remote_endpoint = *resolver.resolve(query); |
| |
| // Establish the control connection to the server. |
| tcp::socket control_socket(io_service); |
| control_socket.connect(remote_endpoint); |
| |
| // Create a datagram socket to receive data from the server. |
| boost::shared_ptr<udp::socket> data_socket( |
| new udp::socket(io_service, udp::endpoint(udp::v4(), 0))); |
| |
| // Determine what port we will receive data on. |
| udp::endpoint data_endpoint = data_socket->local_endpoint(); |
| |
| // Ask the server to start sending us data. |
| control_request start = control_request::start(data_endpoint.port()); |
| boost::asio::write(control_socket, start.to_buffers()); |
| |
| unsigned long last_frame_number = 0; |
| for (;;) |
| { |
| // Receive 50 messages on the current data socket. |
| for (int i = 0; i < 50; ++i) |
| { |
| // Receive a frame from the server. |
| frame f; |
| data_socket->receive(f.to_buffers(), 0); |
| if (f.number() > last_frame_number) |
| { |
| last_frame_number = f.number(); |
| std::cout << "\n" << f.payload(); |
| } |
| } |
| |
| // Time to switch to a new socket. To ensure seamless handover we will |
| // continue to receive packets using the old socket until data arrives on |
| // the new one. |
| std::cout << " Starting renegotiation"; |
| |
| // Create the new data socket. |
| boost::shared_ptr<udp::socket> new_data_socket( |
| new udp::socket(io_service, udp::endpoint(udp::v4(), 0))); |
| |
| // Determine the new port we will use to receive data. |
| udp::endpoint new_data_endpoint = new_data_socket->local_endpoint(); |
| |
| // Ask the server to switch over to the new port. |
| control_request change = control_request::change( |
| data_endpoint.port(), new_data_endpoint.port()); |
| boost::system::error_code control_result; |
| boost::asio::async_write(control_socket, change.to_buffers(), |
| ( |
| lambda::var(control_result) = lambda::_1 |
| )); |
| |
| // Try to receive a frame from the server on the new data socket. If we |
| // successfully receive a frame on this new data socket we can consider |
| // the renegotation complete. In that case we will close the old data |
| // socket, which will cause any outstanding receive operation on it to be |
| // cancelled. |
| frame f1; |
| boost::system::error_code new_data_socket_result; |
| new_data_socket->async_receive(f1.to_buffers(), |
| ( |
| // Note: lambda::_1 is the first argument to the callback handler, |
| // which in this case is the error code for the operation. |
| lambda::var(new_data_socket_result) = lambda::_1, |
| lambda::if_(!lambda::_1) |
| [ |
| // We have successfully received a frame on the new data socket, |
| // so we can close the old data socket. This will cancel any |
| // outstanding receive operation on the old data socket. |
| lambda::var(data_socket) = boost::shared_ptr<udp::socket>() |
| ] |
| )); |
| |
| // This loop will continue until we have successfully completed the |
| // renegotiation (i.e. received a frame on the new data socket), or some |
| // unrecoverable error occurs. |
| bool done = false; |
| while (!done) |
| { |
| // Even though we're performing a renegotation, we want to continue |
| // receiving data as smoothly as possible. Therefore we will continue to |
| // try to receive a frame from the server on the old data socket. If we |
| // receive a frame on this socket we will interrupt the io_service, |
| // print the frame, and resume waiting for the other operations to |
| // complete. |
| frame f2; |
| done = true; // Let's be optimistic. |
| if (data_socket) // Might have been closed by new_data_socket's handler. |
| { |
| data_socket->async_receive(f2.to_buffers(), 0, |
| ( |
| lambda::if_(!lambda::_1) |
| [ |
| // We have successfully received a frame on the old data |
| // socket. Stop the io_service so that we can print it. |
| lambda::bind(&boost::asio::io_service::stop, &io_service), |
| lambda::var(done) = false |
| ] |
| )); |
| } |
| |
| // Run the operations in parallel. This will block until all operations |
| // have finished, or until the io_service is interrupted. (No threads!) |
| io_service.reset(); |
| io_service.run(); |
| |
| // If the io_service.run() was interrupted then we have received a frame |
| // on the old data socket. We need to keep waiting for the renegotation |
| // operations to complete. |
| if (!done) |
| { |
| if (f2.number() > last_frame_number) |
| { |
| last_frame_number = f2.number(); |
| std::cout << "\n" << f2.payload(); |
| } |
| } |
| } |
| |
| // Since the loop has finished, we have either successfully completed |
| // the renegotation, or an error has occurred. First we'll check for |
| // errors. |
| if (control_result) |
| throw boost::system::system_error(control_result); |
| if (new_data_socket_result) |
| throw boost::system::system_error(new_data_socket_result); |
| |
| // If we get here it means we have successfully started receiving data on |
| // the new data socket. This new data socket will be used from now on |
| // (until the next time we renegotiate). |
| std::cout << " Renegotiation complete"; |
| data_socket = new_data_socket; |
| data_endpoint = new_data_endpoint; |
| if (f1.number() > last_frame_number) |
| { |
| last_frame_number = f1.number(); |
| std::cout << "\n" << f1.payload(); |
| } |
| } |
| } |
| catch (std::exception& e) |
| { |
| std::cerr << "Exception: " << e.what() << std::endl; |
| } |
| |
| return 0; |
| } |