您当前的位置:首页 > 电脑百科 > 网络技术 > 网络技术

网络上共享跨平台的点对点文件

时间:2020-09-30 11:00:16  来源:  作者:

原文链接:https://medium.com/@dev2919/cross-platform-peer-to-peer-file-sharing-over-the-web-using-webrtc-and-react-js-525aa7cc342c

 

我的动机

 

我们的目标是制作一个精简易用的点对点文件共享网络应用程序,将更多的精力投入到用户体验与简单地办事上。这个网络应用程序不只是针对特定的个人群体服务的,而是针对整个社区服务。

 

既然有这么多文件共享网站,为什么我们还要做这些呢?

 

当然,我也思考过这个问题,但所有的这些网站都没有真正地说明过这些文件在哪里共享或存储。这可能是一种隐私威胁,因为在当前疫情的情况下,许多人或许经常使用这些服务来共享文件甚至机密文件。使用安全的点对点连接和它的数据通道可以传输大量的文件,却不需要存储在任何服务器上,这使得它真正地结实与私有,因为只有连接的客户端/对等端直接与中间服务器通信,不需要中间服务器进行传输。

 

WebRTC使对等连接和数据通道成为可能。WebRTC基本上是一种相互通信与传送数据的全球网络方式,类似于蓝牙、NFC和WIFI数据共享。我们可以使用WebRTC实现跨平台支持,因为它是基于网络的。

 

让我们更深入地研究WebRTC。

 

WebRTC

 

“WebRTC是一个免费的开放项目,通过简单的APIs为浏览器与移动应用程序提供实时通信(RTC)功能。WebRTC组件已经进行了优化,以更好地满足这一目的。”

webrtc.org

 

好吧,假设,一个“点对点”关联考虑两部设备之间发送的直接信息,而不需要服务器保存这些信息。听起来这对我们的情况很理想对吧?不幸的是,这不是WebRTC工作的方式!

网络上共享跨平台的点对点文件

 

图为使用WebRTC进行数据传输

 

尽管WebRTC实现了点对点连接,但它确实需要一个称为信令服务器的服务器,该服务器用于共享有关预期将其相互连接的设备的数据。这些微妙之处可以通过任何传统的信息共享技术来共享。WebSockets在这里受到青睐,因为它减少了在一个庞大的建立关联的系统中共享这些额外数据的惰性。

 

简而言之,信令服务器帮助建立连接,然而,当连接建立后,服务器将不再涉及相关设备之间共享的信息。

 

一年前,当我开始我的第一个WebRTC项目时,很难找到一个在“production”级别下工作得像样的模型。后来我在网上找到了这个Youtube频道编码。开发人员给出了关于可用于生产的WebRTC应用程序的一些很好的例子。

 

WebRTC如何创建一个连接(技术)

 

好吧,没有简单的方法来解释这一点,但我的看法是,在网络上所有数量可观的设备中,无论如何都必须有一个设备通过产生信号来启动连接,并将其发送到信令服务器上。这个对等点被称为启动器,在simple-peer(此项目中使用的模块)中,当创建一个启动器对等点时,{initiator:true}会被传递给制作者/构造函数。

网络上共享跨平台的点对点文件

 

如图:信号服务器在运行

 

当我们得到对等点的信号信息时,这些信息应该通过某种方式通过信令服务器发送到不同的集线器。不同的集线器获取此信息并尝试与发起程序建立关联。在这个过程中,这些对等体同样产生它们的信号信息并被发送给发起方。发起方获取此信息并尝试与其余对等方建立连接。

 

瞧!这些设备现在已经连接起来,现在有一个数据通道,可以在没有中间服务器的情况下共享信息。

 

尽量不要过分强调你无法理解WebRTC的上述工作方式以及简单对等点如何把它抽象化。当我一开始摆弄WebRTC时,它吓了我一大跳。接下来的部分将对这一点进行更简单和细致的解释。

 

与WebRTC共享文件(使用simple-peer)

const express = require("express");
  const http = require("http");
  const App = express();
  const server = http.createServer(app);
  const socket = require("socket.io");
  const io = socket(server);
  const users = {};
  const socketToRoom = {};
    io.on('connection', socket => {
      socket.on("join room", roomID => {
          if (users[roomID]) {
              const length = users[roomID].length;
              if (length === 2) {
                  socket.emit("room full");
                  return;
              }              users[roomID].push(socket.id);          } else {
              users[roomID] = [socket.id];          }          socketToRoom[socket.id] = roomID;          const usersInThisRoom = users[roomID].filter(id => id !== socket.id);
            socket.emit("all users", usersInThisRoom);
      });        socket.on("sending signal", payload => {
          io.to(payload.userToSignal).emit('user joined', { signal: payload.signal, callerID: payload.callerID });
      });        socket.on("returning signal", payload => {
          io.to(payload.callerID).emit('receiving returned signal', { signal: payload.signal, id: socket.id });
      });        socket.on('disconnect', () => {
          const roomID = socketToRoom[socket.id];
          let room = users[roomID];
          if (room) {
              room = room.filter(id => id !== socket.id);
              users[roomID] = room;              socket.broadcast.emit('user left', socket.id);
          }      });  });    server.listen(process.env.PORT || 8000, () => console.log('server is running on port 8000'));

Websocket服务器JscodeReact前端编码器

import React, { useEffect, useRef, useState } from "react";
import io from "socket.io-client";
import Peer from "simple-peer";
import styled from "styled-components";
import streamSaver from "streamsaver";
const Container = styled.div`
    padding: 20px;
    display: flex;
    height: 100vh;
    width: 90%;
    margin: auto;
    flex-wrap: wrap;
`;
const worker = new Worker("../worker.js");
const Room = (props) => {
    const [connectionEstablished, setConnection] = useState(false);
    const [file, setFile] = useState();
    const [gotFile, setGotFile] = useState(false);
    const chunksRef = useRef([]);
    const socketRef = useRef();
    const peersRef = useRef([]);
    const peerRef = useRef();
    const fileNameRef = useRef("");
    const roomID = props.match.params.roomID;
    useEffect(() => {
        socketRef.current = io.connect("/");
        socketRef.current.emit("join room", roomID);
        socketRef.current.on("all users", users => {
            peerRef.current = createPeer(users[0], socketRef.current.id);
        });        socketRef.current.on("user joined", payload => {
            peerRef.current = addPeer(payload.signal, payload.callerID);        });        socketRef.current.on("receiving returned signal", payload => {
            peerRef.current.signal(payload.signal);            setConnection(true);
        });        socketRef.current.on("room full", () => {
            alert("room is full");
        })    }, []);    function createPeer(userToSignal, callerID) {
        const peer = new Peer({
            initiator: true,
            trickle: false,
        });        peer.on("signal", signal => {
            socketRef.current.emit("sending signal", { userToSignal, callerID, signal });
        });        peer.on("data", handleReceivingData);
        return peer;
    }    function addPeer(incomingSignal, callerID) {
        const peer = new Peer({
            initiator: false,
            trickle: false,
        });        peer.on("signal", signal => {
            socketRef.current.emit("returning signal", { signal, callerID });
        });        peer.on("data", handleReceivingData);
        peer.signal(incomingSignal);        setConnection(true);
        return peer;
    }    function handleReceivingData(data) {
        if (data.toString().includes("done")) {
            setGotFile(true);
            const parsed = JSON.parse(data);
            fileNameRef.current = parsed.fileName;        } else {
            worker.postMessage(data);        }    }    function download() {
        setGotFile(false);
        worker.postMessage("download");
        worker.addEventListener("message", event => {
            const stream = event.data.stream();
            const fileStream = streamSaver.createWriteStream(fileNameRef.current);
            stream.pipeTo(fileStream);        })    }    function selectFile(e) {
        setFile(e.target.files[0]);
    }    function sendFile() {
        const peer = peerRef.current;
        const stream = file.stream();
        const reader = stream.getReader();
        reader.read().then(obj => {
            handlereading(obj.done, obj.value);        });        function handlereading(done, value) {
            if (done) {
                peer.write(JSON.stringify({ done: true, fileName: file.name }));
                return;
            }            peer.write(value);            reader.read().then(obj => {
                handlereading(obj.done, obj.value);            })        }    }    let body;
    if (connectionEstablished) {
        body = (            <div>
                <input onChange={selectFile} type="file" />
                <button onClick={sendFile}>Send file</button>
            </div>
        );
    } else {
        body = (
            <h1>Once you have a peer connection, you will be able to share files</h1>
        );
    }
    let downloadPrompt;
    if (gotFile) {
        downloadPrompt = (
            <div>
                <span>You have received a file. Would you like to download the file?</span>
                <button onClick={download}>Yes</button>
            </div>
        );
    }
    return (
        <Container>
            {body}
            {downloadPrompt}
        </Container>
    );
};
export default Room;

在此Repo上找到整个代码。如果你在浏览器中尝试应用上述代码并选择一些图片文件(最好小于100KB),它会立即下载这些图片文件。这是因为这个对等点位于一个类似的浏览器中,而发送方处于提示状态。

 

传送和获取的信息的大小是相等的。这表明我们可以选择一次性移动整个记录!

 

为什么使用数据缓冲区而不是blob?

 

在我们过去的代码中,如果我们选择了一个巨大的文件(大于100KB),那么文档很可能不会被发送,这是WebRTC通道的某些约束的直接结果。

网络上共享跨平台的点对点文件

 

如图:数组缓冲区漫画插图(mozilla.org)

 

每个数组缓冲区一次只能有16KB的限制。简而言之,这意味着我们必须将文档划分成小数组缓冲区。

 

小文件可以通过WebRTC一次性发处,然而,对于大文档,明智的做法是将文件隔离到较小的数组缓冲区中,并同样发送每个部分。ArrayBuffer和Blob对象都有削减容量,这使得此过程更加简单。为此,如果你仔细查看代码,你会发现我们使用了一个名为stream saver的模块,它可以将数组缓冲区转换回blob。

 

笔记

let array = [];
self.addEventListener("message", event => {
    if (event.data === "download") {
        const blob = new Blob(array);
        self.postMessage(blob);
        array = [];
    } else if (event.data === "abort") {
        array = [];
    } else {
        array.push(event.data);
    }})

因为JAVAscript是单线程的。处理大量数组缓冲区可能导致漂亮的UI无法响应。为了解决这个问题,我们将使用服务工作人员。一个服务工作人员是浏览器在后台运行的脚本,是与Web页面分离的,这为不需要Web页面或用户交互的特性打开大门。

 

在服务工作程序中处理数组缓冲区

 

将文件划分为数组缓冲区的优点

 

虽然它可能会感觉分隔文件只是一些额外的代码,并且会让东西相互纠缠,但我们得到以下好处,并且可以帮助改进我们的文档共享应用程序。

网络上共享跨平台的点对点文件

 

跨平台支持(由mozilla.org提供说明)

 

  • 支持几乎所有的浏览器
  • 支持庞大的文档大小——正如前面提到的,这是我们为什么要实现它的基本解释。
  • 一个更好的方法来破译所发送信息的度量——通过在缓冲区中发送一个记录,我们现在可以显示信息,例如,发送的文档的级别,发送记录的速度等等。
  • 识别未完成发送的文件——在无法完全发送文件的情况下,现在能够以不同的方式获取和处理文件。

 

结论

 

由于我们有一个使用WebRTC的文档直接共享程序,而且它还利用了ArrayBuffer,我们现在应该开始考虑为应用程序的生产做准备的东西了。这些细节需要更多的探索,而不仅仅是遵循一个直接的教程。

 

可以补充的更多内容:

 

  • 信令服务器(STUN和TURN服务器)。
  • 使多个对等连接可拓展。
  • 当WebRTC不能工作时才用的一种混合共享方式。
  • 提高传输效率和速度。

 

我希望我已经提供了足够的信息让你们开始使用WebRTC应用程序。



Tags:跨平台   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
Electron.NET是一个嵌入了ASP.NET Core的Electron的封装,通过Electron.NET可以构建基于.NET5的跨平台的桌面应用,使得开发人员只需要使用ASP.NET Core和 Blazor就可以胜任桌面...【详细内容】
2021-11-30  Tags: 跨平台  点击:(24)  评论:(0)  加入收藏
前言目前,即时通讯在app中逐渐成了不可或缺的功能,尤其是在疫情期间,音视频会议功能更是火了一把,但是想自己开发即时通讯功能,却一点都不简单,如果用原生开发的话,那么Android、iO...【详细内容】
2021-07-29  Tags: 跨平台  点击:(104)  评论:(0)  加入收藏
在 Windows 平台上除了 iTunes 之外,苹果另个重磅产品就是 iCloud 同步客户端,可以同步文件、照片和电子邮件。现在,苹果推出了适用于 Chrome 浏览器的 iCloud Passwords扩展程...【详细内容】
2021-02-03  Tags: 跨平台  点击:(180)  评论:(0)  加入收藏
我们的目标是制作一个精简易用的点对点文件共享网络应用程序,将更多的精力投入到用户体验与简单地办事上。这个网络应用程序不只是针对特定的个人群体服务的,而是针对整个社区服务。...【详细内容】
2020-09-30  Tags: 跨平台  点击:(62)  评论:(0)  加入收藏
各主流的编程语言都有其特有的 UI 库,Go 语言也不甘落后。如果你不习惯用 Dart 或 Javascript 来开发跨平台的 GUI 应用,不妨来看看 Fyne,这个背靠强大易用的Go语言的UI工具库,...【详细内容】
2020-07-22  Tags: 跨平台  点击:(1188)  评论:(0)  加入收藏
大家好,我是 JackTian。今天给大家推荐一款适用于Windows,Linux和Mac的跨平台免费的开源SQL编辑器和数据库管理应用程序 &mdash;&mdash; beekeeper-studio。 Beekeeper Studi...【详细内容】
2020-06-23  Tags: 跨平台  点击:(92)  评论:(0)  加入收藏
介绍Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用...【详细内容】
2020-06-18  Tags: 跨平台  点击:(149)  评论:(0)  加入收藏
作者: 阿里巴巴文娱技术 阿里技术 阿里妹导读:小程序的开发不可避免的会面临跨平台开发的问题。各小程序平台有哪些特点?如何处理各平台的差异?本文分享淘票票在跨平台开发上的...【详细内容】
2020-06-18  Tags: 跨平台  点击:(69)  评论:(0)  加入收藏
FastTunnel是一款跨平台内网穿透工具,提供反向代理内网服务,将内网服务暴露在公网供别人访问,您可以通过本项目快速搭建一个穿透服务,需要的物料如下: 一台公网的服务器 自己的域...【详细内容】
2020-04-09  Tags: 跨平台  点击:(204)  评论:(0)  加入收藏
跨平台应用程序开发有其自身的优点,这也是其流行的原因。随着需求增长,一些跨平台的应用程序开发工具和框架也开始在市场上出现,很多公司都在尝试这些有趣的技术。结果呢?是我们...【详细内容】
2020-01-27  Tags: 跨平台  点击:(81)  评论:(0)  加入收藏
▌简易百科推荐
写一个shell获取本机ip地址、网关地址以及dns信息。经常会遇到取本机ip、网关、dns地址,windows一个命令ipconfig /all全部获取到,但linux系统却并非如此。linux系统都自带ifc...【详细内容】
2021-12-27  K佬食古    Tags:shell   点击:(1)  评论:(0)  加入收藏
步骤1、配置 /etc/sysconfig/network-scripts/ifcfg-eth0 里的文件。it动力的CentOS下的ifcfg-eth0的配置详情:[root@localhost ~]# vim /etc/sysconfig/network-scripts/ifc...【详细内容】
2021-12-24  忆梦如风    Tags:网卡   点击:(9)  评论:(0)  加入收藏
1、查找当前目录下所有以.tar结尾的文件然后移动到指定目录find . -name “*.tar” -execmv {}./backup/ ;注解:find &ndash;name 主要用于查找某个文件名字,-exec 、xargs可...【详细内容】
2021-12-17  郭主任    Tags:运维   点击:(18)  评论:(0)  加入收藏
对于经常上网的朋友来说,除了手机购物上网,pc端玩网页游戏还是很多小伙伴首选的,但是有时候明明宽带链接上了,打开浏览器却出现上不了网的现象,下面小编要来跟大家说说电脑有网络...【详细内容】
2021-12-16  小白系统    Tags:网页无法打开   点击:(28)  评论:(0)  加入收藏
在访问像github、gitlab这样的外国网站时,很有可能会出现页面加载不出来或找不到页面的错误。这时候有的朋友就会以为是网络的问题,于是把Wifi断掉连上自己手机的热点,结果却还...【详细内容】
2021-12-15  启施技术IT狼叔    Tags:外网   点击:(14)  评论:(0)  加入收藏
网络地址来源:获取公网IP地址 https://ipip.yy.com/get_ip_info.phphttp://pv.sohu.com/cityjson?ie=utf-8http://www.ip168.com/json.do?view=myipaddress...【详细内容】
2021-12-15  韦廷华12    Tags:外网ip   点击:(14)  评论:(0)  加入收藏
准备好软件IPOP、用ENSP模拟一下华为交换机 启动交换机 <Huawei>sysEnter system view, return user view with Ctrl+Z.[Huawei]sysname FTPClient[FTPClient]interface vla...【详细内容】
2021-12-15  思源Edward    Tags:交换机   点击:(22)  评论:(0)  加入收藏
我们经常用到netstat命令查看主机连接状况,包括连接ip、端口、状态等,今天就练习下shell分析netsat结果。描述假设netstat命令运行的结果我们存储在nowcoder.txt里,格式如下:Pro...【详细内容】
2021-12-14  K佬食古    Tags:netstat   点击:(19)  评论:(0)  加入收藏
什么是滑动窗口?窗口是操作系统开辟的一块缓存空间,发送方在收到接收方ACK应答之前,必须在缓冲区保留已发送的数据,如果按期收到确认应答,数据就可以从缓冲区移除。什么是滑动窗...【详细内容】
2021-12-14  DifferentJava    Tags:TCP   点击:(28)  评论:(0)  加入收藏
概述日常管理华为路由设备过程中,难为会忘记设备登录密码,那么该如何重置设备登录密码吗?本期文章将全面向各位小伙伴总结分享。重置华为设备登录密码思路先行 采用console登录...【详细内容】
2021-12-10  onme0    Tags:   点击:(26)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条