I'm learning the basics of writing a simple, efficient socket server using GLib. I'm experimenting with GSocketService. So far I can only seem to accept connections but then they are immediately closed. From the docs I can't figure out what step I am missing. I'm hoping someone can shed some light on this for me.
When running the following:
# telnet localhost 4000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
# telnet localhost 4000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
# telnet localhost 4000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
Output from the server:
# ./server
New Connection from 127.0.0.1:36962
New Connection from 127.0.0.1:36963
New Connection from 127.0.0.1:36965
Current code:
/*
* server.c
*
* Created on: Mar 10, 2010
* Author: mark
*/
#include <glib.h>
#include <gio/gio.h>
gchar *buffer;
gboolean
network_read(GIOChannel *source,
GIOCondition cond,
gpointer data)
{
GString *s = g_string_new(NULL);
GError *error;
GIOStatus ret = g_io_channel_read_line_string(source, s, NULL, &error);
if (ret == G_IO_STATUS_ERROR)
g_error ("Error reading: %s\n", error->message);
else
g_print("Got: %s\n", s->str);
}
gboolean
new_connection(GSocketService *service,
GSocketConnection *connection,
GObject *source_object,
gpointer user_data)
{
GSocketAddress *sockaddr = g_socket_connection_get_remote_address(connection, NULL);
GInetAddress *addr = g_inet_socket_address_get_address(G_INET_SOCKET_ADDRESS(sockaddr));
guint16 port = g_inet_socket_address_get_port(G_INET_SOCKET_ADDRESS(sockaddr));
g_print("New Connection from %s:%d\n", g_inet_address_to_string(addr), port);
GSocket *socket = g_socket_connection_get_socket(connection);
gint fd = g_socket_get_fd(socket);
GIOChannel *channel = g_io_channel_unix_new(fd);
g_io_add_watch(channel, G_IO_IN, (GIOFunc) network_read, NULL);
return TRUE;
}
int main(int argc, char **argv) {
g_type_init();
GSocketService *service = g_socket_service_new();
GInetAddress *address = g_inet_address_new_from_string("127.0.0.1");
GSocketAddress *socket_address = g_inet_socket_address_new(address, 4000);
g_socket_listener_add_address(G_SOCKET_LISTENER(service), socket_address, G_SOCKET_TYPE_STREAM,
G_SOCKET_PROTOCOL_TCP, NULL, NULL, NULL);
g_object_unref(socket_address);
g_object_unref(address);
g_socket_service_start(service);
g_signal_connect(service, "incoming", G_CALLBACK(new_connection), NULL);
GMainLoop *loop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(loop);
}
It's not documented in the GSocketService docs (I had to go through the GLib sources to find it), but the routine that calls the callback (new_connection in this case) *does a g_object_unref() on the connection object* after it returns. This effectively closes the connection immediately new_connection() returns to it.
I have no idea why it does this, but the solution is to add a g_object_ref() on entering the callback:
Without that addition, polling the file descriptor in the main loop just returned POLLNVAL because the connection had been closed. In the absence of a handler for that result, it did that continuously -- and that's what caused the 100% CPU load.
The GSocketConnection has to be ref'ed in the incoming callback, this will keep the connection alive. You can pass it to a data structure, a class, or as user_data to the watch callback.
You are not returning in the watch callback network_read(), you must end it with "return true". From the documentation: "the function should return FALSE if the event source should be removed".
The 100% CPU is caused by the fact that at the time the connection is closed the channel is still alive. Make sure to properly remove the event source when no longer needed.
From the GIO docs :
The GIOStream object owns the input and the output streams, not the other way around, so keeping the substreams alive will not keep the GIOStream object alive. If the GIOStream object is freed it will be closed, thus closing the substream, so even if the substreams stay alive they will always just return a G_IO_ERROR_CLOSED for all operations.