断点续传的原理

断点续传是大型文件数据传输的核心。本文将以多线程技术和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 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/39122.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

wget 断点续传

wget是Linux系统中的一个下载工具&#xff0c;最近因为要下载一个比较大的数据集&#xff0c;直接使用浏览器下载每次断开后都会从头开始下载&#xff0c;而wget支持的断点续传功能则可以在已经下载的部分数据的基础上继续下载剩余数据。 &#xff08;1&#xff09;wget安装 …

如何实现文件断点续传功能

本文章首发于公众号攻城狮客栈&#xff0c;有需要的朋友可文末扫描二维码。 相信大家都使用过迅雷、电驴、百度云网盘等一类的工具&#xff0c;所有这些支持上传或下载的工具都有一个功能&#xff0c;那就是断点续传&#xff0c;也就是在你网络不佳传输断开时&#xff0c;传输会…

python 断点续传

python 断点续传 1.前序2.技术原理2.1 Content-Range2.2 Range 3. 代码实现 1.前序 当下载突然断开后&#xff0c;断点续传就需要了&#xff0c;继续前面下载的内容下载。解决了不需要重复下载 2.技术原理 HTTP/1.1 开始支持断点续传&#xff0c;一般断点下载会用到 Range 和…

web前端Tips:断点续传如何实现?

在Web前端中实现断点续传功能的一种常见方式是使用HTTP Range请求和文件分片上传。 以下是一个简单的断点续传实现的步骤&#xff1a; 前端将要上传的文件分成多个固定大小的片段&#xff08;chunk&#xff09;&#xff0c;例如每个片段的大小为1MB。当用户选择上传文件时&am…

前段实现文件的断点续传

早就听说过断点续传这种东西&#xff0c;前端也可以实现一下 断点续传在前端的实现主要依赖着HTML5的新特性&#xff0c;所以一般来说在老旧浏览器上支持度是不高的 本文通过断点续传的简单例子&#xff08;前端文件提交后端PHP文件接收&#xff09;&#xff0c;理解其大致的实…

Android开发——断点续传原理以及实现

0. 前言 在Android开发中&#xff0c;断点续传听起来挺容易&#xff0c;在下载一个文件时点击暂停任务暂停&#xff0c;点击开始会继续下载文件。但是真正实现起来知识点还是蛮多的&#xff0c;因此今天有时间实现了一下&#xff0c;并进行记录。本文原创&#xff0c;转载请注…

如何实现断点续传

断点续传是指在网络传输中&#xff0c;当传输过程中出现异常或者用户主动停止传输时&#xff0c;能够恢复传输过程&#xff0c;避免重新传输已经传输过的数据&#xff0c;提高传输效率。实现断点续传可以通过以下方式&#xff1a; HTTP协议支持的断点续传 在HTTP协议中&#xf…

【MQ学习笔记】RocketMQ知识分析与总结

RocketMQ 为什么使用mq&#xff1f;mq的作用&#xff1f;mq对你项目带来了什么&#xff1f;不选mq行不行&#xff1f; 异步&#xff0c;MQ能够以异步的方式对消息进行处理&#xff0c;能够大大提高了系统的响应以及吞吐量解耦&#xff0c;MQ双方只需要负责生产或消费信息即可…

销售数据分析方法、如何写好一个专题分析报告、Hive大数据知识体系教程、大数据分析平台总体架构方案……| 本周精华...

▲点击上方卡片关注我&#xff0c;回复“8”&#xff0c;加入数据分析领地&#xff0c;一起学习数据分析&#xff0c;持续更新数据分析学习路径相关资料~&#xff08;精彩数据观点、学习资料、数据课程分享、读书会、分享会等你一起来乘风破浪~&#xff09;回复“小飞象”&…

老杨刷完了23个跨年演讲,这6场最适合网工

晚上好&#xff0c;我是老杨。 放假放了一阵子&#xff0c;老杨闲着没事儿&#xff0c;一共刷了23个跨年演讲。 好的演讲&#xff0c;和一本好书一样&#xff0c;都是可以震荡灵魂的。 也知道最近大家都忙得很&#xff0c;这里推荐7个最值得一听的跨年演讲&#xff0c;贴心吧…

朱广权李佳琦直播掉线,1.2 亿人在线等

作者 | 胡巍巍 出品 | 程序人生&#xff08;ID&#xff1a;coder_life&#xff09; 原来央视爸爸也有掉线的时候。 4月6日晚间&#xff0c;“国民段子手”朱广权连麦李佳琦&#xff0c;给湖北做公益带货直播。 “小朱配琦”的神仙组合&#xff0c;让人大呼过瘾&#xff0c;该直…

“我让 AI 来处理我 24 小时的音频信息——这会是未来吗?”

整理 | 郑丽媛 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 这几年 AI 发展和进化的速度&#xff0c;几乎可以用三个字来形容&#xff1a;杀疯了。 AI 下棋、AI 编程、AI 作画、AI 写小说、AI 预测蛋白质结构……当 AI 逐渐融入我们的生活&#xff0c;你是否设…

抗住百万人直播、被联合国推荐,起底飞书技术演进之路!

你去公司上班了吗&#xff1f; 随着近期接连不断传来的好消息&#xff0c;上班族开始关心起这问题来。但许多企业在相关政策的号召下&#xff0c;仍采取远程办公的方式。据艾媒咨询的数据显示&#xff0c;今年春节期间中国远程办公企业规模超过 1800 万家&#xff0c;远程办公人…

听8位淘宝工程师聊聊他们眼中的元宇宙 | 1024特辑

我们是技术工作者&#xff0c;致力于思考和创新&#xff0c;用代码去解决生活中的问题&#xff0c;为消费者的快乐和幸福而努力。 今天是1024程序员节&#xff0c;我们邀请了8位不同岗位的淘宝工程师&#xff0c;聊了聊他们眼中的未来消费生活&#xff0c;他们眼中的元宇宙&…

数字人的新革命,BAT的“冲高”战场

配图来自Canva可画 ChatGPT横空出世&#xff0c;让人们看到了数字人的另一种可能&#xff0c;将ChatGPT与虚拟数字人融合&#xff0c;研发出更加智能化、拟人化的虚拟数字人成为数字人厂商的新命题、新方向。 2月份&#xff0c;岭南股份、风语筑、开普云等10多家公司&#xf…

数字化直播沟通

目录 前言 一、介绍数字化&#xff08;5-10分钟&#xff09; 主播提问&#xff08;王&#xff09;&#xff1a; 回答&#xff08;向&#xff09;&#xff1a; 什么是数字化&#xff1f; 好的&#xff0c;我们来看下一个问题&#xff1a;什么是数字化转型&#xff1f; 好的…

部署React项目到云服务器(步骤清晰)

部署React项目到云服务器 前言(下载相关软件)开始部署服务器以及域名购买实例远程连接 或 XShell 7 远程连接服务器XShell 7 连接云服务器操作使用FileZilla将 打包后的 build(可能你们那里打包是dist)文件塞到服务器文件夹感谢看到这里&#xff01;点个关注再走啊&#xff01;…

云服务安装配置git

搭建网站&#xff0c;我们有时候是直接从github拉取代码&#xff0c;这就要配置git。 云服务器搭建网站还需要一些其他的基础配置&#xff0c;前面我们已经讲到&#xff0c;未进行配置的可以参考之前的文章。 云服务基本配置&#xff1a;腾讯云服务器小白保姆级教程_大鹏bmfm…

【回答问题】ChatGPT上线了!写出coco数据集的图像分类训练和预测函数代码?

写出coco数据集的图像分类训练代码 好的,我会尽力写出图像分类训练代码。首先,你需要准备好 COCO 数据集的图像和标签,并将其转换成适合你模型使用的格式。其次,你需要选择一个图像分类模型,并定义好模型的结构。接下来,你可以使用 PyTorch 等深度学习框架来实现训练代码…

服务器访问次数限制实现的思路

有个每分钟只能访问10次接口的需求&#xff0c;想到2个实现方法分享给大家&#xff0c;希望可以一起进步&#xff0c;一开始的思路是记录下用户访问的时间和次数&#xff0c;然后在写一些方法实现。最先想到是记录在数据库里&#xff0c;刚好数据库有记录访问日志的表&#xff…