wiki:NetworkAPITutorial

HelenOS Network Transport API tutorial

This tutorial is a set of step by step guide to creating a simple network application in HelenOS that communicates over TCP or UDP.

Introduction

HelenOS network stack was designed and written from scratch. Its APIs do not mimic any existing preexisting interfaces (such as BSD sockets). This allows us the freedom to design more sleek and modern APIs compared to just sticking with the legacy APIs.

This means the programmer needs to learn new APIs. However the learning curve is not steep and somebody who knows BSD sockets should be able to learn them quickly. In synchronous modes the mapping from one API to another is not too complicated.

TCP client

Here we describe how to create an application that connects to a remote server via TCP.

We'll need to include the following headers:

#include <inet/endpoint.h>
#include <inet/hostport.h>
#include <inet/tcp.h>

Suppose we want the user to specify the host name (or address) and port to connect to. The user will supply it as a host:port string in the 'hostport' variable.

We need to declare and initialize an endpoint pair - a data type which can hold both a remote and local endpoint (address:port pair).

inet_ep2_t epp;
inet_ep2_init(&epp);

After calling inet_ep2_init() the endpoint is fully unspecified. Now we'll parse the hostport string and save the result to the 'remote' endpoint. Thus we'll have specified to which host and port we want to connect to:

char *errmsg;
rc = inet_hostport_plookup_one(hostport, ip_any, &epp.remote, NULL, &errmsg);
if (rc = EOK) {
	printf("Error: %s (host:port %s).\n", errmsg, hostport);
	goto error;

Note that the 'hostport' string can contain either a host name or a literal IP address. Here are some examples of valid host:port strings:

  • example.com:1234 (with a host name)
  • 192.0.2.3:1234 (with an IPv4 address literal)
  • [2001:db8::23]:1234 (with a literal IPv6 address)

The argument ip_any means that we're willing to work with any IP protocol version (and the system should select an appropriate one).

We need to create an object representing the TCP service.

tcp_t *tcp;
rc = tcp_create(&tcp);
if (rc != EOK)
	goto error;

We can now initiate the connection:

tcp_conn_t *conn;
rc = tcp_conn_create(tcp, &epp, &conn_cb, NULL, &conn);
if (rc != EOK)
	goto error;

Here &epp is the endpoint pair which specifies the local and remote endpoints. Note that if a local address is not provided, it is automatically selected. If a local port is not provided, it is allocated from the set of ephemeral ports.

&conn_cb is a pointer to the structure of type tcp_cb_t containing callbacks to be used with the connection. NULL is a user argument that can be used by the user's callback functions. The callback structure is defined as:

/** TCP connection callbacks */
typedef struct tcp_cb {
        void (*connected)(tcp_conn_t *);
        void (*conn_failed)(tcp_conn_t *);
        void (*conn_reset)(tcp_conn_t *);
        void (*data_avail)(tcp_conn_t *);
        void (*urg_data)(tcp_conn_t *);
} tcp_cb_t;

All the callbacks are optional, i.e. the user only needs to specify handlers for the events he is interested in.

The function tcp_conn_create() returns immediately and does not wait for the connection to be established. If we want to block until the connection is established, we can call:

rc = tcp_conn_wait_connected(conn);
if (rc != EOK)
	goto error;

Alternatively, we can use callbacks to determine connection progress.

  • void (*connected)(tcp_conn_t *) when connection was established
  • void (*conn_failed)(tcp_conn_t *) when the attempt to connect is given up

To send data we can simply use:

int rc = tcp_conn_send(conn, data, size);

note that the function may block until space is available in the connection's outbound buffer. If we don't want to send data anymore, we can close the outbound half of the connection with:

rc = tcp_conn_send_fin(conn);

Note that we can still continue to receive data on the connection after that.

To receive data:

rc = tcp_conn_recv(conn, recv_buf, RECV_BUF_SIZE, &nrecv);

The function tcp_conn_recv() blocks until some data is available. On success it returns EOK and places the number of received bytes in nrecv.

To receive data in an asynchronous manner, we can register for the callback

  • void (*data_avail)(tcp_conn_t *)

This callback is invoked when new data is received on the connection. The user's callback handler should then pick up all available data.

Here's an example how the callback handler might look like:

static void example_data_avail(tcp_conn_t *conn)
{
        int rc;
        size_t nrecv;
        
        while (true) {
                rc = tcp_conn_recv(conn, recv_buf, RECV_BUF_SIZE, &nrecv);
                if (rc != EOK) {
                        printf("Receive error %d\n", rc);
                        break;
                }
        
                example_data_received(recv_buf, nrecv);

                if (nrecv != RECV_BUF_SIZE)
                        break;
        }
}

Another callback that can be registered is

  • void (*conn_reset)(tcp_conn_t *)

which informs us that the connection was reset by the peer. No more data can be sent or received afterwards and the only option is to destroy the connection.

When we are done with a connection we want to destroy it. When we are done with all TCP communications we want to destroy the TCP service object.

tcp_conn_destroy(conn);
tcp_destroy(tcp);

TCP server

TODO

UDP client

TODO

UDP server

TODO

Last modified 8 years ago Last modified on 2017-04-08T15:12:39Z
Note: See TracWiki for help on using the wiki.