Writing UDP Client/Server Application with Node.js
Submitted by admin on Friday, December 23, 2016 - 22:38.
Writing UDP server with Node.js
Different application require different design of networking protocols. Everything that is built for web is using HTTP (recently WebSockets and WebRTC too for real-time data). If your clients are not browsers and you need reliable and guaranteed channel your choice is TCP/IP. There's however one more protocol that is often used for the cases when the speed is more important that delivery guarantees and ordering: UDP.
UDP stands for Unified Datagram Protocol and it shines in the applications like fast-paced online gaming. UDP adds just a tiny layer on top of IP. It does not guarantee the delivery of messages or the particular order of delivery. In fact the only thing that is guaranteed is an integrity of a message. If you sent 250 bytes from the clients once (and if) they arrive to the server, you will see exactly the same 250 bytes. What comes as a reward for easing the protocol restrictions is the speed. Everything that requires speed of deliver above all is a good candidate for UDP.
In this article I will show how to build a simple UDP client and server. The application will be an improvised stock monitor. A server will be sending data about some fantasy-world stocks to the client and it will be client's responsibility to track if the message that arrived is the most recent one. If client detects the message that arrived out of order, it can simply discard that message (since we are not interested in outdated stock prices).
Let's start from the server.
The great thing about this snippet is that it demonstrates all the essential features of any network server application: binding to the port, accepting incoming messages, responding back and reacting on errors.
Now that we have a server, let's build a client for it.
If you run the server and then a client, you will see that they exchange messages. Now let's compare this code to a typical TCP/IP server. First thing that you'll notice is that there is no concept of "connection" or "socket". You receive the message from some remote socket but you can't tell if this socket is still alive and listening. Even if you send a message, you will not be able to check that unless client sends back an acknowledgement or a heartbeat.
Conduct a small experiment now: change the port address on client and try sending the message again. You'll see that even though there's no UDP server listening on that port, you don't get any errors. Bottomline is: if you want to have any additional guarantees like message delivery notifications or list of connected clients, you will have to do it yourself.
That's what we will do in the next fragment of code. In order to send stock data to all connected clients we will have to keep the list of their hosts and ports on our server. We will not be checking if they are still alive and listening: just keeping the registry.
Here's how our renewed server code looks like:
We added setInterval() to send the randomized stock price data each second. For this example, I designed a very simple text-based protocol. I send the message as a comma-separated list of fields. First one being the price of the stock, second one being the server time, when this price was sent.
Re-run both client and server, you'll see that client is now displaying the messages from the server! Now let's complete our small example by making client a little bit smarter. The client will now parse the data and discard the price if it saw the more recent message.
Now client parses the messages (yay! we got our own custom protocol!) and it also checks if the message should be ignored since we already got a fresher one. If you run this example locally, you will not see many outdated messages, but deploy this example to a distant server and increase the frequency of messages, you'll see that some of them are discarded.
Congratulations, you have just finished your UDP server in Node.js!
- const dgram = require('dgram');
- const server = dgram.createSocket('udp4');
- const host = '0.0.0.0';
- const port = 41100;
- server.on('error', (err) => {
- console.log(err.stack);
- server.close();
- });
- server.on('message', (msg, rinfo) => {
- console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`);
- const reply = new Buffer('Got [' + msg + ']');
- server.send(reply, 0, reply.length, rinfo.port, rinfo.address, (err, bytes) => {
- if (err){
- console.log(err.stack);
- }
- });
- });
- server.on('listening', () => {
- const address = server.address();
- console.log(`server listening ${address.address}:${address.port}`);
- });
- server.bind(port, host);
- const dgram = require('dgram');
- const client = dgram.createSocket('udp4');
- const host = '0.0.0.0';
- const port = 41100;
- const message = new Buffer('Hello Server');
- client.on('message', (message, remote) => {
- console.log('Server: ' + message);
- });
- client.send(message, 0, message.length, port, host, (err, bytes) => {
- if (err) {
- throw err;
- }
- console.log('Message sent');
- });
- const dgram = require('dgram');
- const server = dgram.createSocket('udp4');
- const host = '0.0.0.0';
- const port = 41100;
- const clients = [];
- server.on('error', (err) => {
- console.log(err.stack);
- server.close();
- });
- server.on('message', (msg, rinfo) => {
- console.log(`Connected client at ${rinfo.address}:${rinfo.port}`);
- clients.push(rinfo);
- });
- server.on('listening', () => {
- const address = server.address();
- console.log(`server listening ${address.address}:${address.port}`);
- });
- setInterval(() => {
- const price = Math.floor(1000+ Math.random()*100);
- const time = Date.now();
- const data = new Buffer(price + ',' + time);
- clients.forEach((rinfo) => {
- server.send(data, 0, data.length, rinfo.port, rinfo.address, (err, bytes) => {
- if (err){
- console.log(err.stack);
- }
- });
- })
- }, 1000);
- server.bind(port, host);
- const dgram = require('dgram');
- const client = dgram.createSocket('udp4');
- const host = '0.0.0.0';
- const port = 41100;
- const message = new Buffer('Hello Server');
- const parseTick = (message) => {
- const parts = message.toString().split(',').map((part) => +part);
- return {
- price: parts[0],
- time: parts[1]
- }
- };
- let latestTickTime = -1;
- client.on('message', (message, remote) => {
- const tick = parseTick(message);
- if (tick.time > latestTickTime) {
- console.log('Price is', tick.price);
- latestTickTime = tick.time
- } else {
- console.log('Price is outdated, discard');
- }
- });
- client.send(message, 0, message.length, port, host, (err, bytes) => {
- if (err) {
- throw err;
- }
- console.log('Message sent');
- });
Comments
Add new comment
- Add new comment
- 1560 views