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/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