← Back to all posts How I Built a P2P File Sharing System (And What It Taught Me About Networking)

How I Built a P2P File Sharing System (And What It Taught Me About Networking)

Most Python projects I'd built before this one were web apps. Request comes in, database query goes out, response comes back. Clean, predictable, well-documented. The P2P file sharing system was none of those things — and it was the hardest, most educational project I've built.

What the project actually does

A decentralized file transfer system where two peers connect directly and exchange files — no central server involved. Key features:

  • Socket-based peer-to-peer connection over TCP
  • AES encryption for file data in transit
  • Diffie-Hellman key exchange — peers agree on a shared encryption key without ever sending that key over the network
  • 64KB chunked file transfer — large files are sent in pieces rather than loaded into memory all at once
  • Connection management and data integrity checks
  • CustomTkinter GUI for non-technical users

I built this as team lead for a group of five — which meant breaking the system into workable pieces, assigning tasks, reviewing code, and making sure everything connected properly at integration time.

What sockets actually are

Before this project, sockets were something I could define on an exam but had never actually used. A socket is a communication endpoint — think of it as a phone number for a process. You open a socket, bind it to a port, listen for incoming connections, and then read/write data through it like a file.

import socket

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 9999))
server.listen(1)
conn, addr = server.accept()
print(f"Connected by {addr}")

The client side connects to that address and port. Once connected, both sides can send and receive data. That's it. What felt like networking magic in theory became completely concrete the moment I watched two programs actually talk to each other through code I'd written.

The bigger realization: HTTP is just text over a socket. REST APIs are just structured text over HTTP. Every abstraction I'd been working with was sitting on top of something I now understood at a level I hadn't before. Building one layer below where you normally work changes how you understand everything above it.

Why chunked transfer matters

If you try to send a 500MB file through a socket in one operation, you'll either run out of memory or exceed socket buffer limits almost immediately. The solution is to read and send the file in small pieces:

CHUNK_SIZE = 65536  # 64KB

with open(filepath, 'rb') as f:
    while chunk := f.read(CHUNK_SIZE):
        conn.sendall(encrypt(chunk))

The receiver reads the same chunk size, decrypts each piece, and writes it to an output file. The complete file is never in memory at once — just 64KB at a time. This is the same principle behind HTTP chunked transfer encoding, video streaming, and most large file download implementations. I understood it abstractly before this project. After implementing it, I understood it properly.

Diffie-Hellman — agreeing on a secret without sharing it

The hard problem with encryption over a network: how do two parties agree on an encryption key without sending that key over an insecure connection? If you send "here's our key" over the network, anyone watching can see it.

Diffie-Hellman solves this through a mathematical exchange where both sides contribute to a shared result using information they each keep private. An eavesdropper who sees every message exchanged during the key agreement process still can't compute the final shared key — because the key is never transmitted. It's derived independently by each side from their private value and the other side's public value.

Understanding this concept — not just using a library that implements it — was one of those moments where cryptography stopped being magic and became math I could actually follow. That understanding made TLS, HTTPS, and certificate exchanges make sense in a way they hadn't before.

What leading a team of five actually taught me

The technical parts were hard. The coordination was harder.

Breaking a complex system into pieces that five people can build independently, then integrating those pieces without everything breaking, requires a different skill from writing code. The most important thing I did upfront: define interfaces clearly before anyone wrote a line of implementation. What does the encryption module take as input? What does it return? What format does the file transfer function expect?

When interfaces are clear, people can build their pieces in parallel and integration mostly works. When interfaces aren't clear, integration becomes a negotiation about who needs to change their code — and everyone's already attached to what they built.

Things that helped: regular check-ins specifically about blockers (not progress updates — blockers), being willing to rewrite my own code when it became the integration bottleneck, and keeping a shared document of design decisions so we didn't relitigate the same questions twice.

Things that didn't help: assuming everyone had the same mental model of the system without explicitly writing it down.

Why I recommend building something like this

If you've only built web apps and CRUD projects, build something that works at the network level — not because socket programming is a skill you'll use daily, but because it forces you to understand what's actually happening underneath the abstractions you normally rely on.

Every framework you use, every protocol you work with, every "it just works" you've taken for granted — there's real engineering underneath it. Building at a lower level once makes you a better user of the higher-level tools forever.

Build the unglamorous projects. They teach you the things that matter.