Networking¶
Networking in libuv is not much different from directly using the BSD socket interface, some things are easier, all are non-blocking but the concepts stay the same. In addition libuv offers utility functions to abstract the annoying, repetitive and low-level tasks like setting up sockets using the BSD socket structures, DNS lookup, and tweaking various socket parameters.
The uv_tcp_t and uv_udp_t structures are used for network I/O.
TCP¶
TCP is a connection oriented, stream protocol and is therefore based on the libuv streams infrastructure.
Server¶
Server sockets proceed by:
- uv_tcp_init the TCP watcher.
- uv_tcp_bind it.
- Call uv_listen on the watcher to have a callback invoked whenever a new connection is established by a client.
- Use uv_accept to accept the connection.
- Use stream operations to communicate with the client.
Here is a simple echo server
tcp-echo-server/main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | int main() {
loop = uv_default_loop();
uv_tcp_t server;
uv_tcp_init(loop, &server);
struct sockaddr_in bind_addr = uv_ip4_addr("0.0.0.0", 7000);
uv_tcp_bind(&server, bind_addr);
int r = uv_listen((uv_stream_t*) &server, 128, on_new_connection);
if (r) {
fprintf(stderr, "Listen error %s\n", uv_err_name(uv_last_error(loop)));
return 1;
}
return uv_run(loop);
}
|
You can see the utility function uv_ip4_addr being used to convert from a human readable IP address, port pair to the sockaddr_in structure required by the BSD socket APIs. The reverse can be obtained using uv_ip4_name.
Note
In case it wasn’t obvious there are uv_ip6_* analogues for the ip4 functions.
Most of the setup functions are normal functions since its all CPU-bound. uv_listen is where we return to libuv’s callback style. The second arguments is the backlog queue – the maximum length of queued connections.
When a connection is initiated by clients, the callback is required to set up a watcher for the client socket and associate the watcher using uv_accept. In this case we also establish interest in reading from this stream.
tcp-echo-server/main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | void on_new_connection(uv_stream_t *server, int status) {
if (status == -1) {
// error!
return;
}
uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));
uv_tcp_init(loop, client);
if (uv_accept(server, (uv_stream_t*) client) == 0) {
uv_read_start((uv_stream_t*) client, alloc_buffer, echo_read);
}
else {
uv_close((uv_handle_t*) client, NULL);
}
}
|
The remaining set of functions is very similar to the streams example and can be found in the code. Just remember to call uv_close when the socket isn’t required. This can be done even in the uv_listen callback if you are not interested in accepting the connection.
Client¶
Where you do bind/listen/accept, on the client side its simply a matter of calling uv_tcp_connect. The same uv_connect_cb style callback of uv_listen is used by uv_tcp_connect. Try:
uv_tcp_t socket;
uv_tcp_init(loop, &socket);
uv_connect_t connect;
struct sockaddr_in dest = uv_ip4_addr("127.0.0.1", 80);
uv_tcp_connect(&connect, &socket, dest, on_connect);
where on_connect will be called after the connection is established.
UDP¶
The User Datagram Protocol offers connectionless, unreliable network communication. Hence, unlike TCP, it doesn’t offer a stream abstraction since each packet is independent. libuv provides non-blocking UDP support via the uv_udp_t (for receiving) and uv_udp_send_t (for sending) structures and related functions. That said, the actual API for reading/writing is very similar to normal stream reads. To look at how UDP can be used, the example shows the first stage of obtaining an IP address from a DHCP server – DHCP Discover.
Note
You will have to run udp-dhcp as root since it uses well known port numbers below 1024.
udp-dhcp/main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | uv_loop_t *loop;
uv_udp_t send_socket;
uv_udp_t recv_socket;
loop = uv_default_loop();
uv_udp_init(loop, &recv_socket);
struct sockaddr_in recv_addr = uv_ip4_addr("0.0.0.0", 68);
uv_udp_bind(&recv_socket, recv_addr, 0);
uv_udp_recv_start(&recv_socket, alloc_buffer, on_read);
uv_udp_init(loop, &send_socket);
uv_udp_bind(&send_socket, uv_ip4_addr("0.0.0.0", 0), 0);
uv_udp_set_broadcast(&send_socket, 1);
uv_udp_send_t send_req;
uv_buf_t discover_msg = make_discover_msg(&send_req);
struct sockaddr_in send_addr = uv_ip4_addr("255.255.255.255", 67);
uv_udp_send(&send_req, &send_socket, &discover_msg, 1, send_addr, on_send);
return uv_run(loop);
}
|
Note
The IP address 0.0.0.0 is used to bind to all interfaces. The IP address 255.255.255.255 is a broadcast address meaning that packets will be sent to all interfaces on the subnet. port 0 means that the OS randomly assigns a port.
First we setup the receiving socket to bind on all interfaces on port 68 (DHCP client) and start a read watcher on it. Then we setup a similar send socket and use uv_udp_send to send a broadcast message on port 67 (DHCP server).
It is necessary to set the broadcast flag, otherwise you will get an EACCES error [1]. The exact message being sent is irrelevant to this book and you can study the code if you are interested. As usual the read and write callbacks will receive a status code of -1 if something went wrong.
Since UDP sockets are not connected to a particular peer, the read callback receives an extra parameter about the sender of the packet. The flags parameter may be UV_UDP_PARTIAL if the buffer provided by your allocator was not large enough to hold the data. In this case the OS will discard the data that could not fit (That’s UDP for you!).
udp-dhcp/main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | void on_read(uv_udp_t *req, ssize_t nread, uv_buf_t buf, struct sockaddr *addr, unsigned flags) {
if (nread == -1) {
fprintf(stderr, "Read error %s\n", uv_err_name(uv_last_error(loop)));
uv_close((uv_handle_t*) req, NULL);
free(buf.base);
return;
}
char sender[17] = { 0 };
uv_ip4_name((struct sockaddr_in*) addr, sender, 16);
fprintf(stderr, "Recv from %s\n", sender);
// ... DHCP specific code
free(buf.base);
uv_udp_recv_stop(req);
}
|
UDP Options¶
The TTL of packets sent on the socket can be changed using uv_udp_set_ttl.
IPv6 stack only¶
IPv6 sockets can be used for both IPv4 and IPv6 communication. If you want to restrict the socket to IPv6 only, pass the UV_UDP_IPV6ONLY flag to uv_udp_bind6 [2].
Multicast¶
A socket can (un)subscribe to a multicast group using:
UV_EXTERN int uv_udp_set_membership(uv_udp_t* handle,
where membership is UV_JOIN_GROUP or UV_LEAVE_GROUP.
Local loopback of multicast packets is enabled by default [3], use uv_udp_set_multicast_loop to switch it off.
The packet time-to-live for multicast packets can be changed using uv_udp_set_multicast_ttl.
Querying DNS¶
libuv provides asynchronous DNS resolution. For this it provides its own getaddrinfo replacement [4]. In the callback you can perform normal socket operations on the retrieved addresses. Let’s connect to Freenode to see an example of DNS resolution.
dns/main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | int main() {
loop = uv_default_loop();
struct addrinfo hints;
hints.ai_family = PF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = 0;
uv_getaddrinfo_t resolver;
fprintf(stderr, "irc.freenode.net is... ");
int r = uv_getaddrinfo(loop, &resolver, on_resolved, "irc.freenode.net", "6667", &hints);
if (r) {
fprintf(stderr, "getaddrinfo call error %s\n", uv_err_name(uv_last_error(loop)));
return 1;
}
return uv_run(loop);
}
|
If uv_getaddrinfo returns non-zero, something went wrong in the setup and your callback won’t be invoked at all. All arguments can be freed immediately after uv_getaddrinfo returns. The hostname, servname and hints structures are documented in the getaddrinfo man page.
In the resolver callback, you can pick any IP from the linked list of struct addrinfo(s). This also demonstrates uv_tcp_connect. It is necessary to call uv_freeaddrinfo in the callback.
dns/main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | void on_resolved(uv_getaddrinfo_t *resolver, int status, struct addrinfo *res) {
if (status == -1) {
fprintf(stderr, "getaddrinfo callback error %s\n", uv_err_name(uv_last_error(loop)));
return;
}
char addr[17] = {'\0'};
uv_ip4_name((struct sockaddr_in*) res->ai_addr, addr, 16);
fprintf(stderr, "%s\n", addr);
uv_connect_t *connect_req = (uv_connect_t*) malloc(sizeof(uv_connect_t));
uv_tcp_t *socket = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));
uv_tcp_init(loop, socket);
connect_req->data = (void*) socket;
uv_tcp_connect(connect_req, socket, *(struct sockaddr_in*) res->ai_addr, on_connect);
uv_freeaddrinfo(res);
}
|
Network interfaces¶
Information about the system’s network interfaces can be obtained through libuv using uv_interface_addresses. This simple program just prints out all the interface details so you get an idea of the fields that are available. This is useful to allow your service to bind to IP addresses when it starts.
interfaces/main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #include <stdio.h>
#include <uv.h>
int main() {
char buf[512];
uv_interface_address_t *info;
int count, i;
uv_interface_addresses(&info, &count);
i = count;
printf("Number of interfaces: %d\n", count);
while (i--) {
uv_interface_address_t interface = info[i];
printf("Name: %s\n", interface.name);
printf("Internal? %s\n", interface.is_internal ? "Yes" : "No");
if (interface.address.address4.sin_family == AF_INET) {
uv_ip4_name(&interface.address.address4, buf, sizeof(buf));
printf("IPv4 address: %s\n", buf);
}
else if (interface.address.address4.sin_family == AF_INET6) {
uv_ip6_name(&interface.address.address6, buf, sizeof(buf));
printf("IPv6 address: %s\n", buf);
}
printf("\n");
}
uv_free_interface_addresses(info, count);
return 0;
}
|
is_internal is true for loopback interfaces. Note that if a physical interface has multiple IPv4/IPv6 addresses, the name will be reported multiple times, with each address being reported once.
[1] | http://beej.us/guide/bgnet/output/html/multipage/advanced.html#broadcast |
[2] | on Windows only supported on Windows Vista and later. |
[3] | http://www.tldp.org/HOWTO/Multicast-HOWTO-6.html#ss6.1 |
[4] | libuv use the system getaddrinfo in the libuv threadpool. libuv v0.8.0 and earlier also included c-ares as an alternative, but this has been removed in v0.9.0. |