【问题标题】:Need help implementing simple socket server using GIOService (GLib, Glib-GIO)需要帮助使用 GIOService (GLib, Glib-GIO) 实现简单的套接字服务器
【发布时间】:2011-01-25 22:29:41
【问题描述】:

我正在学习使用 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);
}

【问题讨论】:

  • 糟糕。还刚刚注意到此代码在第一次连接后导致 100% 的 CPU 使用率。似乎从 g_main_loop_run() 重复调用 poll()。小时。

标签: c unix glib


【解决方案1】:

GSocketConnection 必须在传入回调中引用,这将使连接保持活动状态。您可以将其传递给数据结构、类或作为 user_data 传递给 watch 回调。

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

【讨论】:

  • 听起来不错!再投几票后我会接受,因为我现在无法验证。
【解决方案2】:

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% CPU 负载的原因。

【讨论】:

  • 很好,罗恩!这两天我一直在为此头疼。谢谢!
  • “GSocketService 文档中没有记录” --- 它是:在GSocketService::incoming 的文档中:“一旦信号处理程序返回,连接将被取消,因此您需要自己引用它如果你打算使用它”
【解决方案3】:

来自the GIO docs

GIOStream 对象拥有输入和输出流,而不是相反,因此保持子流处于活动状态不会保持 GIOStream 对象处于活动状态。如果 GIOStream 对象被释放,它将被关闭,从而关闭子流,因此即使子流保持活动状态,它们也将始终为所有操作返回 G_IO_ERROR_CLOSED。

【讨论】:

    猜你喜欢
    • 2015-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-07
    • 2012-08-29
    • 1970-01-01
    • 2015-11-20
    • 1970-01-01
    相关资源
    最近更新 更多