断点续传的原理

转载:https://blog.csdn.net/cwhatiscs/article/details/89708709
断点续传是大型文件数据传输的核心。本文将以多线程技术和Socket技术为依托,介绍大型文件断点续传的实现方法。

基本实现思想

多线程断点续传实现的基本思想就是在发送端(也称客户端)将要传输的文件分割为大小相当的多块,用多个线程,将这些块同时向目标服务器端发送;在服务器端的服务程序监听数据传输请求,每当接到新的请求,则创建一个新的线程,与客户端的发送线程对应,接收数据,记录数据传输进程

图1是点对点文件断点续传第N块传输过程示意图。在传输发起端(客户端),将大型文件事先分割为大小相当的N块,同时创建N个传输线程,连接目标端服务器。当服务器端接收到每一个连接请求后,告知客户端可以传输文件。当客户端接收到可以传输文件的消息时,首先向服务器发送数据传输信息块(包括第几块、在块中的起始位置)请求,当服务器端接收到该请求后,向客户端发送数据传输信息,客户端然后传输数据传输信息块指定的数据给服务器端,服务器端更新数据传输信息块。

(一)断点续传的原理
其实断点续传的原理很简单,就是在Http的请求上和一般的下载有所不同而已。
打个比方,浏览器请求服务器上的一个文时,所发出的请求如下:
假设服务器域名为wwww.sjtu.edu.cn,文件名为down.zip。
GET /down.zip HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-
excel, application/msword, application/vnd.ms-powerpoint, /
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
Connection: Keep-Alive

服务器收到请求后,按要求寻找请求的文件,提取文件的信息,然后返回给浏览器,返回信息如下:

200
Content-Length=106786028
Accept-Ranges=bytes
Date=Mon, 30 Apr 2001 12:56:11 GMT
ETag=W/“02ca57e173c11:95b”
Content-Type=application/octet-stream
Server=Microsoft-IIS/5.0
Last-Modified=Mon, 30 Apr 2001 12:56:11 GMT

所谓断点续传,也就是要从文件已经下载的地方开始继续下载。所以在客户端浏览器传给
Web服务器的时候要多加一条信息–从哪里开始。
下面是用自己编的一个"浏览器"来传递请求信息给Web服务器,要求从2000070字节开始。
GET /down.zip HTTP/1.0
User-Agent: NetFox
RANGE: bytes=2000070-
Accept: text/html, image/gif, image/jpeg, *; q=.2, /; q=.2

仔细看一下就会发现多了一行RANGE: bytes=2000070-
这一行的意思就是告诉服务器down.zip这个文件从2000070字节开始传,前面的字节不用传了。
服务器收到这个请求以后,返回的信息如下:
206
Content-Length=106786028
Content-Range=bytes 2000070-106786027/106786028
Date=Mon, 30 Apr 2001 12:55:20 GMT
ETag=W/“02ca57e173c11:95b”
Content-Type=application/octet-stream
Server=Microsoft-IIS/5.0
Last-Modified=Mon, 30 Apr 2001 12:55:20 GMT

和前面服务器返回的信息比较一下,就会发现增加了一行:
Content-Range=bytes 2000070-106786027/106786028
返回的代码也改为206了,而不再是200了。

知道了以上原理,就可以进行断点续传的编程了。

(二)Java实现断点续传的关键几点

(1)用什么方法实现提交RANGE: bytes=2000070-。
当然用最原始的Socket是肯定能完成的,不过那样太费事了,其实Java的net包中提供了这种功能。代码如下:
URL url = new URL(“http://www.sjtu.edu.cn/down.zip”);
HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection

();
//设置User-Agent
httpConnection.setRequestProperty(“User-Agent”,“NetFox”);
//设置断点续传的开始位置
httpConnection.setRequestProperty(“RANGE”,“bytes=2000070”);
//获得输入流
InputStream input = httpConnection.getInputStream();

从输入流中取出的字节流就是down.zip文件从2000070开始的字节流。
大家看,其实断点续传用Java实现起来还是很简单的吧。
接下来要做的事就是怎么保存获得的流到文件中去了。

保存文件采用的方法。
我采用的是IO包中的RandAccessFile类。
操作相当简单,假设从2000070处开始保存文件,代码如下:
RandomAccess oSavedFile = new RandomAccessFile(“down.zip”,“rw”);
long nPos = 2000070;
//定位文件指针到nPos位置
oSavedFile.seek(nPos);
byte[] b = new byte[1024];
int nRead;
//从输入流中读入字节流,然后写到文件中
while((nRead=input.read(b,0,1024)) > 0)
{
oSavedFile.write(b,0,nRead);
}

怎么样,也很简单吧。
接下来要做的就是整合成一个完整的程序了。包括一系列的线程控制等等。

(三)断点续传内核的实现
主要用了6个类,包括一个测试类。
SiteFileFetch.java负责整个文件的抓取,控制内部线程(FileSplitterFetch类)。
FileSplitterFetch.java负责部分文件的抓取。
FileAccess.java负责文件的存储。
SiteInfoBean.java要抓取的文件的信息,如文件保存的目录,名字,抓取文件的URL等。
Utility.java工具类,放一些简单的方法。
TestMethod.java测试类。

下面是源程序:
/*
**SiteFileFetch.java
/
package NetFox;
import java.io.
;
import java.net.*;

public class SiteFileFetch extends Thread {

SiteInfoBean siteInfoBean = null; //文件信息Bean
long[] nStartPos; //开始位置
long[] nEndPos; //结束位置
FileSplitterFetch[] fileSplitterFetch; //子线程对象
long nFileLength; //文件长度
boolean bFirst = true; //是否第一次取文件
boolean bStop = false; //停止标志
File tmpFile; //文件下载的临时信息
DataOutputStream output; //输出到文件的输出流

public SiteFileFetch(SiteInfoBean bean) throws IOException
{
siteInfoBean = bean;
//tmpFile = File.createTempFile (“zhong”,“1111”,new File(bean.getSFilePath()));
tmpFile = new File(bean.getSFilePath()+File.separator + bean.getSFileName()+“.info”);
if(tmpFile.exists ())
{
bFirst = false;
read_nPos();
}
else
{
nStartPos = new long[bean.getNSplitter()];
nEndPos = new long[bean.getNSplitter()];
}

}

public void run()
{
//获得文件长度
//分割文件
//实例FileSplitterFetch
//启动FileSplitterFetch线程
//等待子线程返回
try{
if(bFirst)
{
nFileLength = getFileSize();
if(nFileLength == -1)
{
System.err.println(“File Length is not known!”);
}
else if(nFileLength == -2)
{
System.err.println(“File is not access!”);
}
else
{
for(int i=0;i new FileSplitterFetch( siteInfoBean.getSSiteURL(),
siteInfoBean.getSFilePath() + File.separator + siteInfoBean.getSFileName(),
nStartPos[i],nEndPos[i],i);
Utility.log( " Thread " + i + " , nStartPos =" + nStartPos[i]+", nEndPos = " + nEndPos[i]);
fileSplitterFetch[i].start();
}
// fileSplitterFetch[nPos.length-1] = new FileSplitterFetch(siteInfoBean.getSSiteURL(),
siteInfoBean.getSFilePath() + File.separator + siteInfoBean.getSFileName() , nPos[nPos.length-1], nFileLength, nPos.length-1);
// Utility.log("Thread " + (nPos.length-1 ) + " , nStartPos = " + nPos[nPos.length-1] + ",
nEndPos = " + nFileLength);
// fileSplitterFetch[nPos.length-1].start();

//等待子线程结束
//int count = 0;
//是否结束while循环
boolean breakWhile = false;

while(!bStop)
{
write_nPos();
Utility.sleep(500);
breakWhile = true;

for(int i=0;i4)
// siteStop();
}

System.err.println(“文件下载结束!”);
}
catch(Exception e){e.printStackTrace ();}
}

//获得文件长度
public long getFileSize()
{
int nFileLength = -1;
try{
URL url = new URL(siteInfoBean.getSSiteURL());
HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection ();
httpConnection.setRequestProperty(“User-Agent”,“NetFox”);

int responseCode=httpConnection.getResponseCode();
if(responseCode>=400)
{
processErrorCode(responseCode);
return -2; //-2 represent access is error
}

String sHeader;

for(int i=1;;i++)
{
//DataInputStream in = new DataInputStream(httpConnection.getInputStream ());
//Utility.log(in.readLine());
sHeader=httpConnection.getHeaderFieldKey(i);
if(sHeader!=null)
{
if(sHeader.equals(“Content-Length”))
{
nFileLength = Integer.parseInt(httpConnection.getHeaderField(sHeader));
break;
}
}
else
break;
}
}
catch(IOException e){e.printStackTrace ();}
catch(Exception e){e.printStackTrace ();}

Utility.log(nFileLength);

return nFileLength;
}

//保存下载信息(文件指针位置)
private void write_nPos()
{
try{
output = new DataOutputStream(new FileOutputStream(tmpFile));
output.writeInt(nStartPos.length);
for(int i=0;iDataInputStream(new FileInputStream(tmpFile));
int nCount = input.readInt();
nStartPos = new long[nCount];
nEndPos = new long[nCount];
for(int i=0;i 0 & & nStartPos < nEndPos && !bStop)
{
nStartPos += fileAccessI.write(b,0,nRead);
//if(nThreadID == 1)
// Utility.log("nStartPos = " + nStartPos + ", nEndPos = " + nEndPos);
}

Utility.log(“Thread " + nThreadID + " is over!”);
bDownOver = true;
//nPos = fileAccessI.write (b,0,nRead);
}
catch(Exception e){e.printStackTrace ();}
}
}

//打印回应的头信息
public void logResponseHead(HttpURLConnection con)
{
for(int i=1;;i++)
{
String header=con.getHeaderFieldKey(i);
if(header!=null)
//responseHeaders.put(header,httpConnection.getHeaderField(header));
Utility.log(header+" : "+con.getHeaderField(header));
else
break;
}
}

public void splitterStop()
{
bStop = true;
}

}

/*
**FileAccess.java
/
package NetFox;
import java.io.
;

public class FileAccessI implements Serializable{

RandomAccessFile oSavedFile;
long nPos;

public FileAccessI() throws IOException
{
this(“”,0);
}

public FileAccessI(String sName,long nPos) throws IOException
{
oSavedFile = new RandomAccessFile(sName,“rw”);
this.nPos = nPos;
oSavedFile.seek(nPos);
}

public synchronized int write(byte[] b,int nStart,int nLen)
{
int n = -1;
try{
oSavedFile.write(b,nStart,nLen);
n = nLen;
}
catch(IOException e)
{
e.printStackTrace ();
}

return n;
}

}

/*
**SiteInfoBean.java
*/
package NetFox;

public class SiteInfoBean {

private String sSiteURL; //Site’s URL
private String sFilePath; //Saved File’s Path
private String sFileName; //Saved File’s Name
private int nSplitter; //Count of Splited Downloading File

public SiteInfoBean()
{
//default value of nSplitter is 5
this(“”,“”,“”,5);
}

public SiteInfoBean(String sURL,String sPath,String sName,int nSpiltter)
{
sSiteURL= sURL;
sFilePath = sPath;
sFileName = sName;
this.nSplitter = nSpiltter;

}

public String getSSiteURL()
{
return sSiteURL;
}

public void setSSiteURL(String value)
{
sSiteURL = value;
}

public String getSFilePath()
{
return sFilePath;
}

public void setSFilePath(String value)
{
sFilePath = value;
}

public String getSFileName()
{
return sFileName;
}

public void setSFileName(String value)
{
sFileName = value;
}

public int getNSplitter()
{
return nSplitter;
}

public void setNSplitter(int nCount)
{
nSplitter = nCount;
}
}

/*
**Utility.java
*/
package NetFox;

public class Utility {

public Utility()
{

}

public static void sleep(int nSecond)
{
try{
Thread.sleep(nSecond);
}
catch(Exception e)
{
e.printStackTrace ();
}
}

public static void log(String sMsg)
{
System.err.println(sMsg);
}

public static void log(int sMsg)
{
System.err.println(sMsg);
}
}

/*
**TestMethod.java
*/
package NetFox;

public class TestMethod {

public TestMethod()
{ ///xx/weblogic60b2_win.exe
try{
SiteInfoBean bean = new SiteInfoBean(“http://localhost/xx/weblogic60b2_win.exe”," L: emp",“weblogic60b2_win.exe”,5);
//SiteInfoBean bean = new SiteInfoBean(“http://localhost:8080/down.zip”," L: emp",“weblogic60b2_win.exe”,5);
SiteFileFetch fileFetch = new SiteFileFetch(bean);
fileFetch.start();
}
catch(Exception e){e.printStackTrace ();}

}

public static void main(String[] args)
{
new TestMethod();
}
}

一,最重要的一点,断点续传需要服务器的支持,这个是必要条件。
传统的FTP SERVER是不支持断点续传的,因为它不支持REST指令,传统的FTP指令(我是指服务器端指令)并不包括REST指令。
第二,客户端要知道使用REST等一系列指令来作断点续传。
看看断点续传的详细过程(FTP SERVER):
首先客户端使用REST指令来告诉FTP SERVER它需要从文件的某个点开始传,接着用STOR或者RETR命令开始传文件,大概的命令的流程如下:
TYPE I
200 Type set to I.
PASV
227 Entering Passive Mode (204,48,18,69,98,250)
REST 187392
350 Restarting at 187392. Send STORE or RETRIEVE to initiate transfer.
RETR /pub/audio/pci/maestro-3/win2k/1056.zip
150 Opening BINARY mode data connection for /pub/audio/pci/maestro-3/win2k/1056.zip (936098 bytes).
首先使用TYPE命令告诉FTP SERVER使用BINARY模式传送文件;
然后使用PASV命令告诉FTP SERVER使用被动打开模式来传送文件;
接着使用REST 187392指令告诉FTP SERVER要从文件的187392字节开始传送;
最后使用RETR指令来传送文件。
从上面可以看出,这个FTP SERVER支持REST指令,有的FTP SERVER(特别的老的)是不支持这个指令的,这时即使FTP CLIENT支持断点续传也一点用都没有!
支持断点的FTP SERVER:Serv-U FTP,还有一系列的新出现的FTP SERVER;
不支持断点的:IIS4以前版本所带的都不行,IIS5 有,不家可以测试一下,登录进FTP SERVER,然后输入REST 1000命令,看服务器是否认识,认识就是支持断点。
上面说的是FTP SERVER的断点,HTTP的断点续传是这样的:
在以前版本的HTTP SERVER也是不支持断点的,HTTP/1.1开始就支持了,具体如下:
在HTTP请求的头部信息里面,通常是这样的:
GET http://xxx.xxx.xxx.xxx/index.html HTTP/1.1
Host:www.163.net
Accept:/
上面是HTTP请求头的主要内容,是浏览器等客户端发给HTTP SERVER的信息。
在这个请求头里面,第一行叫做Request Line,GET叫做请求方法(通常得到一个HTML页面都是用GET,CGI等请求是用POST),http://bbs.netbuddy.org/index.html是URL,HTTP/1.1为版本号。
Host:bbs.netbuddy.org是HTTP服务器名字,这也是HTTP/1.1的新东东,以前做虚拟主机可是要一个主机名对应多个IP,现在好了呵呵,这个离题太远,不说了)
要做断点续传,浏览器等客户端需要在请求头里面发送
Range: bytes=1140736-
这样的请求,就是告诉HTTP SERVER,这个文件要从1140736字节开始传送。
最 后一点,大家看了上面的描述可能会有一个问题,那么多点传送怎么做呢?那就是多起几个线程,连接到服务器,用断点指令来传送文件,在传送的过程中,会检查 前面的(比如说第一个蚂蚁)得到的文件的部分是否超过了后面的(比如说第二个蚂蚁)的起点,相等就停前面的蚂蚁,最后再合并几个部分,就得到一个完整的文 件了


C版本实现方式:

3 具体实现

在实现过程中我使用了MFC的多线程和Windows的Socket,分客户端和服务器端实现。因为数据传输往往是对等的,所以需要将客户端和服务器端集成在一起,在客户端和服务器端分别实现后,这是件非常简单的工作,只需将它们集成在一起即可。下面将分别介绍客户端和服务器端的实现。

3.1 关键数据结构

文件信息数据结构 用于在服务器端和客户端之间传递文件第N块的属性信息,详细定义如下:

structfileinfo

{

int fileno; //文件号

int type; //消息类别

long len; //文件(块)长度,在客户端向服务器端发送数据时,是文件长度;

//在服务器端向客户端发送已传部分的信息时,是应该续传部分的长度;

long seek; //开始位置,标识要传输数据在原文件中的起始位置

char name[MAX_PATH_LEN];//文件名

};

发送进度记录结构 用户记录文件传输进程,详细定义如下:

structSentInfo

{

long totle; //已经成功发送数据的长度;

int block; //块标识;

long filelen; //文件总长度;

int threadno; //负责传输第N块数据的线程标识;

CString name; //文件名称

};

客户端类 客户端文件发送实例封装,用户记录客户端文件发送过程中的属性信息、已发送大小、发送线程句柄、发送线程状态、发送统计信息等,具体定义是:

classCClient:publicCObject

{

protected:

//Attributes

public:

CClient(CString ip);

~CClient();

SentInfo doinfo;

long m_index; //块索引

BOOL sendOk[BLOCK]; //块发送结束状态

CString SendFileName;

CString DestIp; //目标IP地址

THREADSTATUS SendStatus;

int GetBlockIndex(); //获取当前要传输文件块的序号,例如0,1,2…

CCriticalSection m_gCS;

//Sending File Block Thread Handles

HANDLE m_thread[BLOCK]; //Block Sending Thread Handles Array

HANDLE m_ExitThread; //WaitToExitThread Handle

HANDLE m_ProcessTrackThread;

HANDLE m_hEventKill; //User canceled Event

long m_IdxInListCtrl; //Index in ListView

long m_lBeginTimeTick;

long m_lBytesSent;

// Operations

public:

};

3.2 客户端

发送线程 用于发送由lpparam参数指定的Client对象标识的文件块数据。具体实现是:

//发送线程主函数

//并行进行的线程,针对每个要发送的文件有BLOCK个该线程

DWORDWINAPISendThread(LPVOIDlpparam)

{

CClient* pClient=(CClient*)lpparam;

int block_index = pClient->GetBlockIndex();

//发送文件的第m_index块

//发送线程

sockaddr_in local;

SOCKET m_socket;

int rc=0;

local.sin_family=AF_INET;

local.sin_port=htons(pMainFrm->m_FilePort);

local.sin_addr.S_un.S_addr=inet_addr(pClient->DestIp);

m_socket=socket(AF_INET,SOCK_STREAM,0);

int ret;

char m_buf[MAX_BUFSIZE];

fileinfo file_info;

//Connect to Server

ret = connect(m_socket,(LPSOCKADDR)&local,sizeof(local));

while((ret == SOCKET_ERROR)&&(WaitForSingleObject(pClient->m_hEventKill, 0)

==WAIT_TIMEOUT))

{

closesocket (m_socket);

}

file_info.len = pClient->doinfo.filelen ;

if (file_info.len <= 0) //已经完成传输

return 1;

file_info.seek=0;

file_info.type=1; //从服务器上获取文件的第index块已接收的大小

file_info.fileno = block_index;

strcpy(file_info.name,pClient->doinfo.name);

//发送第index块已发送大小请求

sendn(m_socket,(char*)&file_info,PROTOCOL_HEADSIZE);

long nRead = readn(m_socket,m_buf,PROTOCOL_HEADSIZE);

if(nRead <= 0)

{

closesocket (m_socket);

return -1;

}

fileinfo * pfile_info = (fileinfo*)m_buf;

if (pfile_info->len == 0)

{

closesocket (m_socket);

//block_index块是续传,且发送完毕

pClient->good[block_index] = TRUE;

return 1;

}

CFile SourceFile;

SourceFile.Open(pClient->SendFileName,

CFile::modeRead|CFile::typeBinary| CFile::shareDenyNone);

//Seek right position of sending file

long apos=SourceFile.Seek(pfile_info->seek,CFile::begin);

int len1;

len1=pfile_info->len;

//取得已经发送的长度

if (block_index < BLOCK - 1)

pClient->doinfo.totle += pClient->doinfo.filelen / BLOCK - pfile_info->len;

else

pClient->doinfo.totle += pClient->doinfo.filelen –

pClient->doinfo.filelen/BLOCK* (BLOCK - 1) - pfile_info->len;

int nBegin = GetTickCount();

int SendTotal = 0;

while((len1 > 0) && (WaitForSingleObject(pClient->m_hEventKill, 0) == WAIT_TIMEOUT))

{

nRead = SourceFile.Read(m_buf, (len1 < pMainFrm->m_CurrentBlockSize) ?

len1:pMainFrm->m_CurrentBlockSize);

int send_count=sendn(m_socket,m_buf,nRead);

float nLimit;

if(send_count<=0)

{

closesocket (m_socket);

SourceFile.Close();

return 1;

}

len1 = len1 - send_count;

//进度信息

pClient->doinfo.totle += send_count;

SendTotal += send_count;

}

SourceFile.Close();

shutdown(m_socket,2);

closesocket (m_socket);

if(len1 <= 0)pClient->good[block_index] = TRUE;

return 1;

}

创建发送线程 对应要发送的文件,创建BLOCK个线程,负责发送各自的数据块。

for(inti=0;i

{

//创建线程,状态为停止状态

pClient->m_thread[i] = ::CreateThread(NULL,0,SendThread,(LPVOID)pClient,

CREATE_SUSPENDED,&dwthread);

::SetThreadPriority(pClient->m_thread[i],THREAD_PRIORITY_HIGHEST);

}

3.3 服务器端

监听函数 负责监听文件传输请求,在接受请求后,创建数据块接收线程,具体实现如下:

//监听函数

DWORDWINAPIlistenthread(LPVOIDlpparam)

{

SOCKET pSocket=(SOCKET)lpparam;

//最多监听MAX_LISTENCOUNT个客户端连接

int rc=listen(pSocket,MAX_LISTENCOUNT);

if (rc<0)

{

//Error

return 0;

}

//Listening all the incoming connections

while(1)

{

SOCKET aSocket;

//Listening and Block

aSocket=accept(pSocket,NULL,NULL);

DWORD dwthread;

// Create Server Data Receiving Thread “Serverthread”

// And Transfers data

::CreateThread(NULL,0, Serverthread,(LPVOID)aSocket,0,&dwthread);

}

return 0;

}

接收文件数据 负责接收由参数seek和len以及block_index标识的数据块,实现如下:

//接收文件数据

voidRecvFile(SOCKETso,CStringPeerName,CString DestFileName,long seek,

longlen,intblock_index,CFileInfoRecving*pfileinfo)

{

CFile DestFile;

long fileseek = seek;

long filelen = len;

CMainFrame* pWnd = (CMainFrame*) AfxGetMainWnd();

if (!DestFile.Open(pWnd->m_DestPath + “\IP” + PeerName + “-” + DestFileName,

CFile::modeWrite|CFile::typeBinary| CFile::shareDenyNone))

{

DWORD error = GetLastError();

return;

};

long apos = DestFile.Seek(fileseek,CFile::begin);

char m_buf[MAX_BUFSIZE];

CString m_temp;

m_temp.Format(“%s\Temp\IP%s-%s.%d”,pWnd->m_appPath,PeerName,

DestFileName,block_index);

CFile BlockFile;

BlockFile.Open(m_temp, CFile::modeWrite | CFile::typeBinary);

while((filelen>0) && (WaitForSingleObject(pWnd->m_hEventKill, 0) == WAIT_TIMEOUT))

{

int nRead = readn(so, m_buf, (filelen > MAX_BUFSIZE) ? MAX_BUFSIZE : filelen);

//Exited

if (nRead == 0)

{

CString msg;

msg.Format(“[SOCKET %d] Receiving file %s break(Recv 0).”,so,DestFileName);

pWnd->SendMessage(WM_NEWMSG,(LPARAM)msg.GetBuffer(0),3);

msg.ReleaseBuffer();

break;

}

if(nRead<0)

{

CString msg;

msg.Format(“[SOCKET %d] Receiving file %s break.”,so,DestFileName);

pWnd->SendMessage(WM_NEWMSG,(LPARAM)msg.GetBuffer(0),3);

msg.ReleaseBuffer();

break;

}

DestFile.Write(m_buf,nRead);

filelen -= nRead;

fileseek += nRead;

BlockFile.Seek(0,CFile::begin);

BlockFile.Write(&fileseek,sizeof(long));

BlockFile.Write(&filelen,sizeof(long));

pfileinfo->m_seek = fileseek;

pfileinfo->m_len = filelen;

}

closesocket (so);

//Current Block Received OK

BlockFile.Close();

DestFile.Close();

if (filelen <= 0)

pfileinfo->canDel = true;

}

初始化文件数据块信息 负责初始化文件传输时数据块的大小、开始位置等信息。具体实现是:

//初始化文件数据块信息

CFileInfoRecving*GetRecvedFileBlock(CStringPeerName,CStringFileName,long& filelen,

long&fileseek,intblock_index)

{

CString m_temp;

CMainFrame* pWnd = (CMainFrame*) AfxGetMainWnd();

m_temp.Format(“%s\temp\IP%s-%s.%d”,pWnd->m_appPath,PeerName,FileName,block_index);

FILE* fp=NULL;

CFile Blockfile;

CFile RecvFile;

long seek = fileseek;

long flen = filelen;

CString DestFile = pWnd->m_DestPath + “\IP” + PeerName + “-” + FileName;

bool DestFileNoExist = false;

if((fp=fopen(DestFile,“r”))==NULL)

{

RecvFile.Open(DestFile,

CFile::modeCreate|CFile::modeWrite|CFile::typeBinary| CFile::shareDenyNone);

RecvFile.Close();

DestFileNoExist = true;

}

if (fp) fclose(fp);

CFileInfoRecving* pfileinfo = NULL;

//如果目标文件不存在或者长度记录文件不存在,重新传

if( ((fp=fopen(m_temp,“r”))==NULL) || (DestFileNoExist))

{

//第一次接收

fileseek = block_index*(flen/BLOCK);

filelen = flen/BLOCK;

if (block_index == (BLOCK - 1))

filelen = flen - fileseek;

//Add To Recving File List

pfileinfo = new CFileInfoRecving();

pfileinfo->m_sFileName = FileName;

pfileinfo->m_sPeerName = PeerName;

pfileinfo->m_nBlockIndex = block_index;

pfileinfo->m_sFileSize.Format(“%d”,flen);

seek = fileseek;

flen = filelen;

pfileinfo->m_seek = seek;

pfileinfo->m_len = flen;

pWnd->m_RecvingFileList.AddTail(pfileinfo);

Blockfile.Open(m_temp,

CFile::modeCreate|CFile::modeWrite| CFile::typeBinary | CFile::shareDenyNone);

Blockfile.Write(&seek,sizeof(long));

Blockfile.Write(&flen,sizeof(long));

Blockfile.Close();

}

else

{

POSITION pos = pWnd->m_RecvingFileList.GetHeadPosition();

while (pos)

{

pfileinfo = pWnd->m_RecvingFileList.GetAt(pos);

if (pfileinfo)

{

if ((pfileinfo->m_sFileName == FileName) &&

(pfileinfo->m_sPeerName == PeerName) &&

(pfileinfo->m_nBlockIndex == block_index))

break;

}

pWnd->m_RecvingFileList.GetNext(pos);

}

Blockfile.Open(m_temp,CFile::modeRead | CFile::typeBinary | CFile::shareDenyNone);

Blockfile.Read(&fileseek,sizeof(long));

Blockfile.Read(&filelen,sizeof(long));

if ((pfileinfo == NULL) || (!pos))

{

pfileinfo = new CFileInfoRecving();

pfileinfo->m_sFileName = FileName;

pfileinfo->m_sPeerName = PeerName;

pfileinfo->m_nBlockIndex = block_index;

pfileinfo->m_sFileSize.Format(“%d”,flen);

//Add To Recving File List

//pWnd->m_RecvingFileListCS.Lock();

pWnd->m_RecvingFileList.AddTail(pfileinfo);

//pWnd->m_RecvingFileListCS.Unlock();

}

Blockfile.Close();

pfileinfo->m_seek = fileseek;

pfileinfo->m_len = filelen;

}

if (fp) fclose(fp);

return pfileinfo;

}

数据接收线程 负责从客户端接收数据信息:

//数据接收线程

DWORDWINAPIServerthread(LPVOIDlpparam)

{

fileinfo* pFileInfo;

//

char* m_buf = new char[PROTOCOL_HEADSIZE];

SOCKET pthis=(SOCKET)lpparam;

int nRead = readn(pthis,m_buf,PROTOCOL_HEADSIZE);

if( nRead < 0 )

{

closesocket (pthis);

return -1;

}

pFileInfo = (fileinfo*)m_buf;

CString MsgStr;

int addrlen;

CString FileID;

sockaddr_in* pSocket;

CFileInfoRecving* pfileinfo;

switch(pFileInfo->type)

{

case 1:

//获取文件块的已接收大小

pfileinfo =

GetRecvedFileBlock(FileID,pFileInfo->name,pFileInfo->len,pFileInfo->seek,pFileInfo->fileno);

nRead = sendn(pthis,(char*)pFileInfo,PROTOCOL_HEADSIZE);

if(nRead < 0)

{

closesocket (pthis);

return -1;

}

//开始接收文件

RecvFile(pthis,FileID,pFileInfo->name,pFileInfo->seek,

pFileInfo->len,pFileInfo->fileno,pfileinfo);

closesocket(pthis);

break;

default:

break;

}

delete [] m_buf;

return 0;

}

DEMO下载地址:https://dwz.cn/fgXtRtnu

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值