I am using boost to implement a TCP client and server. On the client side, I have to send multiple files one after the other. I use a separate mechanism to notify the server of a file transfer. If the server is ready to receive the file it will respond to the client and the transfer is initiated.
I defined asynchronous handlers to write the data and then let the OS take care of it by calling io_service.run(). To my knowledge io_service.run() blocks until there are no more handlers to be dispatched, however that does not mean that the data was actually received on the remote side correct? The issue is that after io_service.run() returns I initiate the next transfer, but the server is not done receiving the first one.
Do I need to implement some kind of external mechanism on the remote side to notify the client that the data was received or am I doing something wrong?
Client implementation:
#include "StdAfx.h"
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/thread.hpp>
#include "AsyncTCPClient.h"
AsyncTCPClient::AsyncTCPClient(boost::asio::io_service& iIoService, const std::string& iServerIP, const std::string& iPath)
: mResolver(iIoService), mSocket(iIoService)
{
size_t wPos = iServerIP.find(':');
if(wPos==std::string::npos)
{
return;
}
std::string wPortStr = iServerIP.substr(wPos + 1);
std::string wServerIP = iServerIP.substr(0, wPos);
mSourceFile.open(iPath, std::ios_base::binary | std::ios_base::ate);
if(!mSourceFile)
{
LOG(LOGERROR) << "Failed to open file: " << iPath;
return;
}
size_t wFileSize = mSourceFile.tellg();
mSourceFile.seekg(0);
std::ostream wRequestStream(&mRequest);
wRequestStream << iPath << "\n" << wFileSize << "\n\n";
LOG(LOGINFO) << "File to transfer: " << iPath;
LOG(LOGINFO) << "Filesize: " << wFileSize << " bytes";
tcp::resolver::query wQuery(wServerIP, wPortStr);
mResolver.async_resolve(wQuery, boost::bind(&AsyncTCPClient::HandleResolve, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator));
}
AsyncTCPClient::~AsyncTCPClient()
{
}
void AsyncTCPClient::HandleResolve(const boost::system::error_code & iErr, tcp::resolver::iterator iEndpointIterator)
{
if(!iErr)
{
tcp::endpoint wEndpoint = *iEndpointIterator;
mSocket.async_connect(wEndpoint, boost::bind(&AsyncTCPClient::HandleConnect, this, boost::asio::placeholders::error, ++iEndpointIterator));
}
else
{
LOG(LOGERROR) << "Error: " << iErr.message();
}
}
void AsyncTCPClient::HandleConnect(const boost::system::error_code &iErr, tcp::resolver::iterator iEndpointIterator)
{
if(!iErr)
{
boost::asio::async_write(mSocket, mRequest, boost::bind(&AsyncTCPClient::HandleWriteFile, this, boost::asio::placeholders::error));
}
else if(iEndpointIterator != tcp::resolver::iterator())
{
mSocket.close();
tcp::endpoint wEndpoint = *iEndpointIterator;
mSocket.async_connect(wEndpoint, boost::bind(&AsyncTCPClient::HandleConnect, this, boost::asio::placeholders::error, ++iEndpointIterator));
}
else
{
LOG(LOGERROR) << "Error: " << iErr.message();
}
}
void AsyncTCPClient::HandleWriteFile(const boost::system::error_code& iErr)
{
if(!iErr)
{
if(mSourceFile)
{
mSourceFile.read(mBuffer.c_array(), (std::streamsize)mBuffer.size());
// EOF reached
if(mSourceFile.gcount() <= 0)
{
LOG(LOGINFO) << "File transfer done";
return;
}
//LOG(LOGTRACE) << "Send " << mSourceFile.gcount() << "bytes, total: " << mSourceFile.tellg() << " bytes.\n";
boost::asio::async_write(mSocket, boost::asio::buffer(mBuffer.c_array(), mSourceFile.gcount()), boost::bind(&AsyncTCPClient::HandleWriteFile, this, boost::asio::placeholders::error));
}
else
{
LOG(LOGINFO) << "File transfer done";
return;
}
}
else
{
LOG(LOGERROR) << "Error value: " << iErr.value();
LOG(LOGERROR) << "Error message: " << iErr.message();
throw std::exception();
}
}
Server implementation:
#include "StdAfx.h"
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <iostream>
#include <fstream>
#include <boost/enable_shared_from_this.hpp>
#include "AsyncTCPClient.h"
#include "AsyncTCPServer.h"
#include "Debug.h"
AsyncTCPServer::AsyncTCPServer(unsigned short iPort, const std::string iFilePath)
:mAcceptor(mIoService, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), iPort), true)
{
mAsyncTCPConnectionPtr wNewConnection(new AsyncTCPConnection(mIoService, iFilePath));
mAcceptor.async_accept(wNewConnection->Socket(), boost::bind(&AsyncTCPServer::HandleAccept, this, wNewConnection, boost::asio::placeholders::error));
mIoService.run();
}
AsyncTCPServer::~AsyncTCPServer()
{
mIoService.stop();
}
void AsyncTCPServer::HandleAccept(mAsyncTCPConnectionPtr iCurConnection, const boost::system::error_code& iErr)
{
if (!iErr)
{
iCurConnection->Start();
}
else
{
BIOLOG(BioSans::LOGERROR) << " " << iErr << ", " << iErr.message();
}
}
Connection implementation:
#include "StdAfx.h"
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <iostream>
#include <fstream>
#include "Debug.h"
#include "AsyncTCPConnection.h"
AsyncTCPConnection::AsyncTCPConnection(boost::asio::io_service& iIoService, const std::string iFilePath)
: mSocket(iIoService), mFileSize(0), mFilePath(iFilePath)
{
}
AsyncTCPConnection::~AsyncTCPConnection()
{
}
void AsyncTCPConnection::Start()
{
LOG(LOGINFO) << "Start";
async_read_until(mSocket, mRequestBuffer, "\n\n", boost::bind(&AsyncTCPConnection::HandleReadRequest, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void AsyncTCPConnection::HandleReadRequest(const boost::system::error_code& iErr, std::size_t iBytesTransferred)
{
if(iErr)
{
return HandleError(__FUNCTION__, iErr);
}
LOG(LOGTRACE) << "(" << iBytesTransferred << ")" << ", in_avail = " << mRequestBuffer.in_avail() << ", size = " << mRequestBuffer.size() << ", max_size = " << mRequestBuffer.max_size();
std::istream wRequestStream(&mRequestBuffer);
std::string wFilePath;
wRequestStream >> wFilePath;
wRequestStream >> mFileSize;
wRequestStream.read(mBuffer.c_array(), 2);
mOutputFile.open(mFilePath, std::ios_base::binary);
if(!mOutputFile)
{
LOG(LOGERROR) << "Failed to open: " << wFilePath;
return;
}
do
{
wRequestStream.read(mBuffer.c_array(), (std::streamsize)mBuffer.size());
LOG(LOGTRACE) << "Write " << wRequestStream.gcount() << " bytes";
mOutputFile.write(mBuffer.c_array(), wRequestStream.gcount());
}
while(wRequestStream.gcount() > 0);
async_read(mSocket, boost::asio::buffer(mBuffer.c_array(), mBuffer.size()),boost::bind(&AsyncTCPConnection::HandleReadFileContent, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void AsyncTCPConnection::HandleReadFileContent(const boost::system::error_code& iErr, std::size_t iBytesTransferred)
{
if(iBytesTransferred>0)
{
mOutputFile.write(mBuffer.c_array(), (std::streamsize)iBytesTransferred);
LOG(LOGTRACE) << "Received " << mOutputFile.tellp() << " bytes";
if (mOutputFile.tellp()>=(std::streamsize)mFileSize)
{
return;
}
}
if(iErr)
{
return HandleError(__FUNCTION__, iErr);
}
async_read(mSocket, boost::asio::buffer(mBuffer.c_array(), mBuffer.size()), boost::bind(&AsyncTCPConnection::HandleReadFileContent, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void AsyncTCPConnection::HandleError(const std::string& function_name, const boost::system::error_code& err)
{
LOG(LOGERROR) << " in " << function_name <<" due to " << err <<" " << err.message();
}
Code to send file:
void SendFile(std::string iFilePath, std::string iServerIP)
{
static int wRetries = 0;
try
{
boost::asio::io_service wIoService;
LOG(LOGINFO) << "Sending data to: " << iServerIP;
LOG(LOGINFO) << "Filename is: " << iFilePath;
AsyncTCPClient client(wIoService, iServerIP, iFilePath);
wIoService.run();
// here I want to make sure that the data got to the remote host
// it looks like wIoService.run() returns once bytes are written to the socket
}
catch(std::exception)
{
// retry 3 times in case something goes wrong
if(wRetries < 3)
{
wRetries++;
LOG(LOGWARNING) << "Problem sending file : " << iFilePath << " to address: " << iServerIP;
LOG(LOGWARNING) << "Retry #" << wRetries;
SendFile(iFilePath, iServerIP);
}
else
{
LOG(LOGERROR) << "Unable to send file: " << iFilePath << " to address: " << iServerIP;
wRetries = 0;
return;
}
}
wRetries = 0;
}
You could use 'boost::asio::io_service::work' to keep your IO Service thread alive until you want to shutdown your process. Otherwise, io_service::run will return when all posted tasks are complete.
http://www.boost.org/doc/libs/1_65_1/doc/html/boost_asio/reference/io_service__work.html
I wouldn't think you'd want to exit and recreate threads over and over for every transfer.
You can use a condition variable to signal when you want to shutdown the io_service thread and then destroy the work object, or just simply destroy the work object.
As to knowing when the server has received everything you've sent. You could design something in your protocol or just rely on the guaranteed aspects of TCP. I suggest reading up on TCP and IO-Completion in general.
Related
I'm trying to write a very simple client/server app with boost::socket. I need a server to run and a single client to connect, send data, disconnect and possibly reconnect later and repeat.
The code reduced to the minimum is here:
Server app:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
using boost::asio::ip::tcp;
class TheServer
{
public:
TheServer(int port) : m_port(port)
{
m_pIOService = new boost::asio::io_service;
m_pThread = new boost::thread(boost::bind<void>(&TheServer::run, this));
listenForNewConnection();
}
~TheServer()
{
m_bContinueReading = false;
m_pIOService->stop();
m_pThread->join();
delete m_pThread;
delete m_pSocket;
delete m_pAcceptor;
delete m_pIOService;
}
void listenForNewConnection()
{
if (m_pSocket)
delete m_pSocket;
if (m_pAcceptor)
delete m_pAcceptor;
// start new acceptor operation
m_pSocket = new tcp::socket(*m_pIOService);
m_pAcceptor = new tcp::acceptor(*m_pIOService, tcp::endpoint(tcp::v4(), m_port));
std::cout << "Starting async_accept" << std::endl;
m_pAcceptor->async_accept(*m_pSocket,
boost::bind<void>(&TheServer::readSession, this, boost::asio::placeholders::error));
}
void readSession(boost::system::error_code error)
{
if (!error)
{
std::cout << "Connection established" << std::endl;
while ( m_bContinueReading )
{
static unsigned char buffer[1000];
boost::system::error_code error;
size_t length = m_pSocket->read_some(boost::asio::buffer(&buffer, 1000), error);
if (!error && length != 0)
{
std::cout << "Received " << buffer << std::endl;
}
else
{
std::cout << "Received error, connection likely closed by peer" << std::endl;
break;
}
}
std::cout << "Connection closed" << std::endl;
listenForNewConnection();
}
else
{
std::cout << "Connection error" << std::endl;
}
std::cout << "Ending readSession" << std::endl;
}
void run()
{
while (m_bContinueReading)
m_pIOService->run_one();
std::cout << "Exiting run thread" << std::endl;
}
bool m_bContinueReading = true;
boost::asio::io_service* m_pIOService = NULL;
tcp::socket* m_pSocket = NULL;
tcp::acceptor* m_pAcceptor = NULL;
boost::thread* m_pThread = NULL;
int m_port;
};
int main(int argc, char* argv[])
{
TheServer* server = new TheServer(1900);
std::cout << "Press Enter to quit" << std::endl;
std::string sGot;
getline(std::cin, sGot);
delete server;
return 0;
}
Client app:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
int main(int argc, char* argv[])
{
std::cout << std::endl;
std::cout << "Starting client" << std::endl;
using boost::asio::ip::tcp;
boost::asio::io_service* m_pIOService = NULL;
tcp::socket* m_pSocket = NULL;
try
{
m_pIOService = new boost::asio::io_service;
std::stringstream sPort;
sPort << 1900;
tcp::resolver resolver(*m_pIOService);
tcp::resolver::query query(tcp::v4(), "localhost", sPort.str());
tcp::resolver::iterator iterator = resolver.resolve(query);
m_pSocket = new tcp::socket(*m_pIOService);
m_pSocket->connect(*iterator);
std::cout << "Client conected" << std::endl;
std::string hello = "Hello World";
boost::asio::write( *m_pSocket, boost::asio::buffer(hello.data(), hello.size()) );
boost::this_thread::sleep(boost::posix_time::milliseconds(100));
hello += "(2)";
boost::asio::write(*m_pSocket, boost::asio::buffer(hello.data(), hello.size()));
}
catch (std::exception& e)
{
delete m_pSocket;
m_pSocket = NULL;
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
Note that I use non-blocking async_accept to be able to cleanly stop the server when Enter is pressed.
Under Windows, it works perfectly fine, I run the server, it outputs:
Starting async_accept
Press Enter to quit
For each client app run, it outpts:
Starting client
Client conected
and server app outputs:
Connection established
Received Hello World
Received Hello World(2)
Received error, connection likely closed by peer
Connection closed
Starting async_accept
Ending readSession
Then when I press Enter in server app console, it outputs Exiting run thread and cleanly stops.
Now, when I compile this same code under Linux, the client outputs the same as under Windows, but nothing happens on the server side...
Any idea what's wrong?
There are many questionable elements.
There is a classical data race on m_bContinueReading. You write from another thread, but the other thread may never see the change because of the data race.
The second race condition is likely your problem:
m_pThread = new boost::thread(boost::bind<void>(&TheServer::run, this));
listenForNewConnection();
Here the run thread may complete before you ever post the first work. You can use a work-guard to prevent this. In your specific code you would already fix it by reordering the lines:
listenForNewConnection();
m_pThread = new boost::thread(boost::bind<void>(&TheServer::run, this));
I would not do this, because I would not have those statements in my constructor body. See below for the work guard solution
There is a lot of raw pointer handling and new/delete going on, which merely invites errors.
You use the buffer assuming that it is NUL-terminated. This is especially unwarranted because you use read_some which will read partial messages as they arrive on the wire.
You use a static buffer while the code may have different instances of the class. This is very false optimization. Instead, prevent all the allocations! Combining with the previous item:
char buffer[1000];
while (m_bContinueReading) {
size_t length = m_Socket.read_some(asio::buffer(&buffer, 1000), ec);
std::cout << "Received " << length << " (" << quoted(std::string(buffer, length)) << "), "
<< ec.message() << std::endl;
if (ec.failed())
break;
}
You start a new acceptor always, where there is no need: a single acceptor can accept as many connections as you wish. In fact, the method shown runs into the problems
that lingering connections can prevent the new acceptor from binding to the same port. You could also alleviate that with
m_Acceptor.set_option(tcp::acceptor::reuse_address(true));
the destroyed acceptor may have backlogged connections, which are discarded
Typically you want to support concurrent connection, so you can split of a "readSession" and immediately accept the next connection. Now, strangely your code seems to expect clients to be connected until the server is prompted to shutdown (from the console) but after that you somehow start listening to new connections (even though you know the service will be stopping, and m_bContinueReading will remain false).
In the grand scheme of things, you don't want to destroy the acceptor unless something invalidated it. In practice this is rare (e.g. on Linux the acceptor will happily survive disabling/re-enabling the network adaptor).
you have spurious explicit template arguments (bind<void>). This is an anti-pattern and may lead to subtle problems
similar with the buffer (just say asio::buffer(buffer) and no longer have correctness concerns. In fact, don't use C-style arrays:
std::array<char, 1000> buffer;
size_t n = m_Socket.read_some(asio::buffer(buffer), ec);
std::cout << "Received " << n << " " << quoted(std::string(buffer.data(), n))
<< " (" << ec.message() << ")" << std::endl;
Instead of running a manual run_one() loop (where you forget to handle exceptions), consider "just" letting the service run(). Then you can .cancel() the acceptor to let the service run out of work.
In fact, this subtlety isn't required in your code, since your code already forces "ungraceful" shutdown anyways:
m_IOService.stop(); // nuclear option
m_Thread.join();
More gentle would be e.g.
m_Acceptor.cancel();
m_Socket.cancel();
m_Thread.join();
In which case you can respond to the completion error_code == error::operation_aborted to stop the session/accept loop.
Technically, you may be able to do away with the boolean flag altogether.
I keep it because it allows us to handle multiple session-per-thread in
"fire-and-forget" manner.
In the client you have many of the same problems, and also a gotcha where
you only look at the first resolver result (assuming there was one),
ignoring the rest. You can use asio::connect instead of
m_Socket.connect to try all resolved entries
Addressing the majority of these issues, simplifying the code:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <boost/optional.hpp>
#include <iomanip>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;
using namespace std::chrono_literals;
using boost::system::error_code;
class TheServer {
public:
TheServer(int port) : m_port(port) {
m_Acceptor.set_option(tcp::acceptor::reuse_address(true));
do_accept();
}
~TheServer() {
m_shutdownRequested = true;
m_Work.reset(); // release the work-guard
m_Acceptor.cancel();
m_Thread.join();
}
private:
void do_accept() {
std::cout << "Starting async_accept" << std::endl;
m_Acceptor.async_accept( //
m_Socket, boost::bind(&TheServer::on_accept, this, asio::placeholders::error));
}
void on_accept(error_code ec) {
if (!ec) {
std::cout << "Connection established " << m_Socket.remote_endpoint() << std::endl;
// leave session running in the background:
std::thread(&TheServer::read_session_thread, this, std::move(m_Socket)).detach();
do_accept(); // and immediately accept new connection(s)
} else {
std::cout << "Connection error (" << ec.message() << ")" << std::endl;
std::cout << "Ending readSession" << std::endl;
}
}
void read_session_thread(tcp::socket sock) {
std::array<char, 1000> buffer;
for (error_code ec;;) {
size_t n = sock.read_some(asio::buffer(buffer), ec);
std::cout << "Received " << n << " " << quoted(std::string(buffer.data(), n)) << " ("
<< ec.message() << ")" << std::endl;
if (ec.failed() || m_shutdownRequested)
break;
}
std::cout << "Connection closed" << std::endl;
}
void thread_func() {
// http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service.html#boost_asio.reference.io_service.effect_of_exceptions_thrown_from_handlers
for (;;) {
try {
m_IOService.run();
break; // exited normally
} catch (std::exception const& e) {
std::cerr << "[eventloop] error: " << e.what();
} catch (...) {
std::cerr << "[eventloop] unexpected error";
}
}
std::cout << "Exiting service thread" << std::endl;
}
std::atomic_bool m_shutdownRequested{false};
uint16_t m_port;
asio::io_service m_IOService;
boost::optional<asio::io_service::work> m_Work{m_IOService};
tcp::socket m_Socket{m_IOService};
tcp::acceptor m_Acceptor{m_IOService, tcp::endpoint{tcp::v4(), m_port}};
std::thread m_Thread{boost::bind(&TheServer::thread_func, this)};
};
constexpr uint16_t s_port = 1900;
void run_server() {
TheServer server(s_port);
std::cout << "Press Enter to quit" << std::endl;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
void run_client() {
std::cout << std::endl;
std::cout << "Starting client" << std::endl;
using asio::ip::tcp;
try {
asio::io_service m_IOService;
tcp::resolver resolver(m_IOService);
auto iterator = resolver.resolve("localhost", std::to_string(s_port));
tcp::socket m_Socket(m_IOService);
connect(m_Socket, iterator);
std::cout << "Client connected" << std::endl;
std::string hello = "Hello World";
write(m_Socket, asio::buffer(hello));
std::this_thread::sleep_for(100ms);
hello += "(2)";
write(m_Socket, asio::buffer(hello));
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}
}
int main(int argc, char**) {
if (argc>1)
run_server();
else
run_client();
}
I have an application where I need to connect to a socket, send a handshake message (send command1, get response, send command2), and then receive data. It is set to expire after a timeout, stop the io_service, and then attempt to reconnect. There is no error message when I do my first async_write but the following async_read waits until the timer expires, and then reconnects in an infinite loop.
My code looks like:
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <iostream>
#include <string>
#include <memory>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace std;
using boost::asio::ip::tcp;
static shared_ptr<boost::asio::io_service> _ios;
static shared_ptr<boost::asio::deadline_timer> timer;
static shared_ptr<boost::asio::ip::tcp::socket> tcp_sock;
static shared_ptr<tcp::resolver> _resolver;
static boost::asio::ip::tcp::resolver::results_type eps;
string buffer(1024,0);
void handle_read(const boost::system::error_code& ec, size_t bytes)
{
if (ec)
{
cout << "error: " << ec.message() << endl;
_ios->stop();
return;
}
// got first response, send off reply
if (buffer == "response")
{
boost::asio::async_write(*tcp_sock, boost::asio::buffer("command2",7),
[](auto ec, auto bytes)
{
if (ec)
{
cout << "write error: " << ec.message() << endl;
_ios->stop();
return;
}
});
}
else
{
// parse incoming data
}
// attempt next read
timer->expires_from_now(boost::posix_time::seconds(10));
boost::asio::async_read(*tcp_sock, boost::asio::buffer(buffer,buffer.size()), handle_read);
}
void get_response()
{
timer->expires_from_now(boost::posix_time::seconds(10));
boost::asio::async_read(*tcp_sock, boost::asio::buffer(buffer,buffer.size()), handle_read);
}
void on_connected(const boost::system::error_code& ec, tcp::endpoint)
{
if (!tcp_sock->is_open())
{
cout << "socket is not open" << endl;
_ios->stop();
}
else if (ec)
{
cout << "error: " << ec.message() << endl;
_ios->stop();
return;
}
else
{
cout << "connected" << endl;
// do handshake (no errors?)
boost::asio::async_write(*tcp_sock, boost::asio::buffer("command1",7),
[](auto ec, auto bytes)
{
if (ec)
{
cout << "write error: " << ec.message() << endl;
_ios->stop();
return;
}
get_response();
});
}
}
void check_timer()
{
if (timer->expires_at() <= boost::asio::deadline_timer::traits_type::now())
{
tcp_sock->close();
timer->expires_at(boost::posix_time::pos_infin);
}
timer->async_wait(boost::bind(check_deadline));
}
void init(string ip, string port)
{
// set/reset data and connect
_resolver.reset(new tcp::resolver(*_ios));
eps = _resolver->resolve(ip, port);
timer.reset(new boost::asio::deadline_timer(*_ios));
tcp_sock.reset(new boost::asio::ip::tcp::socket(*_ios));
timer->expires_from_now(boost::posix_time::seconds(5));
// start async connect
boost::asio::async_connect(*tcp_sock, eps, on_connected);
timer->async_wait(boost::bind(check_timer));
}
int main(int argc, char** argv)
{
while (1)
{
// start new io context
_ios.reset(new boost::asio::io_service);
init(argv[1],argv[2]);
_ios->run();
cout << "try reconnect" << endl;
}
return 0;
}
Why would I be timing out? When I do a netcat and follow the same procedure things look ok. I get no errors from the async_write indicating that there are any errors and I am making sure to not call the async_read for the response until I am in the write handler.
Others have been spot on. You use "blanket" read, which means it only completes at error (like EOF) or when the buffer is full (docs)
Besides your code is over-complicated (excess dynamic allocation, manual new, globals, etc).
The following simplified/cleaned up version still exhibits your problem: http://coliru.stacked-crooked.com/a/8f5d0820b3cee186
Since it looks like you just want to limit over-all time of the request, I'd suggest dropping the timer and just limit the time to run the io_context.
Also showing how to use '\n' for message delimiter and avoid manually managing dynamic buffers:
Live On Coliru
#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
#include <memory>
#include <string>
namespace asio = boost::asio;
using asio::ip::tcp;
using boost::system::error_code;
using namespace std::literals;
struct Client {
#define HANDLE(memfun) std::bind(&Client::memfun, this, std::placeholders::_1, std::placeholders::_2)
Client(std::string const& ip, std::string const& port) {
async_connect(_sock, tcp::resolver{_ios}.resolve(ip, port), HANDLE(on_connected));
}
void run() { _ios.run_for(10s); }
private:
asio::io_service _ios;
asio::ip::tcp::socket _sock{_ios};
std::string _buffer;
void on_connected(error_code ec, tcp::endpoint) {
std::cout << "on_connected: " << ec.message() << std::endl;
if (ec)
return;
async_write(_sock, asio::buffer("command1\n"sv), [this](error_code ec, size_t) {
std::cout << "write: " << ec.message() << std::endl;
if (!ec)
get_response();
});
}
void get_response() {
async_read_until(_sock, asio::dynamic_buffer(_buffer /*, 1024*/), "\n", HANDLE(on_read));
}
void on_read(error_code ec, size_t bytes) {
std::cout << "handle_read: " << ec.message() << " " << bytes << std::endl;
if (ec)
return;
auto cmd = _buffer.substr(0, bytes);
_buffer.erase(0, bytes);
// got first response, send off reply
std::cout << "Handling command " << quoted(cmd) << std::endl;
if (cmd == "response\n") {
async_write(_sock, asio::buffer("command2\n"sv), [](error_code ec, size_t) {
std::cout << "write2: " << ec.message() << std::endl;
});
} else {
// TODO parse cmd
}
get_response(); // attempt next read
}
};
int main(int argc, char** argv) {
assert(argc == 3);
while (1) {
Client(argv[1], argv[2]).run();
std::this_thread::sleep_for(1s); // for demo on COLIRU
std::cout << "try reconnect" << std::endl;
}
}
With output live on coliru:
on_connected: Connection refused
try reconnect
on_connected: Success
write: Success
command1
handle_read: Success 4
Handling command "one
"
handle_read: Success 9
Handling command "response
"
write2: Success
command2
handle_read: Success 6
Handling command "three
"
handle_read: End of file 0
try reconnect
on_connected: Success
write: Success
command1
Local interactive demo:
Sidenote: as long as resolve() isn't happening asynchronously it will not be subject to the timeouts.
I want to send a struct from client to server using boost::asio. I followed boost tutorial link https://www.boost.org/doc/libs/1_47_0/doc/html/boost_asio/examples.html#boost_asio.examples.serialization. I slighty modified the code in server.cpp and client.cpp. With the new code, after a connection is established, client.cpp writes the struct stock to server and reads stock information at server side. (in the tutorial version, after a connection established, the server writes stock struct to client and client reads them. This version works for me.)
My problem is that after a connection is established, the async_write in client.cpp causes error
Error in write: An existing connection was forcibly closed by the remote host
and the async_read in server.cpp causes error
Error in read:The network connection was aborted by the local system.
As suggested by some forum answers, I changed this pointers in function handlers of async_write and async_read to shared_from_this. Still the problem exists.
I am not able to identify whether the client or the server side is causing problem. Please help.
server.cpp
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <vector>
#include "connection.h" // Must come before boost/serialization headers.
#include <boost/serialization/vector.hpp>
#include <boost/enable_shared_from_this.hpp>
#include "stock.h"
namespace s11n_example
{
/// Serves stock quote information to any client that connects to it.
class server : public boost::enable_shared_from_this<server>
{
private:
/// The acceptor object used to accept incoming socket connections.
boost::asio::ip::tcp::acceptor acceptor_;
/// The data to be sent to each client.
std::vector<stock> stocks_;
public:
/// Constructor opens the acceptor and starts waiting for the first incoming
/// connection.
server(boost::asio::io_service& io_service, unsigned short port):
acceptor_(io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
{
// Start an accept operation for a new connection.
connection_ptr new_conn(new connection(acceptor_.get_io_service()));
acceptor_.async_accept(new_conn->socket(),
boost::bind(&server::handle_accept, this,boost::asio::placeholders::error, new_conn));
}
/// Handle completion of a accept operation.
void handle_accept(const boost::system::error_code& e, connection_ptr conn)
{
if (!e)
{
std::cout << "Received a connection" <<std::endl;
conn->async_read(stocks_,
boost::bind(&server::handle_read, shared_from_this(),boost::asio::placeholders::error));
}
// Start an accept operation for a new connection.
connection_ptr new_conn(new connection(acceptor_.get_io_service()));
acceptor_.async_accept(new_conn->socket(),
boost::bind(&server::handle_accept, this,boost::asio::placeholders::error, new_conn));
}
/// Handle completion of a read operation.
void handle_read(const boost::system::error_code& e)
{
if (!e)
{
// Print out the data that was received.
for (std::size_t i = 0; i < stocks_.size(); ++i)
{
std::cout << "Stock number " << i << "\n";
std::cout << " code: " << stocks_[i].code << "\n";
std::cout << " name: " << stocks_[i].name << "\n";
std::cout << " open_price: " << stocks_[i].open_price << "\n";
std::cout << " high_price: " << stocks_[i].high_price << "\n";
std::cout << " low_price: " << stocks_[i].low_price << "\n";
std::cout << " last_price: " << stocks_[i].last_price << "\n";
std::cout << " buy_price: " << stocks_[i].buy_price << "\n";
std::cout << " buy_quantity: " << stocks_[i].buy_quantity << "\n";
std::cout << " sell_price: " << stocks_[i].sell_price << "\n";
std::cout << " sell_quantity: " << stocks_[i].sell_quantity << "\n";
}
}
else
{
// An error occurred.
std::cerr << "Error in read:" << e.message() << std::endl;
}
}
};
} // namespace s11n_example
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 2)
{
std::cerr << "Usage: server <port>" << std::endl;
return 1;
}
unsigned short port = boost::lexical_cast<unsigned short>(argv[1]);
boost::asio::io_service io_service;
boost::shared_ptr<s11n_example::server> server(new s11n_example::server(io_service, port));
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
client.cpp
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <vector>
#include "connection.h" // Must come before boost/serialization headers.
#include <boost/serialization/vector.hpp>
#include <boost/enable_shared_from_this.hpp>
#include "stock.h"
namespace s11n_example {
/// Downloads stock quote information from a server.
class client : public boost::enable_shared_from_this<client>
{
private:
/// The connection to the server.
connection connection_;
/// The data received from the server.
std::vector<stock> stocks_;
public:
/// Constructor starts the asynchronous connect operation.
client(boost::asio::io_service& io_service, const std::string& host, const std::string& service)
: connection_(io_service)
{
// Resolve the host name into an IP address.
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::resolver::query query(host, service);
boost::asio::ip::tcp::resolver::iterator endpoint_iterator =
resolver.resolve(query);
// Start an asynchronous connect operation.
boost::asio::async_connect(connection_.socket(), endpoint_iterator,
boost::bind(&client::handle_connect, this,boost::asio::placeholders::error));
}
/// Handle completion of a connect operation.
void handle_connect(const boost::system::error_code& e) //, connection_ptr conn
{
if (!e)
{
std::cout << "Connected to server!" << std::endl;
// Create the data to be sent to each client.
stock s;
s.code = "ABC";
s.name = "A Big Company";
s.open_price = 4.56;
s.high_price = 5.12;
s.low_price = 4.33;
s.last_price = 4.98;
s.buy_price = 4.96;
s.buy_quantity = 1000;
s.sell_price = 4.99;
s.sell_quantity = 2000;
stocks_.push_back(s);
s.code = "DEF";
s.name = "Developer Entertainment Firm";
s.open_price = 20.24;
s.high_price = 22.88;
s.low_price = 19.50;
s.last_price = 19.76;
s.buy_price = 19.72;
s.buy_quantity = 34000;
s.sell_price = 19.85;
s.sell_quantity = 45000;
stocks_.push_back(s);
// Successfully established connection. Start operation to write the list
// of stocks.
connection_.async_write(stocks_,
boost::bind(&client::handle_write, shared_from_this(),boost::asio::placeholders::error)); //,&conn )
}
else
{
// An error occurred. Log it and return.
std::cerr << "Error in connecting to server" << e.message() << std::endl;
}
}
/// Handle completion of a write operation.
void handle_write(const boost::system::error_code& e)//, connection* conn
{
if (!e)
{
std::cout << "Finished writing to server" << std::endl;
}
else
{
// An error occurred. Log it and return. Since we are not starting a new
// operation the io_service will run out of work to do and the client will
// exit.
std::cerr << "Error in write: " << e.message() << std::endl;
}
// Nothing to do. The socket will be closed automatically when the last
// reference to the connection object goes away.
}
};
} // namespace s11n_example
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 3)
{
std::cerr << "Usage: client <host> <port>" << std::endl;
return 1;
}
boost::asio::io_service io_service;
//s11n_example::client client(io_service, argv[1], argv[2]);
boost::shared_ptr<s11n_example::client> client(new s11n_example::client(io_service, argv[1], argv[2]));
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
Thanks.
You need to pass conn to handle read otherwise it will be destructed at the end of the handle_accept method. When it's destructed the socket it contains will also be destructed and the connection will close.
conn->async_read(stocks_,
boost::bind(&server::handle_read, shared_from_this(), conn, boost::asio::placeholders::error));
Lambdas make this easier to read than using bind:
auto self = shared_from_this();
conn->async_read(stocks_,
[self, this, conn] (boost::system::error_code ec) { handle_read(ec); });
The variables listed in the capture list will be copied so the shared pointers will be kept alive.
I'm encapsulating the boost-asio socket, but I got an issue with it, but neither async_read nor async_write calls their callback function and I don't understand why.
I've tried using async_read_some but had the same issue.
Here's the code I've written so far
#include <iostream>
#include "socket.hpp"
Socket::Socket()
{
boost::asio::ip::tcp::endpoint ep_tmp(boost::asio::ip::tcp::v4(), 4242);
endpoint = ep_tmp;
acceptor = new boost::asio::ip::tcp::acceptor(ios, endpoint);
tcp_socket = new boost::asio::ip::tcp::socket(ios);
acceptor->listen();
}
Socket::~Socket()
{
delete(acceptor);
delete(tcp_socket);
}
void Socket::get_connection()
{
acceptor->async_accept(*tcp_socket, [](const boost::system::error_code &ec)
{
std::cout << "Connection received." << std::endl;
if (ec)
std::cout << "Error " << ec << std::endl;
});
this->exec();
}
void Socket::send(std::string &message)
{
async_write(*tcp_socket, boost::asio::buffer(message),
[](const boost::system::error_code &ec,
std::size_t bytes_transferred)
{
std::cout << "Sending datas." << std::endl;
if (ec)
std::cout << "Error " << ec << std::endl;
else
std::cout << bytes_transferred << " bytes transferred." << std::endl;
});
}
void Socket::receive(void)
{
char *buf;
buf = (char *)malloc(sizeof(char) * 50);
buf = (char *)memset(buf, 0, 50);
async_read(*tcp_socket, boost::asio::buffer(buf, 50),
[](const boost::system::error_code &ec,
std::size_t bytes_transferred)
{
std::cout << "Receiving datas." << std::endl;
if (ec)
std::cout << "Error " << ec << std::endl;
else
std::cout << bytes_transferred
<< " bytes transferred." << std::endl;
});
}
void Socket::exec(void)
{
ios.run();
}
int main()
{
Socket serv;
std::string data_test;
data_test = "Test\n";
serv.get_connection();
serv.send(data_test);
serv.exec();
serv.receive();
serv.exec();
return (0);
}
The malloc bit is temporary until I find a way to do it without using C.
I'd be really thankful if someone could enlighten me on that issue
You have to call io_service::reset before second and later calls to io_service::run. And you probably want to use synchronous API instead, as your current approach absolutely defeats the purpose of asynchronicity.
I'm with yuri: prefer non-async unless you know what you're doing.
It could look like this: http://coliru.stacked-crooked.com/a/523a7828a9aee4b2
#include <boost/asio.hpp>
#include <iostream>
namespace ba = boost::asio;
using ba::ip::tcp;
class Socket {
public:
Socket() { acceptor.listen(); }
void get_connection();
void exec();
void send(std::string const &message);
void receive(void);
private:
ba::io_service ios;
tcp::endpoint endpoint{ tcp::v4(), 4242 };
tcp::acceptor acceptor{ ios, endpoint };
tcp::socket tcp_socket{ ios };
};
void Socket::get_connection() {
acceptor.accept(tcp_socket);
std::cout << "Connection received.\n";
}
void Socket::send(std::string const &message) {
std::cout << "Sending datas.\n";
auto bytes_transferred = ba::write(tcp_socket, ba::buffer(message));
std::cout << bytes_transferred << " bytes transferred.\n";
}
void Socket::receive(void) {
std::cout << "Receiving datas.\n";
char buf[50] = { 0 };
auto bytes_transferred = ba::read(tcp_socket, ba::buffer(buf));
std::cout << bytes_transferred << " bytes transferred.\n";
}
int main() {
Socket serv;
serv.get_connection();
serv.send("Test\n");
serv.receive();
}
If you want async behaviour, you have to manage the lifetimes of each buffer/connection-specific resource. There are many examples of that, e.g. in the docs or here: http://coliru.stacked-crooked.com/a/95e2000e49b4db1d
On the perils of buffer lifetime: client server simple example nonblocking
I have implemented some connection classes, using Boost ASIO, to replace some low level C code in an application, and everything is working great, except for one problem.
Basically, I have a UdpConnection class that does synchronous read and write, but it uses async methods to handle time-outs as per the boost examples. The problem is I can't figure out how to make it threadsafe.
I have tried adding strands to the event handlers to make this class threadsafe (code below), but that isn't working. I suspect it is because of the way timeout is implemented. I have included my code in 4 classes in pastebin.
Single threaded is working fine. I also have TcpConnection and UnixSocketConnection classes that don't need to be shared amongst multiple threads and they work fine. However, I can't get multi-threaded UDP code to work.
Am I missing something?
Connection.h && AsioConnection.h http://pastebin.com/Cbbw37gL
UdpConnection.h && UdpConnection.cpp http://pastebin.com/VLnHBnPs
EDIT Attaching code as suggested:
AsioConnection.h
/*
* AsioConnection.h
*
* Created on: 2011-04-08
* Author: cdunphy
*
* All classes that want to use the ASIO io_service
* and deadline timers will want to subclass this.
*/
#ifndef ASIOCONNECTION_H_
#define ASIOCONNECTION_H_
#include "Connection.h"
#include <boost/shared_ptr.hpp>
#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <boost/bind.hpp>
namespace shaw_rsc
{
/*
* This exception throws if there is a timeout when connecting
* to a remote socket.
*/
struct SocketTimeoutException : public std::runtime_error
{
SocketTimeoutException(const std::string& msg) : std::runtime_error(msg)
{ }
}
;
/*
* This is the root class of every Connection
* class that wants to make use of boost asio.
*/
class AsioConnection : public Connection
{
public:
AsioConnection(
int c_timeout,
int r_timeout
) : Connection(),
conn_timeout_int(c_timeout),
read_timeout_int(r_timeout),
conn_timeout(c_timeout),
read_timeout(r_timeout),
io_service(),
strand(io_service),
deadline(strand.get_io_service()),
error()
{
reset_deadline();
}
const boost::system::error_code& getError() const
{
return error;
}
int get_read_timeout() const
{
return read_timeout_int;
}
int get_conn_timeout() const
{
return conn_timeout_int;
}
/*
* These are the callback handlers for our asynchronous
* IO operations.
*/
void handle_write(const boost::system::error_code& ec,
std::size_t len,
boost::system::error_code* out_ec,
std::size_t* out_len)
{
*out_ec = ec;
*out_len = len;
}
/*
* These are the callback handlers for our asynchronous
* IO operations.
*/
void handle_send(const boost::system::error_code& ec,
std::size_t len,
boost::system::error_code* out_ec,
std::size_t* out_len)
{
*out_ec = ec;
*out_len = len;
}
void handle_read(const boost::system::error_code& ec,
std::size_t len,
boost::system::error_code* out_ec,
std::size_t* out_len)
{
*out_ec = ec;
*out_len = len;
}
void handle_receive(const boost::system::error_code& ec,
std::size_t len,
boost::system::error_code* out_ec,
std::size_t* out_len)
{
*out_ec = ec;
*out_len = len;
}
void handle_connect(const boost::system::error_code& ec,
boost::system::error_code* out_ec)
{
*out_ec = ec;
}
protected:
int conn_timeout_int;
int read_timeout_int;
boost::posix_time::seconds conn_timeout;
boost::posix_time::seconds read_timeout;
boost::asio::io_service io_service;
boost::asio::strand strand;
boost::asio::deadline_timer deadline;
boost::system::error_code error;
void reset_deadline()
{
// 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);
}
};
}
#endif /* ASIOCONNECTION_H_ */
Connection.h
/*
* Connection.h
*
* Created on: 2011-02-25
* Author: cdunphy
*/
#ifndef CONNECTION_H_
#define CONNECTION_H_
#include <vector>
#include <string>
#include <sstream>
#include <stdexcept>
#include <boost/thread.hpp>
#include <boost/shared_ptr.hpp>
namespace shaw_rsc
{
class Connection;
const std::size_t BUF_SIZE = 128;
/*
* This is the type of reference we will
* provide to the clients.
*/
typedef boost::shared_ptr<Connection> ConnPtr;
typedef std::vector<char> DataBuffer;
typedef DataBuffer::iterator DB_Iter;
typedef DataBuffer::const_iterator DB_CIter;
// This is the mode we are using for the connection
enum Mode {
CLIENT,
SERVER
};
/*
* This is a generic class that allows data to be read or
* written to using a connection. This is quite abstract
* and it can be used both for file operations and for
* network operations.
*/
class Connection
{
public:
Connection() { }
virtual ~Connection() { }
/*
* This method writes the current contents of the data buffer
* to the connected resource. Be sure to set the right data
* in the buffer by calling the setData method first.
*
* The number of bytes written is returned.
*/
virtual std::size_t write(const DataBuffer& data) = 0;
/*
* This method reads data from the connected resource and stores
* it in our data buffer which we pass in by reference.
* Please note that it clears whatever data was in the buffer prior to
* reading.
*
* The number of bytes read is returned.
*/
virtual std::size_t read(DataBuffer& data) = 0;
virtual const std::string str() const = 0;
};
inline std::vector<unsigned char> convert_data_to_unsigned(const DataBuffer& data)
{
return std::vector<unsigned char>(data.begin(), data.end());
}
inline std::string dataBufferToStr(const DataBuffer& data)
{
return std::string(data.begin(), data.end());
}
}
#endif /* CONNECTION_H_ */
UdpConnection.h
/*
* UdpConnection.h
*
* Created on: 2011-02-25
* Author: cdunphy
*/
// Portions Copyright (c) 2003-2011 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)
//
#ifndef DATAGRAMCONNECTION_H_
#define DATAGRAMCONNECTION_H_
#include "AsioConnection.h"
#include <boost/lexical_cast.hpp>
namespace shaw_rsc
{
struct UdpException: public std::runtime_error
{
UdpException(const std::string& msg) : std::runtime_error(msg) { }
};
/*
* This is the concrete class that manages UDP connections.
*/
class UdpConnection: public AsioConnection
{
public:
/*
* Use this constructor for clients (connecting to a remote socket).
*/
UdpConnection(
const std::string& _host,
const std::string& _port,
int r_timeout,
Mode mode
) : AsioConnection(0, r_timeout),
socket(strand.get_io_service()),
remote_endpoint(),
host(_host),
port(_port)
{
check_deadline();
connect(mode);
}
std::size_t write(const DataBuffer& data);
std::size_t read(DataBuffer& data);
const std::string str() const;
private:
void connect(Mode mode);
void check_deadline();
boost::asio::ip::udp::socket socket;
boost::asio::ip::udp::endpoint remote_endpoint;
std::string host;
std::string port;
};
}
#endif /* DATAGRAMCONNECTION_H_ */
UdpConnection.cpp
/*
* UdpConnection.cpp
*
* Created on: 2011-02-25
* Author: cdunphy
*/
#include "UdpConnection.h"
using std::string;
using std::endl;
using std::stringstream;
using std::exception;
using boost::asio::buffer;
using boost::asio::ip::udp;
using boost::system::error_code;
using boost::system::system_error;
using boost::asio::deadline_timer;
using boost::bind;
using boost::lexical_cast;
namespace shaw_rsc
{
size_t UdpConnection::write(const DataBuffer& data)
{
size_t bytes_written = 0;
/*
* Check to see if the socket is bad before writing
*/
if (error &&
error.value() != boost::asio::error::operation_aborted &&
error.value() != boost::asio::error::timed_out &&
error != boost::asio::error::try_again)
throw UdpException(error.message());
socket.async_send_to(buffer(data), remote_endpoint,
strand.wrap(bind(&AsioConnection::handle_send, this, _1, _2,
&error, &bytes_written)));
do
{
strand.get_io_service().run_one();
}
while (error == boost::asio::error::would_block
|| error == boost::asio::error::try_again || bytes_written == 0);
if (error)
{
if (error.value() == boost::asio::error::operation_aborted
|| error.value() == boost::asio::error::timed_out)
throw SocketTimeoutException(error.message());
else
throw UdpException(error.message());
}
reset_deadline();
return bytes_written;
}
size_t UdpConnection::read(DataBuffer& data)
{
/*
* Check to see if the socket is bad before writing
*/
if (error &&
error.value() != boost::asio::error::operation_aborted &&
error.value() != boost::asio::error::timed_out &&
error != boost::asio::error::try_again)
throw UdpException(error.message());
data.clear();
/*
* Reset the deadline timer to expire according
* to the configured read timeout.
*/
deadline.expires_from_now(read_timeout);
size_t bytes_read = 0;
boost::array<char, BUF_SIZE> buff;
error = boost::asio::error::would_block;
socket.async_receive_from(buffer(buff), remote_endpoint,
strand.wrap(boost::bind(&AsioConnection::handle_receive, this, _1, _2, &error,
&bytes_read)));
do
{
strand.get_io_service().run_one();
}
while (error == boost::asio::error::would_block ||
error == boost::asio::error::try_again || bytes_read == 0);
/*
* Check for errors after the read.
*/
if (error)
{
if (error.value() == boost::asio::error::operation_aborted
|| error.value() == boost::asio::error::timed_out)
throw SocketTimeoutException(error.message());
else
throw UdpException(error.message());
}
else
data.insert(data.end(), buff.begin(), buff.begin() + bytes_read);
// Reset the deadline timer so we can leave this socket open as long
// as we want.
reset_deadline();
return bytes_read;
}
void UdpConnection::connect(Mode mode)
{
socket.open(boost::asio::ip::udp::v4());
if (mode == SERVER)
{
socket.bind(
udp::endpoint(udp::v4(),
lexical_cast<int>(port)), error);
}
else if (mode == CLIENT)
{
udp::resolver resolver(strand.get_io_service());
udp::resolver::query query(udp::v4(), host, port);
remote_endpoint = *resolver.resolve(query, error);
}
}
void UdpConnection::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 outstanding asynchronous operation needs
// to be cancelled so that the blocked receive() function will return.
//
// Please note that cancel() has portability issues on some versions of
// Microsoft Windows, and it may be necessary to use close() instead.
// Consult the documentation for cancel() for further information.
socket.cancel();
// 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.
reset_deadline();
}
// Put the actor back to sleep.
deadline.async_wait(strand.wrap(boost::bind(&UdpConnection::check_deadline, this)));
}
/*
* This member function is good for diagnostic purposes
*/
const string UdpConnection::str() const
{
stringstream sstr;
sstr << "Host: " << host << endl;
sstr << "Port: " << port << endl;
sstr << "Read timeout: " << read_timeout_int << endl;
sstr << "Remote Endpoint Address: " << remote_endpoint.address().to_string()
<< endl;
sstr << "Remote Endpoint Port: " << remote_endpoint.port() << endl;
try
{
sstr << "Socket Remote Endpoint Address: "
<< socket.remote_endpoint().address().to_string() << endl;
sstr << "Socket Remote Endpoint Port: "
<< socket.remote_endpoint().port() << endl;
}
catch (exception& e)
{ }
try
{
sstr << "Socket Local Endpoint Address: "
<< socket.local_endpoint().address().to_string() << endl;
sstr << "Socket Local Endpoint Port: " << socket.local_endpoint().port()
<< endl;
}
catch (exception& e)
{ }
return sstr.str();
}
}
EDIT2:
Here is the test code I am trying to get working:
Server that replies in C++. All tests are working EXCEPT the threaded Udp test:
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE TestLibRSCAsio
#include <cstdio>
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <iostream>
#include <string>
#include <exception>
#include <sstream>
#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <boost/date_time.hpp>
#include <boost/thread.hpp>
#include <boost/lexical_cast.hpp>
#include "boost/date_time/posix_time/posix_time.hpp"
#include <rsc_asio/Connection.h>
#include <rsc_asio/TcpConnection.h>
#include <rsc_asio/UdpConnection.h>
#include <rsc_asio/UnixSocketConnection.h>
#include "Util.h"
#include "sha1/Sha1Calc.h"
#include "servers/TestTcpServer.h"
#include <boost/test/unit_test.hpp>
using std::vector;
using std::string;
using std::size_t;
using std::cerr;
using std::cout;
using std::endl;
using std::flush;
using std::exception;
using std::time;
using std::stringstream;
using boost::lexical_cast;
using boost::thread;
using boost::mutex;
using boost::unique_lock;
using namespace shaw_rsc;
const size_t TCP_BYTE_SZ = 1000000;
const size_t UDP_BYTE_SZ = 64;
const std::string FILE_SOCKET = "/tmp/rofl";
const std::string SERVER_HOST = "0.0.0.0";
const std::string SERVER_PORT = "10999";
const std::string EXPECTED_UDP_REQUEST = "GOT_ANSWER?";
const int TIMEOUT = 3;
const int THREAD_TIMEOUT = 10;
DataBuffer write_data(ConnPtr client, Sha1Calc& sha1calc, size_t size, size_t iter)
{
unique_lock<mutex>(global_mutex);
cout << "Iter: " << iter << endl;
DataBuffer data = getRandomData(size);
sha1calc.calc_client_digest(data);
size_t bytes_written = client->write(data);
cout << "Wrote " << bytes_written << " -> " << dataBufferToStr(data) << " to socket: " << endl << client->str() << endl;
return data;
}
void write_data_threaded(ConnPtr client, Sha1Calc& sha1calc, size_t size, size_t iter)
{
cout << "Iter: " << iter << endl;
DataBuffer data = getRandomData(size);
sha1calc.calc_client_digest(data);
size_t bytes_written = client->write(data);
cout << "Wrote " << bytes_written << " -> " << dataBufferToStr(data) << " to socket: " << endl << client->str() << endl;
}
DataBuffer read_data(ConnPtr server, Sha1Calc& sha1calc, size_t iter)
{
cout << "Iter: " << iter << endl;
DataBuffer data;
size_t bytes_read = server->read(data);
cout << "Read " << bytes_read << " -> " << dataBufferToStr(data) << " from socket: " << endl << server->str() << endl;
sha1calc.calc_server_digest(data);
return data;
}
/*
* This is a suite of tests to provide unit tests
* for the RRE.
*/
BOOST_AUTO_TEST_SUITE(TestLibRSCAsioSuite)
BOOST_AUTO_TEST_CASE(TcpTest)
{
boost::asio::io_service io_service;
cout << endl << "**** TCP Test ****" << endl;
Sha1Calc sha1calc;
cout << endl << "Generating " << TCP_BYTE_SZ << " bytes of data to serve up." << endl;
DataBuffer dataToServe = getRandomData(TCP_BYTE_SZ);
sha1calc.calc_server_digest(dataToServe);
cout << "SHA1 hash of server data: " <<
sha1_to_str(sha1calc.get_server_digest()) << endl;
SrvPtr server(new TestTcpServer(std::atoi(SERVER_PORT.c_str()), dataToServe, io_service));
server->start();
try
{
// Fire up a basic TCP client for testing
cout << "Firing up TCP client on port: " << SERVER_PORT << endl;
DataBuffer clientData;
ConnPtr client(new TcpConnection(SERVER_HOST, SERVER_PORT, TIMEOUT, TIMEOUT, io_service));
size_t bytesRead = client->read(clientData);
BOOST_REQUIRE( bytesRead == TCP_BYTE_SZ );
BOOST_REQUIRE( clientData.size() == TCP_BYTE_SZ );
sha1calc.calc_client_digest(clientData);
BOOST_REQUIRE( sha1calc.compare() );// SHA-1 hashes better matctype filter texth
}
catch (SocketTimeoutException& e)
{
cerr << "Socket timeout: " << e.what() << endl;
BOOST_FAIL("Socket Timeout");
}
catch (const TcpException& e)
{
cerr << "TCP Error: " << e.what() << endl;
BOOST_FAIL("TCP Exception");
}
catch (const exception& e)
{
cerr << "Other Error: " << e.what() << endl;
BOOST_FAIL("Unknown Exception");
}
}
BOOST_AUTO_TEST_CASE(UdpTest)
{
boost::asio::io_service io_service;
std::stringstream error;
try
{
cout << endl << "**** UDP Test ****" << endl;
ConnPtr client(new UdpConnection(SERVER_HOST, SERVER_PORT, TIMEOUT, CLIENT, io_service));
ConnPtr server(new UdpConnection(SERVER_HOST, SERVER_PORT, TIMEOUT, SERVER, io_service));
for (int i = 0; i != 10; ++i)
{
Sha1Calc sha1calc;
// Write the data to the client
DataBuffer clientData = write_data(client, sha1calc, UDP_BYTE_SZ, i);
// Read the data from the server
DataBuffer serverData = read_data(server, sha1calc, i);
// Make sure the client data matches the server data
BOOST_REQUIRE( sha1calc.compare() );
cout << endl; // new-line
}
}
catch (const SocketTimeoutException& e)
{
error << "Socket timeout: " << e.what() << endl;
BOOST_FAIL(error.str());
}
catch (const UdpException& e)
{
error << "UDP Exception: " << e.what() << endl;
BOOST_FAIL(error.str());
}
catch (const exception& e)
{
error << "Other Error: " << e.what() << endl;
BOOST_FAIL(error.str());
}
}
BOOST_AUTO_TEST_CASE(UdpThreadTest)
{
boost::asio::io_service io_service;
std::stringstream error;
try
{
cout << endl << "**** UDP Multi-thread Test ****" << endl;
ConnPtr server(new UdpConnection(SERVER_HOST, SERVER_PORT, THREAD_TIMEOUT, SERVER, io_service));
Sha1Calc sha1calc;
for (int i = 0; i != 10; ++i)
{
// Read the data from the server, make sure it matches
// the expected request?
DataBuffer serverData = read_data(server, sha1calc, i);
BOOST_REQUIRE(dataBufferToStr(serverData) == EXPECTED_UDP_REQUEST);
// Repply on the remote socket
thread t1(bind(&write_data_threaded, server, sha1calc, UDP_BYTE_SZ, i));
cout << endl; // new-line
}
}
catch (const SocketTimeoutException& e)
{
error << "Socket timeout: " << e.what() << endl;
BOOST_FAIL(error.str());
}
catch (const UdpException& e)
{
error << "UDP Exception: " << e.what() << endl;
BOOST_FAIL(error.str());
}
catch (const exception& e)
{
error << "Other Error: " << e.what() << endl;
BOOST_FAIL(error.str());
}
}
BOOST_AUTO_TEST_CASE(UnixSocketTest)
{
boost::asio::io_service io_service;
try
{
cout << endl << "**** UNIX Socket Test ****" << endl;
std::remove(FILE_SOCKET.c_str());
ConnPtr server(new UnixSocketConnection(FILE_SOCKET, TIMEOUT, SERVER, io_service));
ConnPtr client(new UnixSocketConnection(FILE_SOCKET, TIMEOUT, CLIENT, io_service));
Sha1Calc sha1calc;
DataBuffer clientData = write_data(client, sha1calc, UDP_BYTE_SZ, 0);
cout << "Wrote the data to the Unix Socket client:" << dataBufferToStr(clientData) << endl;
DataBuffer serverData = read_data(server, sha1calc, 0);
cout << "Read from UDP Server: " << dataBufferToStr(serverData) << endl;
BOOST_REQUIRE( sha1calc.compare() );
cout << sha1_to_str(sha1calc.get_server_digest()) << endl;
}
catch (const SocketTimeoutException& e)
{
cerr << "Socket timeout: " << e.what() << endl;
BOOST_FAIL("Socket Timeout");
}
catch (const UnixSocketException& e)
{
cerr << "UNIX Socket Error: " << e.what() << endl;
BOOST_FAIL("UNIXSocket Exception");
}
catch (const exception& e)
{
cerr << "Other Error: " << e.what() << endl;
BOOST_FAIL("Unknown Exception");
}
std::remove(FILE_SOCKET.c_str());
}
BOOST_AUTO_TEST_SUITE_END()
}
Client written in Java:
package com.shaw.udp.sender;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
public class Client {
public static void main(String[] args) throws Exception {
DatagramChannel channel = DatagramChannel.open();
SocketAddress address = new InetSocketAddress(0);
SocketAddress client = new InetSocketAddress(SERVER_HOST, 10999);
DatagramSocket socket = channel.socket();
// This is the local socket
socket.setSoTimeout(5000);
socket.bind(address);
for (int i = 0; i != 10; ++i) {
// Send the data to the remote server
ByteBuffer buffer = ByteBuffer.wrap("GOT_ANSWER?".getBytes());
channel.send(buffer, client);
System.out.println("Iter: " + i + " => Sent request: "
+ new String(buffer.array()));
// Listen for reply from the server
buffer = ByteBuffer.allocate(64);
channel.receive(buffer);
System.out.println("Iter: " + i + " => Got reply: "
+ new String(buffer.array()));
}
}
}