forked from cory/tildefriends
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3650 ed5197a5-7fde-0310-b194-c3ffbd925b24
258 lines
9.2 KiB
ReStructuredText
258 lines
9.2 KiB
ReStructuredText
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.
|
|
|
|
.. NOTE::
|
|
|
|
The code samples in this chapter exist to show certain libuv APIs. They are
|
|
not examples of good quality code. They leak memory and don't always close
|
|
connections properly.
|
|
|
|
TCP
|
|
---
|
|
|
|
TCP is a connection oriented, stream protocol and is therefore based on the
|
|
libuv streams infrastructure.
|
|
|
|
Server
|
|
++++++
|
|
|
|
Server sockets proceed by:
|
|
|
|
1. ``uv_tcp_init`` the TCP handle.
|
|
2. ``uv_tcp_bind`` it.
|
|
3. Call ``uv_listen`` on the handle to have a callback invoked whenever a new
|
|
connection is established by a client.
|
|
4. Use ``uv_accept`` to accept the connection.
|
|
5. Use :ref:`stream operations <buffers-and-streams>` to communicate with the
|
|
client.
|
|
|
|
Here is a simple echo server
|
|
|
|
.. rubric:: tcp-echo-server/main.c - The listen socket
|
|
.. literalinclude:: ../../code/tcp-echo-server/main.c
|
|
:language: c
|
|
:linenos:
|
|
:lines: 68-
|
|
:emphasize-lines: 4-5,7-10
|
|
|
|
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::
|
|
|
|
There are ``uv_ip6_*`` analogues for the ip4 functions.
|
|
|
|
Most of the setup functions are synchronous since they are 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 handle for the client socket and associate the handle using ``uv_accept``.
|
|
In this case we also establish interest in reading from this stream.
|
|
|
|
.. rubric:: tcp-echo-server/main.c - Accepting the client
|
|
.. literalinclude:: ../../code/tcp-echo-server/main.c
|
|
:language: c
|
|
:linenos:
|
|
:lines: 51-66
|
|
:emphasize-lines: 9-10
|
|
|
|
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 server, on the client side it's 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_t*)malloc(sizeof(uv_tcp_t));
|
|
uv_tcp_init(loop, socket);
|
|
|
|
uv_connect_t* connect = (uv_connect_t*)malloc(sizeof(uv_connect_t));
|
|
|
|
struct sockaddr_in dest;
|
|
uv_ip4_addr("127.0.0.1", 80, &dest);
|
|
|
|
uv_tcp_connect(connect, socket, (const struct sockaddr*)&dest, on_connect);
|
|
|
|
where ``on_connect`` will be called after the connection is established. The
|
|
callback receives the ``uv_connect_t`` struct, which has a member ``.handle``
|
|
pointing to the socket.
|
|
|
|
UDP
|
|
---
|
|
|
|
The `User Datagram Protocol`_ offers connectionless, unreliable network
|
|
communication. Hence libuv doesn't offer a stream. Instead libuv provides
|
|
non-blocking UDP support via the `uv_udp_t` handle (for receiving) and
|
|
`uv_udp_send_t` request (for sending) 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.
|
|
|
|
.. rubric:: udp-dhcp/main.c - Setup and send UDP packets
|
|
.. literalinclude:: ../../code/udp-dhcp/main.c
|
|
:language: c
|
|
:linenos:
|
|
:lines: 7-11,104-
|
|
:emphasize-lines: 8,10-11,17-18,21
|
|
|
|
.. 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 on it. This will read back responses from any DHCP
|
|
server that replies. We use the UV_UDP_REUSEADDR flag to play nice with any
|
|
other system DHCP clients that are running on this computer on the same port.
|
|
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 [#]_. The exact message being sent is not relevant 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 < 0 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.
|
|
|
|
``nread`` may be zero if there is no more data to be read. If ``addr`` is NULL,
|
|
it indicates there is nothing to read (the callback shouldn't do anything), if
|
|
not NULL, it indicates that an empty datagram was received from the host at
|
|
``addr``. 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!).
|
|
|
|
.. rubric:: udp-dhcp/main.c - Reading packets
|
|
.. literalinclude:: ../../code/udp-dhcp/main.c
|
|
:language: c
|
|
:linenos:
|
|
:lines: 17-40
|
|
:emphasize-lines: 1,23
|
|
|
|
UDP Options
|
|
+++++++++++
|
|
|
|
Time-to-live
|
|
~~~~~~~~~~~~
|
|
|
|
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_bind`` [#]_.
|
|
|
|
Multicast
|
|
~~~~~~~~~
|
|
|
|
A socket can (un)subscribe to a multicast group using:
|
|
|
|
.. code::block:: c
|
|
|
|
int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, const char* interface_addr, uv_membership membership);
|
|
|
|
where ``membership`` is ``UV_JOIN_GROUP`` or ``UV_LEAVE_GROUP``.
|
|
|
|
The concepts of multicasting are nicely explained in `this guide`_.
|
|
|
|
.. _this guide: https://www.tldp.org/HOWTO/Multicast-HOWTO-2.html
|
|
|
|
Local loopback of multicast packets is enabled by default [#]_, 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 [#]_. In the callback you can
|
|
perform normal socket operations on the retrieved addresses. Let's connect to
|
|
Libera.chat to see an example of DNS resolution.
|
|
|
|
.. rubric:: dns/main.c
|
|
.. literalinclude:: ../../code/dns/main.c
|
|
:language: c
|
|
:linenos:
|
|
:lines: 61-
|
|
:emphasize-lines: 12
|
|
|
|
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 <getaddrinfo_>`_. The
|
|
callback can be ``NULL`` in which case the function will run synchronously.
|
|
|
|
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.
|
|
|
|
.. rubric:: dns/main.c
|
|
.. literalinclude:: ../../code/dns/main.c
|
|
:language: c
|
|
:linenos:
|
|
:lines: 42-60
|
|
:emphasize-lines: 8,16
|
|
|
|
libuv also provides the inverse `uv_getnameinfo`_.
|
|
|
|
.. _uv_getnameinfo: http://docs.libuv.org/en/v1.x/dns.html#c.uv_getnameinfo
|
|
|
|
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.
|
|
|
|
.. rubric:: interfaces/main.c
|
|
.. literalinclude:: ../../code/interfaces/main.c
|
|
:language: c
|
|
:linenos:
|
|
:emphasize-lines: 9,17
|
|
|
|
``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.
|
|
|
|
.. _c-ares: https://c-ares.haxx.se
|
|
.. _getaddrinfo: https://man7.org/linux/man-pages/man3/getaddrinfo.3.html
|
|
|
|
.. _User Datagram Protocol: https://en.wikipedia.org/wiki/User_Datagram_Protocol
|
|
.. _DHCP: https://tools.ietf.org/html/rfc2131
|
|
|
|
----
|
|
|
|
.. [#] https://beej.us/guide/bgnet/html/#broadcast-packetshello-world
|
|
.. [#] on Windows only supported on Windows Vista and later.
|
|
.. [#] https://www.tldp.org/HOWTO/Multicast-HOWTO-6.html#ss6.1
|
|
.. [#] 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.
|