I created a server using boost:asio. When a client connects it sends a file_size, file_name and the file_data. The server stores this in a file on disk. This works perfectly! Though now I'm running both client application and server application in the main thread of their application (so I've got a server and client app) which blocks the rest of the application(s) from executing.
So in abstract I want to create something like this:
server app
have one thread to receive and handle all incoming file transfers
have another thread in which the rest of the application can do the things it want to
client app
when I press the space bar, or whenever i want, I want to send a file to the server in a separate thread from the main one so my application can continue doing other stuff it needs to do.
My question: how do I create a manager for my client file transfers?
File transfer server accepts new file transfer client connections
#include "ofxFileTransferServer.h"
ofxFileTransferServer::ofxFileTransferServer(unsigned short nPort)
:acceptor(
io_service
,boost::asio::ip::tcp::endpoint(
boost::asio::ip::tcp::v4()
,nPort
)
,true
)
,port(nPort)
{
}
// test
void ofxFileTransferServer::startThread() {
boost::thread t(boost::bind(
&ofxFileTransferServer::accept
,this
));
}
void ofxFileTransferServer::accept() {
ofxFileTransferConnection::pointer new_connection(new ofxFileTransferConnection(io_service));
acceptor.async_accept(
new_connection->socket()
,boost::bind(
&ofxFileTransferServer::handleAccept
,this
,new_connection
,boost::asio::placeholders::error
)
);
std::cout << __FUNCTION__ << " start accepting " << std::endl;
io_service.run();
}
void ofxFileTransferServer::handleAccept(
ofxFileTransferConnection::pointer pConnection
,const boost::system::error_code& rErr
)
{
std::cout << __FUNCTION__ << " " << rErr << ", " << rErr.message() << std::endl;
if(!rErr) {
pConnection->start();
ofxFileTransferConnection::pointer new_connection(new ofxFileTransferConnection(io_service));
acceptor.async_accept(
new_connection->socket()
,boost::bind(
&ofxFileTransferServer::handleAccept
,this
,new_connection
,boost::asio::placeholders::error
)
);
}
}
File transfer client
#include "ofxFileTransferClient.h"
#include "ofMain.h"
using boost::asio::ip::tcp;
ofxFileTransferClient::ofxFileTransferClient(
boost::asio::io_service &rIOService
,const std::string sServer
,const std::string nPort
,const std::string sFilePath
):resolver_(rIOService)
,socket_(rIOService)
,file_path_(sFilePath)
,server_(sServer)
,port_(nPort)
{
}
ofxFileTransferClient::~ofxFileTransferClient() {
std::cout << "~~~~ ofxFileTransferClient" << std::endl;
}
void ofxFileTransferClient::start() {
// open file / get size
source_file_stream_.open(
ofToDataPath(file_path_).c_str()
,std::ios_base::binary | std::ios_base::ate
);
if(!source_file_stream_) {
std::cout << ">> failed to open:" << file_path_ << std::endl;
return;
}
size_t file_size = source_file_stream_.tellg();
source_file_stream_.seekg(0);
// send file size and name to server.
std::ostream request_stream(&request_);
request_stream << file_path_ << "\n"
<< file_size << "\n\n";
std::cout << ">> request_size:" << request_.size()
<< " file_path: " << file_path_
<< " file_size: "<< file_size
<< std::endl;
// resolve ofxFileTransferServer
tcp::resolver::query query(server_, port_);
resolver_.async_resolve(
query
,boost::bind(
&ofxFileTransferClient::handleResolve
,shared_from_this()
,boost::asio::placeholders::error
,boost::asio::placeholders::iterator
)
);
}
void ofxFileTransferClient::handleResolve(
const boost::system::error_code& rErr
,tcp::resolver::iterator oEndPointIt
)
{
if(!rErr) {
tcp::endpoint endpoint = *oEndPointIt;
socket_.async_connect(
endpoint
,boost::bind(
&ofxFileTransferClient::handleConnect
,shared_from_this()
,boost::asio::placeholders::error
,++oEndPointIt
)
);
}
else {
std::cout << ">> error: " << rErr.message() << std::endl;
}
}
void ofxFileTransferClient::handleConnect(
const boost::system::error_code& rErr
,tcp::resolver::iterator oEndPointIt
)
{
if(!rErr) {
cout << ">> connected!" << std::endl;
boost::asio::async_write(
socket_
,request_
,boost::bind(
&ofxFileTransferClient::handleFileWrite
,shared_from_this()
,boost::asio::placeholders::error
)
);
}
else if (oEndPointIt != tcp::resolver::iterator()) {
// connection failed, try next endpoint in list
socket_.close();
tcp::endpoint endpoint = *oEndPointIt;
socket_.async_connect(
endpoint
,boost::bind(
&ofxFileTransferClient::handleConnect
,shared_from_this()
,boost::asio::placeholders::error
,++oEndPointIt
)
);
}
else {
std::cout << ">> error: " << rErr.message() << std::endl;
}
}
void ofxFileTransferClient::handleFileWrite(
const boost::system::error_code& rErr
)
{
if(!rErr) {
if(source_file_stream_.eof() == false) {
source_file_stream_.read(buf_.c_array(), buf_.size());
if(source_file_stream_.gcount() <= 0) {
std::cout << ">> read file error." << std::endl;
return;
}
std::cout << ">> send: " << source_file_stream_.gcount() << " bytes, total: " << source_file_stream_.tellg() << " bytes\n";
boost::asio::async_write(
socket_
,boost::asio::buffer(buf_.c_array(), source_file_stream_.gcount())
,boost::bind(
&ofxFileTransferClient::handleFileWrite
,this
,boost::asio::placeholders::error
)
);
if(rErr) {
std::cout <<">> send error: " << rErr << std::endl; // not sure bout this one..
}
}
else {
return; // eof()
}
}
else {
std::cout << ">> error:" << rErr.message() << std::endl;
}
}
And a tiny manager to manager client transfers (which is used in the client app)
Again the threading code is only for testing purposes and isnt used.
#include "ofxFileTransferManager.h"
ofxFileTransferManager::ofxFileTransferManager() {
}
void ofxFileTransferManager::transferFile(
const std::string sServer
,const std::string nPort
,const std::string sFile
)
{
ofxFileTransferClient::pointer client(new ofxFileTransferClient(
io_service_
,sServer
,nPort
,sFile
));
client->start();
io_service_.run();
}
void ofxFileTransferManager::startThread() {
boost::thread t(boost::bind(
&ofxFileTransferManager::run
,this
));
}
void ofxFileTransferManager::run() {
cout << "starting filemanager" << std::endl;
while(true) {
io_service_.run();
boost::this_thread::sleep(boost::posix_time::milliseconds(250));
cout << ".";
}
cout << "ready filemanager" << std::endl;
}
It would be awesome if someone can help me out here. The example of boost all use a "one-time" client connection which doesn't really help me further.
roxlu
Great! I just figured it out. I had to wrap my io_service around a boost::asio::io_service::work object! (and forgot a shared_from_this()) somewhere. I've uploaded my code here: http://github.com/roxlu/ofxFileTransfer
For convenience here is the manager code:
#include "ofxFileTransferManager.h"
ofxFileTransferManager::ofxFileTransferManager()
:work_(io_service_)
{
}
void ofxFileTransferManager::transferFile(
const std::string sServer
,const std::string nPort
,const std::string sFile
,const std::string sRemoteFile
)
{
ofxFileTransferClient::pointer client(new ofxFileTransferClient(
io_service_
,sServer
,nPort
,sFile
,sRemoteFile
));
client->start();
}
void ofxFileTransferManager::startThread() {
boost::thread t(boost::bind(
&ofxFileTransferManager::run
,this
));
}
void ofxFileTransferManager::run() {
io_service_.run();
}
From what I can tell, all you really need is to create a new thread and put in its main loop io_service.run();.
Obviously, you would have to take care of protecting classes and variables in mutexes that are shared between the appss main thread and asio's thread.
Edit: Something like this?
static sem_t __semSendFile;
static void* asioThread(void*)
{
while( true )
{
sem_wait( &__semSendFile );
io_service.run();
}
return NULL;
}
void ofxFileTransferManager::transferFile(
const std::string sServer
,const std::string nPort
,const std::string sFile
)
{
ofxFileTransferClient::pointer client(new ofxFileTransferClient(
io_service_
,sServer
,nPort
,sFile
));
client->start();
sem_post( &__semSendFile );
}
int main(int argc, char **argv)
{
if ( sem_init( &__semSendFile, 0, 0 ) != 0 )
{
std::cerr << strerror( errno ) << std::endl;
return -1;
}
pthread_t thread;
if ( pthread_create( &thread, NULL, asioThread, NULL ) != 0 )
{
std::cerr << strerror( errno ) << std::endl;
return -1;
}
[...]
Related
I am building an networking application, and being a newbie to Boost asio and networking as a whole had this doubt which might be trivial. I have this application which reads from a file and calls apis accordingly. I am reading json (example):
test.json
{
"commands":
[
{
"type":"login",
"Username": 0,
"Password": "kk"
}
]
}
My main program looks like this :
int main() {
ba::io_service ios;
tcp::socket s(ios);
s.connect({{},8080});
IO io;
io.start_read(s);
io.interact(s);
ios.run();
}
void start_read(tcp::socket& socket) {
char buffer_[MAX_LEN];
socket.async_receive(boost::asio::null_buffers(),
[&](const boost::system::error_code& ec, std::size_t bytes_read) {
(void)bytes_read;
if (likely(!ec)) {
boost::system::error_code errc;
int br = 0;
do {
br = socket.receive(boost::asio::buffer(buffer_, MAX_LEN), 0, errc);
if (unlikely(errc)) {
if (unlikely(errc != boost::asio::error::would_block)) {
if (errc != boost::asio::error::eof)
std::cerr << "asio async_receive: error " << errc.value() << " ("
<< errc.message() << ")" << std::endl;
interpret_read(socket,nullptr, -1);
//close(as);
return;
}
break; // EAGAIN
}
if (unlikely(br <= 0)) {
std::cerr << "asio async_receive: error, read " << br << " bytes" << std::endl;
interpret_read(socket,nullptr, br);
//close(as);
return;
}
interpret_read(socket,buffer_, br);
} while (br == (int)MAX_LEN);
} else {
if (socket.is_open())
std::cerr << "asio async_receive: error " << ec.value() << " (" << ec.message() << ")"
<< std::endl;
interpret_read(socket,nullptr, -1);
//close(as);
return;
}
start_read(socket);
});
}
void interpret_read(tcp::socket& s,const char* buf, int len) {
if(len<0)
{
std::cout<<"some error occured in reading"<<"\n";
}
const MessageHeaderOutComp *obj = reinterpret_cast<const MessageHeaderOutComp *>(buf);
int tempId = obj->TemplateID;
//std::cout<<tempId<<"\n";
switch(tempId)
{
case 10019: //login
{
//const UserLoginResponse *obj = reinterpret_cast<const UserLoginResponse *>(buf);
std::cout<<"*********[SERVER]: LOGIN ACKNOWLEDGEMENT RECEIVED************* "<<"\n";
break;
}
}
std::cout << "RX: " << len << " bytes\n";
if(this->input_type==2)
interact(s);
}
void interact(tcp::socket& s)
{
if(this->input_type == -1){
std::cout<<"what type of input you want ? option 1 : test.json / option 2 : manually through command line :";
int temp;
std::cin>>temp;
this->input_type = temp;
}
if(this->input_type==1)
{
//std::cout<<"reading from file\n";
std::ifstream input_file("test.json");
Json::Reader reader;
Json::Value input;
reader.parse(input_file, input);
for(auto i: input["commands"])
{
std::string str = i["type"].asString();
if(str=="login")
this->login_request(s,i);
}
std::cout<<"File read completely!! \n Do you want to continue or exit?: ";
}
}
The sending works fine, the message is sent and the server responds in a correct manner, but what I need to understand is why is the control not going to on_send_completed (which prints sent x bytes). Neither it prints the message [SERVER]: LOGIN ACKNOWLEDGEMENT RECEIVED, I know I am missing something basic or am doing something wrong, please correct me.
login_request function:
void login_request(tcp::socket& socket,Json::Value o) {
/*Some buffer being filled*/
async_write(socket, boost::asio::buffer(&info, sizeof(info)), on_send_completed);
}
Thanks in advance!!
From a cursory scan it looks like you redefined buffer_ that was already a class member (of IO, presumably).
It's hidden by the local in start_read, which is both UB (because the lifetime ends before the async read operation completes) and also makes it so the member _buffer isn't used.
I see a LOT of confusing code though. Why are you doing synchronous reads from within completion handlers?
I think you might be looking for the composed-ooperation reads (boost::asio::async_read and boost::asio::async_until)
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
My question is how to set up a WebSocket++ server and create a WebSocket++ client that connects to this server in the same program or function? (for test purpose)
Details:
I would like to use library WebSocket++ in my C++ program to stream data on a websocket. I have a websocket client that sends data to an extern websocket server.
As a good programmer, I try to write some tests to check everything is fine. Therefore I want to setup a WebSocket++ server to test the data I send from the WebSocket++ client.
From the examples, I have managed to create a server in a program and a client in another program. It works like a charm. Problem arises when I try to put the server and the client code in the same program (code is given below): The client can not connect to server, and leads to a timeout handshake.
I guess it is an ASIO problem or a thread problem, but I have no idea how to deal with it.
From the classical example I met, I had to replace echo_server.start() with echo_server.poll(), to have a non stop blocking process. It is not blocking but it prevents the client from connecting to server.
Any advise on how to solve this would be of great help!!
Should I use thread or anything else?
Below is the program I try to get running, where I want the client to connect to the server.
It is based on the merge of tutorials found here and here
#include <websocketpp/config/asio_no_tls_client.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/client.hpp>
#include <websocketpp/server.hpp>
#include <websocketpp/common/thread.hpp>
#include <websocketpp/common/memory.hpp>
#include <cstdlib>
#include <iostream>
#include <map>
#include <string>
#include <sstream>
typedef websocketpp::server<websocketpp::config::asio> server;
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;
// pull out the type of messages sent by our config
typedef server::message_ptr message_ptr;
// Define a callback to handle incoming messages
void on_message(server* s, websocketpp::connection_hdl hdl, message_ptr msg);
void on_message(server* s, websocketpp::connection_hdl hdl, message_ptr msg)
{
std::cout << "on_message called with hdl: " << hdl.lock().get()
<< " and message: " << msg->get_payload()
<< std::endl;
try {
s->send(hdl, msg->get_payload(), msg->get_opcode());
} catch (const websocketpp::lib::error_code& e) {
std::cout << "Echo failed because: " << e
<< "(" << e.message() << ")" << std::endl;
}
}
typedef websocketpp::client<websocketpp::config::asio_client> client;
class connection_metadata {
public:
typedef websocketpp::lib::shared_ptr<connection_metadata> ptr;
connection_metadata(int id, websocketpp::connection_hdl hdl, std::string uri)
: m_id(id)
, m_hdl(hdl)
, m_status("Connecting")
, m_uri(uri)
, m_server("N/A")
, m_error_reason("")
,m_messages()
{}
void on_open(client * c, websocketpp::connection_hdl hdl) {
m_status = "Open";
client::connection_ptr con = c->get_con_from_hdl(hdl);
m_server = con->get_response_header("Server");
}
void on_fail(client * c, websocketpp::connection_hdl hdl) {
m_status = "Failed";
client::connection_ptr con = c->get_con_from_hdl(hdl);
m_server = con->get_response_header("Server");
m_error_reason = con->get_ec().message();
}
void on_close(client * c, websocketpp::connection_hdl hdl) {
m_status = "Closed";
client::connection_ptr con = c->get_con_from_hdl(hdl);
std::stringstream s;
s << "close code: " << con->get_remote_close_code() << " ("
<< websocketpp::close::status::get_string(con->get_remote_close_code())
<< "), close reason: " << con->get_remote_close_reason();
m_error_reason = s.str();
}
void on_message(websocketpp::connection_hdl, client::message_ptr msg) {
if (msg->get_opcode() == websocketpp::frame::opcode::text) {
m_messages.push_back("<< " + msg->get_payload());
} else {
m_messages.push_back("<< " + websocketpp::utility::to_hex(msg->get_payload()));
}
}
websocketpp::connection_hdl get_hdl() const {
return m_hdl;
}
int get_id() const {
return m_id;
}
std::string get_status() const {
return m_status;
}
void record_sent_message(std::string message) {
m_messages.push_back(">> " + message);
}
friend std::ostream & operator<< (std::ostream & out, connection_metadata const & data);
private:
int m_id;
websocketpp::connection_hdl m_hdl;
std::string m_status;
std::string m_uri;
std::string m_server;
std::string m_error_reason;
std::vector<std::string> m_messages;
};
std::ostream & operator<< (std::ostream & out, connection_metadata const & data) {
out << "> URI: " << data.m_uri << "\n"
<< "> Status: " << data.m_status << "\n"
<< "> Remote Server: " << (data.m_server.empty() ? "None Specified" : data.m_server) << "\n"
<< "> Error/close reason: " << (data.m_error_reason.empty() ? "N/A" : data.m_error_reason) << "\n";
out << "> Messages Processed: (" << data.m_messages.size() << ") \n";
std::vector<std::string>::const_iterator it;
for (it = data.m_messages.begin(); it != data.m_messages.end(); ++it) {
out << *it << "\n";
}
return out;
}
class websocket_endpoint {
public:
websocket_endpoint () : m_endpoint(), m_thread(), m_connection_list(), m_next_id(0)
{
m_endpoint.clear_access_channels(websocketpp::log::alevel::all);
m_endpoint.clear_error_channels(websocketpp::log::elevel::all);
m_endpoint.init_asio();
m_endpoint.start_perpetual();
m_thread = websocketpp::lib::make_shared<websocketpp::lib::thread>(&client::run, &m_endpoint);
}
~websocket_endpoint() {
m_endpoint.stop_perpetual();
for (con_list::const_iterator it = m_connection_list.begin(); it != m_connection_list.end(); ++it) {
if (it->second->get_status() != "Open") {
// Only close open connections
continue;
}
std::cout << "> Closing connection " << it->second->get_id() << std::endl;
websocketpp::lib::error_code ec;
m_endpoint.close(it->second->get_hdl(), websocketpp::close::status::going_away, "", ec);
if (ec) {
std::cout << "> Error closing connection " << it->second->get_id() << ": "
<< ec.message() << std::endl;
}
}
m_thread->join();
}
int connect(std::string const & uri) {
websocketpp::lib::error_code ec;
client::connection_ptr con = m_endpoint.get_connection(uri, ec);
if (ec) {
std::cout << "> Connect initialization error: " << ec.message() << std::endl;
return -1;
}
int new_id = m_next_id++;
connection_metadata::ptr metadata_ptr = websocketpp::lib::make_shared<connection_metadata>(new_id, con->get_handle(), uri);
m_connection_list[new_id] = metadata_ptr;
con->set_open_handler(websocketpp::lib::bind(
&connection_metadata::on_open,
metadata_ptr,
&m_endpoint,
websocketpp::lib::placeholders::_1
));
con->set_fail_handler(websocketpp::lib::bind(
&connection_metadata::on_fail,
metadata_ptr,
&m_endpoint,
websocketpp::lib::placeholders::_1
));
con->set_close_handler(websocketpp::lib::bind(
&connection_metadata::on_close,
metadata_ptr,
&m_endpoint,
websocketpp::lib::placeholders::_1
));
con->set_message_handler(websocketpp::lib::bind(
&connection_metadata::on_message,
metadata_ptr,
websocketpp::lib::placeholders::_1,
websocketpp::lib::placeholders::_2
));
m_endpoint.connect(con);
return new_id;
}
void close(int id, websocketpp::close::status::value code, std::string reason) {
websocketpp::lib::error_code ec;
con_list::iterator metadata_it = m_connection_list.find(id);
if (metadata_it == m_connection_list.end()) {
std::cout << "> No connection found with id " << id << std::endl;
return;
}
m_endpoint.close(metadata_it->second->get_hdl(), code, reason, ec);
if (ec) {
std::cout << "> Error initiating close: " << ec.message() << std::endl;
}
}
void send(int id, std::string message) {
websocketpp::lib::error_code ec;
con_list::iterator metadata_it = m_connection_list.find(id);
if (metadata_it == m_connection_list.end()) {
std::cout << "> No connection found with id " << id << std::endl;
return;
}
m_endpoint.send(metadata_it->second->get_hdl(), message, websocketpp::frame::opcode::text, ec);
if (ec) {
std::cout << "> Error sending message: " << ec.message() << std::endl;
return;
}
metadata_it->second->record_sent_message(message);
}
connection_metadata::ptr get_metadata(int id) const {
con_list::const_iterator metadata_it = m_connection_list.find(id);
if (metadata_it == m_connection_list.end()) {
return connection_metadata::ptr();
} else {
return metadata_it->second;
}
}
private:
typedef std::map<int,connection_metadata::ptr> con_list;
client m_endpoint;
websocketpp::lib::shared_ptr<websocketpp::lib::thread> m_thread;
con_list m_connection_list;
int m_next_id;
};
int main() {
bool done = false;
std::string input;
websocket_endpoint endpoint;
server echo_server;
// Set logging settings
echo_server.set_access_channels(websocketpp::log::alevel::all);
echo_server.clear_access_channels(websocketpp::log::alevel::frame_payload);
// Initialize ASIO
echo_server.init_asio();
// Register our message handler
echo_server.set_message_handler(bind(&on_message,&echo_server,::_1,::_2));
// Listen on port 9002
echo_server.listen(9002);
// Start the server accept loop
echo_server.start_accept();
// Start the ASIO io_service run loop
echo_server.poll();
// echo_server.run();
//thread t(bind(&WSServer::poll,echo_server));
//t.detach();
while (!done) {
std::cout << "Enter Command: ";
std::getline(std::cin, input);
if (input == "quit") {
done = true;
} else if (input == "help") {
std::cout
<< "\nCommand List:\n"
<< "connect <ws uri>\n"
<< "send <connection id> <message>\n"
<< "close <connection id> [<close code:default=1000>] [<close reason>]\n"
<< "show <connection id>\n"
<< "help: Display this help text\n"
<< "quit: Exit the program\n"
<< std::endl;
} else if (input.substr(0,7) == "connect") {
int id = endpoint.connect(input.substr(8));
if (id != -1) {
std::cout << "> Created connection with id " << id << std::endl;
}
} else if (input.substr(0,4) == "send") {
std::stringstream ss(input);
std::string cmd;
int id;
std::string message = "";
ss >> cmd >> id;
std::getline(ss,message);
endpoint.send(id, message);
} else if (input.substr(0,5) == "close") {
std::stringstream ss(input);
std::string cmd;
int id;
int close_code = websocketpp::close::status::normal;
std::string reason = "";
ss >> cmd >> id >> close_code;
std::getline(ss,reason);
endpoint.close(id, (websocketpp::close::status::value)close_code, reason);
} else if (input.substr(0,4) == "show") {
int id = atoi(input.substr(5).c_str());
connection_metadata::ptr metadata = endpoint.get_metadata(id);
if (metadata) {
std::cout << *metadata << std::endl;
} else {
std::cout << "> Unknown connection id " << id << std::endl;
}
} else {
std::cout << "> Unrecognized Command" << std::endl;
}
}
return 0;
}
The CMakeLists.txt needed to compile this program looks like this
CMAKE_MINIMUM_REQUIRED(VERSION 2.8.8)
FIND_PACKAGE(Boost 1.53 COMPONENTS random system thread REQUIRED)
IF(Boost_FOUND)
MESSAGE(STATUS "Boost_INCLUDE_DIRS : ${Boost_INCLUDE_DIRS}")
MESSAGE(STATUS "Boost_LIBRARIES : ${Boost_LIBRARIES}")
ENDIF()
INCLUDE_DIRECTORIES(SYSTEM ${Boost_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(SYSTEM websocketpp)
ADD_EXECUTABLE(DemoWebSocket DemoWebSocket.cpp)
TARGET_LINK_LIBRARIES(DemoWebSocket
${Boost_SYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${Boost_RANDOM_LIBRARY})
IF(WIN32)
TARGET_LINK_LIBRARIES(DemoWebSocket wsock32 ws2_32)
ELSE()
TARGET_LINK_LIBRARIES(DemoWebSocket pthread rt)
ENDIF()
The solutions consists in creating a thread that creates a WebSocket server and launches its runnning. Then the client code can be used in the same function.
Below is the code that allows to use a WebSocket++ server and a a WebSocket++ client in the same function/program
void createServerEcho();
void createServerEcho()
{
server echo_server;
// Set logging settings
echo_server.set_access_channels(websocketpp::log::alevel::all);
echo_server.clear_access_channels(websocketpp::log::alevel::frame_payload);
// Initialize ASIO
echo_server.init_asio();
// Register our message handler
echo_server.set_message_handler(bind(&on_message,&echo_server,::_1,::_2));
// Listen on port 9002
echo_server.listen(9002);
// Start the server accept loop
echo_server.start_accept();
// Start the ASIO io_service run loop
echo_server.run();
}
int main()
{
websocket_endpoint endpoint;
std::thread serverThread (createServerEcho);
/*
* Client code part with variable endpoint, with creation, connection, ...
*/
serverThread.join();
}
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()));
}
}
}
I'm working on an application where I need to send a "filename", "filesize" and the filedata over the network. I created a server using boost which, for now, reads in the filesize and name.
I'm wondering how I can fill a buffer with the file data (if necessary) and how to transfer it to the server.
This is what I've got now:
#ifndef OFXFILETRANSFERSENDH
#define OFXFILETRANSFERSENDH
#undef check // necessary to get Boost running on Mac
#include <vector>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include "ofxFileTransferConnection.h"
using boost::asio::ip::tcp;
class ofxFileTransferSend : public boost::enable_shared_from_this<connection> {
public:
typedef boost::shared_ptr<ofxFileTransferSend> pointer;
static pointer create(
boost::asio::io_service& rIOService
,std::string sServer
,const char* sPort)
{
return pointer(new ofxFileTransferSend(rIOService,sServer, sPort));
}
private:
//--------------------------------------------------------------
ofxFileTransferSend(
boost::asio::io_service &rIOService
,const std::string sServer
,const char* sPort
)
:port(sPort)
,socket_(rIOService)
,resolver_(rIOService)
{
tcp::resolver::query query(sServer, sPort);
resolver_.async_resolve(
query
,boost::bind(
&ofxFileTransferSend::handleResolve
,this
,boost::asio::placeholders::error
,boost::asio::placeholders::iterator
)
);
}
//--------------------------------------------------------------
void handleResolve(
const boost::system::error_code &rError
,tcp::resolver::iterator oEndPointIterator
)
{
if (!rError) {
tcp::endpoint end_point = *oEndPointIterator;
socket_.async_connect(
end_point
,boost::bind(
&ofxFileTransferSend::handleConnect
,this
,boost::asio::placeholders::error
,++oEndPointIterator
)
);
}
else {
std::cout << "Error while resolving server: " << std::endl;
}
}
//--------------------------------------------------------------
void handleConnect(
const boost::system::error_code &rError
,tcp::resolver::iterator rOEndPointIterator
)
{
if(!rError) {
std::cout << "Connected to remote server!" << std::endl;
std::size_t size = 1235;
std::ostream send_data_stream(&send_data);
send_data_stream << "filename.jpg" << "\r\n";
send_data_stream << size << "\r\n";
boost::asio::async_write(
socket_
,send_data
,boost::bind(
&ofxFileTransferSend::handleSendFileInfo
,this
,boost::asio::placeholders::error
)
);
}
else {
// #todo on failure retry!
std::cout << "Error connecting to ofxFileTransferServer:" << rError.message()<< std::endl;
}
}
//--------------------------------------------------------------
void handleSendFileInfo(
const boost::system::error_code &rError
)
{
if(!rError) {
cout << "okay nice, send file data done!\n";
}
else {
std::cout << "Error sending file info: " << rError.message() << std::endl;
}
}
tcp::resolver resolver_;
tcp::socket socket_;
boost::asio::streambuf send_data;
const char* port;
};
#endif
How about just dumping the file on the wire? That's how HTTP does it. In fact, I see you are using almost the same protocol as HTTP. Send all metadata as clear text first (name, size, etc), put in an empty line as a terminator for the metadata (\r\n) and now all you need is to dump the file itself:
void handleSendFileInfo(
const boost::system::error_code &rError
)
{
if(!rError) {
std::ofstream fileData(fileName);
boost::asio::async_write(
socket_
,fileData
,boost::bind(
&ofxFileTransferSend::handleSendFileData
,this
,boost::asio::placeholders::error
)
);
}
else {
std::cout << "Error sending file info: " << rError.message() << std::endl;
}
}