2 <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
4 <chapter id="client-tutorial">
5 <title>Writing a UPnP Client</title>
8 <title>Introduction</title>
10 This chapter explains how to write an application which fetches the
11 external IP address from an UPnP-compliant modem. To do this a
12 <glossterm>Control Point</glossterm> is created, which searches for
14 <literal>urn:schemas-upnp-org:service:WANIPConnection:1</literal> (part of
15 the <ulink url="http://upnp.org/standardizeddcps/igd.asp">Internet Gateway
16 Device</ulink> specification). As services are discovered
17 <firstterm>Service Proxy</firstterm> objects are created by GUPnP to allow
18 interaction with the service, on which we can invoke the action
19 <function>GetExternalIPAddress</function> to fetch the external IP
25 <title>Finding Services</title>
27 First, we initialize GUPnP and create a control point targeting the
28 service type. Then we connect a signal handler so that we are notified
29 when services we are interested in are found.
31 <programlisting>#include <libgupnp/gupnp-control-point.h>
33 static GMainLoop *main_loop;
36 service_proxy_available_cb (GUPnPControlPoint *cp,
37 GUPnPServiceProxy *proxy,
44 main (int argc, char **argv)
46 GUPnPContext *context;
47 GUPnPControlPoint *cp;
49 /* Required initialisation */
50 #if !GLIB_CHECK_VERSION(2,35,0)
54 /* Create a new GUPnP Context. By here we are using the default GLib main
55 context, and connecting to the current machine's default IP on an
56 automatically generated port. */
57 context = gupnp_context_new (NULL, NULL, 0, NULL);
59 /* Create a Control Point targeting WAN IP Connection services */
60 cp = gupnp_control_point_new
61 (context, "urn:schemas-upnp-org:service:WANIPConnection:1");
63 /* The service-proxy-available signal is emitted when any services which match
64 our target are found, so connect to it */
66 "service-proxy-available",
67 G_CALLBACK (service_proxy_available_cb),
70 /* Tell the Control Point to start searching */
71 gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (cp), TRUE);
73 /* Enter the main loop. This will start the search and result in callbacks to
74 service_proxy_available_cb. */
75 main_loop = g_main_loop_new (NULL, FALSE);
76 g_main_loop_run (main_loop);
79 g_main_loop_unref (main_loop);
81 g_object_unref (context);
88 <title>Invoking Actions</title>
90 Now we have an application which searches for the service we specified and
91 calls <function>service_proxy_available_cb</function> for each one it
92 found. To get the external IP address we need to invoke the
93 <literal>GetExternalIPAddress</literal> action. This action takes no in
94 arguments, and has a single out argument called "NewExternalIPAddress".
95 GUPnP has a set of methods to invoke actions (which will be very familiar
96 to anyone who has used <literal>dbus-glib</literal>) where you pass a
97 <constant>NULL</constant>-terminated varargs list of (name, GType, value)
98 tuples for the in arguments, then a <constant>NULL</constant>-terminated
99 varargs list of (name, GType, return location) tuples for the out
102 <programlisting>static void
103 service_proxy_available_cb (GUPnPControlPoint *cp,
104 GUPnPServiceProxy *proxy,
107 GError *error = NULL;
110 gupnp_service_proxy_send_action (proxy,
111 /* Action name and error location */
112 "GetExternalIPAddress", &error,
116 "NewExternalIPAddress",
117 G_TYPE_STRING, &ip,
121 g_print ("External IP address is %s\n", ip);
124 g_printerr ("Error: %s\n", error->message);
125 g_error_free (error);
127 g_main_loop_quit (main_loop);
130 Note that gupnp_service_proxy_send_action() blocks until the service has
131 replied. If you need to make non-blocking calls then use
132 gupnp_service_proxy_begin_action(), which takes a callback that will be
133 called from the mainloop when the reply is received.
138 <title>Subscribing to state variable change notifications</title>
140 It is possible to get change notifications for the service state variables
141 that have attribute <literal>sendEvents="yes"</literal>. We'll demonstrate
142 this by modifying <function>service_proxy_available_cb</function> and using
143 gupnp_service_proxy_add_notify() to setup a notification callback:
145 <programlisting>static void
146 external_ip_address_changed (GUPnPServiceProxy *proxy,
147 const char *variable,
151 g_print ("External IP address changed: %s\n", g_value_get_string (value));
155 service_proxy_available_cb (GUPnPControlPoint *cp,
156 GUPnPServiceProxy *proxy,
159 g_print ("Found a WAN IP Connection service\n");
161 gupnp_service_proxy_set_subscribed (proxy, TRUE);
162 if (!gupnp_service_proxy_add_notify (proxy,
165 external_ip_address_changed,
167 g_printerr ("Failed to add notify");
173 <title>Generating Wrappers</title>
175 Using gupnp_service_proxy_send_action() and gupnp_service_proxy_add_notify ()
176 can become tedious, because of the requirement to specify the types and deal
178 alternative is to use <xref linkend="gupnp-binding-tool"/>, which
179 generates wrappers that hide the boilerplate code from you. Using a
180 wrapper generated with prefix 'ipconn' would replace
181 gupnp_service_proxy_send_action() with this code:
183 <programlisting>ipconn_get_external_ip_address (proxy, &ip, &error);</programlisting>
185 State variable change notifications are friendlier with wrappers as well:
187 <programlisting>static void
188 external_ip_address_changed (GUPnPServiceProxy *proxy,
189 const gchar *external_ip_address,
192 g_print ("External IP address changed: '%s'\n", external_ip_address);
196 service_proxy_available_cb (GUPnPControlPoint *cp,
197 GUPnPServiceProxy *proxy
200 g_print ("Found a WAN IP Connection service\n");
202 gupnp_service_proxy_set_subscribed (proxy, TRUE);
203 if (!ipconn_external_ip_address_add_notify (proxy,
204 external_ip_address_changed,
206 g_printerr ("Failed to add notify");