Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1532834
  • 博文数量: 114
  • 博客积分: 10010
  • 博客等级: 上将
  • 技术积分: 1357
  • 用 户 组: 普通用户
  • 注册时间: 2006-11-19 18:13
文章分类
文章存档

2010年(8)

2009年(9)

2008年(27)

2007年(62)

2006年(8)

我的朋友

分类: C/C++

2008-01-08 16:27:38

北京理工大学  20981  陈罡
记得以前曾经写过关于断点续传的文章,只是举例了win32环境下c++实现的单线程断点续传的实现方法和代码。现在时间比较充裕了,就把symbian 2nd下面实现的断点续传代码拿出来晒晒,希望能起到抛砖引玉的效果,更加促进这个底层模块的稳定性和兼容性。
 
现在就把nettest这个symbian 2nd程序各个部分展开来分析一下:
nettest总体上来讲分为两部分组成,一个是常规的s60的ui相关的框架,一个是netcore支持断点续传的下载模块。s60的ui相关框架部分包括NetTestApp.cpp, NetTestAppUi.cpp, NetTestContainer.cpp, NetTestDocument.cpp这些文件都是sdk的向导自动生成的文件,无需多说;netcore部分从逻辑上来看,应该分为两个部分,一个是常规的基于RSocket的socket engine引擎部分,一个是M5HttpDown支持断点续传下载的模块。其中的socket engine只是从nokia sdk中的sockets demo中提取出来的代码,其本身基本上跟demo里面的socket引擎一致;另外的M5HttpDown的断点续传的代码则是实现断点续传的关键之所在了。
 
下面就是代码的内容和简要的分析:
M5HttpDown.h头文件的定义--->
 
#ifndef _M5_HTTP_DOWN_H_
#include
#include
#include "socketsengine.h" // 就是普通的sockets engine了
#include "uinotifier.h"    // uinotifier是用来在屏幕上输出一些状态的虚基类
#include "m5httpdownnotifier.h" // 收到真实的数据后进行回调的虚基类
 
#define HTTP_WEB_PORT            80
#define HTTP_TEMP_BUF_LEN        120
#define HTTP_SEND_BUF_LEN        256
#define HTTP_DOWN_CMWAP    0    // 定义接入点的类型,使用cmwap方式断点续传还是cmnet方式
#define HTTP_DOWN_CMNET    1
 
// 一些常量定义,用于解析服务器返回过来的数据头
_LIT8(KHttpRespOK,    "200 OK") ;
 
// 呵呵,这里就是移动恶心的地方了,这个Content-length通过10.0.0.172返回的竟然是小写的length
// 而通过cmnet返回的则是正常的大写的Length,这里我偷懒定义了两次,其实可以Upper()一下,统一
// 用一种方法来处理的。
_LIT8(KHttpCMWapHdrFileLen, "Content-length: ") ;
_LIT8(KHttpCMNetHdrFileLen, "Content-Length: ") ;
_LIT8(KHttpClip,    "/") ;
_LIT8(KHttpHdrDiv,     "\r\n") ;
_LIT8(KHttpHdrEnd,    "\r\n\r\n") ;
_LIT8(KHttpPrefix,    "http://") ;
_LIT8(KHttpsPrefix,    "https://") ;
 
// 这里的CommonGet,是指不经过断点续传直接下载,虽然支持断点续传,但是第一次下载的时候,
// 还什么文件都没有呢,不必要使用断点续传,直接按照常规的下载方式下载好了
_LIT8(KHttpCommonGet1, "GET ") ;
_LIT8(KHttpCommonGet2, " HTTP/1.1\r\nUser-Agent: Nokia 7610\r\nHost: ") ;
_LIT8(KHttpCommonGet3,  ":") ;
_LIT8(KHttpCommonGet4, "\r\nAccept: */*\r\nConnection: Keep-Alive\r\n\r\n") ;
 
// 这里才是真正的断点续传需要的定义了,需要指定RANGE的
_LIT8(KHttpResumeGet1,  "GET ") ;
_LIT8(KHttpResumeGet2,  " HTTP/1.1\r\nUser-Agent: Nokia 7610\r\nHost: ") ;
_LIT8(KHttpResumeGet3,  ":") ;
_LIT8(KHttpResumeGet4,  "\r\nAccept: */*\r\nRANGE: bytes=") ;
_LIT8(KHttpResumeGet5,  "-\r\nConnection: Keep-Alive\r\n\r\n") ;
 
class CM5HttpDown : public CBase, public MUINotifier {
protected:
    // socket data
    TInt               m_down_type ;  // 下载的类型,是cmnet还是cmwap
    CSocketsEngine *   m_sock_eng ;   // 常规的socket engine
    TBool              m_running ;    // 标志量用于标明下载是否开始
    TBool              m_is_first_resp ; // 由于移动对于cmwap有推送页面,这个标记就是用来
                                         // 检查第一次Get的结果是否为推送页面,hoho,
                                         // 如果是的话。。。屏蔽啦。。。
    TInt               m_web_port ;      // 端口了,默认是80端口
    TInt               m_total_bytes ;   // 这里就是保存数据包总共有多少字节
    TInt               m_recv_bytes;     // 这里保存了已经下载了多少字节,用于断点续传
 
// 下面是发送缓冲区定义以及一些地址字符串定义
 TBuf8 m_send_buf ;
 TBuf8 m_web_addr ;
 TBuf8 m_web_fname ;
 M5HttpDownNotifier&   m_m5_notifier ;   // 用于给调用者回调,收到数据
   
public: // 定义了ui notify的状态打印相关的函数
 void PrintNotify(const TDesC& aMessage, TUint aAttributes = 0) ;
 void RecvNotify(const TDesC8& aMessage) ;
 void ErrorNotify(const TDesC& aErrMessage, TInt aErrCode)  ;
 void SetStatus(const TDesC& aStatus) ;
 
protected:
 // 把字符串转换成整数的函数
 TInt  Str2Int(const TDesC8 & s) ;
 
 // 检查收到的数据是移动的推送页面呢,还是正常的数据
 TBool CheckRecv(const TDesC8& recv_buf) ;
 
    // 解析调用者传入的uri,便于把断点续传的头取出来,例如要下载的地址为:
    // ,那么就需要把这个uri分割为两部分:
    // 域名,用于往这台服务器上发送下载请求;/bbs/mp1.mp3文件名,用于构建
    // 断点续传的请求包的包头,所以这个函数也很重要喔!!
    TBool ParseUri(TDesC8&  uri, TDes8& web_addr, TDes8& web_fname, TInt& web_port) ;
 
    // 解析从服务器返回回来的结果,可以得到文件的大小,以及需要跳过的数据长度
    // PS:肯定是要把服务器返回的http头跳过去,给m5httpdownnotifier传入接收的数据了
    TBool ParseWebFileInfo(const TDesC8& recv_buf, TInt& total_length, TInt& jump_len) ;
 
    // 内部函数用于从http的头获取相应的字段
    TBool GetRespField(const TDesC8& recv_buf, TDesC8& field_name, TDesC8& end_flag, TDes8& res) ; 
    
    // 下面这些就是常规的RSocket的操作了,初始化,发送请求,关闭连接
    TBool InitSock(TDesC8& server_name, TInt server_port) ;
    TBool SendReq(TDesC8& req_str) ;
    TBool CloseSock() ;
 
private:
 CM5HttpDown(M5HttpDownNotifier & m5_notifier) ;
 void ConstructL() ;
public:
 // symbian标准的二段式构造,无需多言。
 ~CM5HttpDown() ;
 static CM5HttpDown * NewL(M5HttpDownNotifier& m5_notifier) ;
 static CM5HttpDown * NewLC(M5HttpDownNotifier& m5_notifier) ;
 
    // 这两个是在下载过程中外部回调函数得到下载进度用的,例如文件的总的大小,现在已经下载的大小
    TBool IsRunning() {return m_running ; }
    TInt  HttpTotalSize() { return m_total_bytes ; }
    TInt  HttpRecvSize() { return m_recv_bytes ; }
   
    // 这里就是用来指定接入点的下载类型的了,是采用cmwap呢还是cmnet
    // 正确的使用流程应该是先连接然后再下载
 TBool HttpConnPorxy(TDesC8& uri, TInt down_type = HTTP_DOWN_CMWAP) ;
 
    // 这个函数就是开始下载了
    TBool HttpDown(TDesC8& uri, TInt recv_bytes = 0) ;
 TBool HttpStopDown() ;
} ;
#endif
 
下面就是M5HttpDown.cpp的关键内容了:
#include "m5httpdown.h"
#include
#include
 
// 移动的代理网关ip地址定义
_LIT(KCMCCWapProxy, "10.0.0.172") ; 
 
CM5HttpDown::CM5HttpDown(M5HttpDownNotifier & m5_notifier):
m_m5_notifier(m5_notifier)
{
 m_sock_eng = NULL ;
}
 
CM5HttpDown::~CM5HttpDown()
{
 if(m_sock_eng->IsActive()) {
  m_sock_eng->Disconnect() ;
 }
 delete m_sock_eng ;
}
 
void CM5HttpDown::ConstructL()
{
 m_running = EFalse ;
 m_down_type = HTTP_DOWN_CMWAP ;
 m_is_first_resp = ETrue ;
 m_total_bytes = 0 ;
 m_recv_bytes = 0 ;
 m_web_port = HTTP_WEB_PORT ;
 m_web_addr.SetLength(0) ;
 m_web_fname.SetLength(0) ; 
 m_sock_eng = CSocketsEngine::NewL(*this) ;
}
 
CM5HttpDown * CM5HttpDown::NewL(M5HttpDownNotifier& m5_notifier)
{
 CM5HttpDown * self = CM5HttpDown::NewLC(m5_notifier);
 CleanupStack::Pop(self);
 return self;
}
 
CM5HttpDown * CM5HttpDown::NewLC(M5HttpDownNotifier& m5_notifier)
{
 CM5HttpDown * self = new (ELeave) CM5HttpDown(m5_notifier);
 CleanupStack::PushL(self);
 self->ConstructL();
 return self;
}
 
void CM5HttpDown::PrintNotify(const TDesC& aMessage, TUint aAttributes)
{
 m_m5_notifier.M5PrintNotify(aMessage) ;
}
 
// 呵呵,这里通过检查在收到的数据中是否含有要下载的web地址来确定是否是移动的推送页面
// 一般来说,移动的推送页面都有一个你的地址,再加上一个&t=xxxxx这样的uri,所以可以
// 利用这一点来做到判别是否是推送页面
TBool CM5HttpDown::CheckRecv(const TDesC8& recv_buf)
{
 TInt find_pos ;
 find_pos = recv_buf.Find(m_web_addr) ;
 if(find_pos != KErrNotFound) return EFalse ;
 return ETrue ;
}
 
void CM5HttpDown::RecvNotify(const TDesC8& aMessage)
{
 TInt recv_bytes = aMessage.Length() ;
 TInt jump_length = 0 ;
 
 if(recv_bytes > 0) {
 
  // 如果是第一次收到的话,就需要判断是否是推送页面
  if(m_is_first_resp) {
   if(CheckRecv(aMessage)) {
    // 如果不是推送页面,则把文件的总共大小读取出来
    if(ParseWebFileInfo(aMessage, m_total_bytes, jump_length)) {
 
     // 第一次接收数据,m_recv_bytes理论上应该等于0;如果不等于0则代表本次下载是
     // 断点续传,需要把这个m_recv_bytes已经下载的字节数加入到m_total_bytes里面去。
     if(m_recv_bytes > 0)
      m_total_bytes += m_recv_bytes ;
 
     // 肯定了,实际收到的字节数是需要跳过http头的,所以这里引入了jump_length
     m_recv_bytes += (recv_bytes - jump_length) ;
   
     // 一切准备妥当,然后调用M5RecvNotify函数来告知收到了数据
     m_m5_notifier.M5RecvNotify(aMessage.Mid(jump_length)) ;
    }
   
    // 既然第一次已经收到数据了,接下来的数据就是源源不断的到来了,就不必每次都跟第一次接收到
    // 数据还需要解析什么http这么麻烦了,直接收到,然后调用recv notify即可。
    m_is_first_resp = false ;
   } else {
    // 这里输出check failed的时候,意味着收到了移动的推送页面,需要重发一遍下载的request才行
    TBuf<20> s ;
    s.Format(_L("check failed !")) ;
    PrintNotify(s) ;
   }
  } else {
   // common receive procdure
   // 这里就是直接接收数据,然后存盘了
   if((m_recv_bytes + recv_bytes) > m_total_bytes) {
    recv_bytes = m_total_bytes - m_recv_bytes ;
    m_recv_bytes = m_total_bytes ;
   } else {
    m_recv_bytes += recv_bytes ;   
   }
   m_m5_notifier.M5RecvNotify(aMessage) ;
  }
 }
}
 
void CM5HttpDown::ErrorNotify(const TDesC& aErrMessage, TInt aErrCode)
{
 m_m5_notifier.M5PrintNotify(aErrMessage) ;
}
 
void CM5HttpDown::SetStatus(const TDesC& aStatus)
{
 m_m5_notifier.M5PrintNotify(aStatus) ;
}
 
TBool CM5HttpDown::SendReq(TDesC8& req_str)
{
  if(m_sock_eng->Connected()) {
    m_sock_eng->WriteL(req_str) ;
    return ETrue ;
  }
    return EFalse ;
}
 
// 这个函数看上去很让人恼火,没办法symbian的字符串描述符就是这个样子的。
TBool CM5HttpDown::ParseUri(TDesC8&  uri, TDes8& web_addr, TDes8& web_fname, TInt& web_port)
{
    TPtrC8 uri_ptr ;
    TBuf8<30> tmp_buf ;
    TInt  find_pos = 0 ;
    web_port = HTTP_WEB_PORT ;
    if(uri.Length() <= 0) return false ;
    // search and jump over the http or https prefix
    uri_ptr.Set(uri.Ptr(), uri.Length()) ;
    find_pos = uri.Find(KHttpPrefix) ;
   if(find_pos != KErrNotFound) {
      tmp_buf.Copy(KHttpPrefix) ;
      uri_ptr.Set(uri.Ptr()+tmp_buf.Length(), uri.Length() - tmp_buf.Length()) ;
   } else {
      find_pos = uri.Find(KHttpsPrefix) ;
      if(find_pos != KErrNotFound) {
          tmp_buf.Copy(KHttpsPrefix) ;   
          uri_ptr.Set(uri.Ptr()+tmp_buf.Length(), uri.Length() - tmp_buf.Length()) ;
      }
   }
   // get web address
   find_pos = uri_ptr.Find(KHttpClip) ;
   if(find_pos != KErrNotFound) {
      web_addr.Copy(uri_ptr.Mid(0, find_pos)) ;
   }
   // get web file name
   m_web_fname.Copy(uri_ptr.Ptr() + find_pos, uri_ptr.Length() - find_pos) ;
   return ETrue ;
}

TBool CM5HttpDown::GetRespField(const TDesC8& recv_buf, TDesC8& field_name, TDesC8& end_flag, TDes8& res)
{
 TPtrC8    ptr_hdr ;
 TInt   find_pos ;
 if(recv_buf.Length() <= 0) return EFalse ;
 find_pos = recv_buf.Find(field_name) ;
 if(find_pos != KErrNotFound) {
     ptr_hdr.Set(recv_buf.Ptr() + find_pos + field_name.Length(),
                 recv_buf.Length() - find_pos - field_name.Length()) ;
     find_pos = ptr_hdr.Find(end_flag) ;
     if(find_pos != KErrNotFound) {
        res.Copy(ptr_hdr.Ptr(), find_pos) ;
     }
     return ETrue ;
 }
  return EFalse ;
}
 
TBool CM5HttpDown::ParseWebFileInfo(const TDesC8& recv_buf, TInt& file_length, TInt& jump_len)
{
 TBuf8<30> tmp_field ;
 TBuf8<30> tmp_end ;
 TBuf8<30> tmp_str ;
 int       find_pos ;
 file_length = 0 ;
 jump_len = 0 ;
 // get web file length
 if(m_down_type == HTTP_DOWN_CMWAP) {
  tmp_field.Append(KHttpCMWapHdrFileLen) ;
 } else {
  tmp_field.Append(KHttpCMNetHdrFileLen) ;
 } 
 tmp_end.Append(KHttpHdrDiv) ;
 if(!GetRespField(recv_buf, tmp_field, tmp_end, tmp_str))
  return EFalse ;
  file_length = Str2Int(tmp_str) ;
 
 // set the jump length
 find_pos = recv_buf.Find(KHttpHdrEnd) ;
 if(find_pos != KErrNotFound) {
  tmp_str.Copy(KHttpHdrEnd) ;
  jump_len = find_pos + tmp_str.Length() ;
 }
    return ETrue ;
}
 
TBool CM5HttpDown::InitSock(TDesC8& server_name, TInt server_port)
{
 TBuf<50> svr_name ;
 svr_name.Copy(server_name) ;
 m_sock_eng->SetServerName(svr_name) ;
 m_sock_eng->SetPort(server_port) ;
 m_sock_eng->ConnectL() ;
    return ETrue ;
}
TBool CM5HttpDown::CloseSock()
{
 if(m_running && m_sock_eng->IsActive()) {
   m_sock_eng->Disconnect() ;
 }
    return true ;
}
 
TInt CM5HttpDown::Str2Int(const TDesC8 & s)
{
 TInt value = 0 ;
 TLex8 lex(s) ;
 lex.Val(value) ;
 return value ;
}
 
TBool CM5HttpDown::HttpConnPorxy(TDesC8& uri, TInt down_type)
{
 m_running = true ;
 m_down_type = down_type ;
 ParseUri(uri, m_web_addr, m_web_fname, m_web_port) ;
 if(m_down_type == HTTP_DOWN_CMWAP) {
    TBuf8<20> proxy_svr ;
    proxy_svr.Copy(KCMCCWapProxy) ;
    if(!InitSock(proxy_svr, 80)) return EFalse ; 
 } else {
    if(!InitSock(m_web_addr, m_web_port)) return EFalse ;
 }
 return ETrue ;
}
 
TBool CM5HttpDown::HttpDown(TDesC8& uri, TInt recv_bytes)
{
 TBuf8<20> tmp_str ;
  m_recv_bytes = recv_bytes ;
  m_send_buf.SetLength(0) ; 
  if(m_recv_bytes == 0) {
      m_send_buf.Append(KHttpCommonGet1) ;
      if(m_down_type == HTTP_DOWN_CMWAP)
          m_send_buf.Append(uri) ; 
      else 
          m_send_buf.Append(m_web_fname) ;
     
      m_send_buf.Append(KHttpCommonGet2) ;
      m_send_buf.Append(m_web_addr) ;
      m_send_buf.Append(KHttpCommonGet3) ;
      tmp_str.Format(_L8("%d"), m_web_port) ;
      m_send_buf.Append(tmp_str) ;
      m_send_buf.Append(KHttpCommonGet4) ;
  } else {
      m_send_buf.Append(KHttpResumeGet1) ;
     if(m_down_type == HTTP_DOWN_CMWAP)
         m_send_buf.Append(uri) ;
     else
         m_send_buf.Append(m_web_fname) ;
     
     m_send_buf.Append(KHttpResumeGet2) ;
     m_send_buf.Append(m_web_addr) ;
     m_send_buf.Append(KHttpResumeGet3) ;
     tmp_str.Format(_L8("%d"), m_web_port) ;
     m_send_buf.Append(tmp_str) ;
     m_send_buf.Append(KHttpResumeGet4) ;
     tmp_str.Format(_L8("%d"), m_recv_bytes) ;
     m_send_buf.Append(tmp_str) ;
     m_send_buf.Append(KHttpResumeGet5) ;
    }
    // send the request
    return SendReq(m_send_buf) ;
}
 
TBool CM5HttpDown::HttpStopDown()
{
    return CloseSock() ;
}

大概就是这个样子了,一些使用方面的注意事项请参阅以前的帖子吧。这个chinaunix似乎把代码排版都给弄乱了,我也没办法了,凑合者用吧。欢迎各位朋友多多提意见和建议,谢谢。
代码和sis文件包下载:
文件: NetTest.rar
大小: 64KB
下载: 下载
 
 
阅读(4245) | 评论(6) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2010-01-07 18:27:40

楼主的代码,在3版先修改后,下载的过程中会断开连接,8百多k的文件,下载到1百多k时,就会断开连接,请问下楼主怎么解决,期待中