TCP/UDP

TCP and UDP are the two low-level network protocols that we have access to in Node.js. Either of these allows us to send and receive messages, but they differ in a couple of key areas. First, TCP needs to have a receiver and sender for the connection. Because of this, we can't just broadcast on a channel and not care whether anybody is listening.

Second, on top of TCP needing the handshake process, it also gives us guaranteed transmission. This means that we know when we send data that it should get to the other end (obviously, there are ways for this to fail, but we aren't going to look at that). Finally, TCP guarantees the order of delivery. If we send data on a channel to a receiver, it will get the data in the order that we sent it. Because of these reasons, we utilize TCP when we need to guarantee delivery and ordering.

TCP actually doesn't necessarily need to send the data in order. Everything is sent in packets. They can actually go to different servers and the routing logic can mean that later packets arrive at the receiver earlier than later ones. However, our receiver's network card reorders them for us so that it looks like we are getting them sequentially. There are many other cool aspects that go into TCP, including the transmission of data, that are outside the scope of this book, but anyone can look up networking and look at more of these concepts and how they are implemented.

This being said, TCP seems like something that we would always want to use. Why wouldn't we use something that guarantees delivery? Also, we don't necessarily need to broadcast if we can just loop through all of the current connections and send the data to everyone. However, because of all of these guarantees, this makes TCP heavier and slower. This isn't good for systems that we need to send data as fast as possible over. For this type of data transmission, we can utilize UDP. UDP gives us something called stateless transmission. Stateless transmission means we can send data on a channel and it will blast the data out and forget it. We don't need to connect to an address; instead, we can just send the data (as long as no one else is bound to that address and port). We can even set up a multicast system where anyone can listen to that address and it might pick up the data.

Some areas where this type of transmission is wanted/needed are as follows:

  • Send buy/sell orders for a stock exchange. Since the data moves fast, we only care about the latest information. Due to this, if we don't receive some of the buy/sell orders, it doesn't really matter.
  • Player position data for video games. We can only update the game so fast. We can also interpolate or infer where a player is on the screen if we already know which direction they were moving and the speed that they were going at. Because of this, we can receive a player position at any rate and figure out where they should be (this is sometimes known as the tick rate of the server).
  • Telecommunication data does not necessarily care if we send all of the data as long as we sent most of it. We don't need to guarantee delivery of the full video/audio signal since we can still give a great picture with most of the data.

These are just a couple of the areas where UDP comes in handy. With an understanding of both these systems, we will take a look at them by building a highly simplified and impractical stock application. The behavior will be as follows:

  1. The server will post new stock symbols and the amount of stock that is available. Then, it will blast the information on a known port to everyone over UDP.
  2. The server will store all of the information related to a client's positions. This way, there is no way for a client to be able to manipulate how many shares they may have.
  3. A client will send a buy or sell order to the server. The server will figure out whether it can handle the request. All of this traffic will be over TCP since we need to guarantee that we know the server received our message.
  4. The server will respond with an error or a success message, telling the client that their book has updated.
  5. The server will blast that a buy or sell happened for stock over the UDP channel.

This application looks as follows:

import dgram from 'dgram';
import { Socket } from 'net';
const multicastAddress = '239.192.0.0';
const sendMessageBadOutput = 'message needs to be formatted as follows: BUY|SELL <SYMBOL> <NUMBER>';
const recvClient = dgram.createSocket({type : 'udp4', reuseAddr: true }); //1.
const sendClient = new Socket().connect(3000, "127.0.0.1");
// receiving client code seen below
process.stdin.setEncoding('utf8');
process.stdin.on('data', (msg) => {
const input = msg.split(' ');
if( input.length !== 3 ) {
console.log(sendMessageBadOutput);
return;
}
const num = parseInt(input[2]);
if( num.toString() === 'NaN' ) {
console.log(sendMessageBadOutput);
return;
}
sendClient.write(msg);
});
sendClient.on('data', (data) => {
console.log(data.toString('utf8'));
});

Most of the preceding program should be familiar, except for the new module that we are utilizing: the dgram module. This module allows us to send data while utilizing UDP.

Here, we are creating a socket that is utilizing UDP4 (UDP over IPv4, or what we normally know as IP addresses). We also state that we are reusing the address and port. We're doing this so that we can test this locally. We wouldn't want this in any other scenario:

recvClient.on('connect', () => {
console.log('client is connected to the server');
});
recvClient.on('message', (msg) => {
console.log('client received message', msg.toString('utf8'));
});
recvClient.bind(3000, () => {
recvClient.addMembership(multicastAddress);
});

We bind to port 3000 since that is where the server is going to send data. Then, we state that we want to add ourselves to the multicast address. For multicasting to work, the server needs to send data over a multicast address. These addresses are usually specific addresses that the OS has set up. Each OS can decide what addresses to use, but the one we have chosen should work on any OS.

Once we receive a message, we print it out. Again, this should look familiar. Node.js is based around events and streams and they are usually named the same thing for consistency.

The other pieces of this program handle user input and then send data over the TCP channel that we opened up when we created a new socket (this should look similar to our IPC program from before, except we pass a port and an IP address).

The server for this application is a bit more involved since it holds all of the logic of the stock application. We will break this down into several steps:

  1. Create a file called main.js and import the dgram and net modules into it:
import dgram from 'dgram';
import net from 'net';
  1. Add some constants for our multicast address, the error message for bad input, and the Maps for our stock tickers and clients:
const multicastAddress = '239.192.0.0';
const badListingNumMessage = 'to list a new ticker the following format needs to be followed <SYMBOL>
<NUMBER>';
const symbolTable = new Map();
const clientTable = new Map();
  1. Next, we create two servers. The first is used to listen for UDP messages, while the second is used to receive TCP messages. We will utilize the TCP server to process client requests. TCP is reliable, while UDP isn't:
const server = dgram.createSocket({type : 'udp4', reuseAddr : true}).bind(3000);
const recvServer = net.createServer().listen(3000, '127.0.0.1');
  1. Then, we need to set up a listener on the TCP server for any connections. Once we have a client connection, we will set them up with a temporary table so that we can store their portfolio:
recvServer.on('connection', (socket) => {
const temp = new Map();
clientTable.set(socket, temp);
});
  1. Now, set up a data listener for the client. When we receive data, we will parse the message according to the following format, SELL/BUY <Ticker> <Number>:
// inside of the connection callback for recvServer
socket.on('data', (msg) => {
const input = msg.toString('utf8').split(' ');
const buyOrSell = input[0];
const tickerSymbol = input[1];
const num = parseInt(input[2]);
});
  1. Based on this parsing, we check to see whether the client can perform the action. If they can, we will change their portfolio and send them a message stating the change was successful:
// inside the socket 'data' handler
const numHeld = symbolTable.get(input[1]);
if( buyOrSell === "BUY" && (num <= 0 || numHeld - num <= 0) ) {
socket.write("ERROR!");
return;
}
const clientBook = clientTable.get(socket);
const clientAmount = clientBook.get(tickerSymbol);
if( buyOrSell === "SELL" && clientAmount - num < 0 ) {
socket.write("ERROR!");
return;
}
if( buyOrSell === "BUY" ) {
clientBook.set(tickerSymbol, clientAmount + num);
symbolTable.set(tickerSymbol, numHeld - num);
} else if( buyOrSell === "SELL" ) {
clientBook.set(tickerSymbol, clientAmount - num);
symbolTable.set(tickerSymbol, numHeld + num);
}
socket.write(`successfully processed request. You now hold ${clientBook.get(tickerSymbol)}` of ${tickerSymbol}`);
  1. Once we have told the client that we have processed their request, we can write to all of the clients through the UDP server:
// after the socket.write from above
const msg = Buffer.from(`${tickerSymbol} ${symbolTable.get(tickerSymbol)}`);
server.send(msg, 0, msg.byteLength, 3000, multicastAddress);
  1. Finally, we need to process new stock tickers from the server through our standard input. Once we have processed the request, we send the data out on the UDP server so that every client knows about the new stock:
process.stdin.setEncoding('utf8');
process.stdin.on('data', (data) => {
const input = data.split(' ');
const num = parseInt(input[1]);
symbolTable.set(input[0], num);
for(const client of clientTable) {
client[1].set(input[0], 0);
}

server.send(Buffer.from(data), 0, data.length, 3000, multicastAddress);
});

Almost all of the error logic has been removed for clarity, but you can find it in this book's GitHub repository. As shown in the preceding example, it is very simple to utilize all of the interfaces for sending data to various other points, be it other parts of our application or remote clients that are listening for data. They all use almost the exact same interface and only differ in slight implementation details. Just remember that if there needs to be a guarantee of delivery, TCP should be used; otherwise, UDP isn't a bad choice.

Next, we will look at the HTTP/2 standard and how the server system is a bit different in Node.js compared to the net, dgram, and http/https modules.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.136.233.153