Нужна помощь в реализации простого сервера сокетов с использованием GIOService (GLib, Glib-GIO)

Я изучаю основы написания простого и эффективного сервера сокетов с использованием GLib. Я экспериментирую с GSocketService. Пока я могу только принимать соединения, но потом они сразу же закрываются. Из документов я не могу понять, какой шаг мне не хватает. Я надеюсь, что кто-то может пролить свет на это для меня.

При выполнении следующего:

# 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.

Вывод с сервера:

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

Текущий код:

/*
 * 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);
}

person Mark Renouf    schedule 10.03.2010    source источник
comment
Упс. Также только что заметил, что этот код приводит к 100% загрузке ЦП после первого подключения. Похоже, что poll() повторно вызывается из g_main_loop_run(). Хрм.   -  person Mark Renouf    schedule 10.03.2010


Ответы (3)


GSocketConnection должен быть указан во входящем обратном вызове, это сохранит соединение. Вы можете передать его в структуру данных, класс или как user_data обратному вызову часов.

gboolean
new_connection(...)
{
  ...

  g_object_ref (connection);
  GSocket *socket = g_socket_connection_get_socket(connection);

  gint fd = g_socket_get_fd(socket);
  GIOChannel *channel = g_io_channel_unix_new(fd);
  // Pass connection as user_data to the watch callback
  g_io_add_watch(channel, G_IO_IN, (GIOFunc) network_read, connection);
  return TRUE;
}

Вы не возвращаетесь в обратном вызове watch network_read(), вы должны завершить его с помощью «return true». Из документации: "функция должна возвращать FALSE, если источник события должен быть удален".

100% CPU вызвано тем, что в момент закрытия соединения канал еще жив. Обязательно правильно удалите источник события, когда он больше не нужен.

gboolean
network_read(GIOChannel *source,
             GIOCondition cond,
             gpointer data)
{
  GString *s = g_string_new(NULL);
  GError *error = NULL;
  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);
    g_warning ("Error reading: %s\n", error->message);
    // Drop last reference on connection
    g_object_unref (data);
    // Remove the event source
    return FALSE;
  }
  else
    g_print("Got: %s\n", s->str);

  if (ret == G_IO_STATUS_EOF) {
    return FALSE;
  }
person m8t    schedule 18.07.2010
comment
Звучит неплохо! Я соглашусь после еще пары голосов, потому что я не могу проверить прямо сейчас. - person Mark Renouf; 23.07.2010

Это не задокументировано в документах GSocketService (мне пришлось просмотреть исходники GLib, чтобы найти его), но подпрограмма, которая вызывает обратный вызов (в данном случае new_connection), *выполняет g_object_unref() для объекта соединения* после его возврата. Это эффективно закрывает соединение сразу же, как new_connection() возвращается к нему.

Я понятия не имею, почему это делается, но решение состоит в том, чтобы добавить g_object_ref() при вводе обратного вызова:

gboolean
new_connection(GSocketService *service,
              GSocketConnection *connection,
              GObject *source_object,
              gpointer user_data)
{

  g_object_ref(connection);    /* Tell glib not to disconnect */

  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));

Без этого добавления опрос файлового дескриптора в основном цикле просто возвращал POLLNVAL, потому что соединение было закрыто. В отсутствие обработчика этого результата он делал это постоянно — и это вызывало 100% загрузку процессора.

person Ron Murray    schedule 27.10.2011
comment
Отличное замечание, Рон! Уже два дня ломаю голову над этим. Спасибо! - person jcoppens; 14.06.2014
comment
«Это не задокументировано в документах GSocketService» --- Это: в документации для GSocketService::incoming: «соединение будет отменено после возврата обработчика сигнала, поэтому вам нужно указать его самостоятельно, если вы планируете его использовать» - person Philip Withnall; 28.07.2017

Из документов GIO:

Объект GIOStream владеет входными и выходными потоками, а не наоборот, поэтому поддержание активности подпотоков не будет поддерживать активность объекта GIOStream. Если объект GIOStream освобождается, он будет закрыт, тем самым закрывая подпоток, поэтому, даже если подпотоки остаются в живых, они всегда будут возвращать G_IO_ERROR_CLOSED для всех операций.

person Arc    schedule 22.05.2010