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/glib_threadable.h> #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<in_addr_t> resolve; sigx::request_f<> stop_resolving; sigx::signal_f<signal_resolving_stopped_t> signal_resolving_stopped; sigx::signal_f<signal_finished_t> signal_finished; sigx::signal_f<signal_resolved_t> signal_resolved; 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 <sigx/tunnel_functor.h> #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 void IPResolverThread::on_startup() { // thread private pdata will be freed when the thread ends (according to the glib docs) m_ThreadData.set(new ThreadData); } //virtual void IPResolverThread::on_cleanup() { 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