On 11/14/05, Tor Lillqvist <[EMAIL PROTECTED]> wrote:
> > g_io_win32_check: WSAEnumNetworkEvents (1576, 0x618) revents={}
> > condition={IN|ERR|HUP} events={}
>
> > What seems to happen is that after watching a socket io channel once,
> > I'll continue to see these messages until I restart the program - even
> > if the io channel should have been cleaned up by g_source_remove().
>
> > Any ideas what could be causing this?
>
> The giowin32.c code was changed in GLib 2.8 to use WSAEventSelect (and
> thus asynchronous IO) on sockets. Apparently this has some odd
> side-effects I haven't noticed. Do you think you can distill the
> problems into a minimal sample program...?
>
> --tml

I've hacked together a sample program using parts of gaim's web page
retrieval code.

It is not as simple as would be optimal, but hopefully it is good
enough to show what is going on.

In addition to the standard glib/gtk+ libs, this will also need to be
linked against ws2_32.

The program downloads a page from a web server using non blocking I/O.
I didn't add support for DNS lookups and redirects, and etc.

To recreate the problem:
set the G_IO_WIN32_DEBUG environment variable and run the program.

Notice that initially the console is not inundated with the
"WSAEnumNetworkEvents" debug output when you move the mouse around the
gtk+ window.

Specify an IP address in the input box and hit the "Do Stuff" button
(it can even be a bogus IP - as long as inet_addr() will recognize
it).

Notice that moving your mouse around the gtk+ window now causes lots
of output in the console.

Hopefully this is an adequate example.

Thanks,
-Daniel
#include <winsock2.h>
#include <ws2tcpip.h>
#include <io.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

#include <glib.h>

#include <gtk/gtk.h>
#include <gdk/gdk.h>


struct PHB {
        GdkInputFunction func;
        gpointer data;
        char *host;
        int port;
        gint inpa;
        GSList *hosts;
};


typedef struct
{
        void (*callback)(void *, const char *, size_t);
        void *user_data;

        char *url;

        int inpa;

        gboolean sentreq;
        gboolean newline;
        gboolean startsaving;
        gboolean has_explicit_data_len;
        char *webdata;
        unsigned long len;
        unsigned long data_len;

} GaimFetchUrlData;

typedef void (*dns_callback_t)(GSList *hosts, gpointer data,
                const char *error_message);

typedef struct {
        gpointer data;
        size_t addrlen;
        struct sockaddr *addr;
        dns_callback_t callback;
} pending_dns_request_t;

static int
proxy_connect_none(struct PHB *phb, struct sockaddr *addr, socklen_t addrlen);

static int try_connect(struct PHB *phb)
{
        size_t addrlen;
        struct sockaddr *addr;
        int ret = -1;

        while (phb->hosts) {
                addrlen = GPOINTER_TO_INT(phb->hosts->data);
                phb->hosts = g_slist_remove(phb->hosts, phb->hosts->data);
                addr = phb->hosts->data;
                phb->hosts = g_slist_remove(phb->hosts, phb->hosts->data);

                ret = proxy_connect_none(phb, addr, addrlen);

                g_free(addr);

                if (ret > 0)
                        break;
        }

        if (ret < 0) {

                phb->func(phb->data, -1, GDK_INPUT_READ);

                g_free(phb->host);
                g_free(phb);
        }

        return ret;
}


static void
no_one_calls(gpointer data, gint source, GdkInputCondition cond)
{
        struct PHB *phb = data;
        unsigned int len;
        int error=0;

        printf("Connected.\n");

        len = sizeof(error);

        /*
         * getsockopt after a non-blocking connect returns -1 if something is
         * really messed up (bad descriptor, usually). Otherwise, it returns 0 
and
         * error holds what connect would have returned if it blocked until now.
         * Thus, error == 0 is success, error == EINPROGRESS means "try again",
         * and anything else is a real error.
         *
         * (error == EINPROGRESS can happen after a select because the kernel 
can
         * be overly optimistic sometimes. select is just a hint that you might 
be
         * able to do something.)
         */
        if (getsockopt(source, SOL_SOCKET, SO_ERROR, (void *)&error, &len) == 
SOCKET_ERROR) {
                error == WSAGetLastError();
                if (error == WSAEINPROGRESS)
                        return; /* we'll be called again later */
                closesocket(source);
                g_source_remove(phb->inpa);

                printf("getsockopt SO_ERROR check: %s\n", strerror(error));

                try_connect(phb);
                return;
        }

        u_long imode = 0;
        ioctlsocket(source, FIONBIO, &imode);
        g_source_remove(phb->inpa);

        phb->func(phb->data, source, GDK_INPUT_READ);

        g_free(phb->host);
        g_free(phb);
}


static gboolean clean_connect(gpointer data)
{
        struct PHB *phb = data;

        phb->func(phb->data, phb->port, GDK_INPUT_READ);

        g_free(phb->host);
        g_free(phb);

        return FALSE;
}


static int
proxy_connect_none(struct PHB *phb, struct sockaddr *addr, socklen_t addrlen)
{
        int fd = -1;

        printf("Connecting to %s:%d with no proxy\n", phb->host, phb->port);

        if ((fd = socket(addr->sa_family, SOCK_STREAM, 0)) == INVALID_SOCKET) {
                printf("Unable to create socket: %s\n", 
strerror(WSAGetLastError()));
                return -1;
        }
        u_long imode = 1;
        if (ioctlsocket(fd, FIONBIO, &imode) == SOCKET_ERROR)
                printf("Unable to set non-blocking mode: %d\n", 
WSAGetLastError());

        if (connect(fd, (struct sockaddr *)addr, addrlen) == SOCKET_ERROR) {
                errno = WSAGetLastError();
                if (WSAGetLastError() == WSAEWOULDBLOCK) {
                        printf("Connect would have blocked.\n");
                        phb->inpa = gdk_input_add(fd, GDK_INPUT_WRITE, 
no_one_calls, phb);
                }
                else {
                        printf( "Connect failed: %s\n", strerror(errno));
                        closesocket(fd);
                        return -1;
                }
        }
        else {
                unsigned int len;
                int error = WSAETIMEDOUT;
                printf("Connect didn't block.\n");
                len = sizeof(error);
                if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&error, &len) 
== SOCKET_ERROR) {
                        printf("getsockopt failed.\n");
                        closesocket(fd);
                        return -1;
                }
                u_long imode = 0;
                ioctlsocket(fd, FIONBIO, &imode);
                phb->port = fd; /* bleh */
                g_timeout_add(50, clean_connect, phb);  /* we do this because 
we never
                                                           want to call our 
callback
                                                           before we return. */
        }

        return fd;
}


static void
destroy_fetch_url_data(GaimFetchUrlData *gfud)
{
        g_free(gfud->webdata);
        g_free(gfud->url);
        g_free(gfud);
}

int
gaim_proxy_connect(const char *host, int port,
                                   GdkInputFunction func, gpointer data)
{
        struct PHB *phb;
        struct sockaddr_in sin;
        struct sockaddr *addr;
        int addrlen;


        g_return_val_if_fail(host != NULL, -1);
        g_return_val_if_fail(port != 0 && port != -1, -1);
        g_return_val_if_fail(func != NULL, -1);

        if ((sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE)
                return -1;

        sin.sin_family = AF_INET;
        sin.sin_port = htons(port);

        addr = (struct sockaddr*) g_memdup(&sin, sizeof(sin));
        addrlen = sizeof(sin);

        phb = g_new0(struct PHB, 1);

        phb->func = func;
        phb->data = data;
        phb->host = g_strdup(host);
        phb->port = port;

        phb->hosts = g_slist_append(phb->hosts, GINT_TO_POINTER(addrlen));
        phb->hosts = g_slist_append(phb->hosts, addr);
        
        return try_connect(phb);
}

static size_t
parse_content_len(const char *data, size_t data_len)
{
        size_t content_len = 0;
        const char *p = NULL;

        /* This is still technically wrong, since headers are case-insensitive
         * [RFC 2616, section 4.2], though this ought to catch the normal case.
         * Note: data is _not_ nul-terminated.
         */
        if (data_len > 16) {
                p = strncmp(data, "Content-Length: ", 16) == 0? data: NULL;
                if (!p) {
                        p = g_strstr_len(data, data_len, "\nContent-Length: ");
                        if (p)
                                p += 1;
                }
        }

        /* If we can find a Content-Length header at all, try to sscanf it.
         * Response headers should end with at least \r\n, so sscanf is safe,
         * if we make sure that there is indeed a \n in our header.
         */
        if (p && g_strstr_len(p, data_len - (p - data), "\n")) {
                sscanf(p, "Content-Length: %" G_GSIZE_FORMAT, &content_len);
                printf("parsed %u\n", content_len);
        }

        return content_len;
}


static void
url_fetched_cb(gpointer url_data, gint sock, GdkInputCondition cond)
{
        GaimFetchUrlData *gfud = url_data;
        char data;
        gboolean got_eof = FALSE;

        if (sock == -1)
        {
                gfud->callback(gfud->user_data, NULL, 0);

                destroy_fetch_url_data(gfud);

                return;
        }

        if (!gfud->sentreq)
        {
                char buf[1024];

                const char *host = gfud->url + 7; //"http://";

                g_snprintf(buf, sizeof(buf),
                                   "GET %s HTTP/1.1\r\n"
                                   "Host: %s\r\n\r\n",
                                   gfud->url,
                                   host);

                printf("Request: %s\n", buf);

                send(sock, buf, strlen(buf), 0);
                u_long imode = 1;
                ioctlsocket(sock, FIONBIO, &imode);
                gfud->sentreq = TRUE;
                gfud->inpa = gdk_input_add(sock, GDK_INPUT_READ, 
url_fetched_cb, url_data);
                gfud->data_len = 4096;
                gfud->webdata = g_malloc(gfud->data_len);

                return;
        }


        /* Read in data, one byte at a time */
        int recv_ret = recv(sock, &data, 1, 0);
        if ((recv_ret != SOCKET_ERROR && recv_ret > 0)
                        || (recv_ret == SOCKET_ERROR && WSAGetLastError() == 
WSAEWOULDBLOCK))
        {
                if (WSAGetLastError() == WSAEWOULDBLOCK)
                {
                        errno = 0;

                        return;
                }

                gfud->len++;

                /* If we've filled up our buffer then make it bigger */
                if (gfud->len == gfud->data_len)
                {
                        gfud->data_len += (gfud->data_len) / 2;

                        gfud->webdata = g_realloc(gfud->webdata, 
gfud->data_len);
                }

                gfud->webdata[gfud->len - 1] = data;
                gfud->webdata[gfud->len] = '\0';

                if (!gfud->startsaving)
                {
                        if (data == '\r')
                                return;

                        if (data == '\n')
                        {
                                if (gfud->newline)
                                {
                                        size_t content_len;
                                        gfud->startsaving = TRUE;

                                        /* No redirect. See if we can find a 
content length. */
                                        content_len = 
parse_content_len(gfud->webdata, gfud->len);

                                        if (content_len == 0)
                                        {
                                                /* We'll stick with an initial 
8192 */
                                                content_len = 8192;
                                        }
                                        else
                                        {
                                                gfud->has_explicit_data_len = 
TRUE;
                                        }

                                        /* Out with the old... */
                                        gfud->len = 0;
                                        g_free(gfud->webdata);
                                        gfud->webdata = NULL;

                                        /* In with the new. */
                                        gfud->data_len = content_len;
                                        gfud->webdata = 
g_try_malloc(gfud->data_len);
                                        if (gfud->webdata == NULL) {
                                                printf("Failed to allocate %lu 
bytes: %s\n", gfud->data_len, strerror(errno));
                                                g_source_remove(gfud->inpa);
                                                closesocket(sock);
                                                gfud->callback(gfud->user_data, 
NULL, 0);
                                                destroy_fetch_url_data(gfud);
                                        }
                                }
                                else
                                        gfud->newline = TRUE;

                                return;
                        }

                        gfud->newline = FALSE;
                }
                else if (gfud->has_explicit_data_len && gfud->len == 
gfud->data_len)
                {
                        got_eof = TRUE;
                }
        }
        else if (errno != WSAETIMEDOUT)
        {
                got_eof = TRUE;
        }
        else
        {
                g_source_remove(gfud->inpa);
                closesocket(sock);

                gfud->callback(gfud->user_data, NULL, 0);

                destroy_fetch_url_data(gfud);
        }

        if (got_eof) {
                gfud->webdata = g_realloc(gfud->webdata, gfud->len + 1);
                gfud->webdata[gfud->len] = 0;

                g_source_remove(gfud->inpa);
                closesocket(sock);
                gfud->callback(gfud->user_data, gfud->webdata, gfud->len);

                destroy_fetch_url_data(gfud);
        }
}



void
gaim_url_fetch(const char *host,
                           void (*cb)(gpointer, const char *, size_t),
                           void *user_data)
{
        int sock;
        GaimFetchUrlData *gfud;

        g_return_if_fail(host != NULL);
        g_return_if_fail(cb  != NULL);

        printf("requested to fetch (%s)\n", host);

        gfud = g_new0(GaimFetchUrlData, 1);

        gfud->callback   = cb;
        gfud->user_data  = user_data;
        gfud->url        = g_strdup_printf("http://%s";, host);

        if ((sock = gaim_proxy_connect(host, 80, url_fetched_cb, gfud)) < 0)
        {
                destroy_fetch_url_data(gfud);

                cb(user_data, g_strdup("g003: Error opening connection.\n"), 0);
        }
}


static void
done_stuff(void *data, const char *page_data, size_t len)
{
  printf("***************************** DATA DONE 
*******************************\n%s\n", page_data ? page_data : "");
}


void
do_stuff(GtkWidget *window, GtkEntry *entry)
{
  const gchar *text;

  text = gtk_entry_get_text (entry);

  gaim_url_fetch(text, done_stuff, NULL);
}

int
main (int argc, char *argv[])
{
  GtkWidget *entry, *window, *vbox, *hbox, *button;

  DWORD wVersionRequested = MAKEWORD(2, 2);
  WSADATA wsaData;
  WSAStartup (wVersionRequested, &wsaData);
  

  gtk_init (&argc, &argv);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  g_signal_connect (window, "destroy",
                    G_CALLBACK (gtk_main_quit), NULL);
  
  gtk_window_set_title (GTK_WINDOW (window), "IO Channel Test");
  gtk_container_set_border_width (GTK_CONTAINER (window), 0);

  vbox = gtk_vbox_new (FALSE, 0);
  gtk_container_add (GTK_CONTAINER (window), vbox);

 
  hbox = gtk_hbox_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);


  entry = gtk_entry_new ();
  gtk_box_pack_start (GTK_BOX(hbox), entry, FALSE, FALSE, 0);

  button = gtk_button_new_with_label ("Do Stuff");
  gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0);

  g_signal_connect (button, "clicked",
                    G_CALLBACK (do_stuff),
                    entry);

  gtk_widget_show_all(window);
  gtk_main();

  WSACleanup();

  return 0;
}

_______________________________________________
gtk-app-devel-list mailing list
gtk-app-devel-list@gnome.org
http://mail.gnome.org/mailman/listinfo/gtk-app-devel-list

Reply via email to