|
160
|
1 Networking
|
|
|
2 ==========
|
|
|
3
|
|
|
4 Networking in libuv is not much different from directly using the BSD socket
|
|
|
5 interface, some things are easier, all are non-blocking, but the concepts stay
|
|
|
6 the same. In addition libuv offers utility functions to abstract the annoying,
|
|
|
7 repetitive and low-level tasks like setting up sockets using the BSD socket
|
|
|
8 structures, DNS lookup, and tweaking various socket parameters.
|
|
|
9
|
|
|
10 The ``uv_tcp_t`` and ``uv_udp_t`` structures are used for network I/O.
|
|
|
11
|
|
|
12 .. NOTE::
|
|
|
13
|
|
|
14 The code samples in this chapter exist to show certain libuv APIs. They are
|
|
|
15 not examples of good quality code. They leak memory and don't always close
|
|
|
16 connections properly.
|
|
|
17
|
|
|
18 TCP
|
|
|
19 ---
|
|
|
20
|
|
|
21 TCP is a connection oriented, stream protocol and is therefore based on the
|
|
|
22 libuv streams infrastructure.
|
|
|
23
|
|
|
24 Server
|
|
|
25 ++++++
|
|
|
26
|
|
|
27 Server sockets proceed by:
|
|
|
28
|
|
|
29 1. ``uv_tcp_init`` the TCP handle.
|
|
|
30 2. ``uv_tcp_bind`` it.
|
|
|
31 3. Call ``uv_listen`` on the handle to have a callback invoked whenever a new
|
|
|
32 connection is established by a client.
|
|
|
33 4. Use ``uv_accept`` to accept the connection.
|
|
|
34 5. Use :ref:`stream operations <buffers-and-streams>` to communicate with the
|
|
|
35 client.
|
|
|
36
|
|
|
37 Here is a simple echo server
|
|
|
38
|
|
|
39 .. rubric:: tcp-echo-server/main.c - The listen socket
|
|
|
40 .. literalinclude:: ../../code/tcp-echo-server/main.c
|
|
|
41 :language: c
|
|
|
42 :linenos:
|
|
|
43 :lines: 68-
|
|
|
44 :emphasize-lines: 4-5,7-10
|
|
|
45
|
|
|
46 You can see the utility function ``uv_ip4_addr`` being used to convert from
|
|
|
47 a human readable IP address, port pair to the sockaddr_in structure required by
|
|
|
48 the BSD socket APIs. The reverse can be obtained using ``uv_ip4_name``.
|
|
|
49
|
|
|
50 .. NOTE::
|
|
|
51
|
|
|
52 There are ``uv_ip6_*`` analogues for the ip4 functions.
|
|
|
53
|
|
|
54 Most of the setup functions are synchronous since they are CPU-bound.
|
|
|
55 ``uv_listen`` is where we return to libuv's callback style. The second
|
|
|
56 arguments is the backlog queue -- the maximum length of queued connections.
|
|
|
57
|
|
|
58 When a connection is initiated by clients, the callback is required to set up
|
|
|
59 a handle for the client socket and associate the handle using ``uv_accept``.
|
|
|
60 In this case we also establish interest in reading from this stream.
|
|
|
61
|
|
|
62 .. rubric:: tcp-echo-server/main.c - Accepting the client
|
|
|
63 .. literalinclude:: ../../code/tcp-echo-server/main.c
|
|
|
64 :language: c
|
|
|
65 :linenos:
|
|
|
66 :lines: 51-66
|
|
|
67 :emphasize-lines: 9-10
|
|
|
68
|
|
|
69 The remaining set of functions is very similar to the streams example and can
|
|
|
70 be found in the code. Just remember to call ``uv_close`` when the socket isn't
|
|
|
71 required. This can be done even in the ``uv_listen`` callback if you are not
|
|
|
72 interested in accepting the connection.
|
|
|
73
|
|
|
74 Client
|
|
|
75 ++++++
|
|
|
76
|
|
|
77 Where you do bind/listen/accept on the server, on the client side it's simply
|
|
|
78 a matter of calling ``uv_tcp_connect``. The same ``uv_connect_cb`` style
|
|
|
79 callback of ``uv_listen`` is used by ``uv_tcp_connect``. Try::
|
|
|
80
|
|
|
81 uv_tcp_t* socket = (uv_tcp_t*)malloc(sizeof(uv_tcp_t));
|
|
|
82 uv_tcp_init(loop, socket);
|
|
|
83
|
|
|
84 uv_connect_t* connect = (uv_connect_t*)malloc(sizeof(uv_connect_t));
|
|
|
85
|
|
|
86 struct sockaddr_in dest;
|
|
|
87 uv_ip4_addr("127.0.0.1", 80, &dest);
|
|
|
88
|
|
|
89 uv_tcp_connect(connect, socket, (const struct sockaddr*)&dest, on_connect);
|
|
|
90
|
|
|
91 where ``on_connect`` will be called after the connection is established. The
|
|
|
92 callback receives the ``uv_connect_t`` struct, which has a member ``.handle``
|
|
|
93 pointing to the socket.
|
|
|
94
|
|
|
95 UDP
|
|
|
96 ---
|
|
|
97
|
|
|
98 The `User Datagram Protocol`_ offers connectionless, unreliable network
|
|
|
99 communication. Hence libuv doesn't offer a stream. Instead libuv provides
|
|
|
100 non-blocking UDP support via the `uv_udp_t` handle (for receiving) and
|
|
|
101 `uv_udp_send_t` request (for sending) and related functions. That said, the
|
|
|
102 actual API for reading/writing is very similar to normal stream reads. To look
|
|
|
103 at how UDP can be used, the example shows the first stage of obtaining an IP
|
|
|
104 address from a `DHCP`_ server -- DHCP Discover.
|
|
|
105
|
|
|
106 .. note::
|
|
|
107
|
|
|
108 You will have to run `udp-dhcp` as **root** since it uses well known port
|
|
|
109 numbers below 1024.
|
|
|
110
|
|
|
111 .. rubric:: udp-dhcp/main.c - Setup and send UDP packets
|
|
|
112 .. literalinclude:: ../../code/udp-dhcp/main.c
|
|
|
113 :language: c
|
|
|
114 :linenos:
|
|
|
115 :lines: 7-11,104-
|
|
|
116 :emphasize-lines: 8,10-11,17-18,21
|
|
|
117
|
|
|
118 .. note::
|
|
|
119
|
|
|
120 The IP address ``0.0.0.0`` is used to bind to all interfaces. The IP
|
|
|
121 address ``255.255.255.255`` is a broadcast address meaning that packets
|
|
|
122 will be sent to all interfaces on the subnet. port ``0`` means that the OS
|
|
|
123 randomly assigns a port.
|
|
|
124
|
|
|
125 First we setup the receiving socket to bind on all interfaces on port 68 (DHCP
|
|
|
126 client) and start a read on it. This will read back responses from any DHCP
|
|
|
127 server that replies. We use the UV_UDP_REUSEADDR flag to play nice with any
|
|
|
128 other system DHCP clients that are running on this computer on the same port.
|
|
|
129 Then we setup a similar send socket and use ``uv_udp_send`` to send
|
|
|
130 a *broadcast message* on port 67 (DHCP server).
|
|
|
131
|
|
|
132 It is **necessary** to set the broadcast flag, otherwise you will get an
|
|
|
133 ``EACCES`` error [#]_. The exact message being sent is not relevant to this
|
|
|
134 book and you can study the code if you are interested. As usual the read and
|
|
|
135 write callbacks will receive a status code of < 0 if something went wrong.
|
|
|
136
|
|
|
137 Since UDP sockets are not connected to a particular peer, the read callback
|
|
|
138 receives an extra parameter about the sender of the packet.
|
|
|
139
|
|
|
140 ``nread`` may be zero if there is no more data to be read. If ``addr`` is NULL,
|
|
|
141 it indicates there is nothing to read (the callback shouldn't do anything), if
|
|
|
142 not NULL, it indicates that an empty datagram was received from the host at
|
|
|
143 ``addr``. The ``flags`` parameter may be ``UV_UDP_PARTIAL`` if the buffer
|
|
|
144 provided by your allocator was not large enough to hold the data. *In this case
|
|
|
145 the OS will discard the data that could not fit* (That's UDP for you!).
|
|
|
146
|
|
|
147 .. rubric:: udp-dhcp/main.c - Reading packets
|
|
|
148 .. literalinclude:: ../../code/udp-dhcp/main.c
|
|
|
149 :language: c
|
|
|
150 :linenos:
|
|
|
151 :lines: 17-40
|
|
|
152 :emphasize-lines: 1,23
|
|
|
153
|
|
|
154 UDP Options
|
|
|
155 +++++++++++
|
|
|
156
|
|
|
157 Time-to-live
|
|
|
158 ~~~~~~~~~~~~
|
|
|
159
|
|
|
160 The TTL of packets sent on the socket can be changed using ``uv_udp_set_ttl``.
|
|
|
161
|
|
|
162 IPv6 stack only
|
|
|
163 ~~~~~~~~~~~~~~~
|
|
|
164
|
|
|
165 IPv6 sockets can be used for both IPv4 and IPv6 communication. If you want to
|
|
|
166 restrict the socket to IPv6 only, pass the ``UV_UDP_IPV6ONLY`` flag to
|
|
|
167 ``uv_udp_bind``.
|
|
|
168
|
|
|
169 Multicast
|
|
|
170 ~~~~~~~~~
|
|
|
171
|
|
|
172 A socket can (un)subscribe to a multicast group using:
|
|
|
173
|
|
|
174 .. code::block:: c
|
|
|
175
|
|
|
176 int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, const char* interface_addr, uv_membership membership);
|
|
|
177
|
|
|
178 where ``membership`` is ``UV_JOIN_GROUP`` or ``UV_LEAVE_GROUP``.
|
|
|
179
|
|
|
180 The concepts of multicasting are nicely explained in `this guide`_.
|
|
|
181
|
|
|
182 .. _this guide: https://www.tldp.org/HOWTO/Multicast-HOWTO-2.html
|
|
|
183
|
|
|
184 Local loopback of multicast packets is enabled by default [#]_, use
|
|
|
185 ``uv_udp_set_multicast_loop`` to switch it off.
|
|
|
186
|
|
|
187 The packet time-to-live for multicast packets can be changed using
|
|
|
188 ``uv_udp_set_multicast_ttl``.
|
|
|
189
|
|
|
190 Querying DNS
|
|
|
191 ------------
|
|
|
192
|
|
|
193 libuv provides asynchronous DNS resolution. For this it provides its own
|
|
|
194 ``getaddrinfo`` replacement [#]_. In the callback you can
|
|
|
195 perform normal socket operations on the retrieved addresses. Let's connect to
|
|
|
196 Libera.chat to see an example of DNS resolution.
|
|
|
197
|
|
|
198 .. rubric:: dns/main.c
|
|
|
199 .. literalinclude:: ../../code/dns/main.c
|
|
|
200 :language: c
|
|
|
201 :linenos:
|
|
|
202 :lines: 61-
|
|
|
203 :emphasize-lines: 12
|
|
|
204
|
|
|
205 If ``uv_getaddrinfo`` returns non-zero, something went wrong in the setup and
|
|
|
206 your callback won't be invoked at all. All arguments can be freed immediately
|
|
|
207 after ``uv_getaddrinfo`` returns. The `hostname`, `servname` and `hints`
|
|
|
208 structures are documented in `the getaddrinfo man page <getaddrinfo_>`_. The
|
|
|
209 callback can be ``NULL`` in which case the function will run synchronously.
|
|
|
210
|
|
|
211 In the resolver callback, you can pick any IP from the linked list of ``struct
|
|
|
212 addrinfo(s)``. This also demonstrates ``uv_tcp_connect``. It is necessary to
|
|
|
213 call ``uv_freeaddrinfo`` in the callback.
|
|
|
214
|
|
|
215 .. rubric:: dns/main.c
|
|
|
216 .. literalinclude:: ../../code/dns/main.c
|
|
|
217 :language: c
|
|
|
218 :linenos:
|
|
|
219 :lines: 42-60
|
|
|
220 :emphasize-lines: 8,16
|
|
|
221
|
|
|
222 libuv also provides the inverse `uv_getnameinfo`_.
|
|
|
223
|
|
|
224 .. _uv_getnameinfo: http://docs.libuv.org/en/v1.x/dns.html#c.uv_getnameinfo
|
|
|
225
|
|
|
226 Network interfaces
|
|
|
227 ------------------
|
|
|
228
|
|
|
229 Information about the system's network interfaces can be obtained through libuv
|
|
|
230 using ``uv_interface_addresses``. This simple program just prints out all the
|
|
|
231 interface details so you get an idea of the fields that are available. This is
|
|
|
232 useful to allow your service to bind to IP addresses when it starts.
|
|
|
233
|
|
|
234 .. rubric:: interfaces/main.c
|
|
|
235 .. literalinclude:: ../../code/interfaces/main.c
|
|
|
236 :language: c
|
|
|
237 :linenos:
|
|
|
238 :emphasize-lines: 9,17
|
|
|
239
|
|
|
240 ``is_internal`` is true for loopback interfaces. Note that if a physical
|
|
|
241 interface has multiple IPv4/IPv6 addresses, the name will be reported multiple
|
|
|
242 times, with each address being reported once.
|
|
|
243
|
|
|
244 .. _c-ares: https://c-ares.haxx.se
|
|
|
245 .. _getaddrinfo: https://man7.org/linux/man-pages/man3/getaddrinfo.3.html
|
|
|
246
|
|
|
247 .. _User Datagram Protocol: https://en.wikipedia.org/wiki/User_Datagram_Protocol
|
|
|
248 .. _DHCP: https://tools.ietf.org/html/rfc2131
|
|
|
249
|
|
|
250 ----
|
|
|
251
|
|
|
252 .. [#] https://beej.us/guide/bgnet/html/#broadcast-packetshello-world
|
|
|
253 .. [#] https://www.tldp.org/HOWTO/Multicast-HOWTO-6.html#ss6.1
|
|
|
254 .. [#] libuv use the system ``getaddrinfo`` in the libuv threadpool. libuv
|
|
|
255 v0.8.0 and earlier also included c-ares_ as an alternative, but this has been
|
|
|
256 removed in v0.9.0.
|