Below is my sample code for socket server using boost asio.
This server will wait on port 10001 for any client to connect. When any client connects it will start thread to read from that client and wait for another client. But what happens when my client disconnects the server socket hangs in my_socket->close() call.
And if new client tries to connect the server crashes.
I am using
g++ (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3
#include <ctime>
#include <iostream>
#include <string>
#include <boost/asio.hpp>
#include <sys/socket.h>
#include <unistd.h>
#include <string>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/date_time.hpp>
using namespace std;
using boost::asio::ip::tcp;
void run(boost::shared_ptr<tcp::socket> my_socket)
{
while (1)
{
char buf[128];
boost::system::error_code error;
size_t len = my_socket->read_some(boost::asio::buffer(buf, 128), error);
std::cout << "len : " << len << std::endl;
if (error == boost::asio::error::eof)
{
cout << "\t(boost::asio::error::eof)" << endl;
if (my_socket->is_open())
{
boost::system::error_code ec;
cout << "\tSocket closing" << endl;
my_socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
cout << "\tShutdown " << ec.message() << endl;
// cout << "normal close : " << ::close(my_socket->native_handle()) << endl;
my_socket->close(ec);
cout << "\tSocket closed" << endl;
}
break; // Connection closed cleanly by peer.
}
else if (error)
{
std::cout << "Exception : " << error.message() << std::endl;
break;
}
else
{
for (unsigned int i = 0; i < len; i++)
printf("%02x ", buf[i] & 0xFF);
printf("\n");
}
}
}
int main()
{
const int S = 1000;
vector<boost::shared_ptr<boost::thread> > arr_thr(S);
try
{
for (uint32_t i = 0;; i++)
{
boost::asio::io_service io_service;
tcp::endpoint endpoint(tcp::v6(), 10001);
boost::shared_ptr<tcp::socket> my_socket(new tcp::socket(io_service));
tcp::endpoint end_type;
tcp::acceptor acceptor(io_service, endpoint);
std::cout << "before accept" << endl;
acceptor.accept(*my_socket, end_type);
std::cout << "connected... hdl : " << my_socket->native_handle() << std::endl;
boost::asio::ip::address addr = end_type.address();
std::string sClientIp = addr.to_string();
std::cout << "\tclient IP : " << sClientIp << std::endl;
arr_thr[i] = boost::shared_ptr<boost::thread>(new boost::thread(&run, my_socket));
}
} catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
After you start the run thread, the for loop in main starts again, destroying and reinitializing the local io_service variable, the next event on the socket will still assume the old io_service object though, leading to your crash.
You should use only one instance of io_service.
Also, you should have a look at the asynchronous functions which boost::asio provides, like async_accept and async_read, see for instance this example: http://www.boost.org/doc/libs/1_52_0/doc/html/boost_asio/example/chat/chat_server.cpp
Related
I am trying to test sending data from 2 distinct network adapters on the same machine to a common remote endpoint, but I keep getting "bind: invalid argument" AFTER the first bind comes through. What am I missing? I have searched, tried to modify the code, but I was not able to find any lead and I keep getting the same error. The same happens when I swap out the IPs.
#include <iostream>
#include <boost/asio.hpp>
#include <sstream>
#include <thread>
#include <chrono>
#include <boost/random.hpp>
const unsigned int MS_INTERVAL = 100;
enum CMD_ARG
{
PROG_NAME = 0,
LOCAL_IP_1,
LOCAL_IP_2,
REMOTE_IP,
REMOTE_PORT
};
using namespace boost::asio;
using std::string;
using std::cout;
using std::endl;
int main(int argc, char *argv[]) {
if(argc == 5)
{
//Test data initialisation
unsigned int counter = 0;
boost::random::mt19937 randSeed; // seed, produces randomness out of thin air
boost::random::uniform_int_distribution<> randGen(-1000,1000); // Random number generator between -100 and 100
//Initialise ASIO service
io_service io_service;
//socket creation and binding (one per network adapter)
std::cout << "Opening and binding local sockets to " << argv[LOCAL_IP_1] << " and " << argv[LOCAL_IP_2] << std::endl;
ip::tcp::socket socket1(io_service);
ip::tcp::socket socket2(io_service);
socket1.open(ip::tcp::v4());
socket2.open(ip::tcp::v4());
socket1.bind(ip::tcp::endpoint(ip::address::from_string(argv[LOCAL_IP_1]), 0));
std::cout << "1/2 done" << std::endl;
socket2.bind(ip::tcp::endpoint(ip::address::from_string(argv[LOCAL_IP_2]), 0));
//Connection to remote end point starting with defining the remote endpoint
std::istringstream iss(argv[REMOTE_PORT]);
unsigned int port = 0;
iss >> port;
ip::tcp::endpoint remoteEndpoint = ip::tcp::endpoint( ip::address::from_string(argv[REMOTE_IP]), port);
std::cout << "Connecting to " << argv[REMOTE_IP] << " on port " << port << std::endl;
socket1.connect(remoteEndpoint);
std::cout << "1/2 done" << std::endl;
socket2.connect(remoteEndpoint);
std::cout << "Ready" << std::endl;
while(1)
{
//Build message
std::ostringstream oss;
oss << counter << "," << randGen(randSeed) << "," << randGen(randSeed) << "," << randGen(randSeed) << std::endl;
//Send message on both interfaces
boost::system::error_code error1, error2;
write(socket1, boost::asio::buffer(oss.str()), error1);
write(socket2, boost::asio::buffer(oss.str()), error2);
//Check errors
if( !error1 && !error2) {
cout << "Sending: " << oss.str() << endl;
counter++;
}
else {
cout << "Error: " << (error1?error1.message():error2.message()) << endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(MS_INTERVAL));
}
}
else
{
std::cout << "Usage: <program> <local IP 1> <local IP 2> <remote server IP> <server's opened port>" << argc << std::endl;
}
return 0;
}
socket1.bind(ip::tcp::endpoint(ip::address::from_string(argv[LOCAL_IP_1]), 0));
...
socket1.bind(ip::tcp::endpoint(ip::address::from_string(argv[LOCAL_IP_2]), 0));
You are trying to bind the same socket1 twice. Likely you mean socket2 in the second statement.
I'm trying UDT with boost UDT, starting from its example, a server and a client.
The client is running an infinite async_write while the server running infinit async_read, but the server stops printing received xx bytes after about 200+ loop, at the same time the client starts printing sent 0 bytes, why is the client sending 0 byte?
Here my code
server:
#include <boost/asio/io_service.hpp>
#include <boost/asio/write.hpp>
#include <boost/asio/read.hpp>
#include <boost/system/error_code.hpp>
#include <boost/thread.hpp>
#include "udt/connected_protocol/logger/file_log.h"
#include "udt/ip/udt.h"
int main() {
using udt_protocol = ip::udt<>;
using Buffer = std::array<uint8_t, 150000>;
using SocketPtr = std::shared_ptr<udt_protocol::socket>;
using ReceiveHandler = std::function<void(const boost::system::error_code&, std::size_t,
SocketPtr)>;
using AcceptHandler = std::function<void(const boost::system::error_code&, SocketPtr)>
;
boost::asio::io_service io_service;
boost::system::error_code resolve_ec;
Buffer r_buffer2;
SocketPtr p_socket(std::make_shared<udt_protocol::socket>(io_service));
udt_protocol::acceptor acceptor(io_service);
udt_protocol::resolver resolver(io_service);
udt_protocol::resolver::query acceptor_udt_query(boost::asio::ip::udp::v4(),
"34567");
auto acceptor_endpoint_it = resolver.resolve(acceptor_udt_query, resolve_ec);
if (resolve_ec) {
BOOST_LOG_TRIVIAL(error) << "Wrong argument provided" << std::endl;
return 1;
}
udt_protocol::endpoint acceptor_endpoint(*acceptor_endpoint_it);
AcceptHandler accepted;
ReceiveHandler received_handler;
accepted = [&](const boost::system::error_code& ec, SocketPtr p_socket) {
if (ec) {
BOOST_LOG_TRIVIAL(trace) << "Error on accept : " << ec.value() << " "
<< ec.message();
return;
}
BOOST_LOG_TRIVIAL(trace) << "Accepted";
boost::asio::async_read(*p_socket, boost::asio::buffer(r_buffer2),
boost::bind(received_handler, _1, _2, p_socket));
SocketPtr p_new_socket(
std::make_shared<udt_protocol::socket>(io_service));
acceptor.async_accept(*p_new_socket,
boost::bind(accepted, _1, p_new_socket));
};
received_handler = [&](const boost::system::error_code& ec,
std::size_t length, SocketPtr p_socket) {
if (ec) {
BOOST_LOG_TRIVIAL(trace) << "Error on receive ec : " << ec.value() << " "
<< ec.message();
return;
} else {
static int counter = 0;
std::cout << "received : " << length << ", counter = " << counter++ << std::endl;
}
boost::asio::async_read(*p_socket, boost::asio::buffer(r_buffer2),
boost::bind(received_handler, _1, _2, p_socket));
};
boost::system::error_code ec;
acceptor.open();
acceptor.bind(acceptor_endpoint, ec);
acceptor.listen(100, ec);
acceptor.async_accept(*p_socket, boost::bind(accepted, _1, p_socket));
boost::thread_group threads;
for (uint16_t i = 1; i <= boost::thread::hardware_concurrency(); ++i) {
threads.create_thread([&io_service]() { io_service.run(); });
}
threads.join_all();
}
client:
#include <boost/asio/io_service.hpp>
#include <boost/asio/write.hpp>
#include <boost/asio/read.hpp>
#include <boost/log/trivial.hpp>
#include <boost/system/error_code.hpp>
#include <boost/thread.hpp>
#include "udt/connected_protocol/logger/file_log.h"
#include "udt/ip/udt.h"
int main() {
using udt_protocol = ip::udt<>;
using Buffer = std::array<uint8_t, 100000>;
using SendHandler =
std::function<void(const boost::system::error_code&, std::size_t)>;
using ReceiveHandler =
std::function<void(const boost::system::error_code&, std::size_t)>;
using ConnectHandler = std::function<void(const boost::system::error_code&)>;
boost::asio::io_service io_service;
boost::system::error_code resolve_ec;
Buffer buffer1;
Buffer r_buffer1;
udt_protocol::socket socket(io_service);
udt_protocol::resolver resolver(io_service);
udt_protocol::resolver::query client_udt_query("127.0.0.1", "34567");
auto remote_endpoint_it = resolver.resolve(client_udt_query, resolve_ec);
if (resolve_ec) {
BOOST_LOG_TRIVIAL(error) << "Wrong arguments provided";
return 1;
}
udt_protocol::endpoint remote_endpoint(*remote_endpoint_it);
ConnectHandler connected;
SendHandler sent_handler;
ReceiveHandler received_handler;
connected = [&](const boost::system::error_code& ec) {
if (!ec) {
BOOST_LOG_TRIVIAL(trace) << "Connected";
boost::asio::async_write(socket, boost::asio::buffer(buffer1),
sent_handler);
} else {
BOOST_LOG_TRIVIAL(trace) << "Error on connection : " << ec.value() << " "
<< ec.message();
}
};
sent_handler = [&](const boost::system::error_code& ec, std::size_t length) {
if (ec) {
BOOST_LOG_TRIVIAL(trace) << "Error on sent : " << ec.value() << " "
<< ec.message();
return;
} else {
std::cout << "sent " << length << " bytes" << std::endl;
}
boost::asio::async_write(socket, boost::asio::buffer(r_buffer1),
sent_handler);
};
socket.async_connect(remote_endpoint, connected);
boost::thread_group threads;
for (uint16_t i = 1; i <= boost::thread::hardware_concurrency(); ++i) {
threads.create_thread([&io_service]() { io_service.run(); });
}
threads.join_all();
}
the result in cmd:
Hello I ve been trying to implement a simple server/client app to communicate through UDP socket and understand how UDP works using boost library, my problem is that async_receive is not being invoked or is not getting complete in order to jump on the handler
UDP server:
#include "udp_server.h"
udp_server::udp_server(boost::asio::io_service& io_service, string bind_address, uint16_t bind_port)
: socket_(io_service)
{
cout << "udp_server constructor start" << endl;
boost::shared_ptr<boost::asio::io_service::work> work(
new boost::asio::io_service::work(io_service));
for(int x=0; x<5; ++x)
{
worker_threads.create_thread(boost::bind(&udp_server::WorkerThread, this , boost::ref(io_service)));
}
boost::system::error_code myError;
boost::asio::ip::address IP;
IP = boost::asio::ip::address::from_string(bind_address, myError);
local_udppoint_.address(IP);
cout << "IP Address: " << local_udppoint_.address().to_string() << endl;
local_udppoint_.port(bind_port);
cout << "Port: " << local_udppoint_.port() << endl;
socket_.open(local_udppoint_.protocol(), myError);
std::cout << "Open - " << myError.message() << std::endl;
socket_.bind( local_udppoint_, myError );
std::cout << "Bind - " << myError.message() << std::endl;
udp::endpoint sender_endpoint_;
struct test *request = (struct test *) malloc (sizeof(struct test));
socket_.async_receive_from(
boost::asio::buffer(&request, sizeof(request->type)), sender_endpoint_,
boost::bind(&udp_server::handle_receive_from, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
cout << "udp_server constructor end" << endl;
}
void udp_server::WorkerThread(io_service &io_service_)
{
std::cout << "Thread Start\n";
io_service_.run();
std::cout << "Thread Finish\n";
}
void udp_server::handle_receive_from(const boost::system::error_code& err, size_t bytes_recvd)
{
cout << "udp_server::handle_receive_from enters?" << endl;
if(!err)
{
cout << "no message" << endl;
}
else
{
cout << err.message() << endl;
}
if (!err && bytes_recvd > 0)
{
cout << "All good" << endl;
}
else
{
cout << err.message() << "2" << endl;
}
}
udp_server::~udp_server(void)
{
//io_service.stop();
worker_threads.join_all();
cout << "udp_server::destructor" << endl;
}
Server's Main:
#include "udp_server.h"
int main()
{
try
{
boost::asio::io_service io_service;
//boost::asio::io_service::work work( io_service);
udp_server s(io_service, "127.0.0.1", 4000);
//io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
string a;
cin >> a;
return 0;
}
UDP Client:
#include "udp_client.h"
udp_client::udp_client(boost::asio::io_service& io_service, string send_address, uint16_t send_port)
: io_service_(io_service), socket_(io_service)
{
cout << "udp_client::constructor_start" << endl;
boost::system::error_code myError;
boost::asio::ip::address IP;
IP = boost::asio::ip::address::from_string(send_address, myError);
remote_endpoint_.address(IP);
cout << "IP Address: " << remote_endpoint_.address().to_string() << endl;
remote_endpoint_.port(send_port);
cout << "Port: " << remote_endpoint_.port() << endl;
struct test *request = (struct test *) malloc (sizeof(struct test));
request->type = START_STORAGE;
socket_.async_send_to(boost::asio::buffer(&request, sizeof(request->type)), remote_endpoint_,
boost::bind(&udp_client::start_handler, this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
cout << "udp_client::constructor_end" << endl;
}
void
udp_client::start_handler(const boost::system::error_code&, std::size_t)
{
cout << "udp_client::start_handler()" << endl;
}
udp_client::~udp_client(void)
{
}
Client's main:
#include "udp_client.h"
int main(int argc, char* argv[])
{
try
{
boost::asio::io_service io_service;
udp_client client(io_service, "127.0.0.1", 4000);
io_service.run ();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
string a;
cin >> a;
return 0;
}
As you can see in the outputs below client invoked async_send_to and the message on the handler is being printed but on the server side nothing happens
UDP Server Console output:
udp_server constructor star
Thread Start
Thread Start
Thread Start
Thread Start
Thread Start
IP Address: 127.0.0.1
Port: 4000
Open - The operation completed successfully
Bind - The operation completed successfullyudp_server constructor end
_
UDP Client Console:
udp_client::constructor_start
IP Address: 127.0.0.1
Port: 4000
udp_client::constructor_end
udp_client::start_handler()
Any ideas why async_receive_from is not completed or invoked?
Right off the bat, calling join_all on your listening threads in your destructor is going to lead to undefined behavior. You're trying to keep your server running while it's right in the middle of being destroyed. Don't do this. As an example, running the io_service from these threads, you have handlers that bind to this* that those threads will be hooking into. Inside the destructor, this* is no longer a valid object. In all of your callbacks, you should be checking the error param you were passed to see if it is set.
if(error)
{
std::cout << "Error in MyClass::MyFunc(...): " << error << std::endl;
}
.. to get the errors printed to the console. Guaranteed you're going to see an error from ::bind that such and such is an invalid object.
You should be doing something inside your server main where you're blocking to prevent main from exiting. Move your thread group that runs your server's io_service and the io_service itself outside of the server object. Wrap the io_service with a ::work() object to prevent the io_service from stopping itself when it thinks that it's run out of work (no more connections to process).
Beyond that, the simplest thing to do is point you to the droves of TCP and UDP client and server examples that boost docs provide. http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/examples.html
What ist he best way to send many buffers with Boost::Asio method async_send_to?
And this whole send procedure can be repeated at any time. And furthermore I want to determine the (correct) elapsed time of each send procedure.
I tried in this way:
//MainWindow.h
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void on_connectPushButton_clicked();
void on_asyncSendPushButton_clicked();
private:
Ui::MainWindow *ui;
QTime m_Timer;
int m_BufferSize;
int m_NumBuffersToSend;
int m_TransferredBuffers;
boost::asio::io_service m_IOService;
std::unique_ptr<boost::asio::ip::udp::socket> m_pSocket;
boost::asio::ip::udp::endpoint m_ReceiverEndpoint;
void handle_send(const boost::system::error_code& error, std::size_t size);
void stopTimerAndLog();
};
//MainWindow.cpp
#include "MainWindow.h"
#include "ui_MainWindow.h"
//Some Qt includes
#include <boost/timer/timer.hpp>
#include <boost/array.hpp>
#include <boost/bind.hpp>
using boost::asio::ip::udp;
MainWindow::MainWindow(QWidget *parent) :
m_BufferSize(0),
m_NumBuffersToSend(0),
m_TransferredBuffers(0),
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_connectPushButton_clicked()
{
try
{
udp::resolver resolver(m_IOService);
udp::resolver::query query(udp::v4(), ui->serverIpAddressLineEdit->text().toStdString(),
ui->serverPortLineEdit->text().toStdString());
m_ReceiverEndpoint = *resolver.resolve(query);
m_pSocket = std::unique_ptr<boost::asio::ip::udp::socket>(new boost::asio::ip::udp::socket(m_IOService));
m_pSocket->open(udp::v4());
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
void MainWindow::stopTimerAndLog()
{
int tmm = m_Timer.elapsed();
double mBitPerSecond = 1000.0 * static_cast<double>(m_BufferSize * m_NumBuffersToSend)
/ ( 1024.0 * 1024.0 * tmm) * 8.0;
LOG_INFO(__FUNCTION__ << ": " << QString("Buffer size: %1").arg(m_BufferSize).toStdString());
LOG_INFO(__FUNCTION__ << ": " << QString("Num Buffers: %1").arg(m_NumBuffersToSend).toStdString());
LOG_INFO(__FUNCTION__ << ": " << QString("Time: %1 ms").arg(tmm).toStdString());
LOG_INFO(__FUNCTION__ << ": " << QString("%1 MBit/s").arg(mBitPerSecond).toStdString());
ui->mBitperSecondDoubleSpinBox->setValue(mBitPerSecond);
}
void MainWindow::handle_send(const boost::system::error_code &error, size_t size)
{
m_TransferredBuffers++;
if (error)
{
//missing error propagation to main thread
LOG_ERROR(__FUNCTION__ << ": ERROR: Client error while sending (error code = " << error << "): ");
LOG_ERROR(__FUNCTION__ << ": ERROR: Recovering...");
}
if ( m_TransferredBuffers >= m_NumBuffersToSend )
{
stopTimerAndLog();
m_IOService.stop();
}
}
void MainWindow::on_asyncSendPushButton_clicked()
{
try
{
m_BufferSize = ui->sendBufferSpinBox->value();
char* data = new char[m_BufferSize];
memset(data, 0, m_BufferSize);
m_NumBuffersToSend = ui->numBufferSpinBox->value();
m_Timer.start();
for (int i=0; i < m_NumBuffersToSend; i++)
{
memset(data, i, m_BufferSize);
m_pSocket->async_send_to(boost::asio::buffer(data, m_BufferSize),
m_ReceiverEndpoint,
boost::bind(&MainWindow::handle_send, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
m_TransferredBuffers = 0;
m_IOService.run();
delete[] data;
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
As you can see, the user can click on the connect button (on_connectPushButton_clicked). And then the send procedure starts by clicking on the async send button (on_asyncSendPushButton_clicked). And here I start the timer and call m_NumBuffersToSend times the async_send_to method. Then I run the IOService. For each async_send_to the handler handle_send will be called And the m_TransferredBuffers variable will be incremented until it reaches m_NumBuffersToSend. If this is the case, I stop the timer and the IOService.
But if I compare the time which was calculated in my program with the real sent udp’s with Wireshark there is always a big difference. How can I have a more accurate time calculation?
Is it possible to place the m_IOService.run(); call outside on_asyncSendPushButton_clicked?
Well.
I'm not sure what you are observing. Here's the answer to
Q. Is it possible to place the m_IOService.run(); call outside on_asyncSendPushButton_clicked
Yes, you should use io_service::work to keep the IO service running. Here's a demo program:
Live On Coliru
I've created a single IO thread to serve the async operations/completion handlers
I've stripped the Qt dependency; demo Runs are configured randomly:
struct Run {
std::vector<char> buffer = std::vector<char>(rand()%800 + 200, '\0');
int remainingToSend = rand()%10 + 1;
int transferredBuffers = 0;
Clock::time_point start = Clock::now();
void stopTimerAndLog() const;
};
As a bonus, I added proper statistics using Boost Accumulators
Instead of doing (expensive) IO in stopTimerAndLog we add the samples to the accumulators:
void stopTimerAndLog()
{
using namespace std::chrono;
Clock::duration const elapsed = Clock::now() - start;
int tmm = duration_cast<microseconds>(elapsed).count();
double mBitPerSecond = tmm
? buffer.size() * transferredBuffers * 8.0 / 1024 / 1024 / (tmm / 1000000.0)
: std::numeric_limits<double>::infinity();
std::lock_guard<std::mutex> lk(demo_results::mx);
demo_results::bufsize(buffer.size());
demo_results::micros(tmm);
if (tmm)
demo_results::mbps(mBitPerSecond);
}
You can run multiple demo Runs in overlap:
Demo demo;
demo.on_connect(argv[1], argv[2]);
for (int i = 0; i<100; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
demo.on_async_testrun();
}
// Demo destructor joins IO thread, making sure all stats are final
The mutex guarding the statistics is redundant but GoodPractive(TM) since you may want to test with multiple IO threads
Output:
avg. Buffer size: 613.82, std.dev. 219.789
avg. b/w: 160.61 mbps, std.dev. 81.061
avg. time: 153.64 μs, std.dev. 39.0163
Full Listing
#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <boost/make_shared.hpp>
#include <boost/bind.hpp>
#include <thread>
#include <mutex>
#include <chrono>
#include <memory>
#include <iostream>
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics.hpp>
using boost::asio::ip::udp;
typedef std::chrono::high_resolution_clock Clock;
namespace demo_results {
using namespace boost::accumulators;
static std::mutex mx;
accumulator_set<double, stats<tag::mean, tag::median, tag::variance> > bufsize, mbps, micros;
}
struct Run {
std::vector<char> buffer = std::vector<char>(rand()%800 + 200, '\0');
int remainingToSend = rand()%10 + 1;
int transferredBuffers = 0;
Clock::time_point start = Clock::now();
Clock::duration elapsed;
void stopTimerAndLog()
{
using namespace std::chrono;
Clock::duration const elapsed = Clock::now() - start;
int tmm = duration_cast<microseconds>(elapsed).count();
double mBitPerSecond = tmm
? buffer.size() * transferredBuffers * 8.0 / 1024 / 1024 / (tmm / 1000000.0)
: std::numeric_limits<double>::infinity();
std::lock_guard<std::mutex> lk(demo_results::mx);
demo_results::bufsize(buffer.size());
demo_results::micros(tmm);
if (tmm)
demo_results::mbps(mBitPerSecond);
#if 0
std::cout << __FUNCTION__ << " -----------------------------------------------\n";
std::cout << __FUNCTION__ << ": " << "Buffer size: " << buffer.size() << "\n";
std::cout << __FUNCTION__ << ": " << "Num Buffers: " << transferredBuffers << "\n";
std::cout << __FUNCTION__ << ": " << "Time: " << tmm << " μs\n";
std::cout << __FUNCTION__ << ": " << mBitPerSecond << " MBit/s\n";
#endif
}
typedef boost::shared_ptr<Run> Ptr;
};
struct Demo {
boost::asio::io_service m_IOService;
std::unique_ptr<boost::asio::io_service::work> m_work;
std::unique_ptr<boost::asio::ip::udp::socket> m_pSocket;
boost::asio::ip::udp::endpoint m_ReceiverEndpoint;
std::thread m_io_thread;
Demo() :
m_IOService(),
m_work(new boost::asio::io_service::work(m_IOService)),
m_io_thread([this] { m_IOService.run(); })
{
}
~Demo() {
m_work.reset();
m_io_thread.join();
}
void on_connect(std::string const& host, std::string const& port)
{
try {
udp::resolver resolver(m_IOService);
m_ReceiverEndpoint = *resolver.resolve(udp::resolver::query(udp::v4(), host, port));
m_pSocket = std::unique_ptr<boost::asio::ip::udp::socket>(new boost::asio::ip::udp::socket(m_IOService));
m_pSocket->open(udp::v4());
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
void perform_run(Run::Ptr state) {
if (state->remainingToSend) {
std::fill(state->buffer.begin(), state->buffer.end(), state->remainingToSend);
m_pSocket->async_send_to(boost::asio::buffer(state->buffer),
m_ReceiverEndpoint,
boost::bind(&Demo::handle_sent, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred,
state));
} else {
state->stopTimerAndLog();
}
}
void handle_sent(boost::system::error_code const&error, size_t actually_transferred, Run::Ptr state)
{
assert(actually_transferred == state->buffer.size());
state->transferredBuffers += 1;
state->remainingToSend -= 1;
if (error) {
// missing error propagation to main thread
std::cerr << __FUNCTION__ << ": ERROR: Client error while sending (error code = " << error.message() << "): ";
std::cerr << __FUNCTION__ << ": ERROR: Recovering...";
}
perform_run(state); // remaining buffers for run
}
void on_async_testrun() {
perform_run(boost::make_shared<Run>());
}
};
int main(int argc, char const** argv)
{
assert(argc==3);
{
Demo demo;
demo.on_connect(argv[1], argv[2]);
for (int i = 0; i<100; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
demo.on_async_testrun();
}
} // Demo destructor joins IO thread, making sure all stats are final
using namespace boost::accumulators;
std::cout << "avg. Buffer size: " << mean(demo_results::bufsize) << ", std.dev. " << sqrt(variance(demo_results::bufsize)) << "\n";
std::cout << "avg. b/w: " << mean(demo_results::mbps) << " mbps, std.dev. " << sqrt(variance(demo_results::mbps)) << "\n";
std::cout << "avg. time: " << mean(demo_results::micros) << " μs, std.dev. " << sqrt(variance(demo_results::micros)) << "\n";
}
Thank you very much for your answer. This was a very good starting point to improve my code.
I changed a little bit the way how to add the async_send_to methods.
void perform_run(Run::Ptr state) {
for(decltype(state->buffersToSend) i = 0; i < state->buffersToSend; i++ )
{
std::fill(state->buffer.begin(), state->buffer.end(), i);
m_pSocket->async_send_to(boost::asio::buffer(state->buffer),
m_ReceiverEndpoint,
boost::bind(&Demo::handle_sent, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred,
state));
}
}
void handle_sent(boost::system::error_code const&error, size_t actually_transferred, Run::Ptr state)
{
assert(actually_transferred == state->buffer.size());
state->transferredBuffers += 1;
if (error) {
// missing error propagation to main thread
std::cerr << __FUNCTION__ << ": ERROR: Client error while sending (error code = " << error.message() << "): ";
std::cerr << __FUNCTION__ << ": ERROR: Recovering...";
}
if (state->transferredBuffers >= state->buffersToSend ) {
state->stopTimerAndLog();
}
}
And here is the full code in coliru
Greetings,
Thomas
My idea was to create X threads, run it using KeepRunning method which has endless loop calling _io_service.run() and send tasks to _io_service when received a new connection using _io_service.poll() in async_accept handler.
I run the server with a code like this:
oh::msg::OHServer s("0.0.0.0", "9999", 200);
ConsoleStopServer = boost::bind(&oh::msg::OHServer::Stop, &s);
SetConsoleCtrlHandler(bConsoleHandler, TRUE);
s.Run();
but when I receive one connection, then serve it in Post() method using blocking read/writes in MsgWorker class, then all the threads are being closed.
I have code like below (it's some mix from http server3 asio example and mine):
OHServer::OHServer(const std::string& sAddress, const std::string& sPort, std::size_t tps)
: _nThreadPoolSize(tps), _acceptor(_io_service), _sockClient(new boost::asio::ip::tcp::socket(_io_service))
{
// Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
boost::asio::ip::tcp::resolver resolver(_io_service);
boost::asio::ip::tcp::resolver::query query(sAddress, sPort);
boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);
_acceptor.open(endpoint.protocol());
_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
_acceptor.bind(endpoint);
_acceptor.listen();
_acceptor.async_accept(
*_sockClient,
boost::bind(
&OHServer::AcceptConnection,
this,
boost::asio::placeholders::error
)
);
}
void OHServer::KeepRunning()
{
global_stream_lock.lock();
std::cout << "[" << boost::this_thread::get_id()
<< "] Thread Start" << std::endl;
global_stream_lock.unlock();
while( true )
{
try
{
boost::system::error_code ec;
_io_service.run( ec );
if( ec )
{
global_stream_lock.lock();
std::cout << "[" << boost::this_thread::get_id()
<< "] Error: " << ec << std::endl;
global_stream_lock.unlock();
}
break;
}
catch( std::exception & ex )
{
global_stream_lock.lock();
std::cout << "[" << boost::this_thread::get_id()
<< "] Exception: " << ex.what() << std::endl;
global_stream_lock.unlock();
}
}
global_stream_lock.lock();
std::cout << "[" << boost::this_thread::get_id()
<< "] Thread Finish" << std::endl;
global_stream_lock.unlock();
}
void OHServer::Run()
{
// Create a pool of threads to run all of the io_services.
for (std::size_t i = 0; i < _nThreadPoolSize; ++i)
{
boost::shared_ptr<boost::thread> thread(new boost::thread(
boost::bind(&OHServer::KeepRunning, this)));
threads.push_back(thread);
}
cout << "Hit enter to close server" << endl;
cin.get();
}
void OHServer::Stop()
{
boost::system::error_code ec;
_acceptor.close(ec);
_sockClient->shutdown( boost::asio::ip::tcp::socket::shutdown_both, ec );
_sockClient->close( ec );
_io_service.stop();
// Wait for all threads in the pool to exit.
for (std::size_t i = 0; i < threads.size(); ++i)
{
threads[i]->join();
cout << "threads[ "<< i << "]->join();" << endl;
}
}
void OHServer::Post()
{
std::cout << "Accepted new connection." << std::endl;
CMsgWorker *msgWorker = new CMsgWorker(_sockClient);
msgWorker->Start();
delete msgWorker;
}
void OHServer::AcceptConnection(const boost::system::error_code& e)
{
if (!e)
{
_io_service.post(boost::bind(&OHServer::Post, this));
_acceptor.async_accept(
*_sockClient,
boost::bind(
&OHServer::AcceptConnection,
this,
boost::asio::placeholders::error
)
);
}
}
What should I do for the threads to be still waiting for some work to do from _io_service?
Thanks for any help!
Check it out:
// Kick off 5 threads
for (size_t i = 0; i < 5; ++i) {
boost::thread* t = threads.create_thread(boost::bind(&boost::asio::io_service::run, &io));
std::cout << "Creating thread " << i << " with id " << t->get_id() << std::endl;
}
See the timer.cc example here for an idea on how to do this: https://github.com/sean-/Boost.Examples/tree/master/asio/timer
Finally I've ended up with some easy-to-use version of server:
Usage:
boost::shared_ptr<CTCPServer> _serverPtr;
void CMyServer::Start()
{
//First we must create a few threads
thread* t = 0;
for (int i = 0; i < COHConfig::_iThreads; ++i)
{
t =_threads.create_thread(bind(&io_service::run, &_io_service));
}
//Then we create a server object
_serverPtr.reset( new CTCPServer(&_io_service, PORT_NUMBER) );
//And finally run the server through io_service
_io_service.post(boost::bind(&CMyServer::RunServer, _serverPtr, &CMyServer::HandleMessage));
}
//This is the function which is called by io_service to start our server
void CMyServer::RunServer(CTCPServer* s, void (*HandleFunction)(shared_ptr<ip::tcp::socket>, deadline_timer*))
{
s->Run(HandleFunction);
}
//And this is our connection handler
void CMyServer::HandleMessage(shared_ptr< ip::tcp::socket > sockClient, deadline_timer* timer)
{
cout << "Handling connection from: " << sockClient->remote_endpoint().address().to_string() << ":" << sockClient->remote_endpoint().port() << endl;
//This is some class which gets socket in its constructor and handles the connection
scoped_ptr<CMyWorker> myWorker( new CMyWorker(sockClient) );
msgWorker->Start();
}
//Thanks to this function we can stop our server
void CMyServer::Stop()
{
_serverPtr->Stop();
}
The TCPServer.hpp file:
#ifndef TCPSERVER_HPP
#define TCPSERVER_HPP
#if defined(_WIN32)
#define BOOST_THREAD_USE_LIB
#endif
#include <boost/asio.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <string>
#include <vector>
class CTCPServer: private boost::noncopyable
{
private:
bool bKeepRunning;
boost::asio::io_service* _io_service;
std::string _sPort;
boost::asio::ip::tcp::acceptor _acceptor;
boost::shared_ptr< boost::asio::ip::tcp::socket > _sockClient;
boost::asio::deadline_timer _timer;
bool _bIPv6;
std::string SessionID();
public:
CTCPServer(boost::asio::io_service* ios, const std::string& sPort, bool bIPv6=false):
_sPort(sPort),
_acceptor(*ios),
_timer(*ios),
_bIPv6(bIPv6)
{
_io_service = ios;
bKeepRunning = false;
};
void Run(void (*HandleFunction)(boost::shared_ptr< boost::asio::ip::tcp::socket > sock, boost::asio::deadline_timer* timer));
void AsyncAccept(void (*HandleFunction)(boost::shared_ptr< boost::asio::ip::tcp::socket > , boost::asio::deadline_timer* ));
void AcceptHandler(const boost::system::error_code& e, void (*HandleFunction)(boost::shared_ptr< boost::asio::ip::tcp::socket >, boost::asio::deadline_timer* ));
void Stop();
void Stop(void (*StopFunction)());
};
#endif
The TCPServer.cpp file:
#include "TCPServer.hpp"
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread/mutex.hpp>
#include <iostream>
using namespace std;
string CTCPServer::SessionID()
{
ostringstream outs;
outs << "[" << boost::this_thread::get_id() << "] ";
return outs.str();
}
void CTCPServer::Run(void (*HandleFunction)(boost::shared_ptr< boost::asio::ip::tcp::socket > , boost::asio::deadline_timer* ))
{
try
{
boost::asio::ip::tcp::resolver resolver(*_io_service);
boost::asio::ip::tcp::endpoint endpoint;
if(_bIPv6)
{
boost::asio::ip::tcp::resolver::query queryv6(boost::asio::ip::tcp::v6(), _sPort);
endpoint = *resolver.resolve(queryv6);
}
else
{
boost::asio::ip::tcp::resolver::query queryv4(boost::asio::ip::tcp::v4(), _sPort);
endpoint = *resolver.resolve(queryv4);
}
_acceptor.open(endpoint.protocol());
_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
_acceptor.set_option(boost::asio::socket_base::enable_connection_aborted(true));
_acceptor.bind(endpoint);
_acceptor.listen();
boost::system::error_code ec;
bKeepRunning = true;
AsyncAccept(HandleFunction);
}
catch(std::exception& e)
{
if(!_bIPv6)
std::cerr << "Exception wile creating IPv4 TCP socket on port "<< _sPort<< ": " << e.what() << std::endl;
else
std::cerr << "Exception wile creating IPv6 TCP socket on port "<< _sPort<< ": " << e.what() << std::endl;
}
}
void CTCPServer::AsyncAccept(void (*HandleFunction)(boost::shared_ptr< boost::asio::ip::tcp::socket > , boost::asio::deadline_timer* ))
{
if(bKeepRunning)
{
try
{
_sockClient.reset(new boost::asio::ip::tcp::socket(*_io_service));
cout << SessionID() << "Waiting for connection on port: " << _sPort << endl;
_acceptor.async_accept(*_sockClient, boost::bind(&CTCPServer::AcceptHandler, this, boost::asio::placeholders::error, HandleFunction));
}
catch(exception& e)
{
string sWhat = e.what();
cout << SessionID() << "Error while accepting connection: " << e.what() << endl;
}
}
}
void CTCPServer::AcceptHandler(const boost::system::error_code& e,
void (*HandleFunction)(boost::shared_ptr< boost::asio::ip::tcp::socket >,
boost::asio::deadline_timer* ))
{
if(!e)
{
try
{
(*_io_service).post(boost::bind(HandleFunction, _sockClient, &_timer));
AsyncAccept(HandleFunction);
}
catch(exception& e)
{
cout << SessionID() << "Exception: " << e.what() << endl;
}
}
}
void CTCPServer::Stop()
{
cout << SessionID() << "STOP port " << _sPort << endl;
if(!bKeepRunning)
return;
bKeepRunning = false;
try
{
_sockClient->close();
}
catch(exception& e)
{
cout << SessionID() << "Exception: " << e.what() << endl;
}
try
{
_acceptor.cancel();
}
catch(exception& e)
{
cout << SessionID() << "Exception: " << e.what() << endl;
}
try
{
_acceptor.close();
}
catch(exception& e)
{
cout << SessionID() << "Exception: " << e.what() << endl;
}
}
void CTCPServer::Stop(void (*StopFunction)())
{
Stop();
StopFunction();
}
It's also very easy to modify to be IPv6 compatible.
It's already tested and working very well. Just copy it and use!