While writing one of my Node.js packages, I had my first opportunity to work with UPnP. Networking and communication using lower-level network protocols isn’t something that I usually do as a web developer. In part one, we’ll learn how to communicate over UDP with Node.js, solving the first piece in implementing the Discovery phase of UPnP.
If you’re new to this series, or you’re looking for a specific part, have a look at the link(s) below:
- Part One, Communicating over UDP
- Part Two, Implementing SSDP over UDP
Read the Spec
If you’re not familiar with UPnP (Universal Plug and Play) and how it all works, you can read the UPnP Device Architecture specification. Yes, it’s a lot of reading, but it explains everything you will need to know to get started with UPnP.
The goal of my package, node-sonos-client
, is to provide an easy method
of controlling Sonos wireless speakers on a network. My first problem to
solve was automatic discovery of the speakers. After doing a little digging on
the “cyberspace”, it turns out that Sonos speakers operate on UPnP. Bingo!
UPnP, or Universal Plug and Play, is an open standard that allows networked devices to discover each others’ presence and communicate with, or control, one another. The discovery is the bit that I needed, and in order to do it I had to venture into an area that I know next-to-nothing about - networking protocols. UDP, specifically.
What the Heck is UDP?
UDP, or User Datagram Protocol, is a layer on top of the Internet Protocol (ya know, IP?) like TCP, or Transmission Control Protocol. They differ in very big ways, and I won’t go into detail here. To put it simply (I’m terrible at analogies, so I’ll just use one that was given in a Stack Overflow answer):
Think of TCP as a dedicated scheduled UPS/FedEx pickup/dropoff of packages between two locations, while UDP is the equivalent of throwing a postcard in a mailbox.
UPS/FedEx will do their damndest to make sure that the package you mail off gets there, and get it there on time. With the post card, you’re lucky if it arrives at all, and it may arrive out of order or late (how many times have you gotten a postcard from someone AFTER they’re gotten home from the vacation?)
TCP is as close to a guaranteed delivery protocol as you can get, while UDP is just “best effort”.
—Marc B, Difference between TCP and UDP? on StackOverflow
UDP is a more efficient protocol, because it’s less reliable and requires less overhead, so it’s commonly used for real-time tasks, such as streaming audio and video. Now that we know what UDP is all about, how do we communicate using UDP with Node.js?
Using UDP
Node.js has a built-in module, dgram
(datagram), for communicating over UDP.
All communication is sent and received as a Buffer
.
To start sending or receiving messages, you first have to create a socket. This
can either be an IPv4 socket (udp4
), or an IPv6 socket (udp6
). Once a socket
has been created, you can start sending messages over UDP.
import dgram from 'dgram';
const socket = dgram.createSocket('udp4');
const message = new Buffer('Hello World!');
function send() {
socket.send(message, 0, message.length, 59999, 'localhost');
}
setInterval(send, 5000);
send();
The code above shows how to send messages over UDP. When sending a message, you
must provide the message (as a Buffer
), the starting offset in the buffer
where the message begins, the message length, the destination port, and the
destination IP address. You can also optionally provide a callback that gets
called once the message has been sent.
When using send
, if a port hasn’t been bound already, the OS will choose a
random port to be bound.
import dgram from 'dgram';
const socket = dgram.createSocket('udp4');
socket.on('message', (message, rinfo) => {
console.log(`${rinfo.address}: ${message.toString()}`);
});
socket.bind(59999);
To listen to messages, you need to explicitly bind a port to listen to. You can
provide a specific port, or use 0
, which will tell the OS to bind a random
port. You can also optionally provide an interface to bind to, otherwise the OS
will automatically bind to “all interfaces” (0.0.0.0
for udp4
, and ::0
for
udp6
).
Sockets emit the following events:
listening
- Emitted right when a socket is bound to a port. Callingbind
on a socket will emit this event, for instance.message
- Emitted when a new message is available on the socket. This event contains the message received (as aBuffer
), as well as anrinfo
object, which contains the address and port the message was sent from.close
- Emitted when theclose
method is called.error
- Emitted when some form of error occurs with the socket.
Try playing around with the code in sender.js
and listener.js
and run them
concurrently in babel-node
.
What’s Next
Now that we know how to communicate over UDP, we need to broadcast and receive SSDP messages over the network so we can discover devices and keep track of their status. We’ll discuss how to do this in the second part of this series, so keep an eye out for it.