![]() | ![]() | ![]() | GNetwork Library Manual | ![]() |
---|
Sub-Classing — Creating a subclass of GTcpConnection
With the design of our object complete, we can now get to work actually writing it. The first step when writing a sub-class is to define the structure for the new sub-classed object. In this case, since we want to subclass GTcpConnection, we should create the object instance and object class structures so the first item is GTcpConnection and GTcpConnectionClass (respectively):
typedef struct _FLPClient FLPClient; typedef struct _FLPClientClass FLPClientClass; struct _FLPClient { GTcpConnection parent; /* Read-Write Object Properties */ FLPStatusType flp_status; gchar *username; gchar *passwd; /* Read-only Object Properties */ gchar *filename; gulong filesize; gulong offset; /* Other Data */ gulong filesize_recieved; gint fd; }; struct _FLPClientClass { GTcpConnectionClass parent_class; /* Protocol Signals */ void (*connect_complete) (FLPClient *client, gboolean connected); void (*quit) (FLPClient *client, gboolean requested); /* Incoming Data Signals */ void (*error) (FLPClient *client, FLPErrorType error); void (*download_start) (FLPClient *client, const gchar *filename, gsize filesize, gsize offset); void (*download_done) (FLPClient *client, const gchar *filename, gboolean completed); };
This gives us the data structures needed for an FLPClient object, which is a sub-class of the GTcpConnection object. The short reason why we need two structures is because that's how the GObject system works. The longer explanation is that every instance of a GObject has a "class", which provides function prototypes for signals, function prototypes for "vtables" (interfaces not used in GTcpConnection), and data items which apply to every object in that class. One copy of the class structure is stored in memory once for each subclass. The FLPClient structure, on the other hand, provides "instance-specific" data, or data specific to each particular FLPClient object.
As you may have noticed, we used two enumerated types in the above example, FLPStatusType and FLPErrorType. Obviously these also need to be defined:
/* Used by the "flp-status" property */ typedef enum /* < prefix=FLP_STATUS > */ { FLP_STATUS_CLOSING = -3, FLP_STATUS_CLOSED = -2, FLP_STATUS_OPENING = -1, FLP_STATUS_READY = 0, FLP_STATUS_RECIEVING = 1 } FLPStatusType; /* Used by the "error" signal */ typedef enum /* < prefix=FLP_ERROR > */ { FLP_ERROR_UNKNOWN, FLP_ERROR_BAD_USERPASS, FLP_ERROR_NOT_FOUND, FLP_ERROR_BAD_PERMISSIONS, FLP_ERROR_NOTRANSFER } FLPErrorType;
So now that have the basic data types we need to handle our subclass, it's time to start coding.
The first step is to create the standardized code that all GObject subclasses need:
static gpointer parent_class = NULL; static void flp_client_class_init (FLPClientClass *class) { parent_class = g_type_class_peek_parent (class); } static void flp_client_instance_init (FlpClient *client) { client->filename = NULL; client->fd = -1; } GType flp_client_get_type (void) { static GType type = 0; if (type == 0) { static const GTypeInfo info = { sizeof (FLPClientClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) flp_client_class_init, (GClassFinalizeFunc) NULL, NULL, sizeof (FLPClient), 0, (GInstanceInitFunc) flp_client_instance_init, NULL }; type = g_type_register_static (GTCP_TYPE_CONNECTION, "FLPClient", &info, 0); } return type; }
The flp_client_get_type() function is what lets the GType system (used by GObject) know that we're registering a new object type called "FLPClient", and this new type is a subclass of GTcpConnection. The other functions are used by the GType system to initialize the structures we've provided for our class. flp_client_class_init() initializes our class structure, and also lets the GObject system know about the signals and properties our object has. The flp_client_class_init() function is also used to initialize any "classwide" variables, in this case, the "parent_class" variable. The function flp_client_instance_init() is used to initialize the basic FLPClient structure.
The next step is to register the signals our object supports:
/* Signal ID Location */ enum { CONNECT_COMPLETE, QUIT, ERROR, DOWNLOAD_START, DOWNLOAD_DONE, LAST_SIGNAL }; /* Cached Signal IDs */ static gint flp_signals[LAST_SIGNAL] = {0}; /* Parent Class (GTcpConnectionClass) */ static gpointer parent_class = NULL; [...] static void flp_client_class_init (FLPClientClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); parent_class = g_type_class_peek_parent (class); /* Signals */ flp_signals[CONNECT_COMPLETE] = g_signal_new ("connect-complete", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (FLPClientClass, connect_complete), NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); flp_signals[QUIT] = g_signal_new ("quit", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (FLPClientClass, quit), NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); flp_signals[ERROR] = g_signal_new ("error", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (FLPClientClass, error), NULL, NULL, g_cclosure_marshal_VOID__ENUM, G_TYPE_NONE, 1, FLP_TYPE_ERROR_TYPE); flp_signals[DOWNLOAD_START] = g_signal_new ("download-start", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (FLPClientClass, download_start), NULL, NULL, _flp_marshal_VOID__STRING_ULONG_ULONG, G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_ULONG, G_TYPE_ULONG); flp_signals[DOWNLOAD_DONE] = g_signal_new ("download-done", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (FLPClientClass, download_done), NULL, NULL, _flp_marshal_VOID__STRING_BOOLEAN, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_BOOLEAN); }
As you can see, the first argument of g_signal_new() is the "friendly" name of the signal, that applications use when the call g_signal_connect(). The next item of importance is the fourth argument, where in our FLPClientClass structure the function prototype for the signal is. Another important item is the seventh argument, which tells the signal system the name of a function which will actually do the grunt work of calling all the functions which have been connected to the signal. The GObject library provides several functions for this purpose:
The first capitalized item is the return value of functions connected to the signal. The capitalized items after the double-underscore are the arguments to the signal handler. Unfortunately, as you can see, our "download-start" and "download-done" signals have arguments that aren't covered by the stock signal marshaller functions that the GObject library provides.
Because GObject doesn't provide the signal marshallers we need, our subclass will have to provide them. Fortunately, these signal marshallers are generic enough that the GObject library ships with a program to automatically create them. The program, glib-genmarshal reads a file which contains definitions for the functions we need, one per line. The basic format for a glib-genmarshal definitions file is:
{RETURN-TYPE}:{ARGUMENT-TYPE}[,ARGUMENT-TYPE...] {RETURN-TYPE}:{ARGUMENT-TYPE}[,ARGUMENT-TYPE...] ...
Therefore, for our signals we need two lines in our file, which we'll call flp-marshal.list:
VOID:STRING,BOOLEAN VOID:STRING,ULONG,ULONG
Now we need to setup the build system to properly create our flp-marshal source code. For the purposes of this tutorial, I'll assume we're using an automake/autoconf build system. To properly generate our code, we need to add the following line to configure.in:
AC_PATH_PROG(GLIB_GENMARSHAL, glib-genmarshal)
and then add the following lines to your source directory's Makefile.am:
$(srcdir)/flp-marshal.h: Makefile.am flp-marshal.list @GLIB_GENMARSHAL@ --header --prefix="_flp_marshal" \ $(srcdir)/flp-marshal.list > tmp-marshal.h && \ (cmp -s tmp-marshal.h flp-marshal.h || cp tmp-marshal.h $(@F)) && \ rm -f tmp-marshal.h tmp-marshal.h~ $(srcdir)/flp-marshal.c: flp-marshal.h @GLIB_GENMARSHAL@ --body --prefix="_flp_marshal" \ $(srcdir)/flp-marshal.list > tmp-marshal.c && \ (cmp -s tmp-marshal.c flp-marshal.c || cp tmp-marshal.c $(@F)) && \ rm -f tmp-marshal.c tmp-marshal.c~
So long as we include flp-marshal.h in our source file, that's all we need to do to register our signals.
The next step is to connect the object callbacks. Each signal that a GObject has actually has two types of callbacks, an "object callback," which is connected in flp_client_class_init(), and any standard callbacks, which are connected using g_signal_connect_data() or it's variants. What order these functions are called in is specified when the signal is registered. Some of our signals use G_RUN_FIRST, which means the object callback is called first, then the regular callbacks are called. The "download-start" and "download-done" signals are G_RUN_LAST, which means the regular callbacks are called first, then the object callback is called afterwords. To connect an object callback, simply assign the appropriate class member for the signal to a function with the same arguments and return type. For our sub-class, it would look like this:
static gpointer parent_class = NULL; static void flp_client_finalize (GObject *object) { if (G_OBJECT_CLASS (parent_class)->finalize != NULL) (*G_OBJECT_CLASS (parent_class)->finalize) (object); } static void flp_client_closed (GTcpConnection *conn, gboolean requested) { if (GTCP_CONNECTION_CLASS (parent_class)->closed != NULL) (*GTCP_CONNECTION_CLASS (parent_class)->closed) (conn, requested); } static void flp_client_connect_done (GTcpConnection *conn, GTcpConnectionStatus status) { if (GTCP_CONNECTION_CLASS (parent_class)->connect_done != NULL) (*GTCP_CONNECTION_CLASS (parent_class)->connect_done) (conn, status); } static void flp_client_recv (GTcpConnection *conn, gconstpointer data, gsize length) { if (GTCP_CONNECTION_CLASS (parent_class)->recv != NULL) (*GTCP_CONNECTION_CLASS (parent_class)->recv) (conn, data, length); } static void flp_client_class_init (FLPClientClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); GTcpConnectionClass *tcp_class = GTCP_CONNECTION_CLASS (class); parent_class = g_type_class_peek_parent (class); object_class->finalize = flp_client_finalize; tcp_class->closed = flp_client_closed; tcp_class->connect_done = flp_client_connect_done; tcp_class->recv = flp_client_recv; /* Signals */ [...] /* Properties */ [...] }
In each object callback, we have lines similar to:
if (SOME_CLASS (parent_class)->signal_function != NULL) (*SOME_CLASS (parent_class)->signal_function) (...);
What this does is calls the parent class' object callback. For example, GTcpConnection also has an object callback assigned to the GObject's "finalize" signal. We need to call GTcpConnection's finalize callback from our object callback, otherwise it won't get called at all, and won't free the memory used by GTcpConnection (which in turn calls GObject's finalize object callback, freeing the memory used by GObject, etc.). We keep a pointer to the GTcpConnectionClass structure in the global static variable, "parent_class".
Signals which require the subclass call the parent class' function are called "parent relative" signals. A base class which sets an object callback for a signal makes that signal parent relative. In GTcpConnection, for example, all signals are parent relative, because the gtcp_connection_class_init() function sets object callbacks for all of GTcpConnectionClass's signals.
The next major step is to install the properties we decided upon for the FLPClient object. GObject has a powerful method for accomplishing this, using GParamSpec. When installing properties (as with signals), it's important to be sure that the property names for your object are different from the property names used by parent objects/classes.
The first step in installing properties is to register the names and property IDs (numbers) inside our class initialization function. To do this,we need another enum (for property ID), and the code inside flp_client_class_init():
/* Property IDs */ enum { PROP_0, FLP_STATUS, USERNAME, PASSWD, FILENAME, FILESIZE, OFFSET }; /* Signal ID Location */ [...] static void flp_client_class_init (FLPClientClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); [...] /* Properties */ g_object_class_install_property (object_class, FLP_STATUS, // Property ID g_param_spec_enum ("flp-status", // Property Name _("Protocol Status"), // Short Description _("The status of the connection."), // Long Description FLP_TYPE_STATUS_TYPE, // Enumerated GType FLP_STATUS_CLOSED, // Default Value G_PARAM_READABLE)); // Property Flags g_object_class_install_property (object_class, USERNAME, g_param_spec_string ("username", _("User name"), _("The username used to authenticate " "connections."), NULL, // Default Value (G_PARAM_READWRITE | G_PARAM_CONSTRUCT))); g_object_class_install_property (object_class, PASSWORD, g_param_spec_string ("password", _("Password"), _("The password used to authenticate " "connections."), NULL, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT))); g_object_class_install_property (object_class, FILENAME, g_param_spec_string ("filename", _("Transfer Filename"), _("The filename currently being transferred."), NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, FILESIZE, g_param_spec_ulong ("filesize", _("Transfer Filename"), _("The size of the file currently being " "transferred."), 0, // Minimum Value G_MAXULONG, // Maximum Value 0, // Default Value G_PARAM_READABLE)); g_object_class_install_property (object_class, OFFSET, g_param_spec_ulong ("offset", _("Transfer Offset"), _("The byte position of the current download."), 0, G_MAXULONG, 0, G_PARAM_READABLE)); /* Signals */ [...] }
To explain what's happening, g_object_class_install_property() takes object_class, and adds a property to it using the given property ID. The calls to g_param_spec_*() create a GParamSpec structure, which defines the name, data type, permissions, default value, and some user-readable descriptive strings for each property. So for our "username" property, it's installing a property who's registered GType is G_TYPE_STRING, and who's default value is NULL. Likewise, for our "flp-status" property, it's installing a property who's type is a FLPStatusType, and who's default value is FLP_STATUS_CLOSED.
However, as you can see from the example, the actual name of the enumerated type that we passed was FLP_TYPE_STATUS_TYPE, not FLPStatusType. This is because we have to register our enumerated types with the GType system in order to use them in signals and properties. This is also why in our "error" signal, we use FLP_TYPE_ERROR_TYPE, not FLPErrorType. The easiest way to do this is to do it automatically, using glib-mkenums. To do this, first add another line to your configure.in file:
[...] AC_PATH_PROG(GLIB_MKENUMS, glib-mkenums) [...]
Then, add the following lines to your source directory's Makefile.am (assuming the definition of our enumerated types is in flp-client.h):
$(srcdir)/flp-type-builtins.h: Makefile.am flp-client.h @GLIB_MKENUMS@ \ --fhead "#ifndef __FLP_TYPE_BUILTINS_H__\n#define __FLP_TYPE_BUILTINS_H__\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n" \ --fprod "/* enumerations from \"@filename@\" */\n" \ --vhead "GType @enum_name@_get_type (void);\n#define FLP_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n" \ --ftail "G_END_DECLS\n\n#endif /* __FLP_TYPE_BUILTINS_H__ */" \ $(srcdir)/flp-client.h > tmp-type-builtins.h && \ (cmp -s tmp-type-builtins.h flp-type-builtins.h || \ cp tmp-type-builtins.h $(@F)) && \ rm -f tmp-type-builtins.h $(srcdir)/flp-type-builtins.c: flp-type-builtins.h @GLIB_MKENUMS@ \ --fhead "#include <glib-object.h>\n#include \"flp-client.h\"\n" \ --fprod "\n/* enumerations from \"@filename@\" */" \ --vhead "GType\n@enum_name@_get_type (void)\n{\n static GType etype = 0;\n if (etype == 0) {\n static const G@Type@Value values[] = {" \ --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \ --vtail " { 0, NULL, NULL }\n };\n etype = g_@type@_register_static (\"@EnumName@\", values);\n }\n return etype;\n}\n" \ $(srcdir)/flp-client.h > tmp-type-builtins.c && \ (cmp -s tmp-type-builtins.c flp-type-builtins.c || \ cp tmp-type-builtins.c $(@F)) && \ rm -f tmp-type-builtins.c
Then include flp-type-builtins.h in flp-client.c, and our enumerated types (FLPStatusType and FLPErrorType) will have their own _get_type() functions, which register them with the GType system, allowing them to be intellegently used for signal arguments and properties.
With that little problem out of the way, it's time to actually write the backend for the properties. GObject provides a common means to access the property data through g_object_get() and g_object_set(), but it is up to us to write the code which actually stores the data. To do this, we have to set the "set_property" and "get_property" GObjectClass structure members to point to our backend functions. To do this, we create two functions, flp_client_get_property() and flp_client_set_property(), and set the appropriate items in our flp_client_class_init() function:
/* Property IDs */ enum { PROP_0, FLP_STATUS, USERNAME, PASSWD, FILENAME, FILESIZE, OFFSET }; /* Signal ID Location */ [...] static void flp_client_set_property (GObject * object, guint property, const GValue * value, GParamSpec * param_spec) { FlpClient *client = FLP_CLIENT (object); switch (property) { case USERNAME: g_return_if_fail (client->flp_status <= GIRC_CLIENT_CLOSED); g_free (client->username); client->username = g_value_dup_string (value); g_object_notify (object, "username"); break; case PASSWORD: g_return_if_fail (client->flp_status <= GIRC_CLIENT_CLOSED); g_free (client->password); client->password = g_value_dup_string (value); g_object_notify (object, "password"); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property, param_spec); break; } } static void flp_client_get_property (GObject * object, guint property, GValue * value, GParamSpec * param_spec) { FLPClient *client = FLP_CLIENT (object); switch (property) { case FLP_STATUS: g_value_set_enum (value, client->flp_status); break; case USERNAME: g_value_set_string (value, client->username); break; case PASSWORD: g_value_set_string (value, client->password); break; case FILENAME: g_value_set_string (value, client->filename); break; case FILESIZE: g_value_set_ulong (value, client->filesize); break; case OFFSET: g_value_set_ulong (value, client->offset); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property, param_spec); break; } } static void flp_client_class_init (FLPClientClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); [...] object_class->set_property = flp_client_set_property; object_class->get_property = flp_client_get_property; [...] }
Unlike signals, the get_property and set_property members do not need to be chained up. The GObject system will take care to call the right function for the property being set/gotten.
So, now that we've gotten the properties connected, and the signals registered, it's time to actually start writing the code to process the connection. The first place to start is with the protocol status signals.
The "connect-complete" and "quit" signals are the signals related to changes in the connection status. To handle these, we need to use the object callbacks for the GTcpConnection object's "connect-done" and "quit" signals.
First, the "connect-done" signal:
[...] static void flp_client_connect_done (GTcpConnection *conn, GTcpConnectionStatus status) { if (GTCP_CONNECTION_CLASS (parent_class)->connect_done != NULL) (*GTCP_CONNECTION_CLASS (parent_class)->connect_done) (conn, status); /* If we're connected... */ if (status == GTCP_CONNECTION_CONNECTED) { gchar *data = NULL; FLPClient *client = FLP_CLIENT (conn); /* Set the "flp-status" property */ client->status = FLP_CLIENT_LOGGING_IN; g_object_notify (G_OBJECT (conn), "flp-status"); /* Construct the login string */ if (client->username != NULL) { if (client->password != NULL) { data = g_strdup_printf ("LOGIN %s %s\n", client->username, client->password); } else { data = g_strdup_printf ("LOGIN %s %s\n", client->username); } } else { data = g_strdup ("LOGIN\n"); } /* Send the login string */ gtcp_connection_send (conn, data, -1); /* Free the login string */ g_free (data); } } [...]
What we're doing is just setting the status property, creating a login string, and sending it. That's it for the "connect-done" signal. The "connect-complete" signal should be emitted after we've got confirmation that we're logged in (more on that later).
Next up is the "closed" signal's object callback. Because the FLP protocol (like most other application-level protocols) has a "QUIT" command, when we send that command, the server disconnects. GTcpConnection, obviously, doesn't know anything about FLP, so when the server disconnects, it only knows that the server disconnected from us on the hardware level, not the other way around.
The best way to to test if we've requested a quit is to check the "flp-status" property:
[...] static void flp_client_closed (GTcpConnection *conn, gboolean requested) { if (GTCP_CONNECTION_CLASS (parent_class)->closed != NULL) (*GTCP_CONNECTION_CLASS (parent_class)->closed) (conn, requested); /* If the user requested the quit, then the quit function should have set the status appropriately. */ g_signal_emit (conn, signals[QUIT], 0, (FLP_CLIENT (conn)->status == FLP_CLIENT_QUITTING)); } [...]
Notice the call to g_signal_emit(). That function emits a signal by it's numeric ID ("signals[QUIT]," in this case), for a given object ("conn," in this case). The third argument is the "detail quark", or a numeric ID for a particular kind of "quit" signal. Since we've only got one kind of "quit" signal, we can leave this at zero. The fourth argument is the boolean "requested" argument for the signal. If we leave this out, the argument it will always be false for callbacks.
With the "connect-done" and "closed" callbacks done, it's time to concentrate on the incoming parsing function. The incoming parsing function is the "recv" signal's object callback:
[...] static void flp_client_recv (GTcpConnection *conn, gconstpointer data, gsize length) { FLPClient *client = FLP_CLIENT (conn); gint line_end_pos = -1; if (GTCP_CONNECTION_CLASS (parent_class)->recv != NULL) (*GTCP_CONNECTION_CLASS (parent_class)->recv) (conn, data, length); /* First, split the raw incoming data into lines */ lines = g_strsplit (data, "\n", -1); if (lines == NULL) return; for (i = 0; lines[i] != NULL; i++) { gchar *msg = NULL; /* verify that we've got a message, and not data for a file. We can do this by checking to see if the following sscanf parse works. If it does, msg will be filled with the message type string (and any other characters up to the newline). */ if (sscanf (lines[i], "FLP %a[^\n]", &msg) == 1) { FLPErrorType error_code; gboolean error_msg = FALSE; /* Find the proper message type */ /* Login successful */ if (g_ascii_strcasecmp (msg, "LOGGED_IN") == 0) { /* Then, emit the "connect-complete" signal */ g_signal_emit (client, signals[CONNECT_COMPLETE], 0, TRUE); } /* Starting to recieve a file */ else if (g_ascii_strcasencmp (msg, "STARTGET", 8) == 0) { gchar *filename = NULL; gulong size = 0; gint sscanf_retval; /* First, get the filename and size */ sscanf_retval = sscanf (msg + 9, "%as %ul", &filename, &size); /* Set the "filename" property */ g_free (client->filename); client->filename = g_strdup (filename); g_free (filename); g_object_notify (G_OBJECT (client), "filename"); /* Set the "filesize" property */ client->size = size; g_object_notify (G_OBJECT (client), "filesize"); /* Reset the bytes recieved for this file */ client->bytes_recieved = 0; client->offset = 0; g_object_notify (G_OBJECT (client), "offset"); /* Emit the "download-start" signal */ g_signal_emit (client, flp_signals[DOWNLOAD_START], 0, client->filename, client->filesize, client->offset); } /* Done recieving a file */ else if (g_ascii_strcasecmp (msg, "DONEGET") == 0) { gboolean complete; complete = (client->bytes_recieved == client->filesize); g_signal_emit (client, flp_signals[DOWNLOAD_DONE], 0, client->filename, client->filesize, complete); } /* Error Types */ else if (g_ascii_strcasecmp (msg, "BADUSERPASS") == 0) { error_msg = TRUE; error_code = FLP_ERROR_BAD_USERPASS; } else if (g_ascii_strcasecmp (msg, "NOTFOUND") == 0) { error_msg = TRUE; error_code = FLP_ERROR_NOT_FOUND; } else if (g_ascii_strcasecmp (msg, "BADPERMS") == 0) { error_msg = TRUE; error_code = FLP_ERROR_BAD_PERMISSIONS; } else if (g_ascii_strcasecmp (msg, "NOTRANSFER") == 0) { error_msg = TRUE; error_code = FLP_ERROR_NOTRANSFER; } /* *msg == "UNKNOWN" or some random bad data. */ else { error_msg = TRUE; error_code = FLP_ERROR_UNKNOWN; } if (error_msg) g_signal_emit (client, signals[ERROR], 0, error_code); } /* We don't have a message, this is data for an incoming file */ else { gsize line_size = strlen (lines[i]); client->filesize_recieved += line_size; write (client->fd, lines[i], line_size); } g_free (msg); } [...]
Obviously, the above example is not the only way to parse incoming data which contains both protocol commands and data for files. In fact, it's not even the best way to handle such connections (for example, what if the binary data happened to start with the letters "FLP"), but it is sufficient to show basically how one can parse incoming data.
<< Getting Started | Object-Specific Code >> |