北京理工大学 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 <e32std.h>
#include <e32base.h>
#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<HTTP_SEND_BUF_LEN> m_send_buf ;
TBuf8<HTTP_TEMP_BUF_LEN> m_web_addr ;
TBuf8<HTTP_TEMP_BUF_LEN> 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,便于把断点续传的头取出来,例如要下载的地址为:
// www.5mbox.com/bbs/mp1.mp3,那么就需要把这个uri分割为两部分:
// www.5mbox.com域名,用于往这台服务器上发送下载请求;/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 <e32std.h>
#include <f32file.h>
// 移动的代理网关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文件包下载:
![](http://img.e-com-net.com/image/info5/48deae3aaa594199b04187d1fbc4005e.jpg) |
文件: |
NetTest.rar |
大小: |
64KB |
下载: |
下载 |
|