一、实验目的
学习p2p知识,并搭建一个局域网p2p下载器
使用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放在距离很远的文件夹会如何结果。
12345678910111213141516171819202122232425262728293031323334353637package 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); }}
运行结果如下:
2)file类java.io.File类用于表示文件系统中的文件和目录,一组方法来创建、删除、重命名文件或目录,以及获取文件的各种属性,如文件大小、最后修改时间。File类只能操作文件或文件夹本身,不能读写文件里面的数据。
123456789101112131415161718192021222324252627282930313233343536373839404142package 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()); } }}
运行结果如下:
3)InputStream和OutPutStream流简介
处理字节流的基本类。分别用于读取写入字节数据。
InputStream是一个抽象类,用于从源读取字节数据。所有的字节输入流都继承自InputStream。其常见的子类有:
FileInputStream从文件系统中的文件读取字节。从文件系统中的一个文件读取字节,打开一个从文件系统中的指定文件到应用程序的输入字节流。
FileOutputStream用于将数据写入文件系统中的文件。它创建一个向文件系统中指定文件提供输出的文件输出流。
ByteArrayInputStream从字节数组读取字节
ObjectInputStream用于反序列化对象
BUfferedInputStream为其他输入流添加缓冲功能,提高读取效率。
PipedInputStream用于线程间的通信,与PipedOutputStream配合使用。
OutputStream同样是一个抽象类,向目的地写入字节数据。其子类与InputStream类似。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950package 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;@RestControllerpublic 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会不让进行编译。
当目标文件夹不存在时:
当创建完毕:
会发现lee.txt被复制到了目标文件夹test2中:
4)使用springboot框架实现P2P节点的IP发现拉取引导节点:git clone https://gitee.com/daitoulin/p2p_bootstrap.git
修改配置文件application.properties
1234#引导节点IP:点开terminal输入ipconfig查看本地WLAN的IP地址bootstrap.ip= 10.39.203.64#引导节点端口bootstrap.port=8883
edit configurations:
run application
对等节点:
同样修改配置文件application.properties
12345678910server.port=8888#引导节点IP:点开terminal输入ipconfig查看本地WLAN的IP地址bootstrap.ip= 10.39.203.64#引导节点端口bootstrap.port=8883node.ip= 10.39.203.64node.port = 8887spring.servlet.multipart.max-request-size=200MBspring.servlet.multipart.max-file-size=200MBshare.file.path=D:/BlockChain/IdeaProjects/p2p_node/sharefiles
run application【同上】出现Bootstrapped? true和引导节点取得连接
至此,完成了P2P下载器的节点发现功能。
5)使用springboot框架完成两个节点内的文件传输①先启动引导节点
②启动对等节点
③postman的使用:指定IP,端口(p2p_node节点配置文件中service port 8888),指定接口,参数 进行文件的传输【post,get,request,body:form-data,raw-json】
6)常见接口
@RequestParam:将参数跟随在url的问号后面
@RequestMapping可以支持get及post传参,将@RequestMapping改为@PostMapping然后使用get传参会发现失败。
123456789@RequestMapping("/testParam")public ResponseEntity
修改之后,GET传参失败,证实了上述说明:
@RequestBody:将参数转为json放在请求体中
@PathVariable:/test/{username},绑定最后一个斜杆后面的字符串
123456789@RequestMapping("/testPathVariable/{username}")public ResponseEntity
/uploadFile:参数multipartFile选择file
不加任何注释的:将自动转为Java实体类,一般前端以form-data形式传递
1234567891011121314@PostMapping("/uploadFile")public ResponseEntity
在POSTMAN中使用uploadFile接口上传文件成功:参数multipartFile选择file,添加要上传的文件。
在HTML页面中查看确认上传成功:
selectIp:查询IP
queryDocument查询文件,F12查看如下:
/findDocument:
/downloadFile以JSON形式上传ip,fileName下载文件失败了(将前端传输的文件名进行拼接,对指定的ip进行请求,收到返回的byte数组,存入本地指定目录)注意:下载的文件不能为空
后面发现是配置文件中sharesfiles路径后面少了一个斜杠【表示共享文件夹,少了斜杠之后变成一个文件的路径了,故无法下载到文件】
/download/{fileName}:将指定的文件读取转为byte数组并返回
四、实验结果与讨论
引导节点、对等节点启动成功
P2P下载器实现文件的上传下载
五、总结
P2P下载器是一类用于获取大文件或数据的应用程序,利用点对点技术(P2P)实现高效的下载过程。与传统的中心化下载方式不同,P2P下载器允许用户从多个源同时下载文件,提高了下载速度和资源利用率。
Spring Boot 应用程序通常遵循分层架构设计原则,其中最常见的是三层架构:Controller、Service 和 Repository。每一层都有其特定的责任。①controller层:在Spring MVC中,控制器通常由带有@RestController或@Controller注解的类实现。②service层:通常由带有@Service注解的类实现。③Repository层:通常由带有@Repository注解的类实现,并且经常使用Spring Data JPA或其他ORM框架来简化数据库操作。
springboot框架实现 本地文件的传输、节点的IP发现、节点间的文件传输。
使用Spring Boot可以快速搭建P2P网络的服务端,包括节点管理、资源分配、用户认证等功能。
通过接口进行功能的访问,每个接口有相对应的参数。
downloadFile接口是通过读取字节流下载,其文件要求不为空否则失败。
引导节点中sharefiles的路径
share.file.path=D:/BlockChain/IdeaProjects/p2p_node/sharefiles/注意这是一个文件夹的路径,最后的一个斜杠如果漏了会变成文件路径,文件下载会失败。同时要注意到斜杆的方向。