Quay lại trang blogDecentralized Local Sharing: Cách WebRTC vận hành LAN-Drop
GoWebRTCWebSocketsDecentralized

Decentralized Local Sharing: Cách WebRTC vận hành LAN-Drop

20 tháng 7, 20264 phút đọc

"Xây dựng giải pháp thay thế AirDrop không cần cài đặt app cho mạng nội bộ sử dụng kênh truyền dữ liệu WebRTC và máy chủ Go."

Chia sẻ tệp hoặc văn bản giữa máy tính và điện thoại của bạn đáng lẽ phải diễn ra ngay lập tức. Đáng tiếc là quy trình thông thường thường yêu cầu tải tệp lên lưu trữ đám mây, tự gửi tin nhắn cho chính mình trên các ứng dụng chat hoặc cài đặt các phần mềm đa nền tảng nặng nề.

Chúng ta có thể giải quyết triệt để vấn đề này bằng cách sử dụng WebRTC (Web Real-Time Communication) để thiết lập các kênh truyền dữ liệu trực tiếp, ngang hàng (P2P) giữa các trình duyệt trong cùng mạng nội bộ, được điều phối bởi một máy chủ tín hiệu (signaling server) siêu nhẹ viết bằng Go.

Dưới đây là phân tích cách LAN-Drop thiết lập kết nối ngang hàng trực tiếp ngay trong trình duyệt web tiêu chuẩn.

Vòng đời kết nối ngang hàng WebRTC

Để bắt đầu truyền dữ liệu trực tiếp, hai thiết bị phải trao đổi siêu dữ liệu (Session Description Protocol - SDP) và các tuyến mạng khả dụng (ICE Candidates). Vì ban đầu chúng không biết địa chỉ IP nội bộ của nhau, một Signaling Server trung gian sẽ làm nhiệm vụ kết nối bước bắt tay này.

[Thiết bị A: Browser] <── WebSocket (SDP/ICE) ──> [Go Signaling Server] <── WebSocket (SDP/ICE) ──> [Thiết bị B: Browser]
         │                                                                                                 │
         └────────────────────────── Kênh P2P đã mã hóa (WebRTC) ──────────────────────────────────────────┘

Sau khi bước bắt tay SDP hoàn tất, các trình duyệt sẽ tự động kết nối trực tiếp với nhau trong mạng LAN, và máy chủ tín hiệu sẽ đi vào trạng thái rảnh.

Thiết kế Signaling Server bằng Go

Máy chủ điều phối tín hiệu cần hoạt động cực kỳ nhẹ và nhanh. Trong Go, chúng ta có thể viết trình xử lý WebSocket chuyển tiếp bằng thư viện gorilla/websocket:

package main

import (
	"log"
	"net/http"
	"sync"
	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool { return true },
}

type SignalServer struct {
	mu      sync.Mutex
	clients map[string]*websocket.Conn
}

func (s *SignalServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println("Lỗi kết nối WebSocket:", err)
		return
	}
	defer conn.Close()

	clientId := r.URL.Query().Get("id")
	
	s.mu.Lock()
	s.clients[clientId] = conn
	s.mu.Unlock()

	log.Printf("Thiết bị %s đã kết nối vào luồng tín hiệu\n", clientId)

	for {
		// Đọc thông điệp từ client (như offer, answer hoặc ICE candidate)
		_, msg, err := conn.ReadMessage()
		if err != nil {
			break
		}

		// Chuyển tiếp tín hiệu sang thiết bị đích...
		s.relayMessage(clientId, msg)
	}

	s.mu.Lock()
	delete(s.clients, clientId)
	s.mu.Unlock()
}

Truyền tệp qua WebRTC Data Channel

Trong trình duyệt, chúng ta đọc file dưới dạng ArrayBuffer và truyền qua kênh RTCDataChannel theo từng khối nhỏ (thường từ 16KB đến 64KB) để tránh tràn bộ nhớ trình duyệt:

const peerConnection = new RTCPeerConnection({
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});

const dataChannel = peerConnection.createDataChannel("fileTransfer");

dataChannel.onopen = () => {
  console.log("Kênh truyền P2P trực tiếp đã mở!");
};

// Truyền file theo từng mảnh (chunk)
function sendFile(file) {
  const chunkSize = 16384; // 16KB
  const reader = new FileReader();
  let offset = 0;

  reader.onload = (e) => {
    dataChannel.send(e.target.result);
    offset += e.target.result.byteLength;
    if (offset < file.size) {
      readNextChunk();
    } else {
      console.log("Đã gửi file thành công!");
    }
  };

  const readNextChunk = () => {
    const slice = file.slice(offset, offset + chunkSize);
    reader.readAsArrayBuffer(slice);
  };

  readNextChunk();
}

Kết luận

WebRTC kết hợp với máy chủ điều phối viết bằng Go tạo nên một giải pháp chia sẻ nội bộ nhanh, bảo mật và phi tập trung. Vì dữ liệu truyền trực tiếp giữa các thiết bị thông qua router nội bộ, tốc độ truyền tải chỉ phụ thuộc vào phần cứng router của bạn, và các file riêng tư của bạn không bao giờ phải đi qua internet công cộng hay đám mây.