前端开发之:大文件传输

1. 大文件上传

1.1. 传输协议

HTTP分块传输和BitTorrent协议适合大文件上传。

HTTP分块传输
- HTTP分块传输是一种HTTP协议的传输方式,它将数据分成若干个块进行传输,每个块都包含自己的长度信息,接收方可以根据长度信息将块重新组装成完整的数据。
- 优点是可以在传输过程中进行流式处理
- 缺点是需要占用较多的服务器资源

  • HTTP分块传输的例子
    假设我们要上传一个名为"bigFile.mp4"的大文件
  1. 客户端将文件分成若干个块,并为每个块添加长度信息
  2. 客户端向服务器发送第一个块,并等待服务器确认
  3. 服务器接收到第一个块后,将其存储,并向客户端发送确认信息
  4. 客户端接收到确认信息后,发送下一个块,并等待服务器确认
  5. 服务器接收到下一个块后,将其存储,并向客户端发送确认信息
  6. 重复步骤4和5,直到所有块都被上传完成
  7. 服务器将所有块组装成完整的文件,并向客户端发送确认信息
  • HTTP分块传输的注意项
  1. HTTP分块传输需要占用较多的服务器资源,因此需要在服务器端进行优化,如使用CDN等技术来减轻服务器的负担。
  2. 由于HTTP分块传输是一种【流式】处理方式,因此在客户端进行处理时需要注意流的顺序,以确保数据的完整性。

BitTorrent协议
- BitTorrent协议是一种点对点文件共享协议,它允许用户在不依赖单个服务器的情况下共享大型文件。它通过将文件分成小块并从多个来源下载这些块来实现高速下载。
- 优点是可以实现高速下载
- 缺点是需要较多的用户参与才能实现高速下载

  • BitTorrent协议的大文件上传的例子
  1. 创建一个种子文件,包含大文件的元数据信息和哈希值
  2. 将种子文件发布到一个公共的Tracker服务器上
  3. 其他用户下载种子文件,并使用其中的哈希值来验证下载的块的完整性
  4. 用户之间相互交换块,直到所有块都被下载完成
  5. 下载完成后,用户可以选择继续做种,以帮助其他用户下载该文件

1.2. 上传思路

将大文件切割为多个分片,分别上传,最后合并

  1. 转换二进制流

- 前端将大文件转换为二进制流的方式有以下几种:
- 1. 使用FileReader API将文件读取为ArrayBuffer对象,再转换为二进制流
- 2. 使用XMLHttpRequest发送Blob对象,将文件转换为二进制流
- 3. 使用Fetch API发送Blob对象,将文件转换为二进制流

// 以下是使用FileReader API将文件读取为ArrayBuffer对象,再转换为二进制流的示例代码
// 1. 创建一个input元素,用于选择文件
// 2. 监听input元素的change事件,获取选择的文件
// 3. 使用FileReader API将文件读取为ArrayBuffer对象
// 4. 将ArrayBuffer对象转换为二进制流
// 5. 创建一个Blob对象,将二进制流作为参数传入
// 6. 创建一个URL对象,将Blob对象作为参数传入
// 7. 创建一个a元素,设置其href属性为URL对象的值,设置其download属性为文件名
// 8. 触发a元素的click事件,下载文件 
// HTML代码
<input type="file" id="fileInput"> 
// JavaScript代码
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', () => {
  const file = fileInput.files[0];
  const reader = new FileReader();
  reader.readAsArrayBuffer(file);
  reader.onload = () => {
      const arrayBuffer = reader.result;
      const blob = new Blob([arrayBuffer]);
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = file.name;
      a.click();
   };
});
  • 前端将大文件转换为二进制流的注意项
    1. 需要使用FileReader API将文件读取为二进制流
    1. 读取文件时需要注意文件大小,避免一次性读取过大文件导致内存溢出
    1. 读取文件时需要注意文件的编码格式,以确保读取的二进制流正确无误。普通大文件转换为二进制流的编码格式通常为utf-8或者base64编码格式,具体取决于文件的内容和使用场景
  1. 切割分片

- 根据网络条件和服务器同时处理多个请求的能力来确定每个分片的大小。通常的分片大小在1MB到10MB之间,但可以根据应用程序的具体要求进行调整。需要注意的是,分片大小不应该太小,否则会导致大量请求和增加服务器负载。相反,如果分片大小太大,上传每个分片的时间可能会更长,增加失败的可能性。因此,建议测试不同的分片大小,找到特定用例的最佳大小。
- 最大50MB ?

  1. 生成hash (非必须)

- 生成文件hash值的原因是为了验证文件的完整性,确保文件在传输过程中没有被篡改或损坏。对每个分片进行hash计算,最后将所有分片的hash值合并成一个总的hash值。这样可以保证文件的完整性。
- 可以在上传文件时快速检查文件是否已经存在于服务器上。

// 生成大文件的hash值并与给定的hash值进行比对
async function compareFileHash(file, expectedHash) {
  const chunkSize = 1024 * 1024; // 每次读取1MB
  const chunks = Math.ceil(file.size / chunkSize);
  let currentChunk = 0;
  const spark = new SparkMD5.ArrayBuffer();
  const fileReader = new FileReader();
  fileReader.onload = function(e) {
    spark.append(e.target.result);
    currentChunk++;
    if (currentChunk < chunks) {
      loadNext();
    } else {
      const hash = spark.end();
      if (hash === expectedHash) {
        console.log('文件hash值匹配');
      } else {
        console.log('文件hash值不匹配');
      }
    }
  };
  fileReader.onerror = function() {
    console.log('文件读取失败');
  };
  function loadNext() {
    const start = currentChunk * chunkSize;
    const end = Math.min(start + chunkSize, file.size);
    fileReader.readAsArrayBuffer(file.slice(start, end));
  }
  loadNext();
}

// 使用示例
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async () => {
  const file = fileInput.files[0];
  const expectedHash = 'd41d8cd98f00b204e9800998ecf8427e'; // 假设期望的hash值为d41d8cd98f00b204e9800998ecf8427e
  await compareFileHash(file, expectedHash);
});

// 以上是前端生成大文件的hash值并与给定的hash值进行比对的示例代码
// 1. 使用SparkMD5库生成hash值
// 2. 将文件分割为多个chunk,每个chunk的大小为1MB
// 3. 使用FileReader API读取每个chunk,并将其添加到SparkMD5实例中
// 4. 当所有chunk都被读取并添加到SparkMD5实例中后,调用end()方法生成最终的hash值
// 5. 将生成的hash值与给定的hash值进行比对,判断文件是否完整
  1. 上传分片(并行个数限制, 延迟上传)

- 上传分片时需要注意以下几点:
- 1. 并行上传的个数需要限制,避免对服务器造成过大的压力
- 2. 上传分片时需要设置适当的延迟,避免网络拥塞

// 以下是上传分片的示例代码:
async function uploadChunks(chunks, url) {
  const maxConcurrentUploads = 4; // 并行上传的个数限制为4
  const delay = 1000; // 设置延迟为1秒
  const requests = [];
  let index = 0;
  while (index < chunks.length) {
    const currentRequests = [];
    for (let i = 0; i < maxConcurrentUploads && index < chunks.length; i++) {
      const chunk = chunks[index];
      const formData = new FormData();
      formData.append('chunk', chunk);
      formData.append('index', index);
      formData.append('total', chunks.length);
      const request = fetch(url, {
        method: 'POST',
        body: formData
      });
      currentRequests.push(request);
      index++;
    }
    await Promise.all(currentRequests);
    await new Promise(resolve => setTimeout(resolve, delay)); // 延迟1秒
  }
}

// 调用示例
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async () => {
  const file = fileInput.files[0];
  const chunkSize = 1024 * 1024; // 每个chunk的大小为1MB
  const chunks = sliceFile(file, chunkSize);
  const url = 'http://example.com/upload'; // 上传分片的URL
  await uploadChunks(chunks, url);
  console.log('所有分片上传完毕');
  // 请求合并分片的代码
});
  1. 全部分片上传完毕,请求合并分片

1.2.1. 整体示例

// 切割大文件为多个分片
function sliceFile(file, chunkSize) {
  const chunks = [];
  let start = 0;
  let end = 0;
  while (start < file.size) {
    end = Math.min(file.size, start + chunkSize);
    chunks.push(file.slice(start, end));
    start = end;
  }
  return chunks;
}
// 上传大文件分片
function uploadChunks(chunks, url) {
  const requests = chunks.map((chunk, index) => {
    const formData = new FormData();
    formData.append('chunk', chunk);
    formData.append('index', index);
    formData.append('total', chunks.length);
    return fetch(url, {
      method: 'POST',
      body: formData
    });
  });
  return Promise.all(requests);
}
// 将前端代码文件转换为二进制流
const fileInput = document.querySelector('input[type="file"]');
const reader = new FileReader();
fileInput.addEventListener('change', () => {
  const file = fileInput.files[0];
  reader.readAsBinaryString(file); // 将文件转换为二进制流
});
reader.addEventListener('load', () => {
  const binaryString = reader.result;
  // 使用二进制流进行进一步处理
  const {file, chunkSize} = [makefunc](binaryString)
  // 切割文件为多个分片
  const chunks = sliceFile(file,chunkSize); // 填入分片大小,单位为字节

  // 上传分片
  uploadChunks(chunks, 'http://example.com/upload')
    .then(() => {
      // 分片上传完成,请求合并分片
      fetch('http://example.com/merge')
        .then(response => {
          // 合并完成
        });
    });
});

1.3. 断点续传

记录已上传的分片,下次上传时跳过已上传的分片

  1. 本地记录
// 断点续传:记录已上传的分片,下次上传时跳过已上传的分片
// 在上传分片时,记录已上传的分片的索引,下次上传时跳过已上传的分片
// 可以使用localStorage或IndexedDB进行记录
// 上传大文件分片
function uploadChunks(chunks, url) {
  const requests = chunks.map((chunk, index) => {
    const formData = new FormData();
    formData.append('chunk', chunk);
    formData.append('index', index);
    formData.append('total', chunks.length);
    return fetch(url, {
      method: 'POST',
      body: formData
    });
  });
  return Promise.all(requests);
}
// 上传大文件,支持断点续传
async function uploadFile(file, url) {
  const chunkSize = 1024 * 1024; // 每个chunk的大小为1MB
  const chunks = sliceFile(file, chunkSize);
  const uploadedChunks = getUploadedChunks(file.name); // 获取已上传的分片索引
  const unuploadedChunks = chunks.filter((chunk, index) => !uploadedChunks.includes(index)); // 过滤已上传的分片
  await uploadChunks(unuploadedChunks, url);
  saveUploadedChunks(file.name, chunks.length); // 保存已上传的分片索引
  console.log('所有分片上传完毕');
  // 请求合并分片的代码
}
// 获取已上传的分片索引
function getUploadedChunks(filename) {
  const uploadedChunks = localStorage.getItem(filename);
  return uploadedChunks ? JSON.parse(uploadedChunks) : [];
}
// 保存已上传的分片索引
function saveUploadedChunks(filename, total) {
  const uploadedChunks = Array.from({length: total}, (_, index) => index);
  localStorage.setItem(filename, JSON.stringify(uploadedChunks));
}
// 调用示例
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async () => {
  const file = fileInput.files[0];
  const url = 'http://example.com/upload'; // 上传分片的URL
  await uploadFile(file, url);
  // 请求合并分片的代码
});
  1. 后端记录
// 后端记录已上传的分片信息,下次上传时跳过已上传的分片
// 在上传分片时,计算分片的hash值,与服务器已有的hash值进行比对,如果一致则跳过该分片,直接返回上传成功
// 可以使用crypto库中的createHash方法计算hash值
const crypto = require('crypto');
// 计算分片的hash值
function calculateChunkHash(chunk) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      const buffer = reader.result;
      const hash = crypto.createHash('md5').update(buffer).digest('hex');
      resolve(hash);
    };
    reader.onerror = reject;
    reader.readAsArrayBuffer(chunk);
  });
}
// 上传大文件分片
async function uploadChunks(chunks, url) {
  const requests = chunks.map(async (chunk, index) => {
    const formData = new FormData();
    formData.append('chunk', chunk);
    formData.append('index', index);
    formData.append('total', chunks.length);
    const hash = await calculateChunkHash(chunk); // 计算分片的hash值
    formData.append('hash', hash);
    const response = await fetch(url, {
      method: 'POST',
      body: formData
    });
    const {uploaded} = await response.json();
    if (uploaded) {
      console.log(`分片${index}已上传`);
    }
    return response;
  });
  return Promise.all(requests);
}
// 上传大文件,支持断点续传
async function uploadFile(file, url) {
  const chunkSize = 1024 * 1024; // 每个chunk的大小为1MB
  const chunks = sliceFile(file, chunkSize);
  const uploadedChunks = await getUploadedChunks(file.name); // 获取已上传的分片信息
  const unuploadedChunks = chunks.filter((chunk, index) => {
    const hash = await calculateChunkHash(chunk); // 计算分片的hash值
    return !uploadedChunks.some(({index: uploadedIndex, hash: uploadedHash}) => index === uploadedIndex && hash === uploadedHash); // 过滤已上传的分片
  });
  await uploadChunks(unuploadedChunks, url);
  await saveUploadedChunks(file.name, uploadedChunks.concat(unuploadedChunks)); // 保存已上传的分片信息
  console.log('所有分片上传完毕');
  // 请求合并分片的代码
}
// 获取已上传的分片信息
async function getUploadedChunks(filename) {
  const response = await fetch(`http://example.com/chunks?filename=${filename}`);
  const {chunks} = await response.json();
  return chunks || [];
}
// 保存已上传的分片信息
async function saveUploadedChunks(filename, chunks) {
  await fetch('http://example.com/chunks', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({filename, chunks})
  });
}
// 调用示例
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async () => {
  const file = fileInput.files[0];
  const url = 'http://example.com/upload'; // 上传分片的URL
  await uploadFile(file, url);
  // 请求合并分片的代码
});

1.3.1. 跨端续传

记录已上传的分片信息,可以在不同设备上进行续传

1.4. 切片原理

将大文件按照固定大小切割为多个分片,通常大小在1MB到50MB之间

原因 描述 切片方案
网络限制 大文件传输受网络的限制 多个切片并行上传,提高上传速度
传输中容易出错 大文件传输中出错,需要重新上传,加重上传负担 多个分片单独排错,重新上传对应的分片
完整性 切片更容易进行大文件传输中完整性的检测 生成hash进行对比
一次性上传 不支持传输中断开 支持断点续传

1.5. 秒传

根据文件内容生成hash值,与服务器已有的hash值进行比对,如果一致则直接返回上传成功

1.6. 传输安全

  1. SparkMD5 进行md5加密
// 使用SparkMD5进行md5加密
async function calculateChunkHash(chunk) {
  return new Promise(resolve => {
    const reader = new FileReader();
    reader.onload = e => {
      const spark = new SparkMD5.ArrayBuffer(); 
      spark.append(e.target.result);
      const hash = spark.end();
      resolve(hash);
    };
    reader.readAsArrayBuffer(chunk);
  });
}
// 在uploadFile函数中调用calculateChunkHash函数计算分片的hash值
async function uploadFile(file, url) {
  const chunkSize = 1024 * 1024; // 每个chunk的大小为1MB
  const chunks = sliceFile(file, chunkSize);
  const uploadedChunks = await getUploadedChunks(file.name); // 获取已上传的分片信息
  const unuploadedChunks = chunks.filter(async (chunk, index) => {
    const hash = await calculateChunkHash(chunk); // 计算分片的hash值
    return !uploadedChunks.some(({index: uploadedIndex, hash: uploadedHash}) => index === uploadedIndex && hash === uploadedHash); // 过滤已上传的分片
  });
  await uploadChunks(unuploadedChunks, url);
  await saveUploadedChunks(file.name, uploadedChunks.concat(unuploadedChunks)); // 保存已上传的分片信息
  console.log('所有分片上传完毕');
  // 请求合并分片的代码
}
  1. 使用HTTPS
  2. 或者更多加密方式(对称|非对称)对分片进行加密

1.7. 并行传输

// 实现方式:使用Promise.all()方法并行上传多个分片
async function uploadChunks(chunks, url) {
  const requests = chunks.map(async (chunk, index) => {
    const formData = new FormData();
    formData.append('chunk', chunk);
    formData.append('index', index);
    const response = await fetch(url, {
      method: 'POST',
      body: formData
    });
    return response.json();
  });
  await Promise.all(requests);
  console.log('所有分片上传完毕');
}
  1. 并行上传的个数需要限制,避免对服务器造成过大的压力
  2. 上传分片时需要设置适当的延迟,避免网络拥塞

1.8. 音视频传输

可以使用WebRTC技术进行音视频传输

接口/方法 描述
RTCPeerConnection 此接口表示本地设备和远程对等体之间的WebRTC连接。它提供了添加和删除轨道、创建和设置本地和远程描述以及建立和关闭连接的方法。
MediaStream 此接口表示媒体数据流,例如音频或视频。它提供了添加和删除轨道以及获取有关轨道的信息的方法。
getUserMedia() 此方法用于请求访问本地媒体设备,例如摄像头或麦克风。它返回一个Promise,该Promise解析为MediaStream对象。
RTCIceCandidate 此接口表示建立两个对等体之间连接的候选项。它提供有关候选项的IP地址、端口和传输协议的信息。
RTCSessionDescription 此接口表示WebRTC会话的SDP描述。它提供有关会话的媒体功能(例如编解码器和带宽)的信息。
RTCIceServer 此接口表示用于建立两个对等体之间连接的STUN或TURN服务器。它提供有关服务器的URL、用户名和密码的信息。

1.8.1. NAT

NAT(Network Address Translation),即网络地址转换,是一种将一个Internet Protocol(IP)地址空间转换为另一个IP地址空间的技术。在使用NAT时,局域网内的多个设备可以共享一个公网IP地址,从而实现对外网络通信。
NAT有助于减少IP地址的使用,并且提高了网络安全性,因为在NAT网络中,局域网内的设备无法直接从互联网上被访问到。
WebRTC中使用NAT协议通过两个对等体之间交换ICE候选项和交换方式来克服两个主机之间的NAT和防火墙等问题。可以通过添加STUN和TURN服务器来使用NAT协议。

1.8.2. ICE

Interactive Connectivity Establishment (ICE)是一种网络协议,用于在两个对等体之间建立网络连接
它通过在两个对等体之间交换ICE候选项和交换方式来克服两个主机之间的NAT(Network Address Translation)和防火墙等问题
WebRTC使用ICE来建立对等体之间的连接

//为了处理WebRTC中两个对等体之间的NAT穿越问题,我们可以将STUN和TURN服务器添加到`RTCPeerConnection`对象的`iceServers`配置中。STUN服务器将帮助对等体之间交换IP地址和端口,而当直接通信不可能时,TURN服务器可以充当中间人来中继数据。下面是已更新了NAT穿越处理的代码片段:
const iceConfiguration = {
  iceServers: [
    // 添加STUN服务器
    { urls: 'stun:stun.example.org' },
    // 添加TURN服务器
    { urls: 'turn:turn.example.org', username: 'yourUsername', credential: 'yourCredential' }
  ]
};

const peerConnection = new RTCPeerConnection({ iceServers: iceConfiguration });

peerConnection.addEventListener('icecandidate', event => {
  if (event.candidate) {
    // 发送ICE candidate给远程peer
  }
});

// 在远程peer处添加收到ICE candidate的监听器
peerConnection.addEventListener('icecandidate', event => {
  if (event.candidate) {
    // 将收到的ICE candidate发送给本地peer
  }
});

1.8.3. STUN

STUN是WebRTC使用的网络协议,在两个对等体之间建立网络连接时克服了NAT和防火墙等问题。它通过在两个对等体之间交换ICE候选项和通信方式来实现。STUN服务器用于在对等体之间交换IP地址和端口。要将STUN服务器添加到WebRTC连接中,您可以将其添加到RTCPeerConnection对象的iceServers配置中,如PREFIX部分的代码示例所示。

1.8.4. TURN

TURN(Traversal Using Relays around NAT)是WebRTC使用的网络协议之一。当两个对等体之间的通信不能通过STUN服务器直接建立时,TURN服务器可以用作中间人来传递数据,并充当通信的中继,以克服NAT和防火墙等问题。
要将TURN服务器添加到WebRTC连接中,可以使用RTCIceServers对象的urls、username和credential属性。

const iceConfiguration = {
  iceServers: [
    // 添加STUN服务器
    { urls: 'stun:stun.example.org' },
    // 添加TURN服务器
    { urls: 'turn:turn.example.org', username: 'yourUsername', credential: 'yourCredential' }
  ]
};

1.8.5. SDP

SDP(Session Description Protocol)是一种描述流媒体初始化参数的格式。它用于会话公告、会话邀请和协商以及会话建立。
在WebRTC中,它在两个对等体之间交换以协商和建立连接。
SDP包含有关媒体类型、编码编解码器、带宽、网络地址和其他会话特定参数的信息。
它作为提议-应答交换在对等体之间发送,其中一个对等体(启动方)生成SDP提议并将其发送到另一个对等体(接收方)。接收方以其自己的信息回应一个SDP答复。两个对等体使用此信息建立连接。

你可以使用这些API来实现WebRTC协议,如前面的消息所述。如果您需要更多关于特定API的信息,可以参考官方文档或在网上搜索示例。

// 使用WebRTC技术进行音视频传输
// 首先需要创建RTCPeerConnection对象
const peerConnection = new RTCPeerConnection();

// 然后需要获取本地媒体流
const localStream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});

// 将本地媒体流添加到RTCPeerConnection对象中
localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));

// 创建一个offer并设置本地SDP
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);

// 将offer发送给远程端

在远程端,创建一个RTCPeerConnection对象,并使用remotePeerConnection.ontrack将远程媒体流添加到它中。使用remotePeerConnection.setRemoteDescription()设置远程SDP,并使用remotePeerConnection.createAnswer()和remotePeerConnection.setLocalDescription()创建一个答案并将其发送回本地对等体。最后,使用peerConnection.setRemoteDescription()在本地对等体上设置远程SDP。

1.8.6. 全面介绍推荐

【前端实时音视频系列】WebRTC入门概览
Web前端WebRTC攻略(一) 基础介绍
Web前端WebRTC攻略(二) 音视频设备及数据采集
Web前端WebRTC攻略(三) 传输协议UDP/RTP/RTC
Web前端WebRTC攻略(四) 媒体协商与SDP简析
Web前端WebRTC 攻略(五) NAT 穿越与 ICE

1.8.7. 示例

// 使用WebRTC技术进行音视频传输
// 首先需要创建RTCPeerConnection对象
const peerConnection = new RTCPeerConnection();

// 然后需要获取本地媒体流
const localStream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});

// 将本地媒体流添加到RTCPeerConnection对象中
localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));

// 创建一个offer并设置本地SDP
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);

// 将offer发送给远程端

// 远程端收到offer后,需要创建一个RTCPeerConnection对象
const remotePeerConnection = new RTCPeerConnection();

// 将远程媒体流添加到RTCPeerConnection对象中
remotePeerConnection.ontrack = event => {
  const remoteStream = event.streams[0];
  // 将远程媒体流显示在页面上
};

// 设置远程SDP并创建answer
await remotePeerConnection.setRemoteDescription(offer);
const answer = await remotePeerConnection.createAnswer();
await remotePeerConnection.setLocalDescription(answer);

// 将answer发送给本地端

// 本地端收到answer后,设置远程SDP
await peerConnection.setRemoteDescription(answer);

/* 
===========================================
                   FLOWCHART
===========================================

     +---------+   Create    +----------------+ 
     |         |------------>|  peerConnection| 
     |         |             +----------------+ 
     |  local  | 
     | Stream  |          +->| addAudioTrack   |  
     |         |   Add    |  | addVideoTrack   | 
     |         |<---------+  +----------------+ 
     +---------+ 

     +---------+
     |         |   Create    +----------------+ 
     |         |------------>|      offer     |
     |  peer   |             +----------------+ 
     |Connection|          
     |         |   Set      +----------------+ 
     |         |---------->|localDescription| 
     +---------+ 

     +---------+   Send    +----------------+
     |remote   |<-----------|      offer     | 
     |peer     |            +----------------+ 
     |connection|
     +---------+   Create  +----------------+
                   and      |     answer     | 
                   Set     +----------------+
                   local   +----------------+
                   description|localDescription| 

     +---------+   Send    +----------------+ 
     |  local  |<-----------|     answer     |
     |  peer   |            +----------------+ 
     |Connection|
     +---------+   Set      +----------------+ 
                   remote   |remoteDescription|
                   description+----------------+ 

 */

1.9. 局域网传输

对于大文件局域网传输,可以使用局域网内的IP地址进行传输,避免了通过公网传输的网络延迟和带宽限制,提高了传输速度。同时,可以使用上述的断点续传、切片原理、秒传、传输安全和并行传输等技术,保证大文件的高效、安全传输。

1.9.1. 文件共享

  1. 右键单击要共享的文件夹,选择“属性”。
  2. 转到“共享”选项卡,单击“高级共享”。
  3. 选中“共享此文件夹”复选框。
  4. 然后可以设置文件夹的共享名称和权限。

1.9.2. webServer

//例如Node.js,您可以使用内置的http模块创建Web服务器。以下是一个示例代码块:
const http = require('http');
const fs = require('fs');

const server = http.createServer((req, res) => {
  const filePath = '.' + req.url;
  fs.readFile(filePath, (err, data) => {
    if (err) {
      res.writeHead(404, {'Content-Type': 'text/plain'});
      res.end('File not found');
    } else {
      res.writeHead(200, {'Content-Type': 'text/html'});
      res.end(data);
    }
  });
});

server.listen(8080, () => {
  console.log('Server running at http://localhost:8080/');
});
//此代码创建一个Web服务器,侦听端口8080,并从当前目录提供文件。您可以修改filePath变量以从不同的目录提供文件。

//要运行此代码,请将其保存到具有.js扩展名的文件中(例如server.js),然后使用Node.js运行它:
node server.js
//一旦服务器正在运行,您可以使用Web浏览器访问文件,方法是输入http://localhost:8080/,后跟文件路径。例如:

浏览器访问:`http://localhost:8080/index.html`

//如果您想从同一网络上的另一台计算机访问文件,则可以使用服务器的IP地址 “http://192.168.1.204:8080/index.html” 而不是“localhost”。

1.9.3. 工具Everything

要使用Everything访问本地网络上的文件,请按照以下步骤操作:
  1. 打开Everything并转到“工具”菜单。
  2. 选择“选项”,然后单击“网络”选项卡。
  3. 选中“启用服务器”复选框。
  4. 选择服务器要侦听的端口号。
  5. 可选地,为服务器设置密码。
  6. 单击“确定”以保存设置。
  7. 要访问另一台计算机上的文件,请打开Everything并转到“文件”菜单。
  8. 选择“连接到服务器”,然后输入运行Everything服务器的计算机的IP地址,后跟端口号。
  9. 如果为服务器设置了密码,请在提示时输入密码。
  10. 连接后,您可以浏览和搜索远程计算机上的文件。

// Everything的官方网站:https://www.voidtools.com/zh-cn/
// Everything的下载地址:https://www.voidtools.com/downloads/

// 请注意,Everything只能访问本地网络上的文件,不能通过Internet访问。
上一篇
下一篇