Congratulations on getting this far! We've now built a secure file downloading tool that can discover servers running anywhere on the internet sharing the file we're looking for.
However there are still many ways we can improve our program. Right now it's only possible to download the enitre file from a server. This is not very distributed. In p2p systems, it's normal to fetch small parts (called chunks) of the file from different peers at the same time. This distributes the load of downloading.
To do this however we need to change our small program a little bit:
There's a Node module that can read and write the file in fixed size chunks. It's called fs-chunk-store.
To create the message protocol, let's use the Node module called msgpack5-stream. Using this moudule, it's really easy to encode complex JavaScript objects into a binary form that we can send over TCP and then decode on the other side as illustrated below.
Client:
var stream = msgpack(socket) // wrap server TCP socket with msgpack-stream
stream.write({hello: 'world'}) // send JavaScript object over the socket
Server:
var stream = msgpack(socket) // wrap client TCP socket with msgpack-stream
// emitted every time a message is received on the socket
stream.on('data', function (obj) {
console.log(obj) // {hello: 'world'}
})
Invent a protocol we can use to both request a chunk of a file and respond with the requested chunk. Feel free to come up with your own protocol, but here's our suggestion for reference:
// requesting chunk
{type: 'request', index: <chunk-num>}
// responding with chunk
{type: 'response', index: <chunk-num>, data: <buffer>}
Here's a basic server and client that understands msgpack. Your job is to fill in the missing parts indicated by the TODO
comments.
Server:
var fs = require('fs')
var net = require('net')
var DC = require('discovery-channel')
var msgpack = require('msgpack5-stream')
var fsChunkStore = require('fs-chunk-store')
var id = process.argv[2]
var filename = process.argv[3]
if (!id || !filename) {
console.log('Usage: node server.js [id] [filename]')
process.exit(1)
}
var CHUNK_SIZE = 1024 // Arbitrary chunk size that fits in memory (not too big, not too small)
var FILE_LENGTH = fs.statSync(filename).size
var file = fsChunkStore(CHUNK_SIZE, {path: filename, length: FILE_LENGTH})
var channel = DC({dht: false}) // set true to work over the internet
var server = net.createServer(function (socket) {
console.log('New peer connected: %s:%s', socket.remoteAddress, socket.remotePort)
// Wrap our TCP socket with a msgpack5 protocol wrapper
var protocol = msgpack(socket)
protocol.on('data', function (msg) {
// TODO: Respond to request messages with the requested chunk
})
})
server.listen(function () {
channel.join(id, server.address().port)
console.log('Sharing %s as %s', filename, id)
})
Client:
var fs = require('fs')
var net = require('net')
var DC = require('discovery-channel')
var msgpack = require('msgpack5-stream')
var id = process.argv[2]
var chunk = process.argv[3]
if (!id || !chunk) {
console.log('Usage: node client.js [id] [chunk]')
process.exit(1)
}
var channel = DC({dht: false}) // set true to work over the internet
channel.join(id)
channel.once('peer', function (peerId, peer, type) {
console.log('New peer %s:%s found via %s', peer.host, peer.port, type)
var socket = net.connect(peer.port, peer.host)
// Wrap our TCP socket with a msgpack5 protocol wrapper
var protocol = msgpack(socket)
protocol.on('data', function (msg) {
// For now just output the message we got from the server
console.log(msg)
})
console.log('Fetching chunk %d from %s...', chunk, id)
// TODO: Send request for `chunk` to server
})
Use the client to ask the server for a specific chunk:
node client.js <channel-id> <chunk-number>
Validate that you get different chunks depending on which <chunk-number>
you request.
When you are done click here to go to the next exercise