初始时只有资源根服务器(APP服务器)拥有资源,资源根服务器上线后向资源中心注册自己的资源;用户通过网络来获取自己要请求的的资源,用户申请资源时需要向资源中心申请,系统将筛选多个发送端向接收端发送不同的资源片段,接收端接收资源以后,也成为拥有该资源的一个发送节点;那么随着时间的推移,不同用户下载了不同的资源,资源发送节点会逐渐增加,资源根服务器的压力也会逐渐减小。
用于资源提供端与资源接收端的资源传输。
/**
* @author dingxiang
*规范底层收发的接口(以字节流形式传输),TCP/IP应用层数据包最大长度是2的16次方-1
*因此这里的缓冲区大小务必设置为比2的16次方大。
*/
public interface ISendReceive {
int DEFAULT_BUFFER_SIZE=1<<16;
void sendData(DataOutputStream dos,byte[] value) throws IOException;
byte[] receiveData(DataInputStream dis,int size) throws IOException;
}
/**
* @author dingxiang
*ISendReceive接口的实现类,实现底层收发传输。
*/
public class SendReceive implements ISendReceive{
private int buffer;
public SendReceive() {
this(DEFAULT_BUFFER_SIZE);
}
public SendReceive(int buffersize) {
this.buffer=buffersize;
}
@Override
public void sendData(DataOutputStream dos, byte[] value) throws IOException {
dos.write(value);
}
@Override
public byte[] receiveData(DataInputStream dis, int size) throws IOException {
byte[] value=new byte[size];
int readlen=0;
int restlen=size;
int offset=0;
int len;
while (restlen>0) {
len=restlen>buffer?buffer:restlen;
readlen=dis.read(value, offset, len);
offset+=readlen;
restlen-=readlen;
}
return value;
}
}
用于表示网络中的节点(一个节点既可以是资源提供端,也可以是资源请求端)。
/**
* @author dingxiang
* 网络节点抽象类
*/
public abstract class INetNode {
public static int SERVER = 1;
public static int CLIENT = 0;
abstract String getIp();
abstract int getPort();
abstract int getType();//网络节点类型(是资源根(APP)服务器,还是客户端)
abstract int getSendTime();//接点的发送次数
abstract void increaseSendTime();//当参与发送后,增加自身发送次数,以供下一轮的节点选择策略选择。
abstract int getSendPort();//作为发送端时的发送端口
abstract int getReceivePort();//作为接收端时的接收端口
abstract void increaseSendingTime();//增加该节点同时参与的发送次数
abstract void decreaseSendingTime();//减少该节点同时参与的发送次数
abstract int getSendingTime();//获取该节点同时参与的发送个数
}
网络节点具体类表示。
/**
* @author dingxiang
*网络节点信息类
*/
public class NetNode extends INetNode implements Serializable {
private static final long serialVersionUID = -6820240474455702629L;
private String Ip;
private int port;
private int sendPort;//发送端口
private int receivePort;//接收端口
private int sendTime;//发送次数
private int sendingTime;//正在参与的发送个数
private int type;//节点类型
public NetNode() {
}
public NetNode(String ip, int port,int type) {
this.Ip = ip;
this.port = port;
this.type=type;
}
public void setSendPort(int sendPort) {
this.sendPort = sendPort;
}
public void setReceivePort(int receivePort) {
this.receivePort = receivePort;
}
public String getIp() {
return this.Ip;
}
public void setIp(String ip) {
Ip = ip;
}
public int getPort() {
return this.port;
}
public int getSendTime() {
return this.sendTime;
}
public void increaseSendTime() {
this.sendTime++;
}
public void increaseSendingTime() {
this.sendingTime++;
}
void decreaseSendingTime() {
this.sendingTime--;
}
public int getSendingTime() {
return sendingTime;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public int getSendPort() {
return this.sendPort;
}
public int getReceivePort() {
return this.receivePort;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((Ip == null) ? 0 : Ip.hashCode());
result = prime * result + port;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if(getClass()!=obj.getClass()) {
return false;
}
NetNode other = (NetNode) obj;
if (Ip.equals(other.getIp())&&port==other.getPort()) {
return true;
}
return false;
}
@Override
public String toString() {
return "[ip= "+this.Ip+" port="+this.port+(type==INetNode.SERVER?"服务器":"客户端");
}
}
我们知道,网络中传输的资源可以是文件也可以是文件夹。当我们下载一个资源的时候,里面的资源结构是没有改变的,因此有必要描述资源里面的文件在资源结构里面的位置。因此引入ResourceStructInfo 。当一个文件在资源里面被定位以后,我们可以把它切片,不同的资源提供端发送不同的片段。由此引入SectionInfo来描述文件片段信息,而FileSection则表示具体的文件片段。里面涉及到字节数组与int,long型的转化。关于字节数组与int和long的转化,请参见ByteExchanger。
/**
* @author dingxiang
*用4(文件句柄)+8(片段在文件中的偏移量)+4(片段大小)=16字节来表示文件片段。
*/
public class SectionInfo implements Serializable{
private static final long serialVersionUID = -5339066912898528409L;
public static final int SECTION_INFO_LENGTH=16;
private int fileHandle;//片段所在文件句柄
private long offset;//片段偏移量
private int size;//片段大小
public SectionInfo() {
}
//利用ByteExchanger实现bytes[]转化为Int
public SectionInfo(byte[] value) {
if (value.length!=SECTION_INFO_LENGTH) {
System.out.println("the length of the array is not 16B!");
return;
}
byte[] bfileHandle=ByteExchanger.GetBytesAt(value, 0, 4);
byte[] boffset=ByteExchanger.GetBytesAt(value, 4, 8);
byte[] bsize=ByteExchanger.GetBytesAt(value, 12, 4);
this.fileHandle=ByteExchanger.BytesToInt(bfileHandle);
this.offset=ByteExchanger.BytesToLong(boffset);
this.size=ByteExchanger.BytesToInt(bsize);
}
/**判断偏移位置与大小是否合理。
* @param offset
* @param size
* @return
*/
public boolean isRightSection(long offset,int size) {
return (this.offset<=offset)&&(this.offset+this.size>=(offset+size));
}
public SectionInfo(int fileHandle,long offset,int size) {
this.fileHandle=fileHandle;
this.offset=offset;
this.size=size;
}
public int getFileHandle() {
return fileHandle;
}
public void setFileHandle(int fileHandle) {
this.fileHandle = fileHandle;
}
public long getOffset() {
return offset;
}
public void setOffset(long offset) {
this.offset = offset;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
//利用ByteExchanger实现Int转化为bytes[]
public byte[] ToBytes() {
byte[] bFileHandle =ByteExchanger.IntToBytes(fileHandle);
byte[] bOffset =ByteExchanger.LongToBytes(offset);
byte[] bSize =ByteExchanger.IntToBytes(size);
byte[] target = new byte[16];
ByteExchanger.SetBytesAt(target, 0, bFileHandle);
ByteExchanger.SetBytesAt(target, 4, bOffset);
ByteExchanger.SetBytesAt(target, 12, bSize);
return target;
}
@Override
public String toString() {
StringBuffer res = new StringBuffer();
res.append("fileHandle:").append(this.fileHandle).append(" , ")
.append("offset:").append(this.offset).append(" , ")
.append("size:").append(this.size);
return res.toString();
}
}
需要注意的是,sectionInfo的size为int型,由此单个sectionInfo的大小不能超过2^32-1(约2G),如果超过了,可以把它分得更小些,直至没有超过再把分的片段加入到请求列表。
/**
* @author dingxiang
*文件片段类,包含文件某一片段的具体内容
*/
public class FileSection {
private SectionInfo sectionInfo;//片段描述信息
private byte[] value;//文件片段内容(字节形式表示)
private ISendReceive sendReceive;//底层收发传输接口
public FileSection() {
if (sendReceive==null) {
this.sendReceive=new SendReceive();
}
}
public void setSendReceive(ISendReceive sendReceive) {
this.sendReceive = sendReceive;
}
public byte[] getValue() {
return value;
}
public void setValue(byte[] value) {
this.value = value;
}
public void setSectionInfo(SectionInfo sectionInfo) {
this.sectionInfo = sectionInfo;
}
public SectionInfo getSectionInfo() {
return sectionInfo;
}
//先发送16字节文件片段描述信息,再发送具体文件片段内容
public void sendSection(DataOutputStream dos) throws IOException {
sendReceive.sendData(dos, sectionInfo.ToBytes());
sendReceive.sendData(dos, value);
}
//先接受文件片段信息,再接收文件片段具体内容。
public void receiveSection(DataInputStream dis) throws IOException {
byte[] sectionHead=sendReceive.receiveData(dis, SectionInfo.SECTION_INFO_LENGTH);
this.sectionInfo=new SectionInfo(sectionHead);
this.value=sendReceive.receiveData(dis, sectionInfo.getSize());
}
}
根据资源根路径扫描形成资源框架信息的时候,一个ResourceStructInfo一般对应着一个文件在资源里面的相对路径,但是有一种特殊情况,资源里面某一个文件夹是一个空文件夹(如果不是空文件夹,就是说文件夹里面有东西,这个时候,继续递归处理)。有人说,对应着文件夹和文件,以相对路径中的后缀名标识不就行了吗?然而,如下图所示。
/**
* @author dingxiang
*表示相对于资源根的文件框架信息
*/
public class ResourceStructInfo implements Serializable{
/**
*
*/
private static final long serialVersionUID = -6314015119146684779L;
private int fileHandle;//文件句柄
private String filePath;//文件相对于资源根的路径
private long filesize;//文件大小
private boolean fileTruth;//标识是文件还是空文件夹
public ResourceStructInfo() {
}
public ResourceStructInfo(ResourceStructInfo rsi) {
this.fileHandle=rsi.getFileHandle();
this.filePath=rsi.getFilePath();
this.filesize=rsi.getFileSize();
this.fileTruth=rsi.isFileTruth();
}
public boolean isFileTruth() {
return fileTruth;
}
public void setFileTruth(boolean fileTruth) {
this.fileTruth = fileTruth;
}
public int getFileHandle() {
return fileHandle;
}
public void setFileHandle(int fileHandle) {
this.fileHandle = fileHandle;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public long getFileSize() {
return filesize;
}
public void setFileSize(long filesize) {
this.filesize = filesize;
}
@Override
public String toString() {
return "fileHandle="+this.fileHandle+" ,filePath="+this.filePath+" ,fileSize="
+this.filesize+" ,type="+(fileTruth==true?"文件":"空文件夹");
}
}
由此可见,一个资源的基本信息必然包含资源框架信息,以及文件片段信息。资源请求端得到资源列表以后,对比本地,查找缺失资源,然后选择具体的要请求的资源进行请求。这里涉及到一个问题,拿到资源框架信息以后,是资源请求端开启接收服务器后接收线程边创建边接收?还是在资源请求之前就创建好资源框架信息,然后再接收?为了提高性能以及遵守职责尽量单一的原则,这里选择后者。
**
* @author dingxiang
*资源的表示
*/
public class ResourceBaseInfo implements Serializable{
/**
*
*/
private static final long serialVersionUID = -3071705794619909008L;
private String Name;//资源名称(可包含版本信息)
private String absolutePath;//资源路径
private List<ResourceStructInfo> rsiList;//资源框架信息
private List<SectionInfo> siList;//资源所有文件片段信息
public ResourceBaseInfo() {
}
public ResourceBaseInfo(ResourceBaseInfo rbi) {
this.Name = rbi.getName();
this.absolutePath = rbi.getAbsolutePath();
this.rsiList=rbi.getRsiList();
}
public String getName() {
return Name;
}
public void setName(String Name) {
this.Name = Name;
}
public List<ResourceStructInfo> getRsiList() {
return rsiList;
}
public void setRsiList(List<ResourceStructInfo> rsiList) {
this.rsiList = rsiList;
}
public String getAbsolutePath() {
return absolutePath;
}
public void setAbsolutePath(String absolutePath) {
this.absolutePath = absolutePath;
}
public List<SectionInfo> getSectionList() {
return siList;
}
public void setSectionList(List<SectionInfo> sectionList) {
this.siList = sectionList;
}
public ResourceBaseInfo LoadResource() {
return LoadResource(absolutePath);
}
/**
* @param absoluteRoot
* 根据传入的资源根路径,加载资源信息。
* @return
* 返回加载以后的资源实体
*/
public ResourceBaseInfo LoadResource(String absoluteRoot) {
absoluteRoot=absoluteRoot==null?this.getAbsolutePath():absoluteRoot;
File file=new File(absoluteRoot);
List<ResourceStructInfo> rsiList=new ArrayList<>();
scanResource(rsiList, 1, absoluteRoot, file);
this.setRsiList(rsiList);
return this;
}
/**
* @param rbiList 资源框架信息列表
* @param startFileHandle 文件句柄
* @param absolutePath 资源根路径
* @param curFile 当前路径对应的文件或者文件夹
* @param isEmptyDirectory 判断是否是空文件夹
* @return
*/
public int createResourceStructInfo(List<ResourceStructInfo> rbiList,
int startFileHandle,String absolutePath,File curFile,Boolean isEmptyDirectory) {
ResourceStructInfo rsInfo=new ResourceStructInfo();
rsInfo.setFileHandle(startFileHandle++);
rsInfo.setFilePath(curFile.getAbsolutePath().replace(absolutePath, ""));//根据资源根截取相对路径
rsInfo.setFileSize(curFile.length());
rsInfo.setFileTruth(isEmptyDirectory);
rbiList.add(rsInfo);
return startFileHandle;
}
/**扫描资源,形成资源框架信息。
* @param rbiList
* @param startFileHandle
* @param absolutePath
* @param curFile
* @return
*/
public int scanResource(List<ResourceStructInfo> rbiList,int startFileHandle,String absolutePath,File curFile) {
if (curFile==null) {
return 0;
}
//如果是文件,形成对应文件的文件框架
if (curFile.isFile()) {
startFileHandle=createResourceStructInfo(rbiList, startFileHandle, absolutePath, curFile,true);
}
//如果是目录,则遍历
if (curFile.isDirectory()) {
File[] files=curFile.listFiles();
//空文件夹处理
if (files.length==0) {
startFileHandle=createResourceStructInfo(rbiList, startFileHandle, absolutePath, curFile,false);
}
//非空文件夹处理
for (File file : files) {
if (file.isFile()) {
startFileHandle=createResourceStructInfo(rbiList, startFileHandle, absolutePath, file,true);
}else {
startFileHandle=scanResource(rbiList, startFileHandle, absolutePath, file);
}
}
}
return startFileHandle;
}
/**
* 用于资源请求端向资源中心请求资源,请求的资源与请求到的资源唯一不同的是资源根所在绝对路径。
* @param absoluteRoot 对应请求资源保存的绝对路径
* @return ResourceBaseInfo //
*/
public ResourceBaseInfo getRequestBaseInfo(String absoluteRoot) {
ResourceBaseInfo rbi = new ResourceBaseInfo();
rbi.setName(this.Name);
rbi.setAbsolutePath(absoluteRoot);
rbi.setRsiList(this.rsiList);
rbi.getRequestSectionList(this.rsiList);//具体要请求的文件片段集合
return rbi;
}
/**
*根据请求资源的框架信息以后对比本地,搜集缺失的的资源文件片段。
*对于已经存在的文件,则不参与请求。
*对于不存在的文件,则先创建,再参与请求(如果是空目录,则只创建,不参与请求)。
*对于未接收完的接收完的文件,则重新参与请求。
* @param rsiList
*/
public void getRequestSectionList(List<ResourceStructInfo> rsiList) {
this.siList = new ArrayList<>();
for (ResourceStructInfo rsi : rsiList) {
String filePath = absolutePath + rsi.getFilePath();
if (isRight(rsi, filePath)) {
continue;
}
createFile(filePath,rsi);//对于不存在的文件,则创建。
if (!rsi.isFileTruth()) {//如果是空文件夹,则只创建而不参与请求。
continue;
}
SectionInfo sectionInfo = new SectionInfo(rsi.getFileHandle(), 0L, (int) rsi.getFileSize());
this.siList.add(sectionInfo);
}
}
//资源请求端根据请求的资源的框架信息创建本地对应保存请求资源的框架信息。
private void createFile(String filePath,ResourceStructInfo rsi) {
File targetFile=new File(filePath);
File parentFile=targetFile.getParentFile();
if (!targetFile.exists()) {
if (!parentFile.exists()) {
parentFile.mkdirs();
}
try {
if (rsi.isFileTruth()) {//如果是文件,则按文件方式创建新文件。
targetFile.createNewFile();
}else {
targetFile.mkdir();//如果是空文件夹,则创建空文件夹。
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 如果根据资源框架信息的文件已经存在,并且文件片段没有缺失,则是已经接收完的文件。
* @param resourceStructInfo
* @param filePath
* @return
*/
private boolean isRight(ResourceStructInfo resourceStructInfo, String filePath) {
File file = new File(filePath);
if (!file.exists()) {
return false;
}
if (file.length() != resourceStructInfo.getFileSize()) {
return false;
}
// 还可以进行其他验证
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((Name == null) ? 0 : Name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ResourceBaseInfo other = (ResourceBaseInfo) obj;
if (Name == null) {
if (other.Name != null)
return false;
} else if (!(Name.equals(other.Name)))
return false;
return true;
}
@Override
public String toString() {
StringBuffer sbf=new StringBuffer();
sbf.append("Name="+this.Name).append("\n")
.append("absolutePath="+this.absolutePath);
if (rsiList!=null) {
for (ResourceStructInfo structInfo:rsiList) {
sbf.append(" \n\t"+structInfo);
}
}
if (siList != null) {
sbf.append("\nrequest list:");
for (SectionInfo si : siList) {
sbf.append(si).append("\n\t");
}
}
return sbf.toString();
}
}
涉及到资源的保存问题,要么以数据库,要么以文件的形式,我这里采用xml文件来保存资源,并且提供XmlOperation来实现xml文件与对象的转换。
package com.xd.mfct.resource;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**实现xml文件与对象的转换。
* @author dingxiang
* @param
*
*/
public class XmlOperation<T> {
private File operateFile;//操作的xml文件
private XMLEncoder encoder;//xml编码器
private XMLDecoder decoder;//xml解码器
private List<T> objects;//保存的的对象列表
public XmlOperation(String xmlFilePath) {
File xmlFile = new File(xmlFilePath);
if (!xmlFile.exists()) {
int lastIndex = xmlFilePath.lastIndexOf("\\");
String xmlFileDirs = xmlFilePath.substring(0, lastIndex);
File xmlFileDirPath = new File(xmlFileDirs);
xmlFileDirPath.mkdirs();
try {
xmlFile.createNewFile();
} catch (IOException e) {
System.out.println("无法创建"+xmlFile.getAbsolutePath());
}
}
this.operateFile=xmlFile;
this.objects=new LinkedList<T>();
}
public List<T> getObjects() {
return objects;
}
/**删除指定的对象对应的文件
* @param object
*/
public void remove(T object) {
Iterator<T> iterators=objects.iterator();
while (iterators.hasNext()) {
T obj = (T) iterators.next();
if (object.equals(obj)) {
iterators.remove();
}
}
try {
this.encoder=new XMLEncoder(new BufferedOutputStream(new FileOutputStream(operateFile)));
this.encoder.flush();
while (iterators.hasNext()) {
T tempObj = (T) iterators.next();
this.encoder.writeObject(tempObj);
}
this.encoder.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
/**加入object以后,将对象列表覆盖式写入xml文件
* @param object
* @throws FileNotFoundException
* @throws IOException
*/
public void addObject(T object) throws FileNotFoundException {
if (object==null) {
return;
}
this.encoder=new XMLEncoder(new BufferedOutputStream(new FileOutputStream(operateFile)));
this.encoder.flush();
this.objects.add(object);
Iterator<T> objectIterators=(Iterator<T>) objects.iterator();
while (objectIterators.hasNext()) {
T tempObj = (T) objectIterators.next();
this.encoder.writeObject(tempObj);
}
this.encoder.close();
}
/**读取xml文件中的对象列表。
* @return
* @throws IOException
*/
@SuppressWarnings("unchecked")
public List<T> readObjects() throws IOException{
this.decoder=new XMLDecoder(new BufferedInputStream(new FileInputStream(operateFile)));
T obj=null;
try {
while ((obj=(T) decoder.readObject()) != null) {
objects.add(obj);
}
} catch (Exception e) {
this.decoder.close();
}
return objects;
}
}
每个网络节点都有一个资源池,资源池提供了根据资源保存路径加载资源的方法,保存资源的方法,以及对比从资源中心获取的资源列表,查找缺失资源列表的方法。
1.资源提供端上线时加载本地资源到资源池,然后选择性地注册到资源中心。
2.当请求端根据缺失资源列表选择要请求的资源接收完以后,将该资源写入资源池,资源池内部将其保存到本地。
3.资源中心也可以用资源池来保存注册的资源以及节点信息。
package com.xd.mfct.resource;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author dingxiang
*资源池,以资源名称(包含版本信息)为键,资源为值
*/
public class ResourePool {
private static final String DEFAULT_RESOURCE_PATH="C:\\Users\\48450\\Videos\\resource.xml";
/**
* 保存资源信息的路径
*/
private static String resourcePath;
/**
* 资源池
*/
private static final Map<String, ResourceBaseInfo> rbiPool
= new ConcurrentHashMap<String, ResourceBaseInfo>();
/**
* 专属ResourceBaseInfo的xml操作类
*/
private static XmlOperation<ResourceBaseInfo> xmlOperation
=new XmlOperation<ResourceBaseInfo>(DEFAULT_RESOURCE_PATH);
public ResourePool() {
}
/**
* 1、本地第一次接收到该资源信息:
* 将rbi存储到rbiPool中。
* 2、本地已经拥有该资源,则不再添加;
* 3.将资源信息保存到本地
* @param rbi
* @throws IOException
*/
public static boolean insertResource(ResourceBaseInfo rbi) {
String rbiName = rbi.getName();
ResourceBaseInfo oldRbi = rbiPool.get(rbiName);
if (oldRbi != null) {
return false;
}
rbiPool.put(rbiName, rbi);
try {
//保存到本地xml文件
xmlOperation.addObject(rbi);
}catch (Exception e) {
e.printStackTrace();
}
return true;
}
/**设置本地资源保存路径
* @param resourceRootPath
*/
public static void setResourcePath(String resourceRootPath) {
resourcePath=resourceRootPath;
xmlOperation=new XmlOperation<ResourceBaseInfo>(resourcePath);
}
public static ResourceBaseInfo getResourceBaseInfo(String appName) {
return rbiPool.get(appName);
}
/**根据从资源中心取得的资源列表与本地的资源列表对比,查找缺失资源列表。
* @param rbisFromCenter
* @return
*/
public static List<ResourceBaseInfo> getUnReceivedResourceBaseInfo(List<ResourceBaseInfo> rbisFromCenter){
List<ResourceBaseInfo> unReceivedRbis=new LinkedList<ResourceBaseInfo>();
for(ResourceBaseInfo resourceBaseInfo:rbisFromCenter) {
if (rbiPool.containsValue(resourceBaseInfo)) {
continue;
}
unReceivedRbis.add(resourceBaseInfo);
}
return unReceivedRbis;
}
/**
* 根据传入的路径加载本地资源
*/
public static void loadResource(String resourceSavedXMLPath){
if (resourceSavedXMLPath==null) {
loadResource();
return;
}
//这里可以做更多的验证,比如文件后缀名等等。
if (resourceSavedXMLPath.length()==0) {
System.out.println("该路径错误");
return;
}
setResourcePath(resourceSavedXMLPath);
loadResource();
}
/**
* 加载默认存储路径文件保存的本地资源
*/
public static void loadResource(){
try {
List<ResourceBaseInfo> objects=xmlOperation.readObjects();
for (Object object : objects) {
ResourceBaseInfo resourceBaseInfo=(ResourceBaseInfo) object;
rbiPool.put(resourceBaseInfo.getName(), resourceBaseInfo);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static Map<String, ResourceBaseInfo> getRbipool() {
return rbiPool;
}
/**
* 将资源池里所有的资源保存到本地
*/
public void saveResource() {
for(Entry<String, ResourceBaseInfo> resourceEntry:rbiPool.entrySet()) {
try {
xmlOperation.addObject(resourceEntry.getValue());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
}
资源接收端每接收到一个文件判断,就更新文件片段列表的信息。
/**
* @author dingxiang
*断点续传的基础,未接收文件片段记录类。
*
*当接收时,用来检测文件句柄为fileHandle的文件是否被完整接收
*/
public class UnReceivedFileSection {
private int fileHandle;
private List<SectionInfo> sectionInfos;
public UnReceivedFileSection() {
}
public UnReceivedFileSection(int fileHandle,int size) {
this.fileHandle=fileHandle;
this.sectionInfos=new LinkedList<>();
SectionInfo sectionInfo=new SectionInfo(fileHandle, 0L, size);
sectionInfos.add(sectionInfo);
}
/**通过给定的文件片段找到列表中对应的合适位置
* @param sectionInfo
* @return
* @throws Exception
*/
public int GetRightSection(SectionInfo sectionInfo) throws Exception {
long offset=sectionInfo.getOffset();
int size=sectionInfo.getSize();
int len=sectionInfos.size();
for(int i=0;i<len;i++) {
SectionInfo section=sectionInfos.get(i);
if (section.isRightSection(offset, size)) {
return i;
}
}
throw new Exception("片段 "+sectionInfo+" 异常");
}
public List<SectionInfo> getSectionInfos() {
return sectionInfos;
}
/**通过接收新片段更新列表中的片段信息
* @param section
*/
public void afterReceiveSection(SectionInfo section) {
try {
int index = GetRightSection(section);
SectionInfo org = sectionInfos.get(index);
long orgOffset = org.getOffset();
int orgSize = org.getSize();
long curOffset = section.getOffset();
int curSize = section.getSize();
long leftOffset = orgOffset;
int leftSize = (int) (curOffset - orgOffset);
long rightOffset = curOffset + curSize;
int rightSize = (int) (orgOffset + orgSize - rightOffset);
sectionInfos.remove(index);
if (leftSize > 0) {
sectionInfos.add(new SectionInfo(fileHandle, leftOffset, leftSize));
}
if (rightSize > 0) {
sectionInfos.add(new SectionInfo(fileHandle, rightOffset, rightSize));
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**该文件句柄对应的文件的片段是否接收完毕
* @return
*/
public boolean IsReceivedCompleted() {
return sectionInfos.isEmpty();
}
}
图解示例如下:
假如有某个未接收文件,初始化为一个片段后,其起始偏移地址为0,大小size=11;并且加入到未接收文件片段列表。
1.当接收到该文件的片段1(offset=3,size=4)以后,先通过GetRightSection找到合适的片段,即片段1,再进行新的偏移量以及片段大小的计算,得到两个新的文件片段2(offset=0:size=1)和3(offset=5,size=6),然后将片段1(offset=3,size=4)删除,将片段2和3加入到未接收片段列表。
2.当在接收到新的文件片段4(offset=7,size=8)的时候,重复第一步的步骤,找到片段3,删除片段3,重新计算得到片段2,5,6,再加入未接收列表。
…
最后,如果该文件的片段都接收完毕后,则该文件对应的未接收文件列表为空。由此,当请求端接收任务完成后,可以去判断文件对应的未接收文件片段列表是否为空,如果不为空,则出现了漏发或者数据缺失的情况,这个时候再进行断点续传再次请求。
资源请求端通过RPC的方式,根据要请求的资源的,从资源中心获取拥有该资源的节点列表,并对节点进行筛选,取得参与发送的几个节点。这里涉及到选择的节点是否可用的问题。
一种思路是在资源中心通过心跳检测来检测出异常的节点进行剔除,但是这样会增加资源中心的压力,所以将这件事让资源请求端来做;资源请求端可以通过RPC去访问各个节点的状态,过滤到不可用或者说没有响应的节点,再进行节点选择。当然这种方式的话,当面对拥有该资源的节点较多的情况时,挨个访问节点状态也是耗时的;并且节点选择的时候其可用,但不能保证参与发送的时候就一定可用。
还有一种思路就是,节点选择的时候不考虑节点是否可用的问题,而是给节点发送任务的时候,根据返回值判断其工作状态,如果可用,则不做处理;如果不可用,那么报告错误,并且更新参与发送的节点个数,等待下一轮断点续传的时候再选择。但是需不要向资源中心报告该节点不可用,并且将其剔除呢,这就是策略问题了,可以选择剔除也可以不剔除。
这里还要考虑到一些特别的情况,比如,多个请求端请求同一资源提供端,会使该资源提供端压力过大,所以给节点设置sendingTime最大同时发送个数就是用来解决这个问题的,在节点选择的时候不仅考虑到节点的发送次数,还要考虑到其正在同时发送的个数。当然节点选择策略也可以通过其他方法进行负载压力的平衡,所以这里我给了一个节点选择策略的接口INodeSelectStrategy ,具体由资源请求端来实现。
/**
* @author dingxiang
*提供给资源请求端节点选择策略的接口
*/
public interface INodeSelectStrategy {
/**根据请求到的拥有某资源的节点(资源提供端)列表,从中选出参与发送的节点列表。
* @param orgSendNodeList
* @return
*/
List<NetNode> selectSendNodeList(List<NetNode> orgSendNodeList);
void setMaxSendCount(int maxSendCount);
void setServerDoSend(boolean serverDoSend);
void setMaxSendingCount(int maxSendingCount);
}
/**
* @author dingxiang
*节点选择策略实现类
*这里是通过桶排序来选择发送次数最小并且同时发送个数小于阈值的的节点
*通过节点的发送次数以及发送状态来判断是否要发送
*支持发送节点以及节点最大同时发送个数的设置
*/
public class NodeSelectStrategy implements INodeSelectStrategy {
private static int DEFAULT_MAX_SEND_COUNT=5;//默认参与发送的最大节点数目
public static final int DEFAULT_MAX_SENDING_COUNT = 3;//结点的默认最大同时发送个数
private int maxSendCount;
private int maxSendingCount;
private boolean serverDoSend;//资源根服务器(app服务器)是否参与发送
public NodeSelectStrategy() {
this.serverDoSend=true;
}
public boolean isServerDoSend() {
return serverDoSend;
}
public void setServerDoSend(boolean serverDoSend) {
this.serverDoSend = serverDoSend;
}
public int getMaxSendCount() {
return maxSendCount;
}
public void setMaxSendCount(int maxSendCount) {
this.maxSendCount = maxSendCount>DEFAULT_MAX_SEND_COUNT?
DEFAULT_MAX_SEND_COUNT:maxSendCount;
}
public void setMaxSendingCount(int maxSendingCount) {
this.maxSendingCount=
maxSendingCount>DEFAULT_MAX_SEND_COUNT
?DEFAULT_MAX_SENDING_COUNT:maxSendingCount;
}
/**
* 如果请求某一资源得到的节点数大于设置的最大发送节点数,
* 则从中选取设置的最大的发送节点个数参与发送
* @param nodeList
* @return
*/
public List<NetNode> selectMinSendNodes(List<NetNode> nodeList) {
List<NetNode> selectNodes=new ArrayList<NetNode>();
NetNode maxNode=nodeList.get(0);
//遍历找出最大发送次数
for (NetNode node : nodeList) {
if (maxNode.getSendTime()<node.getSendTime()) {
maxNode=node;
}
}
int[] sendTime =new int[maxNode.getSendTime()+1];
//遍历填充以发送次数为下标计数的数组
for (NetNode node : nodeList) {
sendTime[node.getSendTime()]++;
}
int maxSendTime=sendTime.length;
for(int index=0;index<maxSendTime;index++) {
if (sendTime[index]>0) {
for(NetNode node:nodeList) {
if (node.getSendTime()==index&&
node.getSendingTime()<=maxSendingCount) {
selectNodes.add(node);
if (selectNodes.size()==maxSendCount) {
return selectNodes;
}
}
}
}
}
return null;
}
/* 实现的根据请求到的拥有某一资源的节点列表选择参与发送的节点列表。
* @see com.xd.mfct.strategy.INodeSelectStrategy#selectSendNodeList(java.util.List)
*/
@Override
public List<NetNode> selectSendNodeList(List<NetNode> orgSendNodeList) {
List<NetNode> nodeList=orgSendNodeList;
int senderCount=nodeList.size();
if (senderCount<=1) {
return nodeList;
}
if (!serverDoSend) {//资源根服务器不参与发送的选择
List<NetNode> serverDontSendNodeList = new ArrayList<>();
for (NetNode node : nodeList) {
if (node.getType()== NetNode.SERVER) {
continue;
}
serverDontSendNodeList.add(node);
}
nodeList = serverDontSendNodeList;
}
senderCount = nodeList.size();
if (senderCount > maxSendCount) {
nodeList = selectMinSendNodes(nodeList);
}
return nodeList;
}
}
算法图解:假如请求某一资源的得到的节点列表有10个节点,先遍历这10个节点,找出发送次数最大值m(这里取6),那么对应创建大小为m+1的数组,然后将这10个节点以发送次数为下标装入,如果有节点对应着发送次数相同,则其发送次数对应的节点个数加1。由此得到下图。
然后遍历(从小到大)以发送次数为下标的数组,选择其中同时发送个数小于3的节点,直至达到设置的最大发送节点数为止,得到选择的发送节点列表。
在选择好参与发送的节点以后,将要请求的资源片段分配给各个发送节点。
/**
* @author dingxiang
*处理资源分配的接口
*/
public interface IResourceAllocationStrategy {
int DEFAULT_SECTION_SIZE=1<<20;//默认的文件片段的大小为1M
void setMaxSectionLength(int maxSectionLength);//设置最大的文件片段大小
/**
* 在得到需要请求的文件片段列表以及选择参与发送的节点个数以后,进行任务分发。
* @param orgResource
* @param sendCount
* @return
*/
List<List<SectionInfo>> allocateSectionInfo(List<SectionInfo> orgResource, int sendCount);
}
这里我实现了两种不同的资源分配策略。
一、以资源大小均分
对请求端对应某资源的请求列表进行遍历,如果有文件片段大小超过设置的最大的文件片段限制,则以设置的最大片段大小平均分配;
优点:能实现资源分配的相对平衡;
缺点:逻辑复杂;
二、以文件数量均分
请求端知道要请求的资源对应的文件片段个数,可以将文件按个数均分给每个发送结点;
优点:逻辑简单;
缺点:若文件片段之间大小相差太大,那么可能出现结点间发送的字节数相差非常大;
/**
* @author dingxiang
*实现资源分配策略的接口
*策略1:如果要请求的资源片段个数大于发送个数,则按个数分配
*策略2:如果要请求的资源片段个数小于发送个数,则按大小分配
*/
public class ResourceAllocationStrategy implements IResourceAllocationStrategy {
private int maxSectionLength;//最大的文件片段大小
public ResourceAllocationStrategy() {
this.maxSectionLength=DEFAULT_SECTION_SIZE;
}
@Override
public void setMaxSectionLength(int maxSectionLength) {
this.maxSectionLength = maxSectionLength>DEFAULT_SECTION_SIZE?
DEFAULT_SECTION_SIZE:maxSectionLength;
}
/**策略1:
* 根据文件片段个数分配
* @param sectionList
* @param sectionLList
* @return
*/
private List<List<SectionInfo>> allocateSectionInfoByCount(List<SectionInfo> sectionList, List<List<SectionInfo>> sectionLList,int sendCount) {
int index = 0;
for (SectionInfo section : sectionList) {
List<SectionInfo> sList = sectionLList.get(index);
sList.add(section);
index = (index + 1) % sendCount;
}
return sectionLList;
}
/*策略2
* 根据文件片段大小来分配
*/
private List<List<SectionInfo>> allocateSectionInfoBySize(List<SectionInfo> sectionList, List<List<SectionInfo>> sectionLList,int sendCount) {
int index = 0;
for (SectionInfo section : sectionList) {
int sectionSize = section.getSize();
if (sectionSize <= maxSectionLength) {//如果文件片段小于设定的,则直接分配
List<SectionInfo> sectionInfoList = sectionLList.get(index);
sectionInfoList.add(section);
index = (index + 1) % sendCount;
continue;
}
//如果文件片段过大,则将其划分成更小的片段再分配
long offset = 0L;
int restLen = sectionSize;
int len;
while (restLen > 0) {
len = restLen > maxSectionLength ? maxSectionLength : restLen;
List<SectionInfo> sectionInfoList = sectionLList.get(index);
sectionInfoList.add(new SectionInfo(section.getFileHandle(),
offset + section.getOffset(), len));
offset += len;
restLen -= len;
index = (index + 1) % sendCount;
}
}
return sectionLList;
}
/*
* 根据要请求的文件片段列表和选择的发送节点个数进行分配。
*/
@Override
public List<List<SectionInfo>> allocateSectionInfo(List<SectionInfo> orgResource, int sendCount) {
if (sendCount <= 0) {
return null;
}
//初始化任务列表
List<List<SectionInfo>> sectionListList = new ArrayList<>();
for (int index = 0; index < sendCount; index++) {
List<SectionInfo> sectionList = new ArrayList<>();
sectionListList.add(sectionList);
}
return orgResource.size()>sendCount?
allocateSectionInfoByCount(orgResource, sectionListList, sendCount)
:allocateSectionInfoBySize(orgResource, sectionListList, sendCount);
}
}
/**
* @author dingxiang
*用以描述发送任务
*/
public class SendSectionInfo {
private NetNode receiveNode;//接收节点
private NetNode sendNode;//发送节点
private ResourceBaseInfo sendRbi;//要请求的资源(内置分配以后的请求列表);
public SendSectionInfo() {
}
public NetNode getReceiveNode() {
return receiveNode;
}
public void setReceiveNode(NetNode receiveNode) {
this.receiveNode = receiveNode;
}
public NetNode getSendNode() {
return sendNode;
}
public void setSendNode(NetNode sendNode) {
this.sendNode = sendNode;
}
public ResourceBaseInfo getSendRbi() {
return sendRbi;
}
public void setSendRbi(ResourceBaseInfo sendRbi) {
this.sendRbi = sendRbi;
}
@Override
public String toString() {
StringBuffer res=new StringBuffer();
res.append("接收端:"+receiveNode).append(" 发送端:"+sendNode)
.append("\n")
.append(sendRbi);
return res.toString();
}
}
RPC资源中心服务器
/**
* @author dingxiang
*资源中心服务器
*/
public class ResourceCenter {
private int centerPort;//资源中心端口
private RMIServer rmiServer;//RMI服务器
public ResourceCenter() {
this.rmiServer=new RMIServer();
}
public int getCenterPort() {
return centerPort;
}
public void setCenterPort(int centerPort) {
this.centerPort = centerPort;
}
public RMIServer getRmiServer() {
return rmiServer;
}
public void setRmiServer(RMIServer rmiServer) {
this.rmiServer = rmiServer;
}
public void startCenterServer() {
this.rmiServer.setRmiPort(centerPort);
this.rmiServer.startRmiServer();
}
}
/**
* 提供给外界调用资源中心服务的接口。
* @author dingxiang
*
*/
public interface IResourceCenterService {
@MecDialog(caption="注册节点资源")
boolean registryNode(NetNode node, ResourceBaseInfo resourceBaseInfo);
@MecDialog(caption="注销节点资源")
boolean logOutNode(NetNode node,ResourceBaseInfo resourceBaseInfo);
@MecDialog(caption="获取拥有某资源的节点列表")
List<NetNode> requestNodesByResourceBaseInfo(ResourceBaseInfo resourceBaseInfo) throws Exception;
@MecDialog(caption="获取资源中心资源列表")
List<ResourceBaseInfo> requestResourceBaseInfos();
void increaseSendingTime(String service,NetNode node);
void decreaseSendingTime(String service,NetNode node);
}
通过RPC调用资源中心服务。
/**
* @author dingxiang
*资源中心服务类,包括资源与节点的注册注销等服务
*/
@RmiAction(RmiInterfaces= {IResourceCenterService.class})
public class ResourceCenterService implements IResourceCenterService{
public ResourceCenterService() {
}
/*
* 注册资源与节点
*/
@Override
public boolean registryNode(NetNode node, ResourceBaseInfo resourceBaseInfo) {
if (resourceBaseInfo==null||node==null) {
return false;
}
synchronized (ResourceCenterTable.class) {
List<NetNode> nodeList=ResourceCenterTable.getNodesByResourceBaseInfo(resourceBaseInfo);
if (nodeList==null) {
nodeList=new ArrayList<NetNode>();
}
if(!nodeList.contains(node)){
nodeList.add(node);
}
ResourceCenterTable.insert(resourceBaseInfo, nodeList);
}
return true;
}
/*
* 注销资源与节点
*/
@Override
public boolean logOutNode(NetNode node,ResourceBaseInfo resourceBaseInfo) {
synchronized (ResourceCenterTable.class) {
if (node==null||resourceBaseInfo==null||
(!ResourceCenterTable.containsResourceBaseInfo(resourceBaseInfo))) {
return false;
}
List<NetNode> nodeList=ResourceCenterTable.getNodesByResourceBaseInfo(resourceBaseInfo);
if (nodeList==null) {
ResourceCenterTable.remove(resourceBaseInfo);
return true;
}
Iterator<NetNode> nodeIterator=nodeList.iterator();
while(nodeIterator.hasNext()) {
NetNode netNode = (NetNode) nodeIterator.next();
if (netNode.getIp().equalsIgnoreCase(node.getIp())&&netNode.getPort()==node.getPort()) {
nodeIterator.remove();
}
}
ResourceCenterTable.insert(resourceBaseInfo, nodeList);
}
return true;
}
/*
* 根据资源获取拥有该资源的节点列表
*/
@Override
public List<NetNode> requestNodesByResourceBaseInfo(ResourceBaseInfo resourceBaseInfo) throws Exception {
synchronized(ResourceCenterTable.class) {
return ResourceCenterTable.getNodesByResourceBaseInfo(resourceBaseInfo);
}
}
/**
* 根据资源名称资源名称(包含版本信息)找到对应资源
* @param service
* @param version
* @return
*/
private ResourceBaseInfo findResourceBaseInfoByServiceName(String resource) {
List<ResourceBaseInfo> rbiList=requestResourceBaseInfos();
for(ResourceBaseInfo resourceBaseInfo:rbiList) {
if (resourceBaseInfo.getName().equals(resource)) {
return resourceBaseInfo;
}
}
return null;
}
/*
* 获取资源列表
*/
@Override
public List<ResourceBaseInfo> requestResourceBaseInfos() {
Set<ResourceBaseInfo> rbiSet;
synchronized(ResourceCenterTable.class) {
rbiSet=ResourceCenterTable.getResourceBaseInfos();
}
if (rbiSet==null) {
return null;
}
List<ResourceBaseInfo> rbiList=new ArrayList<>();
for (ResourceBaseInfo resourceBaseInfo : rbiSet) {
rbiList.add(resourceBaseInfo);
}
return rbiList;
}
/*
* 增加节点同时发送个数
*/
@Override
public void increaseSendingTime(String service, NetNode node) {
ResourceBaseInfo resourceBaseInfo=findResourceBaseInfoByServiceName(service);
try {
List<NetNode> nodeList=requestNodesByResourceBaseInfo(resourceBaseInfo);
for (NetNode netNode : nodeList) {
if (netNode.equals(node)) {
netNode.increaseSendingTime();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/*
* 资源提供端发送完成以后,对应节点同时发送个数减1,发送次数+1
*/
@Override
public void decreaseSendingTime(String service, NetNode node) {
ResourceBaseInfo resourceBaseInfo=findResourceBaseInfoByServiceName(service);
try {
List<NetNode> nodeList=requestNodesByResourceBaseInfo(resourceBaseInfo);
for (NetNode netNode : nodeList) {
synchronized(ResourceCenter.class) {
if (netNode.equals(node)) {
netNode.decreaseSendingTime();
netNode.increaseSendTime();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
资源中心维护的一张Map,键是资源,值是拥有该资源的节点列表。
/**
* @author dingxiang
*资源与节点对应关系表
*/
public class ResourceCenterTable {
private static Map<ResourceBaseInfo, List<NetNode>> resourceNodeMap
=new ConcurrentHashMap<>();
public static Map<ResourceBaseInfo, List<NetNode>> getResourceNodeMap() {
return resourceNodeMap;
}
public static boolean containsResourceBaseInfo(ResourceBaseInfo resourceBaseInfo) {
return resourceNodeMap.containsKey(resourceBaseInfo);
}
public static void insert(ResourceBaseInfo resourceBaseInfo,List<NetNode> nodeList) {
resourceNodeMap.put(resourceBaseInfo, nodeList);
}
public static List<NetNode> getNodesByResourceBaseInfo(ResourceBaseInfo resourceBaseInfo) {
return resourceNodeMap.get(resourceBaseInfo);
}
public static void remove(ResourceBaseInfo resourceBaseInfo) {
resourceNodeMap.remove(resourceBaseInfo);
}
public static Set<ResourceBaseInfo> getResourceBaseInfos(){
return resourceNodeMap.keySet();
}
}
解析配置文件,完成请求前的各项配置,包括节点选择策略,资源分发策略以及接收端口范围等。
/**
* @author dingxiang
*资源请求端请求准备类
*解析配置文件,解析配置的节点选择策略,资源分配策略以及接收端口范围等。
*/
public class ReceivePreparation {
private static IResourceAllocationStrategy resourceAllocateStrategy;
private static INodeSelectStrategy nodeSelectStrategy;
private static ReceiveServerPortPool receiveServerPortPool;
public static IResourceAllocationStrategy getResourceAllocateStrategy() {
return resourceAllocateStrategy;
}
public void setResourceAllocateStrategy(IResourceAllocationStrategy resourceAllocateStrategy) {
ReceivePreparation.resourceAllocateStrategy = resourceAllocateStrategy;
}
public static INodeSelectStrategy getNodeSelectStrategy() {
return nodeSelectStrategy;
}
public void setNodeSelectStrategy(INodeSelectStrategy nodeSelectStrategy) {
ReceivePreparation.nodeSelectStrategy = nodeSelectStrategy;
}
protected static ReceiveServerPortPool getReceiveServerPortPool() {
return receiveServerPortPool;
}
/**
* 加载资源分配策略配置
*/
public static void loadResourceAllocationStrategy() {
String allocateStrategy=PropertiesParser.value("ResourceAllocationStrategy");
String maxSectionLength=PropertiesParser.value("maxSectionLength");
if (allocateStrategy!=null&&allocateStrategy.length()>0) {
try {
Class<?> klass=Class.forName(allocateStrategy);
resourceAllocateStrategy=(IResourceAllocationStrategy) klass.newInstance();
if (maxSectionLength!=null&&maxSectionLength.length()>0) {
int maxLength=Integer.valueOf(maxSectionLength);
resourceAllocateStrategy.setMaxSectionLength(maxLength);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
/**
* 加载资源池配置
*/
public static void loadResourcePoolConfig() {
String resourceXmlPath=PropertiesParser.value("resourceXmlPath");
if (resourceXmlPath!=null&&resourceXmlPath.length()>0) {
ResourePool.setResourcePath(resourceXmlPath);
}
}
/**
* 加载节点选择策略
*/
public static void loadNodeSelectStrategy() {
String selectStrategy=PropertiesParser.value("NodeSelectStrategy");
String maxSend=PropertiesParser.value("maxSenderCount");
String maxSendingTime=PropertiesParser.value("maxSendingTime");
String serverDoSend=PropertiesParser.value("serverDoSend");
if (selectStrategy!=null&&selectStrategy.length()>0) {
try {
Class<?> klass=Class.forName(selectStrategy);
nodeSelectStrategy=(INodeSelectStrategy) klass.newInstance();
if (maxSend!=null&&maxSend.length()>0) {
int maxSendCount=Integer.valueOf(maxSend);
nodeSelectStrategy.setMaxSendCount(maxSendCount);
}
if (maxSendingTime!=null&&maxSendingTime.length()>0) {
int sendingTime=Integer.valueOf(maxSendingTime);
nodeSelectStrategy.setMaxSendingCount(sendingTime);
}
if (serverDoSend!=null&&serverDoSend.length()>0) {
boolean serverSend;
serverSend=Boolean.valueOf(serverDoSend);
nodeSelectStrategy.setServerDoSend(serverSend);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
/**
* 加载接收节点池配置
*/
public static void loadResourcePortPool() {
String minPortStr=PropertiesParser.value("minReceivePort");
String maxPortStr=PropertiesParser.value("maxReceivePort");
int minPort = 0;
int maxPort = 0;
if (minPortStr!=null&&minPortStr.length()>0) {
minPort=Integer.valueOf(minPortStr);
}
if (maxPortStr!=null&&maxPortStr.length()>0) {
maxPort=Integer.valueOf(maxPortStr);
}
receiveServerPortPool=new ReceiveServerPortPool(minPort, maxPort);
}
/**根据路径加载配置文件
* @param resConfigPath
*/
public static void loadResourceConfig(String resConfigPath) {
PropertiesParser.loadProperties(resConfigPath);
loadResourceAllocationStrategy();
loadNodeSelectStrategy();
loadResourcePortPool();
loadResourcePoolConfig();
}
}
对于每台计算机而言,端口号是非常宝贵的资源,不能随意使用,所以我使用了端口池,资源请求端只能使用固定数量和端口范围的端口号,避免影响其它应用;端口号池的容量和端口范围是可以设置或通过文件配置的。对于接收端口池子,可以按照以下三种模式进行选择,这里我采用的是普通模式。
/**
* 关于port池,首先有如下基本设定:
* prot池的取值范围必须有限,且设置最大和最小值范围;
* 其中的port,可以反复使用。
* 实现手段有简单、普通、可回收三种:
* 简单模式:定义一个整型量,初值为minPort;每次申请后自增;当增加为maxPort后,回绕重新分配
* 普通模式:定义一个线程安全的队列,并用从minPort到maxPort的整型量初始化;
* 设定如下几个方法:
*
* - boolean hasNext();只要队列非空,则,返回真;
* - int next();总是返回队首port,并"出队列";
* - void returnPort(int port);归还port到队尾。
*
* 可回收模式:定义两个线程安全的队列,分别为:已分配port队列和未分配port队列;
* 且,其中的已分配port队列的泛型类,包括:
* int port;
* long time;
* ReceiveServer server;
* 其中的time是分配时间,以System.currentMilliTime()为值;
* server是用port建立的接收服务器;
* 并启用定时器,将超过30分钟未归还的port,通过server强制关闭,并回收port。
* 超时时间可配置;port范围可配置。
* @author dingxiang
*
*/
public class ReceiveServerPortPool {
private volatile static Queue<Integer> portQueue;
static {
portQueue=new ConcurrentLinkedQueue<Integer>();
}
public ReceiveServerPortPool(int minPort,int maxPort) {
for(int i=minPort;i<=maxPort;i++) {
portQueue.add(i);
}
}
/**取队首端口
* @return
*/
public int nextPort() {
synchronized (portQueue) {
return portQueue.remove();
}
}
/**队列非空
* @return
*/
public boolean hasNext() {
synchronized (portQueue) {
return !portQueue.isEmpty();
}
}
/**归还使用的端口,以待下次分配
* @param port
*/
public void returnPort(int port) {
synchronized (portQueue) {
portQueue.add(port);
}
}
}
/**随机访问池
* 1.对于资源请求端,当接收线程接收到提供端发送来的数据的时候,要将数据写到相应的位置。
* 2.这里根据路径名取得文件的读写指针。
* 3.由于在请求资源之前已经根据资源框架信息创建好相应目录和文件,
* 因此,直接根据路径找到相应文件并将接受到的片段的具体信息写入。
* @author dingxiang
*
*/
public class RandAccessFilePool {
private Map<String, RandomAccessFile> rafPool;
RandAccessFilePool() {
rafPool = new ConcurrentHashMap<>();
}
/**
* 根据filePath返回读写指针
* @param filePath
* @returnRandomAccessFile
*/
RandomAccessFile getRaf(String filePath) {
RandomAccessFile raf = rafPool.get(filePath);
if (raf == null) {
try {
raf = new RandomAccessFile(filePath, "rw");
rafPool.put(filePath, raf);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
return raf;
}
/**
* 接收完毕后,关闭随机访问指针池
*/
void close() {
if (rafPool!=null) {
rafPool.clear();
rafPool=null;
}
}
}
/**
* 资源请求/接收者
* @author dingxiang
*
*/
public class Receiver {
private ReceiveServer receiveServer;//接收服务器
private IResourceCenterService centerService;//资源中心RPC接口
private RMIClientProxy rmiClientProxy;//RMI客户端代理
private RMIClient rmiClient;//RMI客户端
private IReceiveActionView receiveActionView;
private NetNode receiveNode;//接收节点
public Receiver() {
this.rmiClientProxy=new RMIClientProxy();
this.rmiClient=new RMIClient();
this.rmiClientProxy.setRmiClient(rmiClient);
this.centerService=(IResourceCenterService) rmiClientProxy.getProxy(IResourceCenterService.class);
}
public void setReceiveActionView(IReceiveActionView receiveActionView) {
this.receiveActionView = receiveActionView;
}
public void setReceiveNode(NetNode receiveNode) {
this.receiveNode = receiveNode;
}
/**根据路径加载配置资源
* @param configPath
*/
public void loadStrategyConfig(String configPath) {
ReceivePreparation.loadResourceConfig(configPath);
}
/**1.根据要请求的资源先去资源中心获取节点列表。
* 2.根据配置的节点选择策略选择出参与发送的发送节点。
* @param resourceBaseInfo
* @return
*/
public List<NetNode> selectSendNodes(ResourceBaseInfo resourceBaseInfo){
List<NetNode> senderList = null;
try {
senderList = centerService.requestNodesByResourceBaseInfo(resourceBaseInfo);
senderList=ReceivePreparation.getNodeSelectStrategy().selectSendNodeList(senderList);
} catch (Exception e) {
e.printStackTrace();
}
return senderList;
}
/**根据配置的资源分配策略分配资源,返回发送任务列表。
* @param resourceBaseInfo
* @param senderList
* @return
*/
public List<SendSectionInfo> allocateResource(ResourceBaseInfo resourceBaseInfo,List<NetNode> senderList){
int sendNodeCount=senderList.size();
List<List<SectionInfo>> sectionListList =
ReceivePreparation.getResourceAllocateStrategy().allocateSectionInfo(
resourceBaseInfo.getSectionList(), sendNodeCount);
List<SendSectionInfo> result = new ArrayList<>();
for (int index = 0; index < sendNodeCount; index++) {
SendSectionInfo sendSectionInfo = new SendSectionInfo();
sendSectionInfo.setReceiveNode(receiveNode);
sendSectionInfo.setSendNode(senderList.get(index));
ResourceBaseInfo sendRbi = new ResourceBaseInfo(resourceBaseInfo);
sendRbi.setSectionList(sectionListList.get(index));
sendSectionInfo.setSendRbi(sendRbi);
result.add(sendSectionInfo);
}
return result;
}
/**通过RPC请求资源中心的所有资源.
* @return
*/
public List<ResourceBaseInfo> requestResourceBaseInfos(){
return centerService.requestResourceBaseInfos();
}
/**请求具体的资源。
* @param requestRbi
*/
public void requestResourceBaseInfo(ResourceBaseInfo requestRbi) {
if (requestRbi.getSectionList().size()==0) {
System.out.println("已拥有该"+requestRbi.getName()+"资源");
return;
}
List<NetNode> senderList=selectSendNodes(requestRbi);
if (senderList.isEmpty()) {
if (receiveActionView!=null) {
receiveActionView.hasNoSender();
}
return;
}
int senderCount=senderList.size();
if(receiveServer==null) {
this.receiveServer=new ReceiveServer();
int receivePort=ReceivePreparation.getReceiveServerPortPool().nextPort();
this.receiveServer.setReceivePort(receivePort);
this.receiveServer.startUp();
System.out.println("开启接收服务器:"+ReceiveServer.ip+"--"+receivePort);
this.receiveNode.setIp(ReceiveServer.ip);
this.receiveNode.setReceivePort(receivePort);
}else {
if (!this.receiveServer.isOk()) {
receiveServer.startUp();
}
}
this.receiveServer.setSenderCount(senderCount);
this.receiveServer.setResourceBaseInfo(requestRbi);
this.receiveServer.setReceiveView(receiveActionView);
this.receiveServer.setReceiver(this);
List<SendSectionInfo> sendTasks=allocateResource(requestRbi, senderList);
int count = distributeSendSection(sendTasks);
if (count<senderCount) {
receiveServer.setSenderCount(count);
}
}
/**接收完成后的工作
* 1.归还接收服务器占用的端口到端口池。
* 2.接收完资源后,请求端也可以成为该资源的提供端,所以将其注册到资源中心。
* @param resourceBaseInfo
*/
public void accomplishWork(ResourceBaseInfo resourceBaseInfo) {
ReceivePreparation.getReceiveServerPortPool().returnPort(receiveServer.getReceivePort());
resourceBaseInfo.setSectionList(null);
ResourePool.insertResource(resourceBaseInfo);
centerService.registryNode(receiveNode, resourceBaseInfo);
}
/**资源请求端通过RPC给资源提供端发送发送任务,并验证资源提供端的状态。
* @param sectionMatrix
* @return
*/
private int distributeSendSection(List<SendSectionInfo> sectionMatrix) {
int reallySendCount=0;
RMIClientProxy proxy=new RMIClientProxy();
RMIClient client=new RMIClient();
for (SendSectionInfo sendSectionInfo : sectionMatrix) {
NetNode sendNode=sendSectionInfo.getSendNode();
client.setRmiIP(sendNode.getIp());
client.setRmiPort(sendNode.getSendPort());
proxy.setRmiClient(client);
try {
IResourceSender resourceSender=(IResourceSender) proxy.getProxy(IResourceSender.class);
ResourceBaseInfo sendRbi=sendSectionInfo.getSendRbi();
boolean isSendReady=resourceSender.sendSectionInfo(receiveNode, sendRbi);
if (isSendReady) {
reallySendCount++;
}
} catch (Exception e) {
System.out.println("第"+reallySendCount+"个发送端异常!");
}
}
return reallySendCount;
}
}
当资源请求端完成节点选择以及资源分配以后就可以开启接收服务器。
/**
* @author dingxiang
*接收服务器
*/
public class ReceiveServer implements Runnable{
public static String ip;
static {
try {//导入资源请求端本地Ip
InetAddress inetAddress = InetAddress.getLocalHost();
ip = inetAddress.getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
private ServerSocket serverSocket;
private int receivePort;//接收端口
private int senderCount;//发送节点个数
private int receiveCount;//接收任务完成次数
private volatile boolean ok;//服务器是否开启
/**
* 接收线程池,每侦听到一个资源提供端的,就开启一个具体的接收线程去接收。
*/
private ThreadPoolExecutor receiveThreadPool;
/**
* 要请求的资源
*/
private ResourceBaseInfo resourceBaseInfo;
/**
* 未接收文件Map
*/
private Map<Integer, UnReceivedFileSection> unReceivedFileSectionMap;
/**
* 资源框架信息
*/
private Map<Integer, ResourceStructInfo> rsiMap;
/**
* 接收界面侦听类
*/
private IReceiveActionView receiveView;
/**
* 总未接收文件片段
*/
private int totalUnreceivedCount;
/**
* 请求者
*/
private Receiver receiver;
public ReceiveServer() {
}
public int getSenderCount() {
return senderCount;
}
public void setReceiver(Receiver receiver) {
this.receiver = receiver;
}
public Receiver getReceiver() {
return receiver;
}
public ResourceBaseInfo getResourceBaseInfo() {
return resourceBaseInfo;
}
/**设置要请求的资源的时候,将要请求的资源里面的文件
* 以未接收文件的文件句柄为键,未接收文件文件片段记录类为值加入到一个Map里面
* 方面接受的时候,做判断处理以及接收进度条更新。。
* @param resourceBaseInfo
* @return
*/
public ReceiveServer setResourceBaseInfo(ResourceBaseInfo resourceBaseInfo) {
this.resourceBaseInfo = resourceBaseInfo;
this.rsiMap = new HashMap<Integer, ResourceStructInfo>();
List<ResourceStructInfo> rsiList = resourceBaseInfo.getRsiList();
for (ResourceStructInfo rsi : rsiList) {
int fileHandle = rsi.getFileHandle();
rsiMap.put(fileHandle, rsi);
}
unReceivedFileSectionMap = new HashMap<Integer, UnReceivedFileSection>();
List<SectionInfo> requestSectionList=resourceBaseInfo.getSectionList();
for (SectionInfo sectionInfo : requestSectionList) {
int fileHandle=sectionInfo.getFileHandle();
int size=sectionInfo.getSize();
UnReceivedFileSection ufs = new UnReceivedFileSection(fileHandle, size);
unReceivedFileSectionMap.put(fileHandle, ufs);
}
this.totalUnreceivedCount=unReceivedFileSectionMap.size();
return this;
}
public Map<Integer, ResourceStructInfo> getRsiMap() {
return rsiMap;
}
protected boolean isOk() {
return ok;
}
/**
* 获取总未接收片段数
*/
public int getTotalUnreceivedCount() {
return totalUnreceivedCount;
}
public void setReceivePort(int receivePort) {
this.receivePort = receivePort;
}
protected int getReceivePort() {
return receivePort;
}
/**设置发送端个数
* @param senderCount
*/
public void setSenderCount(int senderCount) {
this.senderCount = senderCount;
//选择性显示
if (receiveView!=null) {
receiveView.getSenderCount(senderCount);
}
}
/**设置接收界面
* @param receiveView
*/
public void setReceiveView(IReceiveActionView receiveView) {
this.receiveView = receiveView;
if (this.receiveView!=null) {
this.receiveView.showReceiveActionView();
}
}
public Map<Integer, UnReceivedFileSection> getUnReceivedFileSectionMap() {
return unReceivedFileSectionMap;
}
public void setUnReceivedFileSectionMap(Map<Integer, UnReceivedFileSection> unReceivedFileSectionMap) {
this.unReceivedFileSectionMap = unReceivedFileSectionMap;
}
/**
* 每当一个接收线程执行完毕,receiveCount就会增加1
*/
public int receiveCountIncrease() {
receiveCount++;
return receiveCount;
}
/**
* 开启接收服务器
*/
public void startUp() {
try {
this.serverSocket=new ServerSocket(receivePort);
this.ok=true;
receiveThreadPool=new ThreadPoolExecutor(10, 50, 5000, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<>());
new Thread(this).start();
} catch (IOException e) {
e.printStackTrace();
}
}
/**检测响应的发送端个数与选择的发送节点个数是否一致。
* 如果一致,则可以shutdown()线程池。
* @return
*/
public boolean checkCount() {
return receiveCountIncrease()==senderCount;
}
/**用以判断要请求的文件片段是否接收完毕
* @return
*/
public boolean checkIsCompleteReceived() {
if (this.unReceivedFileSectionMap.isEmpty()) {
return true;
}
return false;
}
/**返回没有接收完的文件片段
* @return
*/
public List<SectionInfo> returnUnReceivedSections(){
List<SectionInfo> sectionList=new ArrayList<>();
Iterator<UnReceivedFileSection> unReceivedSectionsCollection=unReceivedFileSectionMap.values().iterator();
while (unReceivedSectionsCollection.hasNext()) {
UnReceivedFileSection unReceivedFileSection = (UnReceivedFileSection) unReceivedSectionsCollection.next();
if (!unReceivedFileSection.IsReceivedCompleted()) {
sectionList.addAll(unReceivedFileSection.getSectionInfos());
}
}
return sectionList;
}
/**
* 关闭接收服务器
*/
public void close() {
if (!receiveThreadPool.isShutdown()) {
if (ok) {
ok=false;
}
receiveView.closeReceiveActionView();
receiveThreadPool.shutdown();
if (this.serverSocket != null && !this.serverSocket.isClosed()) {
try {
this.serverSocket.close();
} catch (IOException e) {
}finally {
this.serverSocket=null;
}
}
System.out.println("结束时间:"+System.currentTimeMillis());
System.out.println("接受资源"+resourceBaseInfo.getName()+"完成,关闭接收服务器:"+ip+"--"+receivePort);
}else {
System.out.println("接收服务器已关闭!");
return;
}
}
@Override
public void run() {
// TODO 通过日志,记录已接受的文件片段和接收进度,以免暂停和网络终止以后重头开始请求。
System.out.println("开始时间:"+System.currentTimeMillis());
int count=0;
while (ok&&count<senderCount) {
try {
Socket sender=serverSocket.accept();
//选择性告知View,链接到一个发送端
if (receiveView!=null) {
receiveView.linkedOneSender(sender);
}
//启动一个接收线程,完成具体的接收线程
receiveThreadPool.execute(new ReceiveAction(sender,this,receiveView));
} catch (IOException e) {
System.out.println("第"+count+"个发送端异常掉线");
}finally {
count++;
}
}
}
}
具体接收一个资源提供端发送的文件片段,并更新接收进度条。
/**
* @author dingxiang
*具体的接收线程,除了接收资源提供端发过来的文件片段,还要更新文件进度条。
*/
public class ReceiveAction implements Runnable{
private Socket socket;
private DataInputStream dis;
private ReceiveServer receiveServer;
private IReceiveActionView receiveView;
private Map<Integer, ResourceStructInfo> rsiMap;//资源框架信息
private volatile boolean goon;//线程开关
private int endReceiveCount;//总未接收文件片段大小
private ResourceBaseInfo rbi;
private int curCount;//已接收文件片段大小
public ReceiveAction() {
}
public ReceiveAction(Socket socket, ReceiveServer receiveServer,IReceiveActionView receiveView) {
this.socket = socket;
this.receiveView = receiveView;
this.receiveServer=receiveServer;
this.rsiMap = receiveServer.getRsiMap();
this.rbi=receiveServer.getResourceBaseInfo();
this.endReceiveCount=receiveServer.getTotalUnreceivedCount();
this.goon=true;
}
@Override
public void run() {
try {
String absoluteRoot=rbi.getAbsolutePath();
this.dis=new DataInputStream(socket.getInputStream());
RandAccessFilePool rafp=new RandAccessFilePool();
//具体的接受过程
FileSection fileSection = new FileSection();
while (goon) {
fileSection.receiveSection(dis);
SectionInfo section = fileSection.getSectionInfo();
byte[] value = fileSection.getValue();
int fileHandle = section.getFileHandle();
if (fileHandle==0) {
goon=false;
break;
}
ResourceStructInfo rsi = rsiMap.get(fileHandle);
String filePath = absoluteRoot + rsi.getFilePath();
RandomAccessFile raf = rafp.getRaf(filePath);
raf.seek(section.getOffset());
raf.write(value);
try {
synchronized (ReceiveServer.class) {//更新未接收文件片段Map
Map<Integer, UnReceivedFileSection> unReceivedMap=receiveServer.getUnReceivedFileSectionMap();
UnReceivedFileSection ufs = unReceivedMap.get(fileHandle);
ufs.afterReceiveSection(section);
if (ufs.IsReceivedCompleted()) {
unReceivedMap.remove(fileHandle);
}else {
unReceivedMap.put(fileHandle, ufs);
}
receiveServer.setUnReceivedFileSectionMap(unReceivedMap);
if (receiveView != null) {//更新接收进度条
curCount=endReceiveCount-unReceivedMap.size();
System.out.println(curCount+":"+endReceiveCount);
receiveView.updateReceiveState(curCount,endReceiveCount);
}
}
}catch (Exception e) {
}
}
rafp.close();
//如果是最后一个接收线程,那么接收完毕后关闭接收服务器,检查接收情况。
if (receiveServer.checkCount()) {
receiveServer.close();
new ReceiveChecker().checkForReRequest(receiveServer,rbi);
}
} catch (IOException e) {
e.printStackTrace();
//建立通信信道失败,是否需要向view层告知
}
}
}
/**
* @author dingxiang
* 资源接收检查类。
*
*/
public class ReceiveChecker {
public ReceiveChecker() {
}
/**检查是否需要重新请求
* 1.如果接收完毕,则归还端口,并且将自己注册到资源中心。
* 2.如果未接收完毕,则更新请求要请求的资源列表,再次请求。
* @param receiveServer
* @param resourceBaseInfo
*/
public void checkForReRequest(ReceiveServer receiveServer,ResourceBaseInfo resourceBaseInfo) {
Receiver receiver=receiveServer.getReceiver();
if (receiveServer.checkIsCompleteReceived()) {
receiver.accomplishWork(resourceBaseInfo);
return;
}else {
resourceBaseInfo.setSectionList(receiveServer.returnUnReceivedSections());
receiver.requestResourceBaseInfo(resourceBaseInfo);
}
}
/**
* 发送任务接口
* 资源提供端提供给资源请求端的接口。
* 资源请求端可以通过RPC将分发后的任务发送给资源提供端。
* @author dingxiang
*
*/
public interface IResourceSender {
boolean sendSectionInfo(NetNode receiveNode, ResourceBaseInfo sendRbi);
}
/**资源提供端
* 1.开启服务器接收请求端发过来的发送任务
* 2.根据接收到的发送任务开启一个发送线程向资源请求端的接收服务器发送文件片段的数据。
* 3.接受任务发送前后要更新资源提供端的发送次数和同时发送个数。
* @author dinxgiang
*
*/
@RmiAction(RmiInterfaces= {IResourceSender.class})
public class Sender implements IResourceSender{
private RMIClientProxy rmiClientProxy;//RMI代理
private RMIClient rmiClient;//RMI客户端
private IResourceCenterService centerService;//资源中心服务接口
private RMIServer rmiServer;//RMI服务器
private NetNode sendNode;//资源提供端对应的发送节点
public Sender() {
this.rmiServer=new RMIServer();
this.rmiClient=new RMIClient();
this.rmiClientProxy=new RMIClientProxy();
this.rmiClientProxy.setRmiClient(rmiClient);
this.centerService=(IResourceCenterService) rmiClientProxy.getProxy(IResourceCenterService.class);
}
public void setRmiServer(RMIServer rmiServer) {
this.rmiServer = rmiServer;
}
public void setRmiClient(RMIClient rmiClient) {
this.rmiClient = rmiClient;
}
public Sender setSendPort(int sendPort) {
this.sendNode.setSendPort(sendPort);
return this;
}
public void setSendNode(NetNode sendNode) {
this.sendNode = sendNode;
}
public NetNode getSendNode() {
return sendNode;
}
public void setCenterIp(String centerIp) {
this.rmiClient.setRmiIP(centerIp);
}
public IResourceCenterService getCenterService() {
return centerService;
}
public void setCenterPort(int centerPort) {
this.rmiClient.setRmiPort(centerPort);
}
public void setRmiClientProxy(RMIClientProxy rmiClientProxy) {
this.rmiClientProxy = rmiClientProxy;
}
/**通过RPC注册资源与节点
* @param node
* @param resourceBaseInfo
*/
public void registryNodeResource(NetNode node,ResourceBaseInfo resourceBaseInfo) {
centerService.registryNode(node, resourceBaseInfo);
}
/**通过RPC注销资源与节点
* @param node
* @param resourceBaseInfo
*/
public void logOutNodeResource(NetNode node,ResourceBaseInfo resourceBaseInfo) {
centerService.logOutNode(node, resourceBaseInfo);
}
/**
* 根据节点设置的发送端口,开启服务器接收请求端发送来的任务。
*/
public void startUp() {
this.rmiServer.setRmiPort(this.sendNode.getSendPort());
this.rmiServer.startRmiServer();
}
/**
* 关闭服务器
*/
public void close() {
this.rmiServer.stopRmiServer();
}
/* 1.接收资源请求端发送来的任务,并返回true,如果提供端能接收和返回,则说明它是正常工作的。
* 2.开启一个发送线程,根据接受到的任务发送相应的文件片段的实际数据。
*/
@Override
public boolean sendSectionInfo(NetNode receiveNode, ResourceBaseInfo sendRbi) {
SenderAction senderClient=new SenderAction(this);
senderClient.setReceiver(receiveNode);
senderClient.setRbi(sendRbi);
//该提供端同时发送个数增加1
centerService.increaseSendingTime(sendRbi.getName(),sendNode);
new Thread(senderClient).start();
return true;
}
}
当获得到被分配的任务后就启动一个发送线程去连接对应的接收/请求端按照任务发送读取本地文件片段的数据并发送。
/**
*发送线程
*根据接收到的任务发送具体的文件片段的实际数据。
* @author dingxiang
*
*/
public class SenderAction implements Runnable{
private Map<String, RandomAccessFile> rafMap;
private NetNode receiver;
private Socket socket;
private DataOutputStream dos;
private ResourceBaseInfo sendRbi;
private Sender sender;
SenderAction(Sender sender) {
this.sender=sender;
}
SenderAction setReceiver(NetNode receiver){
this.receiver=receiver;
return this;
}
SenderAction setRbi(ResourceBaseInfo rbi){
this.sendRbi=rbi;
return this;
}
/**
* 根据发送任务里面的资源请求端的信息,连接资源请求端的接收服务器。
* @throws UnknownHostException
* @throws IOException
*/
void connectToReceiver() throws UnknownHostException, IOException {
this.socket=new Socket(receiver.getIp(),receiver.getReceivePort());
this.dos=new DataOutputStream(this.socket.getOutputStream());
}
/**
* 根据文件句柄找到某一个文件的框架信息
* @param filehandle
* @param rsiList
* @return
*/
ResourceStructInfo getRsiByFileHandle(int filehandle,List<ResourceStructInfo> rsiList) {
for (ResourceStructInfo resourceStructInfo : rsiList) {
int handle=resourceStructInfo.getFileHandle();
if (handle==filehandle) {
return resourceStructInfo;
}
}
return null;
}
/**
* 根据路径,返回该路径对应文件的随机访问指针
* @param filePath
* @return
* @throws FileNotFoundException
*/
private RandomAccessFile getRaf(String filePath) throws FileNotFoundException {
RandomAccessFile raf=rafMap.get(filePath);
if (raf!=null) {
return raf;
}
raf=new RandomAccessFile(filePath, "r");
rafMap.put(filePath, raf);
return raf;
}
void closeFile() {
for(RandomAccessFile raf:rafMap.values()) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 根据任务列表,读取本地文件片段实际数据发送。
*/
void sendSectionInfo() {
String appName=sendRbi.getName();
ResourceBaseInfo resourceBaseInfo=ResourePool.getResourceBaseInfo(appName);
String absoluteRoot=resourceBaseInfo.getAbsolutePath();
List<ResourceStructInfo> rsiList=resourceBaseInfo.getRsiList();
rafMap=new HashMap<String, RandomAccessFile>();
List<SectionInfo> sectionInfos=sendRbi.getSectionList();
for (SectionInfo sectionInfo : sectionInfos) {
int fileHandle=sectionInfo.getFileHandle();
ResourceStructInfo rsi=getRsiByFileHandle(fileHandle, rsiList);
String filePath=absoluteRoot+rsi.getFilePath();
long offset=sectionInfo.getOffset();
int size=sectionInfo.getSize();
try {
RandomAccessFile raf=getRaf(filePath);
raf.seek(offset);
byte[] value=new byte[size];
raf.read(value);
FileSection fileSection=new FileSection();
fileSection.setSectionInfo(sectionInfo);
fileSection.setValue(value);
fileSection.sendSection(dos);
} catch (IOException e) {
e.printStackTrace();
}
}
closeFile();
System.out.println("一发送端完成发送任务!");
}
private void close() {
try {
if (dos != null) {
dos.close();
}
} catch (IOException e) {
} finally {
dos = null;
}
try {
if (socket != null && !socket.isClosed()) {
socket.close();
}
} catch (IOException e) {
} finally {
socket = null;
}
}
@Override
public void run() {
try {
connectToReceiver();
sendSectionInfo();
close();
//发送完毕后,要更新资源提供端的发送次数以及同时发送个数。
sender.getCenterService()
.decreaseSendingTime(sendRbi.getName(), sender.getSendNode());
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**接收界面接口。
* @author dingxiang
*
*/
public interface IReceiveActionView {
//获取参与发送的发送节点个数
void getSenderCount(int senderCount);
//显示连接的发送端
void linkedOneSender(Socket sender);
//报告没有发送端
void hasNoSender();
//更新接收状态
void updateReceiveState(int start,int end);
//显示
void showReceiveActionView();
//关闭
void closeReceiveActionView();
}
/**侦听接收状况的具体类。
* @author dingxiang
*
*/
public class receiveActionView implements IReceiveActionView {
private IProgressBar receiveProgressBar;//接收进度条
public receiveActionView() {
this.receiveProgressBar=new ProgressBar();
}
@Override
public void getSenderCount(int senderCount) {
}
@Override
public void linkedOneSender(Socket sender) {
}
@Override
public void hasNoSender() {
}
/* 更新接收状态
*/
@Override
public void updateReceiveState(int start,int end) {
receiveProgressBar.change(start, end);
}
@Override
public void showReceiveActionView() {
receiveProgressBar.showView();
}
@Override
public void closeReceiveActionView() {
receiveProgressBar.closeView();
}
}
/**
* @author dingxiang
* 接收进度条接口。
*
*/
public interface IProgressBar {
//更新进度条
void change(int curValue, int maxValue);
//显示进度条
void showView();
//关闭进度条
void closeView();
}
/**接收进度条实现类
* @author dingxiang
*
*/
public class ProgressBar implements IProgressBar {
private JFrame jfrmView;
private JProgressBar jpgbTest;
public ProgressBar() {
jfrmView = new JFrame("接受进度");
jfrmView.setSize(300, 80);
jfrmView.setLayout(new BorderLayout());
jfrmView.setLocationRelativeTo(null);
jfrmView.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel jlblTopic = new JLabel("", 0);
jlblTopic.setFont(new Font("微软雅黑", Font.BOLD, 28));
jfrmView.add(jlblTopic, "North");
JPanel jpnlProgressBar = new JPanel(new FlowLayout());
jfrmView.add(jpnlProgressBar);
jpgbTest = new JProgressBar();
jpnlProgressBar.add(jpgbTest);
jpgbTest.setStringPainted(true);
jpgbTest.setMaximum(100);
}
@Override
public void showView() {
jfrmView.setVisible(true);
}
@Override
public void closeView() {
jfrmView.dispose();
}
/* 根据接收的文件片段数/初始未接收文件片段总数计算接收进度百分比
*/
@Override
public void change(int curValue, int maxValue) {
int per = (int) (curValue / (double) maxValue * 100.0);
String strPer = "已完成" + per + "%";
jpgbTest.setValue(per);
jpgbTest.setString(strPer);
}
}
致此,多文件自平衡云传输系统框架建立完成,经多次测试,传输速率在28~40MB/s左右,,从此再也不用苦苦等待文件传输了。
资源链接已上传至:github:https://github.com/dx99606707/depository/分布式多文件自平衡云传输系统。