DEV Community

Ramanand Tomar
Ramanand Tomar

Posted on

Building a Zero-Cloud P2P File Sharing Engine with React Native, TCP Sockets, and UDP Broadcasting

Standard mobile file sharing solutions frequently route local data through intermediary cloud storage infrastructure. Relaying gigabyte-scale payloads through centralized cloud buckets introduces substantial network latency, incurs redundant bandwidth expenditure, and presents clear data isolation risks. To overcome these constraints, I designed and developed ShareConnect, a high-throughput, serverless, peer-to-peer (P2P) file transfer application built with React Native, raw TCP socket streams, UDP subnet broadcasting, and MMKV. This document details the network handshake protocol, chunked streaming pipeline, backpressure management, and architecture required to achieve 30 to 50+ MB/s direct throughput across local network interfaces.


1. Direct Peer Discovery and Handshake Protocol

Establishing an offline transport between two client runtimes without an intermediary signaling server requires a deterministic three-phase lifecycle:

P2P Sockets Architecture Diagram

sequenceDiagram
    autonumber
    actor Receiver as Receiver Node
    actor Sender as Sender Node
    Note over Receiver: Phase 1: Subnet Beacon
    Receiver->>Receiver: Bind Socket to Local Hotspot / WLAN
    Receiver->>Sender: UDP Broadcast on 255.255.255.255:8888
    Note over Receiver,Sender: Phase 2: Visual Cryptographic Handshake
    Receiver->>Receiver: Render QR Payload (IP: 192.168.43.1, Port: 4000, Nonce)
    Sender->>Receiver: Capture QR Payload via Camera Sensor
    Sender->>Sender: Extract Transport Coordinates and Session Key
    Note over Receiver,Sender: Phase 3: Raw TCP Connection Initialization
    Sender->>Receiver: TCP SYN -> Connect to 192.168.43.1:4000
    Receiver-->>Sender: TCP SYN-ACK -> Establish Persistent Socket
    Sender->>Receiver: Emit Handshake Frame (`client_connected`)
    Receiver-->>Sender: Acknowledge Handshake Frame
    Note over Receiver,Sender: Full-Duplex Binary Stream Established

The Three Protocol Stages:

  1. UDP Subnet Discovery (react-native-udp): The receiving device issues periodic datagram beacons across the local broadcast domain (255.255.255.255:8888) to advertise availability.
  2. Visual QR Exchange: Eliminates manual IP configuration and local mDNS discovery latency by embedding endpoint coordinates, port configuration, and an ephemeral session nonce into an optically scannable matrix.
  3. Persistent TCP Socket (react-native-tcp-socket): The sender connects directly to the receiver's socket server over the local IP interface, creating an uninterrupted, full-duplex transmission path.

2. Chunked Binary Streaming Engine

Reading large binary payloads into JavaScript heap memory triggers Out-of-Memory (OOM) termination in the Hermes and V8 runtimes. The transmission pipeline segments the input stream into 64 KB binary frames, streaming them sequentially while enforcing write backpressure at the destination node.

flowchart TD
    Init[Select Source File: 1.5 GB Binary] --> PreFlight[Extract File Stats: Total Chunks, Size, SHA-256 Checksum]
    PreFlight --> HeaderFrame[Transmit Metadata Acknowledgment Frame]
    subgraph Transmission Loop
        HeaderFrame --> Slice[Read 64 KB Block via react-native-fs]
        Slice --> SocketWrite[socket.write: Binary Chunk Frame]
        SocketWrite --> WaitAck{Wait for send_chunk_ack?}
        WaitAck -- ACK Received --> Incr[Advance Window: Index + 1]
        Incr --> BoundaryCheck{All Chunks Dispatched?}
        BoundaryCheck -- No --> Slice
    end
    subgraph Ingestion Loop
        SocketRead[Socket Event: data] --> FrameParse[Extract Chunk Index and Payload]
        FrameParse --> DiskWrite[Append to File Stream via MMKV / Storage Engine]
        DiskWrite --> EmitAck[socket.write: send_chunk_ack]
        EmitAck --> WaitAck
    end
    BoundaryCheck -- Yes --> Finalize[Compute Disk SHA-256 Digest]
    Finalize --> IntegrityVerify{Digest Valid?}
    IntegrityVerify -- Yes --> TargetFile[Mark Complete in Storage]
    IntegrityVerify -- No --> ErrorState[Flag Transmission Corruption]

3. Backpressure Flow Control: Mitigating Buffer Saturation

Over a 5 GHz local Wi-Fi interface, network throughput often surpasses flash memory sequential write speeds. Uncontrolled socket writes lead to rapid expansion of the underlying OS socket buffer, leading to high garbage collection overhead and dropped TCP connections. To prevent this, the client implements explicit frame-level acknowledgment flow control:

// Receiver: TCPUtils.ts
export const receiveFileAck = async (
  metadata: FileMetadata,
  socket: any,
  setReceivedFiles: any,
): Promise<void> => {
  const { setChunkStore } = useChunkStore.getState();

  // Initialize receiving chunk state machine
  setChunkStore({
    id: metadata.id,
    totalChunks: metadata.totalChunks,
    name: metadata.name,
    size: metadata.size,
    mimeType: metadata.mimeType,
    chunkArray: []
  });

  // Signal sender to begin transmitting chunk index 0
  socket.write(JSON.stringify({ event: 'send_chunk_ack', chunkNo: 0 }));
};
Enter fullscreen mode Exit fullscreen mode

The sender waits for send_chunk_ack before slicing and dispatching the subsequent segment from the storage layer:


typescript
// Sender: TCPUtils.ts
export const handleChunkAck = async (
  ackPayload: { chunkNo: number },
  fileRecord: FileRecord,
  socket: any
): Promise<void> => {
  const nextChunk = ackPayload.chunkNo;
  if (nextChunk < fileRecord.totalChunks) {
    const chunkData = await RNFS.read(
      fileRecord.uri,
      CHUNK_SIZE,
      nextChunk * CHUNK_SIZE,
      'base64'
    );
    socket.write(JSON.stringify({ event: 'file_chunk', chunkNo: nextChunk, data: chunkData }
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
alexcodebytes profile image
Oleksandr •

Implementing a strict chunked streaming pipeline with frame-level acknowledgment is an exceptional, production-grade approach to local peer-to-peer data transfer. Managing high-throughput socket streaming within memory-constrained environments like mobile JavaScript runtimes requires exactly this type of rigorous, defensive systems engineering.
The protocol layout demonstrates several key architectural practices for mobile P2P architecture:

  1. Optical Signaling over mDNS: Utilizing a visual QR matrix to exchange IP coordinates and port configurations is a brilliant choice. It completely sidesteps the unpredictable discovery latency and frequent multicast blocking rules enforced by modern consumer Wi-Fi routers, ensuring an immediate connection path.
  2. Mitigating Memory Constraints: Acknowledging that reading large payloads directly into the JavaScript heap triggers Out-of-Memory (OOM) events on the Hermes or V8 engines is a crucial optimization insight. Restricting frames strictly to 64 KB binary frames ensures a flat, predictable memory footprint during high-throughput I/O loops.
  3. Explicit Frame-Level Backpressure: Decoupling the network transmission rate from the sequential flash memory write speed via the send_chunk_ack control loop is mandatory over 5 GHz channels. Without this explicit acknowledgment window, the underlying OS socket buffers saturate rapidly, causing severe garbage collection thrashing and dropouts. To enhance this streaming architecture and maintain maximum stability when writing heavy data payloads to disk, here are two actionable performance patterns you can consider: • Replacing State Engine Append with Native File Appends: In your transmission loop diagram, appending chunk payloads via MMKV or standard reactive storage engines can introduce significant serialization overhead for large files. MMKV is heavily optimized for fast key-value states, not for staging gigabyte-scale streams. Instead, pipe the incoming TCP buffer slices directly into a persistent file stream using react-native-fs with raw append flag execution (e.g., writing the binary chunk directly to the target URI path on the native side), keeping the JS bridge completely clear of binary aggregation. • Decoupled JSON Event Parsing from Binary Streams: In section 3, your control signals are structured as JSON strings (socket.write(JSON.stringify(...))) while the primary pipeline handles raw binary chunk frames. Mixing stringified JSON frames and raw bytes on a single persistent TCP socket requires a custom framing protocol (like a simple Length-Type-Value prefix) to avoid fragmentation corruption at the packet boundaries. Alternatively, you can use a separate, lightweight socket channel exclusively for metadata control execution and acknowledgments, leaving the main persistent connection dedicated to pure, unparsed binary throughput. This architectural blueprint provides a pristine framework for building zero-trust, high-speed local data pipelines on mobile devices!