DEV Community

Cover image for Fun Curiosities 1 : Exploring Non-Persistent and Persistent TCP Connections using Node.js
Sabarish M
Sabarish M

Posted on

Fun Curiosities 1 : Exploring Non-Persistent and Persistent TCP Connections using Node.js

I was just exploring the evolution of HTTP and then came to know that up to HTTP/1.0 non-persistent connections were used, which means that earlier versions of web had to establish new connection for each request.

Let me take you through the steps :

1. Server Listening: First server creates a socket for listening new connections from clients. This is called Listening Socket.
2. Client initiates: Client creates a socket in the OS and initiates the connection to the server.
3. TCP handshake: Now the 3-way TCP handshake happens.
Client sends the SYN to the server, telling that I want to initiate the connection and my sequence number is x. Then server acknowledges it and sends it sequence number. Server sends SYN + ACK, seq = y, ack = x+1. Client confirms by responding with ACK, ack= y+1.

3-way tcp handshake

After this, server creates a new connection socket for this client and listening socket stills exists for listening other new clients.

Listening socket — accepts new client
Connection socket — communication with one client
Enter fullscreen mode Exit fullscreen mode

4. Client request: Client/Browser sends request to the server.

client request

5. Server response: Server gives response corresponding to the client’s request.

server response

6. TCP termination: 4 step termination process takes place. Server sends FIN to client, telling I won’t send any more data. Client acknowledges it by responding with ACK. Then once client completed sending its data it sends FIN to the server. Server acknowledges with ACK, then closes the connection.

tcp 4-way termination

// Non Persistent Connection
// server.ts
import net from 'net';
import type {Socket} from 'net';

type MySocket = Socket & {id: number}

let socketCounter = 0;

const server = net.createServer((socket: Socket) => {
    const mySocket = socket as MySocket;

    mySocket.id = ++socketCounter;
    console.log('Client connection successful');
    console.log('Socket: ', mySocket.id);
    mySocket.on('data', (data: Buffer) => {
        console.log('Message from client: ', data.toString());
        mySocket.write(data.toString());
        mySocket.end();
    })
    mySocket.on('end', () => {
        console.log(`Socket ${mySocket.id} stops receiving data`);
    })
    mySocket.on('close', (hadError: boolean) => {
        console.log(`Socket ${mySocket.id} closed`, hadError ? 'due to error' : '');
    })
    mySocket.on('error', (err) => {
        console.log('Error: ', err.message);
    })
})

server.listen(3000, () => {
    console.log('Server listening at port 3000');
})
/*
Logs:
Server listening at port 3000
Client connection successful
Socket:  1
Client connection successful
Socket:  2
Client connection successful
Socket:  3
Client connection successful
Socket:  4
Client connection successful
Socket:  5
Message from client:  Luffy
Socket 1 stops receiving data
Socket 1 closed
Message from client:  Zoro
Message from client:  Nami
Message from client:  Sanji
Message from client:  Robin
Socket 2 stops receiving data
Socket 2 closed
Socket 3 stops receiving data
Socket 4 stops receiving data
Socket 5 stops receiving data
Socket 5 closed
Socket 4 closed
Socket 3 closed
*/

// client.ts

import net from 'net';

let socketCounter = 0;

function sendRequest(message: string){
    const client = net.createConnection({port: 3000}, () => {
        const clientId = ++socketCounter;
        console.log(`Socket ID ${clientId} successful`);
        client.write(message);
        client.end();
        client.on('data', (data: Buffer) => {
            console.log('Response from server: ', data.toString());
        })
        client.on('end', () => {
            console.log(`Socket ID ${clientId} stops receiving data`);
        })
        client.on('close', (hadError: boolean) => {
            console.log(`Connection closed ID ${clientId}`, hadError ? 'due to error' : '');
        })
        client.on('error', (err) => {
            console.log('Error: ', err.message);
        })
    })
}
sendRequest('Luffy');
sendRequest('Zoro');
sendRequest('Sanji');
sendRequest('Nami');
sendRequest('Robin');
/*
Logs:
Luffy send by Client 1
Zoro send by Client 1
Sanji send by Client 1
Socket closing initiated for Client 1
Response from server:  LuffyZoroSanji
Server stopped sending for Client 1
Socket closed for Client 1
PS F:\my-grit\fun> node .\dist\non-persistent-connection\client.js
Socket ID 1 successful
Socket ID 2 successful
Socket ID 3 successful
Socket ID 4 successful
Socket ID 5 successful
Response from server:  Luffy
Socket ID 1 stops receiving data
Connection closed ID 1
Response from server:  Zoro
Response from server:  Nami
Response from server:  Sanji
Response from server:  Robin
Socket ID 2 stops receiving data
Socket ID 3 stops receiving data
Socket ID 4 stops receiving data
Socket ID 5 stops receiving data
Connection closed ID 5 
Connection closed ID 4
Connection closed ID 3
Connection closed ID 2
*/
Enter fullscreen mode Exit fullscreen mode

Now imagine a page needs 10 resources like HTML, CSS, JS, images, fonts.

Become a Medium member
Then
10 TCP connections
10 socket pairs
10 handshakes (30 packets)
10 teardowns (40 packets)
Adding to it, TCP is slow start because it begins with small congestion window — large amount of data can’t be sent immediately.

These inefficiencies lead to the persistent connection model.

Persistent model was introduced in version HTTP/1.1 in which 1 TCP connection can be used for multiple request-response cycles.
So, only 1 handshake, 1 socket creation, destruction, 1 time TCP slow start, 1 teardown.

// Persistent connection
// server.ts
import net from 'net';
import type {Socket} from 'net';

type MySocket = Socket & {id: number};

let socketCounter = 0;

const server = net.createServer((socket: Socket) => {

    const mySocket = socket as MySocket;
    mySocket.id = ++socketCounter;

    console.log(`Connection successful Client - Socket ${mySocket.id}`);

    mySocket.on('data', (data: Buffer) => {
        console.log('Message from client: ', data.toString());
        mySocket.write(data);
    })
    mySocket.on('end', () => {
        console.log(`Stopped receiving from Socket ${mySocket.id}`);
        mySocket.end();
    })
    mySocket.on('close', () => {
        console.log(`Socket ${mySocket.id} closed`);
    })
    mySocket.on('error', (err) => {
        console.log(`Error in Socket ${mySocket.id}: `, err.message);
    })
})
server.listen(3000, () => {
    console.log('Server listening on PORT 3000');
})
/*
Logs: 
Server listening on PORT 3000
Connection successful Client - Socket 1
Message from client:  Luffy
Message from client:  ZoroSanji
Stopped receiving from Socket 1
Socket 1 closed
*/

// client.ts
import net from 'net';
import type {Socket} from 'net';

type MyClient = Socket & {id: number};

let clientCounter = 0;
const client = net.createConnection({port: 3000});
const myClient = client as MyClient;

myClient.id = ++clientCounter;

client.on('data', (data: Buffer) => {
    console.log(`Response from server: `, data.toString());
})
client.on('end', () => {
    console.log(`Server stopped sending for Client ${myClient.id}`);
})
client.on('close', () => {
    console.log(`Socket closed for Client ${myClient.id}`);
})
client.on('error', (err) => {
    console.log(`Error for Client ${myClient.id}: `, err.message);
})

function sendRequest(client: MyClient, message: string){
    client.write(message);
    console.log(`${message} send by Client ${myClient.id}`);
}

function closeConnection(client: MyClient){
    client.end();
    console.log(`Socket closing initiated for Client ${client.id}`);
}
sendRequest(myClient, 'Luffy');
sendRequest(myClient, 'Zoro');
sendRequest(myClient, 'Sanji');
closeConnection(myClient);
/*
Logs: 
Luffy send by Client 1
Zoro send by Client 1
Sanji send by Client 1
Socket closing initiated for Client 1
Response from server:  LuffyZoroSanji
Server stopped sending for Client 1
Socket closed for Client 1
*/
Enter fullscreen mode Exit fullscreen mode

All the above code was done using sockets, I had only basic idea about it till now. Then I became curious and decided to dig deeper into it.

socket

What are sockets?
Sockets are just data structure created by OS kernel representing 1 endpoint of a connection. It provides a way for the user programs to interact with Networking stack.

A socket typically contains

local IP
local port
remote IP
remote port
protocol (tcp/udp)
send buffer
receive buffer
etc (other tcp reliability and congestion variables.)

So while creating connections, Kernel creates and binds the socket and provide generic system calls to access it.

If there is any mistake, forgive me — it is not intentional. Kindly bring it to my notice. We discuss, share knowledge, and help each other.

Top comments (0)