Writing a Generic Client

Tutorial: Writing a client — How to write a client

Writing a Client

This section of the tutorial describes how to write a client.

The first step in implementing a client is figuring out how to determine the host and port to connect to:

  • Is there a default host? If not, the host should either be determined programmatically (eg via an environment variable, a configuration file, a command-line parameter, or some other way), or the host may be a standard IP address or name. You may have to wait on DNS lookups; use the gsk_name_resolve function to lookup the name.

  • The port often defaults to a service-specific default port. Traditionally /etc/services gives a map from the port number to service name. More often, the default port is just hardcoded into the program.

Example: A TCP-Client API

Let us suppose that you have a server that responds to NUL-terminated requests with NUL-terminated responses. You want to make a client API and a sample client program.

First you have to design the client API:

typedef struct _MyClient MyClient;

MyClient *my_client_new         (const char    *hostname,
                                 int            port);
void      my_client_trap_errors (MyClient      *client,
                                 MyClientErrorFunc func,
				 gpointer       data,
				 GDestroyNotify destroy);
void      my_client_request     (MyClient      *client,
                                 const char    *request,
				 MyClientResponseCallback func,
				 gpointer       data,
				 GDestroyNotify destroy);
void      my_client_destroy     (MyClient      *client);

Then you have to design the MyClient data structure. We will keep a buffer of data that hasn't made it out to the server, and a queue of response callbacks, information about the actual connection, and the error-handlers.

struct _MyClient
{
  GskNameResolverTask *name_lookup;
  int port;
  GskStream *connection;

  GskBuffer outgoing;
  GskBuffer incoming;

  GQueue *response_callbacks;

  MyClientErrorFunc func;
  gpointer       data;
  GDestroyNotify destroy;
};

TODO: Implement it!

Sample Use of API

Whenever you make an API like that, you should use it in a trivial test program. This is vital for testing and debugging.

In that spirit, we provide a trivial use of the MyClient API.

#include "my-client.h"
static void
handle_response (const char *response, gpointer data)
{
  g_print ("%s\n", response);
  gsk_main_quit ();
}
int main (int argc, char **argv)
{
  gsk_init (&argc, &argv, NULL);
  if (argc != 4)
    g_error ("usage: %s HOST PORT QUERY", argv[0]);
  client = my_client_new (argv[1], atoi (argv[2]));
  my_client_request (client, argv[3], handle_response, NULL, NULL);
  return gsk_main_run ();
}