Likt's Blog

天空一直都在,是云来了又去。

0%

学习P2P知识,并搭建一个局域网p2p下载器

一、实验目的

  1. 学习p2p知识,并搭建一个局域网p2p下载器
  2. 使用p2p下载器进行文件的传输

二、实验环境

  • IDEA【p2p_bootstrap,p2p_node】
  • postman

三、实验步骤

1. 拉库

①git clone https://gitee.com/daitoulin/p2p_bootstrap.git

②git clone https://gitee.com/daitoulin/p2p_node.git

2. 具体操作

1)path类

  • java.nio.file.Path类是Java7引入的一个新类,用于表示文件系统的路径。提供了对文件路径的高级抽象,使得处理文件路径变得更加简单和一致。

  • 平台无关性:Windows用\,Linux/unix使用/

  • 提供了一系列静态工厂方法和实例方法,用来创建和操作路径。

  • path类和filesystem紧密集成,可以轻松获取文件系统的相关信息。

  • 在p2p_node文件夹下src/main/java/com.example.p2pshare.cotroller/下创建PathExample类,实践代码理解java.nio.file.Path类的使用,常见的一些方法的含义。这里需要注意一下resolvedpath=path.resolve("subdir/newfile.txt")

  • 如果这个subdir放在距离很远的文件夹会如何结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.example.p2pshare.cotroller;
import org.springframework.http.codec.multipart.PartHttpMessageWriter;

import java.nio.file.Path;
import java.nio.file.Paths;

public class PathExample {
public static void main(String[] args) {
//创建路径
Path path=Paths.get("D:/BlockChain/IdeaProjects/testfile/Lee.txt");

//获取父路径
Path parent=path.getParent();
System.out.println("Parent:"+parent);

//获取文件名
Path fileName=path.getFileName();
System.out.println("File name:"+fileName);

//解析相对路径
Path resolvePath=path.resolve("E:/newnewLee.txt");
System.out.println("Resloved path:"+resolvePath);

//规范化路径
Path normalizedPath=path.normalize();
System.out.println("Normalized path:"+normalizedPath);

//转换为绝对路径
Path absolutePath=path.toAbsolutePath();
System.out.println("Absolute path:"+absolutePath);

//转换为file对象
java.io.File file=path.toFile();
System.out.println("File object:"+file);
}
}

运行结果如下:

image-20240923235743654

2)file类

java.io.File类用于表示文件系统中的文件和目录,一组方法来创建、删除、重命名文件或目录,以及获取文件的各种属性,如文件大小、最后修改时间。File类只能操作文件或文件夹本身,不能读写文件里面的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.example.p2pshare.cotroller;
import java.io.File;
import java.io.IOException;

public class FileExample {
public static void main(String[] args) {
//创建file对象
File file=new File("D:/BlockChain/IdeaProjects/testfile/Lee.txt");

//输出文件信息
System.out.println("File exists:"+file.exists());
System.out.println("Is directory:"+file.isDirectory());
System.out.println("Is file:"+file.isFile());
System.out.println("Path:"+file.getPath());
System.out.println("Name:"+file.getName());
System.out.println("Parent:"+file.getParent());
System.out.println("Absolute path:"+file.getAbsolutePath());
System.out.println("Length:"+file.length());
System.out.println("Last modified:"+file.lastModified());

try {
//创建文件或目录
File newFile=new File("E:/Likt/test/lee3.txt");
boolean created=newFile.createNewFile();
System.out.println("Created new file:"+created);

File newDir=new File("E:/Likt/test/newdir/");
boolean dirCreated=newDir.mkdir();
System.out.println("Created new directory:"+dirCreated);

//列出目录中的文件
File dir=new File("E:/Likt/test/");
String[] files=dir.list();
for (String fileName:files){
System.out.println("File in directory:"+fileName);
}
}catch (IOException e){
System.err.println("An IOException occurred:"+e.getMessage());
}
}
}

运行结果如下:

image-20240924004102309

3)InputStream和OutPutStream流简介

  • 处理字节流的基本类。分别用于读取写入字节数据。

  • InputStream是一个抽象类,用于从源读取字节数据。所有的字节输入流都继承自InputStream。其常见的子类有:

  • FileInputStream从文件系统中的文件读取字节。从文件系统中的一个文件读取字节,打开一个从文件系统中的指定文件到应用程序的输入字节流。

  • FileOutputStream用于将数据写入文件系统中的文件。它创建一个向文件系统中指定文件提供输出的文件输出流。

  • ByteArrayInputStream从字节数组读取字节

  • ObjectInputStream用于反序列化对象

  • BUfferedInputStream为其他输入流添加缓冲功能,提高读取效率。

  • PipedInputStream用于线程间的通信,与PipedOutputStream配合使用。

  • OutputStream同样是一个抽象类,向目的地写入字节数据。其子类与InputStream类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.example.p2pshare.cotroller;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

@RestController
public class TestController {
public static void main(String[] args) {
String sourceFilePath = "D:\\BlockChain\\IdeaProjects\\testfile\\Lee.txt";
String destinationFolderPath = "D:\\BlockChain\\IdeaProjects\\testfile\\test2\\";
Path sourcePath = Paths.get(sourceFilePath);//源文件路径
Path destFolderPath = Paths.get(destinationFolderPath);//目标文件夹路径
Path destFilePath = destFolderPath.resolve(sourcePath.getFileName());//目标文件的地址;这里还是相对路径的问题
try {
copyFileToFolder(sourcePath, destFilePath);
}catch (IOException e){
System.out.println("这里异常了一下");
e.printStackTrace();
}
}
public static void copyFileToFolder(Path sourcePath,Path destFilePath) throws IOException{
//检查源文件是否存在
if(!Files.exists(sourcePath)){
throw new IOException("源文件不存在:"+sourcePath);
}
if(!Files.isDirectory(destFilePath.getParent())){
throw new IOException("目标文件不存在:"+destFilePath.getParent());
}
try(FileInputStream fis =new FileInputStream(sourcePath.toFile());
FileOutputStream fos=new FileOutputStream(destFilePath.toFile())
){
//创建一个字节数组来存储文件内容
byte[] buffer= new byte[1024];//使用较大的缓冲区
int bytesRead;
//循环读取文件内容直到读完
while ((bytesRead=fis.read(buffer))!=-1){
//写入到目标文件
fos.write(buffer,0,bytesRead);
}
}
}

}

这里在main函数调用copyFileToFolder函数的时候添加了捕获异常的处理,否则IDEA会不让进行编译。

当目标文件夹不存在时:

image-20240924211113414

当创建完毕:

image-20250112172529573

会发现lee.txt被复制到了目标文件夹test2中:

image-20240924211310721

4)使用springboot框架实现P2P节点的IP发现

拉取引导节点:git clone https://gitee.com/daitoulin/p2p_bootstrap.git

修改配置文件application.properties

1
2
3
4
#引导节点IP:点开terminal输入ipconfig查看本地WLAN的IP地址
bootstrap.ip= 10.39.203.64
#引导节点端口
bootstrap.port=8883

edit configurations:

image-20240924213208543

run application

image-20240924213357651

对等节点:

同样修改配置文件application.properties

1
2
3
4
5
6
7
8
9
10
server.port=8888
#引导节点IP:点开terminal输入ipconfig查看本地WLAN的IP地址
bootstrap.ip= 10.39.203.64
#引导节点端口
bootstrap.port=8883
node.ip= 10.39.203.64
node.port = 8887
spring.servlet.multipart.max-request-size=200MB
spring.servlet.multipart.max-file-size=200MB
share.file.path=D:/BlockChain/IdeaProjects/p2p_node/sharefiles

run application【同上】出现Bootstrapped? true和引导节点取得连接

image-20240924214623015

至此,完成了P2P下载器的节点发现功能。

5)使用springboot框架完成两个节点内的文件传输

①先启动引导节点

②启动对等节点

③postman的使用:指定IP,端口(p2p_node节点配置文件中service port 8888),指定接口,参数 进行文件的传输【post,get,request,body:form-data,raw-json】

6)常见接口

  1. @RequestParam:将参数跟随在url的问号后面

  2. @RequestMapping可以支持get及post传参,将@RequestMapping改为@PostMapping然后使用get传参会发现失败。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @RequestMapping("/testParam")
    public ResponseEntity<JSONResult>testParam(@RequestParam("username")String username){
    JSONResult json=JSONResult.getInstance();
    json.setCode("200");
    json.setMsg("用户名为:"+username);
    json.setContent("");
    return new ResonseEntity<JSONResult>(json,HttpStatus.OK);

    }

    image-20240924222212001

    修改之后,GET传参失败,证实了上述说明:

    image-20240924222501455

  3. @RequestBody:将参数转为json放在请求体中

  4. @PathVariable:/test/{username},绑定最后一个斜杆后面的字符串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @RequestMapping("/testPathVariable/{username}")
    public ResponseEntity<JSONResult> testPathVariable(@PathVariable("username") String username){
    JSONResult json = JSONResult.getInstance();

    json.setCode("200");
    json.setMsg("用户名为:" + username);
    json.setContent("");
    return new ResponseEntity<JSONResult>(json, HttpStatus.OK);
    }

    image-20240924223720919

  5. /uploadFile:参数multipartFile选择file

    • 不加任何注释的:将自动转为Java实体类,一般前端以form-data形式传递
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @PostMapping("/uploadFile")
    public ResponseEntity<JSONResult> testUpload(MultipartFile multipartFile) throws IOException {
    JSONResult json = JSONResult.getInstance();

    String fileName = multipartFile.getOriginalFilename();
    Path targetLocation = Paths.get(shareFilePath, fileName);

    // 保存文件到指定位置
    multipartFile.transferTo(targetLocation);
    json.setCode("200");
    json.setMsg("上传成功");
    json.setContent("");
    return new ResponseEntity<JSONResult>(json, HttpStatus.OK);
    }

    在POSTMAN中使用uploadFile接口上传文件成功:参数multipartFile选择file,添加要上传的文件。

    image-20240924225659175

    在HTML页面中查看确认上传成功:

    image-20240924225727420

    image-20240924225814855

  6. selectIp:查询IP

    image-20240926230749038

    image-20240924230352265

  7. queryDocument查询文件,F12查看如下:

    image-20240924230547313

    image-20240924231053189

  8. /findDocument:

    image-20240926232515380

  9. /downloadFile以JSON形式上传ip,fileName下载文件失败了(将前端传输的文件名进行拼接,对指定的ip进行请求,收到返回的byte数组,存入本地指定目录)注意:下载的文件不能为空

    image-20240924234930913

    后面发现是配置文件中sharesfiles路径后面少了一个斜杠【表示共享文件夹,少了斜杠之后变成一个文件的路径了,故无法下载到文件】

    image-20240926000142444

    image-20240926000054087

  10. /download/{fileName}:将指定的文件读取转为byte数组并返回

    image-20240926233431075

四、实验结果与讨论

  1. 引导节点、对等节点启动成功

    image-20240927000052117

  2. P2P下载器实现文件的上传下载

    image-20240927000248147

五、总结

  1. P2P下载器是一类用于获取大文件或数据的应用程序,利用点对点技术(P2P)实现高效的下载过程。与传统的中心化下载方式不同,P2P下载器允许用户从多个源同时下载文件,提高了下载速度和资源利用率。

  2. Spring Boot 应用程序通常遵循分层架构设计原则,其中最常见的是三层架构:Controller、Service 和 Repository。每一层都有其特定的责任。①controller层:在Spring MVC中,控制器通常由带有@RestController@Controller注解的类实现。②service层:通常由带有@Service注解的类实现。③Repository层:通常由带有@Repository注解的类实现,并且经常使用Spring Data JPA或其他ORM框架来简化数据库操作。

  3. springboot框架实现 本地文件的传输、节点的IP发现、节点间的文件传输。

  4. 使用Spring Boot可以快速搭建P2P网络的服务端,包括节点管理、资源分配、用户认证等功能。

  5. 通过接口进行功能的访问,每个接口有相对应的参数。

  6. downloadFile接口是通过读取字节流下载,其文件要求不为空否则失败。

  7. 引导节点中sharefiles的路径

    share.file.path=D:/BlockChain/IdeaProjects/p2p_node/sharefiles/注意这是一个文件夹的路径,最后的一个斜杠如果漏了会变成文件路径,文件下载会失败。同时要注意到斜杆的方向。

-------------本文结束感谢您的阅读-------------