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 establishedvoid (*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