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

2010年(8)

2009年(9)

2008年(27)

2007年(62)

2006年(8)

我的朋友

分类: 系统运维

2007-11-30 21:09:27

我们继续上一篇的内容,需要了解原理的朋友可以参照“断点续传(1)”所描述的部分。
 
(4)断点续传的实现
懒得一个个解释,直接上代码了(请注意,下面的代码是在win32上做验证用的,不是给手机用的),
采用c++编写,dev-c++ 4.9.9.2编译的,我相信g++都行vs系列自然不在话下:
 
M5HttpDown.h文件的内容:
#ifndef _M5_HTTP_DOWN_H_
#include
#include
#include
#include
#define HTTP_DEBUG_MODE 1
 
#define HTTP_WEB_PORT            80
#define HTTP_TEMP_BUF_LEN        120
#define HTTP_SEND_BUF_LEN        256
#define HTTP_RECV_BUF_LEN        4096
#define HTTP_HDR_OK              "200 OK"
#define HTTP_HDR_FILE_LEN        "Content-Length: "
#define HTTP_HDR_DIV             "\r\n"
#define HTTP_HDR_END             "\r\n\r\n"
#define HTTP_PREFIX              "http://"
#define HTTPS_PREFIX             "https://"
 
// 这里分成了两个get字符串的定义,主要是为了兼容普通的http下载
// 以及支持断点续传的http下载
#define HTTP_COMMON_GET "GET /%s HTTP/1.1\r\n\
User-Agent: Opera 8.0\r\n\
Host: %s:%d\r\nAccept: */\
*\r\nConnection: Keep-Alive\r\n\r\n"
 
#define HTTP_RESUME_GET "GET /%s HTTP/1.1\r\n\
User-Agent: Opera 8.0\r\n\
Host: %s:%d\r\nAccept: */\
*\r\nRANGE: bytes=%d-\r\n\
Connection: Keep-Alive\r\n\r\n"
 
// 这里为了方便起见,就没有用什么notifier或者虚拟函数之类的东西了,直接回调
// recv_buf:里面装着二进制数据,就是要下载的文件中的数据部分
// recv_len:数据的长度
// data:既然是回调函数,就需要允许caller把相关的数据结构也带进来。
typedef void (*RECV_CALLBACK)(char * recv_buf, int recv_len, void * data) ;

class CM5HttpDown {
protected:
    // socket data
    SOCKET        m_sock ;    
    bool          m_running ;  // 标志是否运行
    bool          m_is_first_resp ;  // 第一次收到数据的标志,用于跳过服务器的http头
    char *        m_web_addr ; // 存放从uri中解析出来的网址
    char *        m_web_fname ; // 存放uri中的文件名
    int           m_web_port ;  // uri中的服务器端口好,缺省值是80
    char *        m_recv_buf ;  // 接收缓冲区
    int           m_total_bytes ; // uri中文件的总大小,单位字节
    int           m_recv_bytes;   // 已经接收了多少字节,用于断点续传中接着传
   
    // custom defined receive handler 
    RECV_CALLBACK m_custom_callback ; // 回调函数指针
    void *        m_custom_data ; // call的自定义数据结构指针
public:
    // common receive thread func
    static void recv_thread(void * data) ; // 线程函数,必须是静态的
    void recv_thread_handler() ; // 在线程函数中调用,是实际上的接收函数
   
protected:
    // uri解析函数,能够把诸如的uri分解为
    // web_addr :
    // web_fname : bbs/mp1.mp3
    bool parse_uri(char * uri,
                   char * web_addr, char * web_fname, int * web_port) ; 
    
    // 第一次收到http回应的时候,解析出来文件的大小(toal_length),以及需要跳过的长度
    // (jumplen),这样就可以只把有用数据给call传过去了,而无用的http头就丢弃了。
    bool parse_webfile_info(char * recv_buf,
                            int * total_length, int * jump_len) ; 
    
    // 用于把指定的field的值字符串从http头中读取出来,例如回应http的头为:
    // 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-Length: "的数据"106786028"给取出来
    bool get_resp_field(char * recv_buf,
                        char * field_name, char * end_flag, char * res) ; 
    
    // socket的常规操作函数
    bool init_sock(char * server_name, int server_port) ;
    bool send_req(char * req_str, int req_len) ;
    bool close_sock() ;
public:
    CM5HttpDown() ;
    ~CM5HttpDown() ;
   
    bool is_running() {return m_running ; }
    int  http_total_size() { return m_total_bytes ; }
    int  http_recv_size() { return m_recv_bytes ; }
 
    // 这个就是主要的http下载函数了,使用的时候直接调用这个函数就行了。
    // uri : 要下载的web文件地址,例如
    // custom_func : 回调函数,收到二进制数据的时候,会自动调用该函数
    // custom_data : call的自定义数据类型
    // recv_bytes : 已经接收了多少个字节的数据,用于断点续传。如果该数值为0
    // 则采用普通的get方法下载;如果不为0,则采用断点续传接着下载
    bool http_down(char * uri,
                   RECV_CALLBACK custom_func, void * custom_data,
                   int recv_bytes = 0) ;
 
    // 下载过程中强制关闭下载用的
    bool http_stop() ;
} ;
 
#endif
 
M5HttpDown.cpp的文件内容:(作家侯杰曾经说过,源码之下了无秘密,不需要我解释什么了吧。。。)
#include "M5HttpDown.h"
#include
CM5HttpDown::CM5HttpDown()
{
    m_sock = (SOCKET)(NULL) ;
    m_running = false ;
    m_is_first_resp = true ;
    m_web_addr = NULL ;
    m_web_fname = NULL ;
    m_custom_callback = NULL ;
    m_custom_data = NULL ;
    m_total_bytes = 0 ;
    m_recv_bytes = 0 ;
    m_recv_buf = new char [HTTP_RECV_BUF_LEN] ;
    memset(m_recv_buf, 0, HTTP_RECV_BUF_LEN) ; 
}
CM5HttpDown::~CM5HttpDown()
{
    if(m_recv_buf) delete [] m_recv_buf ;
    if(m_web_addr) delete [] m_web_addr ;
    if(m_web_fname) delete [] m_web_fname ;
}
void CM5HttpDown::recv_thread(void * data)
{
    CM5HttpDown * http_down_ptr = static_cast(data) ;
    http_down_ptr->recv_thread_handler() ; 
    _endthread() ;    
}
void CM5HttpDown::recv_thread_handler()
{
    fd_set recv_fd ;
    struct timeval tmv ;
    int    recv_bytes ;
    int    jump_length ;
   
    while(m_running) {
        FD_ZERO(&recv_fd) ;
        FD_CLR(m_sock, &recv_fd) ;
        FD_SET(m_sock, &recv_fd) ;
        tmv.tv_sec = 1 ;
        tmv.tv_usec = 0 ;
        if(select(m_sock+1, &recv_fd, NULL, NULL, &tmv) < 0) {
#ifdef HTTP_DEBUG_MODE
            printf("select recv failed !\n") ;
            fflush(stdout) ;
#endif
            return ;
        }
       
        if(FD_ISSET(m_sock, &recv_fd)) {
            // time to read
            recv_bytes = 0 ;
            jump_length = 0 ;
            memset(m_recv_buf, 0, HTTP_RECV_BUF_LEN) ;
            recv_bytes = recv(m_sock, m_recv_buf, HTTP_RECV_BUF_LEN, 0) ;
            if(recv_bytes > 0) {
                if(m_is_first_resp) {
                    if(parse_webfile_info(m_recv_buf, &m_total_bytes, &jump_length)) {
                        // 这里比较乱,意思是:如果是断点续传的话,第一次收到response
                        // 的时候,m_recv_bytes就有数据,此时整个文件的大小应该是
                        // 服务器返回的content-length长度加上已经接收过了的数据长度
                        if(m_recv_bytes > 0) m_total_bytes += m_recv_bytes ;
#ifdef HTTP_DEBUG_MODE
                        printf("file length : %d\n", m_total_bytes) ;
#endif
                        m_recv_bytes += (recv_bytes - jump_length) ; 
                        (*m_custom_callback)(m_recv_buf + jump_length,
                                             recv_bytes - jump_length,
                                             m_custom_data) ;
                    }
                    m_is_first_resp = false ;
                    continue ;
                } 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_custom_callback)(m_recv_buf, recv_bytes, m_custom_data) ;
                }
            } else if(recv_bytes == 0) {
                // conn down
#ifdef HTTP_DEBUG_MODE
                printf("disconn...\n") ;
#endif
                m_running = false ;
            }
        }
    }
}
bool CM5HttpDown::send_req(char * req_str, int req_len)
{
    fd_set send_fd ;
    struct timeval tmv ;
    int    send_bytes ;
    if(!m_sock || req_len <= 0 || req_str == NULL) return false ;
   
    FD_ZERO(&send_fd) ;
    FD_CLR(m_sock, &send_fd) ;
    FD_SET(m_sock, &send_fd) ;
    tmv.tv_sec = 1 ;
    tmv.tv_usec = 0 ;
    if(select(m_sock+1, NULL, &send_fd, NULL, &tmv) < 0) {
#ifdef HTTP_DEBUG_MODE
        printf("select send failed !\n") ;
        fflush(stdout) ;
#endif
        return false ;
    }
   
    if(FD_ISSET(m_sock, &send_fd)) {
        send_bytes = send(m_sock, req_str, req_len, 0) ;
        if(req_len != send_bytes) return false ;
        return true ;
    }
    return false  ;
}
bool CM5HttpDown::parse_uri(char * uri, char * web_addr,
                            char * web_fname, int * web_port)
{
    char * ptr_a = NULL ;
    char * ptr_b = NULL ;
   
    *web_port = HTTP_WEB_PORT ;
    if(!uri) return false ;
    // search for http or https prefix
    ptr_a = uri ;
    if(!strncmp(ptr_a, HTTP_PREFIX, strlen(HTTP_PREFIX)))
        ptr_a = uri + strlen(HTTP_PREFIX) ;
    else if(!strncmp(ptr_a, HTTPS_PREFIX, strlen(HTTPS_PREFIX))) 
        ptr_a = uri + strlen(HTTPS_PREFIX) ;
    // get web_addr without "http://" or "https://" prefix
    ptr_b = strchr(ptr_a, '/');
    if(ptr_b) {
        memcpy(web_addr, ptr_a, strlen(ptr_a) - strlen(ptr_b));
        if(ptr_b + 1)  {
            // get web file name
            memcpy(web_fname, ptr_b + 1, strlen(ptr_b) - 1);
            web_fname[strlen(ptr_b) - 1] = '\0' ;
        }
    } else  memcpy(web_addr, ptr_a, strlen(ptr_a)) ;
    if(ptr_b)  web_addr[strlen(ptr_a) - strlen(ptr_b)] = '\0' ;
    else  web_addr[strlen(ptr_a)] = '\0' ;
    // search for uri port number
    ptr_a = strchr(web_addr, ':') ;
    if(ptr_a)  *web_port = atoi(ptr_a + 1);
    else *web_port = HTTP_WEB_PORT ;
    return true ;
}
bool CM5HttpDown::get_resp_field(char * recv_buf, char * field_name, char * end_flag, char * res)
{
  char * start_ptr = NULL ;
  char * end_ptr = NULL ;
 
  start_ptr = strstr(recv_buf, field_name) ;
  if(start_ptr == NULL) return false ;
 
  start_ptr += strlen(field_name) ;
  end_ptr = strstr(start_ptr, end_flag) ;
 
  if(end_ptr == NULL) return false ;
  memcpy(res, start_ptr, end_ptr - start_ptr) ;
  res[end_ptr - start_ptr] = '\0' ;
  return true ;
}
bool CM5HttpDown::parse_webfile_info(char * recv_buf, int * file_length, int * jump_len)
{
    char   tmp_str[50] ;
    char * offset_str = NULL ;
 
#ifdef HTTP_DEBUG_MODE
    printf("%s\n", recv_buf) ;
#endif
    // get file length
    if(!get_resp_field(recv_buf, HTTP_HDR_FILE_LEN, HTTP_HDR_DIV, tmp_str))
        return false ;
   
    *file_length = atoi(tmp_str) ;
 
    // get current offset
    offset_str = strstr(recv_buf, HTTP_HDR_END) ;
    if(offset_str == NULL) return false ;
    *jump_len = (int)(offset_str + strlen(HTTP_HDR_END) - recv_buf) ;
    return true ;
}
bool CM5HttpDown::init_sock(char * server_name, int server_port)
{
    struct sockaddr_in sock_in ;
    struct hostent * he ;
    {
        // only worked in dos
        WSADATA wsadata ;
        if (WSAStartup(0x0202, &wsadata) != 0) return false ;
    }
   
    // get server ip address
    he = gethostbyname(server_name) ;
    if (!he) sock_in.sin_addr.s_addr = inet_addr(server_name) ;
    else {
        sock_in.sin_addr.s_addr = *(unsigned long *)(he->h_addr_list[0]) ;
#ifdef HTTP_DEBUG_MODE
        printf("ip : %s\n", inet_ntoa(sock_in.sin_addr)) ;
#endif
    }
   
    sock_in.sin_family = AF_INET;
    sock_in.sin_port = htons(server_port);
    m_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) ;
   
    if(!connect(m_sock,(struct sockaddr *)(&sock_in), sizeof(sock_in))) {
       HANDLE thread_handle ;
       m_running = true ;
       thread_handle = (HANDLE)(_beginthread(CM5HttpDown::recv_thread, 0,(void *)(this))) ;
       return true ;
    }
    return false ;
}
bool CM5HttpDown::close_sock()
{
    if(m_running) {
        m_running = false ;
        Sleep(1000) ;
        if(m_sock) closesocket(m_sock) ;
    }
    {
        // only worked in dos
        WSACleanup() ;
    }
    return true ;
}
bool CM5HttpDown::http_down(char * uri,
                            RECV_CALLBACK custom_func, void * custom_data,
                            int recv_bytes)
{
    char buffer[HTTP_SEND_BUF_LEN] ;    
    memset(buffer, 0, HTTP_TEMP_BUF_LEN) ;
   
    if(uri == NULL) return false ; 
    m_recv_bytes = recv_bytes ;
    m_custom_callback = custom_func ;
    m_custom_data = custom_data ;
   
    m_web_addr = new char [HTTP_TEMP_BUF_LEN] ;
    m_web_fname = new char [HTTP_TEMP_BUF_LEN] ;
    
    memset(m_web_addr, 0, HTTP_TEMP_BUF_LEN) ;
    memset(m_web_fname, 0, HTTP_TEMP_BUF_LEN) ;
   
    parse_uri(uri, m_web_addr, m_web_fname, &m_web_port) ;
    if(m_recv_bytes == 0) {
        snprintf(buffer, HTTP_SEND_BUF_LEN, HTTP_COMMON_GET,
                 m_web_fname, m_web_addr, m_web_port) ;
    } else {
        snprintf(buffer, HTTP_SEND_BUF_LEN, HTTP_RESUME_GET,
                 m_web_fname, m_web_addr, m_web_port, m_recv_bytes) ;
    }
#ifdef HTTP_DEBUG_MODE
    printf("%s\n", buffer) ;
#endif
    m_running = true ;
    if(!init_sock(m_web_addr, m_web_port)) return false ;
   
    // send the request
    return send_req(buffer, strlen(buffer)) ;
}
bool CM5HttpDown::http_stop()
{
    return close_sock() ;
}
 
贴上整个dev-c++的工程,感兴趣的朋友直接下了玩玩。
文件: nettest.rar
大小: 15KB
下载: 下载
运行nettest.exe,就会在其当前目录生成一个叫test.mp3的文件,屏幕上还显示下载进度。
可以随时关闭,然后再次打开,看看断点续传的效果。
下面的这个是将上述代码,通过RSocket移植到symbian s60 2nd平台上的测试程序(我做了一些修改
使之可以同时支持cmwap和cmnet的gprs环境,再此鄙视一下移动的行为),
出于公司的利益考虑,就不开放代码了。
文件: NetTestSIS.rar
大小: 9KB
下载: 下载
当调用cmwap conn的时候,接入点要选择"移动梦网"或者"nokia.com";
当调用cmnet conn的时候,接入点要选择"gprs连接互联网";
看到屏幕上显示"connected"的时候,选择"resume",就会开始断点续传过程。
对于cmwap由于移动有推送页面,在程序里面加入了效验,如果有推送页面,
程序会显示"check failed",这时,再按一次"resume"即可。
更换连网方式之前,需要选择"stop",断开gprs连接,然后再连。
还有一个小小的意外是对于移动的网关来说,Content-Length这是标准http服务器返回的;
但是移动的代理返回的结果是Content-length这个"l"是小写的,这个细节一定要注意才行喔!!
 
呵呵,期待5mbox网络版早日成功。
 
阅读(3015) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~