• We just launched and are currently in beta. Join us as we build and grow the community.

Writing UDP Client/Server Application with Node.js

TheSulnac78

Low-Level Programming Expert
T Rep
0
0
0
Rep
0
T Vouches
0
0
0
Vouches
0
Posts
100
Likes
195
Bits
2 MONTHS
2 2 MONTHS OF SERVICE
LEVEL 1 100 XP
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.

  1. const

    dgram =

    require(

    'dgram'

    )

    ;
  2. const

    server =

    dgram.createSocket

    (

    'udp4'

    )

    ;

  3. const

    host =

    '0.0.0.0'

    ;
  4. const

    port =

    41100

    ;

  5. server.on

    (

    'error'

    ,

    (

    err)

    =>

    {
  6. console.log

    (

    err.stack

    )

    ;
  7. server.close

    (

    )

    ;
  8. }

    )

    ;

  9. server.on

    (

    'message'

    ,

    (

    msg,

    rinfo)

    =>

    {
  10. console.log

    (

    `server got:

    ${

    msg}

    from ${

    rinfo.address

    }

    :

    ${

    rinfo.port

    }

    `)

    ;

  11. const

    reply =

    new

    Buffer(

    'Got ['

    +

    msg +

    ']'

    )

    ;
  12. server.send

    (

    reply,

    0

    ,

    reply.length

    ,

    rinfo.port

    ,

    rinfo.address

    ,

    (

    err,

    bytes)

    =>

    {
  13. if

    (

    err)

    {
  14. console.log

    (

    err.stack

    )

    ;
  15. }
  16. }

    )

    ;
  17. }

    )

    ;

  18. server.on

    (

    'listening'

    ,

    (

    )

    =>

    {
  19. const

    address =

    server.address

    (

    )

    ;
  20. console.log

    (

    `server listening ${

    address.address

    }

    :

    ${

    address.port

    }

    `)

    ;
  21. }

    )

    ;

  22. server.bind

    (

    port,

    host)

    ;

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.

  1. const

    dgram =

    require(

    'dgram'

    )

    ;
  2. const

    client =

    dgram.createSocket

    (

    'udp4'

    )

    ;

  3. const

    host =

    '0.0.0.0'

    ;
  4. const

    port =

    41100

    ;

  5. const

    message =

    new

    Buffer(

    'Hello Server'

    )

    ;

  6. client.on

    (

    'message'

    ,

    (

    message,

    remote)

    =>

    {
  7. console.log

    (

    'Server: '

    +

    message)

    ;
  8. }

    )

    ;

  9. client.send

    (

    message,

    0

    ,

    message.length

    ,

    port,

    host,

    (

    err,

    bytes)

    =>

    {
  10. if

    (

    err)

    {
  11. throw

    err;
  12. }

  13. console.log

    (

    'Message sent'

    )

    ;
  14. }

    )

    ;

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:

  1. const

    dgram =

    require(

    'dgram'

    )

    ;
  2. const

    server =

    dgram.createSocket

    (

    'udp4'

    )

    ;

  3. const

    host =

    '0.0.0.0'

    ;
  4. const

    port =

    41100

    ;

  5. const

    clients =

    [

    ]

    ;

  6. server.on

    (

    'error'

    ,

    (

    err)

    =>

    {
  7. console.log

    (

    err.stack

    )

    ;
  8. server.close

    (

    )

    ;
  9. }

    )

    ;

  10. server.on

    (

    'message'

    ,

    (

    msg,

    rinfo)

    =>

    {
  11. console.log

    (

    `Connected client at ${

    rinfo.address

    }

    :

    ${

    rinfo.port

    }

    `)

    ;
  12. clients.push

    (

    rinfo)

    ;
  13. }

    )

    ;

  14. server.on

    (

    'listening'

    ,

    (

    )

    =>

    {
  15. const

    address =

    server.address

    (

    )

    ;
  16. console.log

    (

    `server listening ${

    address.address

    }

    :

    ${

    address.port

    }

    `)

    ;
  17. }

    )

    ;

  18. setInterval(

    (

    )

    =>

    {
  19. const

    price =

    Math

    .floor

    (

    1000

    +

    Math

    .random

    (

    )

    *

    100

    )

    ;
  20. const

    time =

    Date

    .now

    (

    )

    ;
  21. const

    data =

    new

    Buffer(

    price +

    ','

    +

    time)

    ;

  22. clients.forEach

    (

    (

    rinfo)

    =>

    {
  23. server.send

    (

    data,

    0

    ,

    data.length

    ,

    rinfo.port

    ,

    rinfo.address

    ,

    (

    err,

    bytes)

    =>

    {
  24. if

    (

    err)

    {
  25. console.log

    (

    err.stack

    )

    ;
  26. }
  27. }

    )

    ;
  28. }

    )
  29. }

    ,

    1000

    )

    ;

  30. server.bind

    (

    port,

    host)

    ;

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.

  1. const

    dgram =

    require(

    'dgram'

    )

    ;
  2. const

    client =

    dgram.createSocket

    (

    'udp4'

    )

    ;

  3. const

    host =

    '0.0.0.0'

    ;
  4. const

    port =

    41100

    ;

  5. const

    message =

    new

    Buffer(

    'Hello Server'

    )

    ;

  6. const

    parseTick =

    (

    message)

    =>

    {
  7. const

    parts =

    message.toString

    (

    )

    .split

    (

    ','

    )

    .map

    (

    (

    part)

    =>

    +

    part)

    ;
  8. return

    {
  9. price:

    parts[

    0

    ]

    ,
  10. time:

    parts[

    1

    ]
  11. }
  12. }

    ;

  13. let latestTickTime =

    -

    1

    ;

  14. client.on

    (

    'message'

    ,

    (

    message,

    remote)

    =>

    {
  15. const

    tick =

    parseTick(

    message)

    ;

  16. if

    (

    tick.time

    >

    latestTickTime)

    {
  17. console.log

    (

    'Price is'

    ,

    tick.price

    )

    ;
  18. latestTickTime =

    tick.time
  19. }

    else

    {
  20. console.log

    (

    'Price is outdated, discard'

    )

    ;
  21. }
  22. }

    )

    ;

  23. client.send

    (

    message,

    0

    ,

    message.length

    ,

    port,

    host,

    (

    err,

    bytes)

    =>

    {
  24. if

    (

    err)

    {
  25. throw

    err;
  26. }

  27. console.log

    (

    'Message sent'

    )

    ;
  28. }

    )

    ;

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!


Download
You must upgrade your account or reply in the thread to view the hidden content.
 

452,496

337,656

337,664

Top