打造高效 P2P 文件传输与桌面共享工具:基于 WebRTC、Go 和 React
🎉 经过数月的开发,我的个人项目 File-Transfer-Go 终于实现了一个小目标:支持文件传输、桌面共享和文本同步!无需复杂配置,打开网页即开即用使用,数据传输全程 P2P,安全又高效!
项目地址: GitHub - MatrixSeven/file-transfer-go
体验地址: File Transfer - 文件传输
1. 项目背景与初心
作为一个经常需要向 Windows 服务器传输文件的开发者,我对传统网盘的繁琐流程(登录、下载客户端)感到厌倦。同时,我对 WebRTC 的 P2P 技术充满兴趣,想借此机会深入学习并打造一个即开即用的工具,集文件传输、桌面共享和文本同步于一体。目标是:直观、简洁、高效,符合高频使用场景。
2. 技术架构
项目采用前后端分离架构,所有数据传输基于 WebRTC 实现 P2P 连接,服务器仅用于信令交换,保障隐私和安全:
- 后端:基于 Go 开发的轻量信令服务器,负责 WebRTC 的 ICE 候选交换和会话协商。
- 前端:使用 React 和 Next.js,提供流畅的交互界面和状态管理。
- 核心技术:WebRTC 实现 P2P 数据通道,涵盖文件传输、桌面共享和文本同步。
- 隐私设计:服务器不存储任何设备信息或传输数据,连接通过取件码手动匹配。
3. 核心功能与实现
3.1 p2p打洞
基于webrtc进行网络打洞和穿透

3.2 文件传输
通过 WebRTC 的 useSharedWebRTCManager和 useFileTransferBusiness 实现点对点文件传输,支持大文件分片和传输。以下是核心代码示例:
// 安全发送单个文件块
const sendChunkWithAck = useCallback(async (
fileId: string,
chunkIndex: number,
chunkData: ArrayBuffer,
checksum: string,
retryCount = 0
): Promise<boolean> => {
return new Promise((resolve) => {
const chunkKey = `${fileId}-${chunkIndex}`;
// 设置确认回调
const ackCallback = (ack: ChunkAck) => {
if (ack.success) {
resolve(true);
} else {
console.warn(`文件块 ${chunkIndex} 确认失败,准备重试`);
resolve(false);
}
};
// 注册确认回调
if (!chunkAckCallbacks.current.has(chunkKey)) {
chunkAckCallbacks.current.set(chunkKey, new Set());
}
chunkAckCallbacks.current.get(chunkKey)!.add(ackCallback);
// 设置超时定时器
const timeout = setTimeout(() => {
console.warn(`文件块 ${chunkIndex} 确认超时`);
chunkAckCallbacks.current.get(chunkKey)?.delete(ackCallback);
resolve(false);
}, ACK_TIMEOUT);
pendingChunks.current.set(chunkKey, timeout);
// 发送块信息
connection.sendMessage({
type: 'file-chunk-info',
payload: {
fileId,
chunkIndex,
totalChunks: 0, // 这里不需要,因为已经在元数据中发送
checksum
}
}, CHANNEL_NAME);
// 发送块数据
connection.sendData(chunkData);
});
}, [connection]);
然后通过webrtc的数据通道进行发送
// 发送二进制数据
const sendData = useCallback((data: ArrayBuffer) => {
const dataChannel = dcRef.current;
if (!dataChannel || dataChannel.readyState !== 'open') {
console.error('[SharedWebRTC] 数据通道未准备就绪');
return false;
}
try {
dataChannel.send(data);
console.log('[SharedWebRTC] 发送数据:', data.byteLength, 'bytes');
return true;
} catch (error) {
console.error('[SharedWebRTC] 发送数据失败:', error);
return false;
}
}, []);
3.3 桌面共享
通过webrtcAPI拿到 MediaStream 然后进行传输
// 添加媒体轨道
const addTrack = useCallback((track: MediaStreamTrack, stream: MediaStream) => {
const pc = pcRef.current;
if (!pc) {
console.error('[SharedWebRTC] PeerConnection 不可用');
return null;
}
try {
return pc.addTrack(track, stream);
} catch (error) {
console.error('[SharedWebRTC] 添加轨道失败:', error);
return null;
}
}, []);
接收方
// 设置视频流
useEffect(() => {
if (videoRef.current && stream) {
console.log('[DesktopViewer] 🎬 设置视频流,轨道数量:', stream.getTracks().length);
stream.getTracks().forEach(track => {
console.log('[DesktopViewer] 轨道详情:', track.kind, track.id, track.enabled, track.readyState);
});
videoRef.current.srcObject = stream;
console.log('[DesktopViewer] ✅ 视频元素已设置流');
// 重置状态
hasAttemptedAutoplayRef.current = false;
setNeedsUserInteraction(false);
setIsPlaying(false);
// 添加事件监听器来调试视频加载
const video = videoRef.current;
const handleLoadStart = () => console.log('[DesktopViewer] 📹 视频开始加载');
const handleLoadedMetadata = () => {
console.log('[DesktopViewer] 📹 视频元数据已加载');
console.log('[DesktopViewer] 📹 视频尺寸:', video.videoWidth, 'x', video.videoHeight);
};
const handleCanPlay = () => {
console.log('[DesktopViewer] 📹 视频可以开始播放');
// 只在还未尝试过自动播放时才尝试
if (!hasAttemptedAutoplayRef.current) {
hasAttemptedAutoplayRef.current = true;
video.play()
.then(() => {
console.log('[DesktopViewer] ✅ 视频自动播放成功');
setIsPlaying(true);
setNeedsUserInteraction(false);
})
.catch(e => {
console.log('[DesktopViewer] 📹 自动播放被阻止,需要用户交互:', e.message);
setIsPlaying(false);
setNeedsUserInteraction(true);
});
}
};
const handlePlay = () => {
console.log('[DesktopViewer] 📹 视频开始播放');
setIsPlaying(true);
setNeedsUserInteraction(false);
};
const handlePause = () => {
console.log('[DesktopViewer] 📹 视频暂停');
setIsPlaying(false);
};
const handleError = (e: Event) => console.error('[DesktopViewer] 📹 视频播放错误:', e);
video.addEventListener('loadstart', handleLoadStart);
video.addEventListener('loadedmetadata', handleLoadedMetadata);
video.addEventListener('canplay', handleCanPlay);
video.addEventListener('play', handlePlay);
video.addEventListener('pause', handlePause);
video.addEventListener('error', handleError);
return () => {
video.removeEventListener('loadstart', handleLoadStart);
video.removeEventListener('loadedmetadata', handleLoadedMetadata);
video.removeEventListener('canplay', handleCanPlay);
video.removeEventListener('play', handlePlay);
video.removeEventListener('pause', handlePause);
video.removeEventListener('error', handleError);
};
} else if (videoRef.current && !stream) {
console.log('[DesktopViewer] ❌ 清除视频流');
videoRef.current.srcObject = null;
setIsPlaying(false);
setNeedsUserInteraction(false);
hasAttemptedAutoplayRef.current = false;
}
}, [stream]);


浙公网安备 33010602011771号