Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a TCP chat server with Deno?

One of the demos that really convinced me of the power of Node was the simple TCP chat server that Ryan Dahl presented in this video: https://www.youtube.com/watch?v=jo_B4LTHi3I&t=28m23s

Here's what the code in the demo looked like:

const net = require('net');
const server = net.createServer();

const sockets = [];

server.on('connection', (socket) => {
  sockets.push(socket);

  socket.on('data', (message) => {
    for (const current_socket of sockets) {
      if (current_socket !== socket) {
        current_socket.write(message);
      }
    }
  });

  socket.on('end', () => {
    const index = sockets.indexOf(socket);
    sockets.splice(index, 1);
  });
});

server.listen(8000, () => console.log('tcp server listening on port 8000'));

The only TCP example I found on the Deno website is an echo server that looks like this:

const listener = Deno.listen({ port: 8080 });
console.log("listening on 0.0.0.0:8080");
for await (const conn of listener) {
  Deno.copy(conn, conn);
}

It's nice and compact, but I haven't been able to use Deno.Conn's read and write methods to turn this example into a TCP chat server. Any help would be much appreciated! I also think it would be a useful example to add to the website.

like image 487
Félix Poulin-Bélanger Avatar asked May 16 '20 16:05

Félix Poulin-Bélanger


2 Answers

Use Deno.listen to create the server and Deno.connect to connect to that server.

A simple example of tcp server/client would be:

server.js

const encoder = new TextEncoder();
const decoder = new TextDecoder();

const listener = Deno.listen({ port: 8080 });

console.log("listening on 0.0.0.0:8080");
for await (const conn of listener) {
  // Read message
  const buf = new Uint8Array(1024);
  await conn.read(buf);
  console.log('Server - received:', decoder.decode(buf))
  // Respond
  await conn.write(encoder.encode('pong'))
  conn.close();
}

client.js

const encoder = new TextEncoder();
const decoder = new TextDecoder();

const conn = await Deno.connect({ hostname: "127.0.0.1", port: 8080 })
// Write to the server
await conn.write(encoder.encode('ping'));
// Read response
const buf = new Uint8Array(1024);
await conn.read(buf);
console.log('Client - Response:', decoder.decode(buf))
conn.close();

You can build from here. For a chat server, you'll keep the connection open, and send multiple messages for example.

like image 170
Marcos Casagrande Avatar answered Sep 20 '22 20:09

Marcos Casagrande


Alright, after more playing around, here's my TCP chat server:

const server = Deno.listen({ port: 8000 });
console.log("tcp server listening on port 8000");

const connections: Deno.Conn[] = [];

for await (const connection of server) {
  // new connection
  connections.push(connection);
  handle_connection(connection);
}

async function handle_connection(connection: Deno.Conn) {
  let buffer = new Uint8Array(1024);
  while (true) {
    const count = await connection.read(buffer);
    if (!count) {
      // connection closed
      const index = connections.indexOf(connection);
      connections.splice(index, 1);
      break;
    } else {
      // message received
      let message = buffer.subarray(0, count);
      for (const current_connection of connections) {
        if (current_connection !== connection) {
          await current_connection.write(message);
        }
      }
    }
  }
}

The code looks quite different from the Node version. That said, TCP does not maintain message boundaries and the Deno version makes that explicit by reading into a Uint8Array buffer. That's similar to how Rust's std::net and tokio::net modules handle TCP. Actually, I'm not too sure what the socket.on('data') events represent in Node; it seems like just an arbitrary-length piece of data from the TCP stream.

like image 40
Félix Poulin-Bélanger Avatar answered Sep 17 '22 20:09

Félix Poulin-Bélanger