传统的客户端和服务端来说,客户端面对的是单一的服务器,
服务器及网络的带宽决定了网络的性能,每台服务器提供的信息数量,受到自身存储空间的限制,而任意时刻它所能支持的客户端的访问数量,则受到自身处理能力和网络带宽的限制,一旦服务器崩溃,整个网络也随之瘫痪。
对于服务器来说,当拥有大量的客户端进行访问的时候,服务器将承受巨大的压力。
对我们的多文件传输来说,我们的客户端想要请求某一数据资源的时候,我们只能访问单一的服务器,如果存在其他的客户端需要请求的资源和之前某一个客户端请求的资源相同,那么对于服务器来说,就需要再次进行响应相同的请求,那无疑对服务器来说是一种压力。
所以我的多文件自平衡云传输框架,有一个显著的特点,那就是当某个客户端在进行服务器请求的时候,最开始只有服务器有资源,当某个客户端拥有资源后,自己同样可以充当一个服务端,那么当再有客户端请求相同的资源的时候,那就会存在两个服务端可以进行资源的发送,以此类推,在出现了大量客户端的请求是时,对于真正的服务器来说,压力有一定的降低。
对于真正的服务器来说,首先向注册中心服务器进行资源的注册,告知注册资源自己的信息以及自己拥有的资源,当某个客户端向服务器请求资源时,服务端只是响应相关资源的一些基本的信息,而不会直接将真正的资源发送给客户端,此时客户端根据资源的基本信息,去向注册中心请求相关拥有该资源的节点列表,客户端根据返回的节点列表的信息,进行负载均衡挑选合适的能够发送资源的节点,进行链接这些节点,由这些节点服务器进行资源不同片段的发送。客户端根据一定的方案,将分散的资源进行整合。同时在接收到完整的资源之后,自已同样也可以作为一个发送端。
话不多说,先来看看整个框架最基础的部分,底层的传输和主要的基本资源信息的流动。
首先来说资源基本的信息,可谓是贯穿整个框架,客户端通过向服务器请求资源的资本信息,再根据基本信息进行可发送资源节点列表的获取。同样客户端可以根据依据资源的基本信息进行判断自己是否将资源接受完整,同时也是做为断点续传参考的依据。
ResourceInfo
public class ResourceInfo {
//对应服务器要求的本地xml的路径,可根据不同个的服务器进行定义,通过读取配置文件的方式。
private static final String defultAppXml = "E:\\.KSGApp\\app.xml";
private String resourceName;//资源的名称,简称
private String root;//资源的相对路径
private int version;
private Map<Integer, FileInfo> fileInfoMap;//存储的是每个资源对应的多个文件的信息
private List<FileHead> fileHeadList;//需要请求的文件的片段或者整个文件
private xmlEditor xd;//用于将资源的资本信息保存到所请求的服务器默认的本地xml路径中。
public ResourceInfo (ResourceInfo ri) {
this.resourceName = ri.getResourceName();
this.root = ri.getRoot();
this.version = ri.getVersion();
this.fileInfoMap = ri.getFileInfoMap();
this.fileHeadList = ri.getFileHeadList();
this.xd = ri.getXd();
}
public Map<Integer, FileInfo> getFileInfoMap() {
return fileInfoMap;
}
public void setFileInfoMap(Map<Integer, FileInfo> fileInfoMap) {
this.fileInfoMap = fileInfoMap;
}
public ResourceInfo() {
fileInfoMap = new HashMap<Integer, FileInfo>();
fileHeadList = new ArrayList<FileHead>();
try {
xd = new xmlEditor();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
}
public FileInfo getFileInfo(int handle) {
return fileInfoMap.get(handle);
}
public static String getDefultAppXml() {
return defultAppXml;
}
public void getAllFiles(Map<Integer, FileInfo> fileInfoMap) {
//对于本地没有资源,直接将资源得全部文件进行请求
//将map中的FileInfo变为,构造一个请求的list
if(fileInfoMap == null) {
return;
}
Set<Integer> keys = fileInfoMap.keySet();
for(Integer one : keys) {
FileInfo fi = fileInfoMap.get(one);
FileHead fh = new FileHead();
fh.setFileHandle(fi.getFileHandle());
fh.setSectionLength((int)fi.getFileLength());
fh.setOffset(0L);
fileHeadList.add(fh);
}
}
public void saveObject() {
//通过getResource如果返回值为null,表明本地没有该资源
//那么将设置本地root,并将资源存储在xml中
//保存在本地不需要保存list和map,只需要保存基本的信息
Map<Integer, FileInfo> tempMap = fileInfoMap;
List<FileHead> tempList = fileHeadList;
fileInfoMap = null;
fileHeadList = null;
xd.addObject(defultAppXml, this);
fileInfoMap = tempMap;
fileHeadList = tempList;
}
public ResourceInfo getResoureFromXml() {
//通过本地xml来查找,生成一个Object
return (ResourceInfo)xd.getObject(defultAppXml, this.getClass(), "resourceName", resourceName);
}
public void addFileInfo(int hash, FileInfo fi) {
//将资源的多个文件进行记录,每个文件的句柄采用的是文件的相对路径和文件的长度
fileInfoMap.put(hash, fi);
}
public List<FileHead> getFileHeadList() {
return fileHeadList;
}
public void setFileHeadList(List<FileHead> fileHeadList) {
this.fileHeadList = fileHeadList;
}
}
TransferDetail
用于字节流的传输,可通过设置传输中每次读取的字节数,
/*
* 用于底层数据的传输模块
*/
public class TransferDetail {//
private static int signByte = 16;
private ISendAndReceive Isa;
public TransferDetail() {
Isa = new SendAndReceive();
}
public ISendAndReceive getIsa() {
return Isa;
}
/*
* 可通过设置进行ISendAndReceive的实现类的方案,主要是进行读数据方式的实现,
* 可以调节读取的速率,设置每次进行读取的的字节的数量。
*/
public void setIsa(ISendAndReceive isa) {
Isa = isa;
}
public byte[] readContentBytes(DataInputStream dis, int contentLength) throws IOException{
return Isa.receive(dis, contentLength);
}
public byte[] readSignBytes(DataInputStream dis) throws IOException {
return Isa.receive(dis, signByte);
}
public void writeBytes(DataOutputStream dos, byte[] bytes) {
try {
Isa.send(dos, bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
}
通过ResourceInfo中的成员我们能够看到有两个自定义类FileInfo和Filehead
这就要说到我们的底层传输的过程了,ResourceInfo这个类的对象通过在短连接的过程中进行传递,但客户端需要接收到真正的资源,而不仅仅是资源的一些资本信息,那么就需要FileInfo和Filehead来作为辅助。
FileInfo
/*
* 用来表示每个文件的信息
* fileHandle 文件的句柄,用来确定文件的唯一性,值为其相对路径和文件长度的哈希值
* fileRoot 文件的相对路径,对接收端来讲和发送单来讲可以确定本地存储的位置
* fileLength 文件的长度大小,接收端可通过此判断一个文件文件是否完整。
*/
public class FileInfo {
private int fileHandle;
private String fileRoot;
private long fileLength;
public FileInfo() {
}
public int getFileHandle() {
return fileHandle;
}
public void setFileHandle(int fileHandle) {
this.fileHandle = fileHandle;
}
public String getFileRoot() {
return fileRoot;
}
public void setFileRoot(String fileRoot) {
this.fileRoot = fileRoot;
}
public long getFileLength() {
return fileLength;
}
public void setFileLength(long fileLength) {
this.fileLength = fileLength;
}
@Override
public String toString() {
return "FileInfo [fileHandle=" + fileHandle + ", fileRoot=" + fileRoot + ", fileLength=" + fileLength + "]";
}
/*
*采用文件的长度和相对路径的hashcode作为文件的句柄,确定文件的唯一性
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (fileLength ^ (fileLength >>> 32));
result = prime * result + ((fileRoot == null) ? 0 : fileRoot.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;
FileInfo other = (FileInfo) obj;
if (fileLength != other.fileLength)
return false;
if (fileRoot == null) {
if (other.fileRoot != null)
return false;
} else if (!fileRoot.equals(other.fileRoot))
return false;
return true;
}
}
FileHead
在进行真正的资源传输的时候,底层的传输过程中,采用字节流的方式。此类在传输的时候转化为字节流,作为传输的标志信息由发送端先发送,发送端根据本类成员文件句柄,偏移量和长度在本地找到对应的文件片段片段进行再发送真正内容的字节流。对于接收端来说根据先发送的该类转化的字节流反转化为一个该类的对象,再根据对象成员的长度在流中读取相应的字节长度写入接收端本地。
/*
* 此类用来表示每个文件中的一个片段信息,当某个资源只拥有一个文件,并且此文件过大的
* 话,我们要将一个文件进行分片。
* 同样这个类是做为底层传输的一个重要的基础。
* fileHandle 文件句柄,用来确定文件的唯一性
* offset 片段处于文件的偏移量位置
* sectionLength 片段的长度大小
*
*
*/
public class FileHead {
private int fileHandle;
private long offset;
private int sectionLength;
public FileHead() {
}
public FileHead(byte[] value) {
bytesToMember(value);
}
public FileHead(int fileHandle, long offset, int sectionLength) {
this.fileHandle = fileHandle;
this.offset = offset;
this.sectionLength = sectionLength;
}
/*
*FIleHead表示的是文件的某个片段的信息,对于一个完整的文件来说,是由不同的fileHead进行组合
*该方法用来判断某一片段是否为合理的文件片段
*
*/
public boolean ifRightHead(FileHead fh) {
return fh.getOffset() >= this.offset && (this.offset + this.sectionLength)
>= (fh.getOffset() + fh.getSectionLength());
}
/*
*将该对象转化为一个字节流
*是发送端内部使用的方法,用于将片段信息进行转化发送
*/
public byte[] getHeadBytes() {
byte[] bytes = new byte[16];
byte[] intBOne = BytesThransefor.intToBytes(this.fileHandle);
CombinBytes.produce(bytes, 0, intBOne);
byte[] longB = BytesThransefor.longToBytes(this.offset);
CombinBytes.produce(bytes, 4, longB);
byte[] intBTwo = BytesThransefor.intToBytes(this.sectionLength);
CombinBytes.produce(bytes, 12, intBTwo);
return bytes;
}
/*
*将字节流转化为一个fileHead对象
*在接收端内部进行给方法的调用,根据将字节流转化后成员的长度进行真正片段内容的读取。
*/
public FileHead bytesToMember(byte[] value) {//将一个字节流转化为成员
byte[] intBOne = new byte[4];
CombinBytes.dispose(value, 0, intBOne);
this.fileHandle = BytesThransefor.bytesToInt(intBOne);
byte[] longB = new byte[8];
CombinBytes.dispose(value, 4, longB);
this.offset = BytesThransefor.bytesToLong(longB);
byte[] intBTwo = new byte[4];
CombinBytes.dispose(value, 12, intBTwo);
this.sectionLength = BytesThransefor.bytesToInt(intBTwo);
return this;
}
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 getSectionLength() {
return sectionLength;
}
public void setSectionLength(int sectionLength) {
this.sectionLength = sectionLength;
}
@Override
public String toString() {
return "(" + "fileHandle=" + fileHandle + ", offset=" + offset + ", sectionLength=" + sectionLength + ")" + "\n";
}
@Override
public int hashCode() {//采用fileHead的hashcode来进行
return fileHandle;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
FileHead other = (FileHead) obj;
if (fileHandle != other.fileHandle)
return false;
return true;
}
}