首先有几个工具类:
UnreceivedFileSectionInfo
用于存储单个文件中未接收到的部分,其中有Section列表用于存储、更改所收信息。(在之前文件片段传输处理就是这个类的作用)
UnreceivedFilePool
用于依据UnreceivedFileSectionInfo
存储整个工程中缺少的文件。
接着整个过程:
最顶层是由用户所需的Resource建立对应的ResourceReceiver
,该类依据所拥有的Resource先在本地创建接收环境(即对应的工程空目录先建立起来),接收准备做好之后就创建Requester并连接资源注册中心进行对应资源拥有者列表的索取。
在服务器发回拥有该资源的持有者列表后,就轮到接收者这边进行发送者的筛选及每个发送者任务的分配。
public boolean dealResourceSenderList(List<NodeAddress> senderList) {
if (senderList == null || senderList.isEmpty()) {
resourceReceiverAction.resourceNotFound();
close();
return false;
}
List<NodeAddress> senders = selectSender(senderList);
int senderCount = senders.size();
receiveServer = new ReceiveServer(ResourceReceiver.this,
senderCount, requestResourceStructor);
try {
receiveServer.startReceiveServer();
receiverServerAddress = receiveServer.getReceiverServerAddress();
requestResourceStructor.distributeResource(senderCount);
List<ResourceFileSectionInfo> resourceFileSectionInfoList =
requestResourceStructor.getResourceFileSectionList();
splitSendContent(senderCount, resourceFileSectionInfoList, senderList);
} catch (Exception e) {
// 资源接收服务器启动异常,资源接收失败!
resourceReceiverAction.receiveServerFailure(e);
close();
}
return true;
}
在筛选完发送者之后,需要接收端搭建起自身的接收服务器,等待分配到任务的发送者进行连接。
分发策略在先前解析过,这一步就省略掉。
分发后通过调用splitSendContent
(这一步是RMI调用,实际调用的是ResourceSender
的getSendContent
)从这一步开始进入到发送者的任务,和接收端创立连接、发送资源。
这里注意的是在接收端,筛选出来的发送者数为n,那么在接收服务器会创建n个接收线程进行,即创建n个FileReceiver
接收,内部用FileSectionReceiver
进行接收。
public void run() {
this.receiveServerStartup = true;
int senderCount = 0;
while (senderCount < this.senderCount) {
try {
Socket sender = this.server.accept();
++senderCount;
DataInputStream dis = new DataInputStream(sender.getInputStream());
new Thread(new Runnable() {
@Override
public void run() {
FileReceiver fileReceiver = new FileReceiver(
dis, resourceStructor, randomAccessFilePool,
unreceivedFilePool);
fileReceiver.receiveFile();
}
}).start();
} catch (IOException e) {
e.printStackTrace();
break;
}
}
this.resourceReceiver.close();
}
Section接收过程:先接收一个头,记录该片段的偏移量、长度等,因为网络传输限制32k,那么这个类就会依据头片段里的长度持续接收32k,直到接收完整所需的片段再回溯到FileReceiver
。
public int receive() throws IOException {
byte[] fileSectionHead = new byte[FileSectionInfo.FILE_SECTION_INFO_LEN];
this.dis.read(fileSectionHead);
this.fileSectionInfo = new FileSectionInfo(fileSectionHead);
int totalLen = (int) this.fileSectionInfo.getLength();
this.fileSection = new byte[totalLen];
int offset = 0;
int currentLen = 0;
while (totalLen > 0) {
currentLen = this.dis.read(this.fileSection, offset, totalLen);
offset += currentLen;
totalLen -= currentLen;
}
return (int) this.fileSectionInfo.getLength();
}
FileReceiver会依据回溯来的片段借助FileSectionWriter
(实际就是借助从randomAccessFilePool中取出该文件的RandomAccessFile进行寻址、写入)把接收到的片段写入文件。
public void receiveFile() {
FileSectionReceiver fileSectionReceiver = new FileSectionReceiver(this.dis);
int receiveSectionLen = 1;
try {
while (receiveSectionLen > 0) {
receiveSectionLen = fileSectionReceiver.receive();
if (receiveSectionLen <= 0) {
break;
}
FileSectionWriter fileSectionWriter = new FileSectionWriter();
FileSectionInfo fileSectionInfo = fileSectionReceiver.getFileSectionInfo();
int fileNo = fileSectionInfo.getFileNo();
fileSectionWriter.setFileSectionInfo(fileSectionInfo);
fileSectionWriter.setFileSection(fileSectionReceiver.getFileSection());
RandomAccessFile rafForWrite = this.randomAccessFilePool
.getRandomAccessFile(fileSectionInfo.getFileNo(), "rw");
fileSectionWriter.setRafForWrite(rafForWrite);
fileSectionWriter.writeFileSection();
UnreceivedFileSectionInfo unreceivedFileSectionInfo = this.unreceivedFilePool
.getUnreceivedFileSection(fileNo);
unreceivedFileSectionInfo.receiveFileSection(fileSectionInfo);
if (unreceivedFileSectionInfo.isReceiveAll()) {
this.randomAccessFilePool.close(fileNo);
this.unreceivedFilePool.removeUnreceivedFileSection(fileNo);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
接收完片段就要在最开始的unreceivedFilePool中找到对应该文件的UnreceivedFileSectionInfo并进行未接收片段信息的更改。