sigx++  2.0.1
ipresolver/main.cpp

The IPResolver example shows a way how to delegate IP to hostname resolving to a thread.

Starting with the IPResolverThread

#ifndef RESOLVERTHREAD_H
#define RESOLVERTHREAD_H
#include <sigxconfig.h>
#ifdef SIGC_MSC
#include <Winsock2.h>
// in_addr_t is not defined on windows
typedef ULONG in_addr_t;
#else
#include <netinet/in.h> // in_addr
#endif
#include <sigc++/functors/slot.h>
#include <glibmm/thread.h> // Glib::Private
#include <sigx/request_f.h>
#include <sigx/signal_f.h>
/* Resolves IP addresses to host names.
*
* IP addresses are resolved by using gethostbyaddr().
* You can feed the resolver with addresses by calling resolve(). Results
* are signaled after resolving with signal_resolved() to which you can
* connect.
* @attention Do not use two IPResolverThreads but only one instance because
* gethostbyaddr() might not be threadsafe on some systems.
*/
class IPResolverThread: public sigx::glib_threadable
{
public:
// convenience sigc::slot typedefs
typedef sigc::slot<void> slot_resolving_stopped_t;
typedef sigc::slot<void> slot_finished_t;
typedef sigc::slot<void, const std::string&, in_addr_t, int> slot_resolved_t;
protected:
typedef sigc::signal<void> signal_resolving_stopped_t;
typedef sigc::signal<void> signal_finished_t;
typedef sigc::signal<void, const std::string&, in_addr_t, int /*error*/> signal_resolved_t;
public:
IPResolverThread();
protected:
// virtuals from sigx::glib_threadable
virtual void on_startup();
virtual void on_cleanup();
void on_marshall_resolving(in_addr_t nIP);
bool resolve_next();
int resolve_this(in_addr addr);
void on_stop_resolving();
public:
sigx::request_f<> stop_resolving;
private:
struct ThreadData;
Glib::Private<ThreadData> m_ThreadData;
};
#endif


.. its thread private data

#include <list>
#include <string>
#include <sigxconfig.h>
#ifdef SIGC_MSC
#include <Winsock2.h>
#else
#include <netinet/in.h> // in_addr_t
#endif
#include <sigc++/connection.h>
struct IPResolverThread::ThreadData
{
std::list<in_addr_t> m_msgQueue;
sigc::connection m_connIdle;
in_addr_t m_nIPPrev;
std::string m_strHost;
bool m_bStopResolving;
IPResolverThread::signal_resolving_stopped_t m_sigResolvingStopped;
IPResolverThread::signal_finished_t m_sigFinished;
IPResolverThread::signal_resolved_t m_sigResolved;
ThreadData();
};


.. and its implementation

#include <cstddef>
#include <string>
#include <iostream>
#include <algorithm>
#include <sigxconfig.h>
#ifdef SIGX_MSC
// windows and msvc++
# if (_WIN32_WINNT >= 0x0501)
# include <winsock2.h>
# else
// must include for versions earlier than win xp
# include <Wspiapi.h>
# endif
#else
# include <sys/socket.h> // AF_INET
# include <arpa/inet.h>
# include <netdb.h> // hostent, gethostbyaddr, ..
#endif
#include "resolver.h"
#include "resolver_p.h"
using namespace std;
#define DEBUG 1
IPResolverThread::ThreadData::ThreadData():
m_msgQueue(),
m_connIdle(),
m_nIPPrev(),
m_strHost(),
m_bStopResolving(),
m_sigResolvingStopped(),
m_sigFinished(),
m_sigResolved()
{}
IPResolverThread::IPResolverThread():
sigx::glib_threadable(),
m_ThreadData(),
// request api
resolve(sigc::mem_fun(this, &IPResolverThread::on_marshall_resolving)),
stop_resolving(sigc::mem_fun(this, &IPResolverThread::on_stop_resolving)),
// signal api, signals live in threadprivate data
signal_resolving_stopped(*this, m_ThreadData, &ThreadData::m_sigResolvingStopped),
signal_finished(*this, m_ThreadData, &ThreadData::m_sigFinished),
signal_resolved(*this, m_ThreadData, &ThreadData::m_sigResolved)
{}
//virtual
{
// thread private pdata will be freed when the thread ends (according to the glib docs)
m_ThreadData.set(new ThreadData);
}
//virtual
{
ThreadData* pthreaddata = m_ThreadData.get();
if (pthreaddata->m_connIdle.connected())
pthreaddata->m_connIdle.disconnect();
// tell others that I'm about to finish, even they might have joined me
pthreaddata->m_sigFinished.emit();
}
void IPResolverThread::on_stop_resolving()
{
ThreadData* pthreaddata = m_ThreadData.get();
pthreaddata->m_bStopResolving = true;
pthreaddata->m_sigResolvingStopped.emit();
}
void IPResolverThread::on_marshall_resolving(in_addr_t nIP)
{
ThreadData* pthreaddata = m_ThreadData.get();
if (pthreaddata->m_bStopResolving)
return;
const list<in_addr_t>::const_iterator it =
find(pthreaddata->m_msgQueue.begin(), pthreaddata->m_msgQueue.end(), nIP);
if (it == pthreaddata->m_msgQueue.end())
{ // if ip is not yet in the message queue, append it
pthreaddata->m_msgQueue.push_back(nIP);
}
// wait until we've got all messages;
// if there are still messages in the dispatcher queue,
// defer resolving.
// the main purpose is to optimize this thread:
// 1) resolving could take a longer time and there could be already
// the "finish" message in the dispatcher queue. if we just blindly resolve
// the next ip then we've got a problem.
// 2) it could be that there are the same ips to resolve in the dispatcher
// queue; we can skip them because everyone connected to the signal_resolved
// gets the resolved ip and so we can speed up resolving
// 3) if we would process messages all the time then this thread could end up in
// a denial of service, so watch out for finish
//
// process other tunneled messages waiting in the dispatcher queue
// (the IPResolverThread is a dispatchable and thus has a dispatcher)
if (dispatcher()->queued_contexts() > 0)
return;
// upon receiving the next idle signal resolve the next IP
if (!pthreaddata->m_connIdle.connected())
{
pthreaddata->m_connIdle = maincontext()->signal_idle().connect(
sigc::mem_fun(this, &IPResolverThread::resolve_next));
}
}
bool IPResolverThread::resolve_next()
{
ThreadData* pthreaddata = m_ThreadData.get();
const in_addr_t nIP = pthreaddata->m_msgQueue.front();
pthreaddata->m_msgQueue.pop_front();
in_addr addr = {};
addr.s_addr = nIP;
int nErr(0);
if ((pthreaddata->m_nIPPrev != nIP) || (pthreaddata->m_strHost.empty()))
{ // if the current ip differs from the previous one
// or ip couldn't be resolved before
pthreaddata->m_nIPPrev = nIP;
if (DEBUG)
{
cout << "Resolving: " << inet_ntoa(addr) << endl;
}
nErr = resolve_this(addr);
}
else if (DEBUG)
{
cout << "Resolved: " << inet_ntoa(addr) << " to " << pthreaddata->m_strHost << endl;
}
pthreaddata->m_sigResolved.emit(pthreaddata->m_strHost, nIP, nErr);
// the IPResolverThread is a dispatchable and thus has a dispatcher reference
//if ((m_disp_ptr->invoke()->queued_contexts() > 0) ||
if ((dispatcher()->queued_contexts() > 0) ||
(pthreaddata->m_msgQueue.empty() ) )
{
// disconnect from idle signal if there are messages waiting
// in the dispatcher queue. there could be a "finish"-signal
// waiting;
// also disconnect if there are no addresses to resolve anymore;
// this method will be triggered again by the next ip to resolve;
return false;
}
// otherwise stay connected;
return true;
}
int IPResolverThread::resolve_this(in_addr addr)
{
ThreadData* pthreaddata = m_ThreadData.get();
int nErr(0);
#ifdef SIGC_MSC
const hostent* phe = gethostbyaddr(inet_ntoa(addr), sizeof(in_addr), AF_INET);
#else
const hostent* phe = gethostbyaddr(&addr, sizeof(in_addr), AF_INET);
#endif
if (phe)
{ // resolved?
if (DEBUG)
{
cout << "Resolved: " << inet_ntoa(addr) << " to " << phe->h_name << endl;
}
pthreaddata->m_strHost = phe->h_name;
}
else
{
nErr = h_errno;
if (DEBUG)
cerr << "not resolvable, error " << nErr << endl;
pthreaddata->m_strHost.clear();
}
return nErr;
}


The user interface

#include <string>
#include <tr1/memory>
#include <gtkmm.h>
#include <sigx/sigx.h>
#include "resolver.h"
class TheGUI: public Gtk::Window, public sigx::glib_auto_dispatchable
{
private:
/* An info dialog that gets displayed if the resolver does not
* stop in a timely fashion
*/
class InfoDialog: public Gtk::Dialog
{
public:
typedef std::tr1::shared_ptr<InfoDialog> threadsafe_type;
public:
InfoDialog(Gtk::Window& parent);
};
public:
TheGUI();
private:
// virtuals from Gtk::Widget
virtual bool on_delete_event(GdkEventAny*);
void on_gui_ready();
void on_resolve();
void on_resolved(const std::string& strHost, guint32 nIP, int nErr);
void on_resolving_stopped(InfoDialog::threadsafe_type pDlg);
void on_display_infomessage(InfoDialog::threadsafe_type pDlg);
private:
IPResolverThread m_resolver;
sigx::connection_wrapper m_connResolved;
sigx::connection_wrapper m_connResolvingStopped;
Gtk::Entry* m_pentryIP;
Gtk::Entry* m_pentryHostname;
Gtk::TextView* m_ptvError;
};


.. the user interface implementation

#include <iostream>
#include <iomanip>
#include <locale>
#include <sstream>
#include <sigxconfig.h>
#ifdef SIGC_MSC
// windows and msvc++
# if (_WIN32_WINNT >= 0x0501)
# include <winsock2.h>
# else
// must include for versions earlier than win xp
# include <Wspiapi.h>
# endif
#else
#include <arpa/inet.h>
#include <netdb.h>
#endif
#include <sigc++/sigc++.h>
#include "thegui.h"
using namespace std;
TheGUI::InfoDialog::InfoDialog(Gtk::Window& parent):
Gtk::Dialog("", parent, true)
{
Gtk::HBox* hbox = Gtk::manage(new Gtk::HBox(false, 5));
Gtk::Image* ico = Gtk::manage(new Gtk::Image(Gtk::Stock::DIALOG_INFO, Gtk::ICON_SIZE_DIALOG));
Gtk::Label* lbl = Gtk::manage(new Gtk::Label("The IP address resolver is still working.\nThis could take up to some minutes..."));
hbox->pack_start(*ico, Gtk::PACK_SHRINK);
hbox->pack_start(*lbl, Gtk::PACK_SHRINK);
get_vbox()->add(*hbox);
add_button(Gtk::Stock::STOP, Gtk::RESPONSE_OK);
set_has_separator();
show_all_children();
}
TheGUI::TheGUI():
Gtk::Window(),
sigx::glib_auto_dispatchable(),
m_resolver(),
m_connResolved(),
m_connResolvingStopped(),
m_pentryIP(),
m_pentryHostname(),
m_ptvError()
{
Gtk::Label* pLabelIP = Gtk::manage(new Gtk::Label("IP Address:", Gtk::ALIGN_LEFT));
m_pentryIP = Gtk::manage(new Gtk::Entry);
m_pentryIP->set_activates_default();
m_pentryIP->set_editable(false);
Gtk::Label* plabelHostname = Gtk::manage(new Gtk::Label("Hostname:", Gtk::ALIGN_LEFT));
m_pentryHostname = Gtk::manage(new Gtk::Entry);
m_pentryHostname->set_editable(false);
Gtk::Label* plabelError = Gtk::manage(new Gtk::Label("Error:", Gtk::ALIGN_LEFT));
m_ptvError = Gtk::manage(new Gtk::TextView);
m_ptvError->set_editable(false);
Gtk::Button* pbtnResolve = Gtk::manage(new Gtk::Button(Gtk::Stock::CONVERT));
pbtnResolve->property_can_default() = true;
Gtk::HBox* phboxIP = Gtk::manage(new Gtk::HBox(false, 5));
phboxIP->pack_start(*pLabelIP, Gtk::PACK_SHRINK);
phboxIP->pack_start(*m_pentryIP);
phboxIP->pack_start(*pbtnResolve, Gtk::PACK_SHRINK);
Gtk::HBox* phboxHostname = Gtk::manage(new Gtk::HBox(false, 5));
phboxHostname->pack_start(*plabelHostname, Gtk::PACK_SHRINK);
phboxHostname->pack_start(*m_pentryHostname);
Gtk::HBox* phboxError = Gtk::manage(new Gtk::HBox(false, 5));
phboxError->pack_start(*plabelError, Gtk::PACK_SHRINK);
phboxError->pack_start(*m_ptvError);
Gtk::VBox* pvboxAll = Gtk::manage(new Gtk::VBox);
pvboxAll->pack_start(*phboxIP, Gtk::PACK_SHRINK, 5);
pvboxAll->pack_start(*phboxHostname, Gtk::PACK_SHRINK, 5);
pvboxAll->pack_start(*phboxError, Gtk::PACK_SHRINK, 5);
add(*pvboxAll);
set_default(*pbtnResolve);
show_all_children();
pbtnResolve->signal_clicked().connect(sigc::mem_fun(this, &TheGUI::on_resolve));
// one-shot idle handler
Glib::signal_idle().connect(sigc::bind_return(sigc::mem_fun(this, &TheGUI::on_gui_ready), false));
// we connect to the resolver's signals in on_gui_ready() when the
// resolver thread is started and ready
}
bool TheGUI::on_delete_event(GdkEventAny*)
{
m_pentryIP->property_editable() = false;
// display an info dialog after 3 seconds if the program does not
// end because the resolver is still resolving
InfoDialog::threadsafe_type dlg(new InfoDialog(*this));
Glib::signal_timeout().connect(
sigc::bind_return(
sigc::bind(
sigc::mem_fun(this, &TheGUI::on_display_infomessage),
dlg
),
false
),
3000
);
m_connResolved.disconnect();
cout << "waiting for resolver to stop resolving.." << endl;
m_connResolvingStopped = m_resolver.signal_resolving_stopped().connect(
sigc::bind(
sigc::mem_fun(this, &TheGUI::on_resolving_stopped),
dlg
)
);
m_resolver.stop_resolving();
return true; // do not proceed
}
void TheGUI::on_gui_ready()
{
cout << "waiting for resolver to be ready" << endl;
m_resolver.run();
m_connResolved = m_resolver.signal_resolved().connect(
sigc::mem_fun(this, &TheGUI::on_resolved)
);
cout << "connected to the resolver: " << boolalpha << m_connResolved.connected() << endl;
m_pentryIP->set_editable(true);
}
void TheGUI::on_resolved(const std::string& strHost, guint32 nIP, int nErr)
{
m_pentryHostname->set_text(strHost);
if (nErr)
{
const Glib::RefPtr<Gtk::TextBuffer> pTextBuf = m_ptvError->get_buffer();
#ifdef SIGC_MSC
char* pBuf(0);
// FormatMessage returns the number of characters stored in the output buffer, excluding the terminating null character
const DWORD nLen = FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
0, nErr, 0,
// must be a char** if FORMAT_MESSAGE_ALLOCATE_BUFFER is specified above
reinterpret_cast<char*>(&pBuf), 0,
0
);
pTextBuf->set_text(Glib::locale_to_utf8(pBuf));
LocalFree(pBuf);
#else
pTextBuf->set_text(Glib::locale_to_utf8(hstrerror(nErr)));
#endif
}
}
void TheGUI::on_display_infomessage(InfoDialog::threadsafe_type pDlg)
{
if (pDlg->run() == Gtk::RESPONSE_OK)
// user clicked "stop", don't wait for resolver thread
hide();
}
void TheGUI::on_resolving_stopped(InfoDialog::threadsafe_type pDlg)
{
cout << "resolver stopped resolving" << endl;
// quit the info dialog eventually
pDlg->response(Gtk::RESPONSE_DELETE_EVENT);
m_connResolvingStopped.disconnect();
m_resolver.finish();
// now quit the main loop
hide();
}
void TheGUI::on_resolve()
{
m_pentryHostname->set_text(Glib::ustring());
m_ptvError->get_buffer()->set_text(Glib::ustring());
const Glib::ustring& strIP = m_pentryIP->get_text();
#ifdef SIGC_MSC
const in_addr_t nIP = inet_addr(strIP.c_str());
if (nIP != INADDR_NONE)
m_resolver.resolve(nIP);
#else
in_addr addr = {};
if (inet_aton(strIP.c_str(), &addr) != 0)
m_resolver.resolve(addr.s_addr);
#endif
else
{
m_ptvError->get_buffer()->set_text("\"" + strIP + "\" is not a valid ip address");
cerr << ("\"" + strIP + "\" is not a valid ip address") << endl;
}
}


and finally the main entry point

#include <iostream>
#include <sigxconfig.h>
#include <glibmm/thread.h>
#include <gtkmm/main.h>
#include "thegui.h"
#ifdef SIGC_MSC
// windows and msvc++
# if (_WIN32_WINNT >= 0x0501)
# include <winsock2.h>
# else
// must include for versions earlier than win xp
# include <Wspiapi.h>
# endif
int main(int argc, char** argv)
{
// initialization
Glib::thread_init();
WSADATA wsad = {};
// require socket library min version 1.1
WSAStartup(MAKEWORD(1, 1), &wsad);
// scope for application
{
Gtk::Main theApp(argc, argv);
TheGUI gui;
theApp.run(gui);
}
WSACleanup();
return 0;
}
#else
int main(int argc, char** argv)
{
// initialization
Glib::thread_init();
// scope for application
{
Gtk::Main theApp(argc, argv);
TheGUI gui;
theApp.run(gui);
}
return 0;
}
#endif