2024年3月14日发(作者:)

10 1

毕业设计

(论文)

题目基于HTTP协议的多线程下载和

断点续传的实现

学生姓名 学号

专业 计算机科学与技术 班级

指导教师

评阅教师

年 完成日期 月 日

10 1

学位论文原创性声明

本人郑重声明:所呈交的论文是本人在导师的指导下独立进行研究所取得的研究成

果。除了文中特别加以标注引用的内容外,本论文不包含任何其他个人或集体已经发表

或撰写的成果作品。本人完全意识到本声明的法律后果由本人承担。

作者签名: 年 月 日

学位论文版权使用授权书

本学位论文作者完全了解学校有关保障、使用学位论文的规定,同意学校保留并向

有关学位论文管理部门或机构送交论文的复印件和电子版,允许论文被查阅和借阅。本

人授权省级优秀学士学位论文评选机构将本学位论文的全部或部分内容编入有关数据

库进行检索,可以采用影印、缩印或扫描等复制手段保存和汇编本学位论文。

本学位论文属于

1、保密 □,在_________年解密后适用本授权书。

2、不保密 □。

(请在以上相应方框内打“√”)

作者签名: 年 月 日

导师签名: 年 月 日

10 1

目 录

摘要

........................................................................................................................................... 1

前言

........................................................................................................................................... 2

1 HTTP协议

............................................................................................................................ 3

1.1

HTTP协议的发展 .................................................................................................... 3

1.2

HTTP协议的特点 .................................................................................................... 4

1.3

HTTP会话及报文格式 ............................................................................................ 6

2 Windows套接字

................................................................................................................ 8

2.1 什么是套接字 ............................................................................................................. 8

2.2 套接字规范 ................................................................................................................. 9

2.3 Windows套接字的发展 ............................................................................................ 11

2.4 套接字的使用和WinSock API ................................................................................ 12

3 多线程及断点续传技术

............................................................................................... 15

3.1 多线程的优点 ........................................................................................................... 18

3.2 多线程之间的互斥和同步 ....................................................................................... 16

3.3 什么是断点续传技术 ............................................................................................... 17

4 下载工具的设计与实现

............................................................................................... 18

4.1 基本结构与数据流程图 ........................................................................................... 18

4.2 程序基本功能设计与实现 ....................................................................................... 19

4.3 代码分析 ................................................................................................................... 24

4.4主要功能实现算法 .................................................................................................... 28

5 总结

.................................................................................................................................... 35

致谢

......................................................................................................................................... 36

参考文献

................................................................................................................................ 37

附录

......................................................................................................................................... 38

10 1

基于HTTP协议的多线程下载和断点续传的实现

学 生:叶升路

指导教师:覃 颖

(三峡大学 电气信息学院)

摘 要:本文介绍了网络下载软件中的最新技术——多线程下载和断点续传技术,同时

也介绍了HTTP协议的发展、特点以及WinSock编程技术。最后在这些技术的基础上成

功设计并实现了基于HTTP协议的具有多线程下载和断点续传功能的下载软件。本软件

的实现代码未使用任何WinInet API函数如InternetOpen , InternetConnect等,而是直接

使用WinSock编程,逐步解析HTTP协议来完成会话和文件下载等功能。经测试,下载

速度有所提高。

关键词:下载;多线程;断点续传;HTTP;WinSock;

Abstract:This paper introduces the latest downloading technology called multi-threaded

downloading and resume in network downloading software. But also descripes the

development of HTTP protocol, characteristics and WinSock programming. Finally, based on

these technologies successfully designed and implemented a downloading tool based on the

HTTP protocol with multi-threaded and resume features. The realization of the software code

does not use any WinInet API functions such as InternetOpen, InternetConnect, etc., but

directly use WinSock to programming, and complete the functions of conversation and file

downloads and others by parse HTTP protocol steply. After tested, the speed of downloading

has increased.

Keywords: Downloading;Multi-thread;Resume;HTTP;WinSock;

前言

最近几年,随着计算机网络的飞速发展,因特网(Internet)已经逐渐成为人们生活、

工作、学习必不可缺的一部分。因特网上存储了大量丰富的信息资源,我们可以使用下载

工具,把需要的信息资源下载到本地。但是由于受到各种因素的限制,例如服务器性能、

网络带宽、下载的信息量以及下载工具等等,下载速度受到不同程度的影响。因此人们不

断地提高服务器性能,扩展网络带宽,开发效率更高的下载工具以达到最大化提高下载

速度的目的。

在限制下载速度的众多因素中,研究新的网络下载技术开发出更高效的下载工具无

疑是其中最节约,环保以及方便的方式。网络下载技术,也可以称为网络文件共享技术,

10 1

它一直是网络发展的重要推动力之一。早期人们共享资源的普遍方法是将资源文件上传

至服务器上,然后其他用户可以通过HTTP或FTP等协议将其下载到本地电脑。这种模

式称为客户机/服务器模式即C/S模式,它对服务器的依赖性很大,当下载用户很少时,

比如说一个,他将独享服务器的带宽,很显然其下载速度会非常快。然而当下载的人数

较多而服务器带宽有限时,比如服务器带宽为3MB/S,而下载人数为100人,则众多下

载用户不得不共享一个带宽(3MB/S)最终结果是下载速度均分(30KB/S),普遍不高。

P2P技术的出现使得人们终于摆脱了服务器的枷锁。它的主要特点是资源分散、负

载均衡、和非中心化,它将共享的文件存储在各个客户机节点上,用户之间可以直接共

享和传输文件而不需要通过服务器。客户机不再只利用服务器带宽进行下载,它同时也

可以利用其他客户机节点的带宽,这样大大提高了下载速度。

纵观网络下载技术发展的历史,可以将其划分为四个阶段:单线程下载阶段、多线

程下载及断点续传阶段、P2P阶段、P2SP阶段。

一、单线下载时代:应对有限时间流量的办法

早在上个世纪90年代,当时互联网并不普及,很多人使用Modem拨号,通过Telnet

软件连接到拨接式BBS上获取资讯并与别人交流(收发邮件等),由于服务器的电话线

路数量有限,因此都会限制连接时间,一般新注册用户只有10分钟左右。这点时间用来

看帖回帖显然不够的,因此有人就开发了软件,进入BBS后,能够将整个BBS上所有内

容都下载回来,然后可以断线慢慢看慢慢回,最后再次拨入BBS上传回复。

二、断点续传与多线程下载时代:大幅度提高速度

进入Windows与WWW(World Wide Web,互联网)时代之后,IE,Netscape等浏览

器都可以通过点击左键下载,那个时候网络速度最快不过5KB/s,下载一首5MB的MP3

歌曲要15分钟以上!中途万一断线就前功尽弃,于是有人开发了支持断点续传的下载软

件。

世界上第一款支持断点续传的下载软件应该是GetRight。它可让你用浏览器下载文

件时有续传功能,可设定时间来下载文件或是中断Modem拨接,下载完毕时自动中断

Modem拨接或关机。

为了更好的利用带宽,在断点续传的基础上,多线程下载软件逐渐发展了起来。最

早出现的多线程下载软件是中国人开发的NetAnts(网络蚂蚁)。网络蚂蚁其实也是一

个断点续传软件,但它对断点续传功能进行了扩展:可进行多点续传,即利用断点续传

的原理同时建立多个连接下载同一个软件并最终将其合并为一个完整的软件。

三、P2P时代:下载再也不怕人多挤破服务器

最早的P2P网络当属1979年的FidoNet(惠多网)和1984年的Usenet,经过不断发展,

10 1

才有了现在人们常用的“BT”,“电驴”等P2P软件。这类软件应用了P2P(Peer-to-Peer)技

术,能够最大限度地利用网络带宽。如今,BitComet、BitTorrent、eMule等P2P软件已经

拥有极为庞大的用户群,每个人既是下载者也是上传者,一个新发布的文件转瞬之间就

会像燎原之火一样遍布全世界,这是任何服务器都无法比拟的。

四、P2SP时代:多技术结合,进一步提高速度

现在人们在整合了HTTP和FTP的服务器技术之后,对BT下载也进行了改进,独创

了P2SP技术P2SP(Peer to Server&Peer)即点对服务器和点对点。P2SP除了包含P2P,

还多了一个“S”是指服务器。P2SP有效地把原本孤立的服务器和其镜像资源以及P2P资源

整合到了一起。在下载的稳定性和下载的速度上,都比传统单一的P2P有了非常大的提

高。

1 HTTP协议

1.1 HTTP协议的发展

万维网WWW(World Wide Web) 之父蒂姆•贝纳斯•李早在1990年就提出了超文本传

输协议HTTP(Hyper Text Transport Protocol),HTTP是WWW的基本协议,它是一个

应用层的,面向对象的协议。WWW 联盟成立后,组织了 IETE ( Internet Engineering

Task Force)小组进一步完善和发布HTTP 协议。至今,HTTP协议经历了0.9、1.0、1.1

三个阶段。各阶段特征如下:

1)HTTP/0.9特征:①适用于各种数据信息的简洁快速协议;②具有典型的无状态

性;③无连接性;④无法使用内容协商;⑤无法显示和处理图片。

2)HTTP/1.0特征:①简单快速;②无状态性;③无连接性;④无法使用内容协商;

⑤增加了元信息:在主要数据前加上一块信息,即信息的信息。它使服务器能够提供传

送数据的有关信息。例如, 传送对象是哪种类型, 是用哪种语言书写的等等;⑥支持

多种内容的形式,如图片、音频等。

3)HTTP /1.1特征:①持续性连接:允许请求一个 web页面的浏览器发起一次连接

就可从服务器上下载多个文件;②仍无状态性,但可提供状态控制;③新增加了资源请

求:在原有GET、HEAD 、POST 几种方法的基础上增加了OPTIONS、 PUT 、DELETE

和TRACE;④身份认证:一种简单的“提问-回答”式的基本访问授权方法。过程是先由

服务器向客户发出身份鉴别请求,再由客户发出确认信息;⑤使用内容协商机制;⑥缓

存 (Cache) 机制:将先前的客户请求以及请求所对应的Web 服务器响应的内容暂时保

存在机器的内存或物理存储器中,使得在处理新的客户请求时可以利用。

目前新一代的HTTP协议:HTTP-NG也已经处于研究阶段,它将很有可能取代现有

10 1

的HTTP,它的最大变化是是客户机可以一次连续发送多个请求,服务器依次响应每个

请求。方法可进一步缩短服务器的响应时间,提供更加高效优质的服务。

1.2 HTTP协议的特点

HTTP是一个客户端和服务器端请求和应答的标准(TCP)。客户端是终端用户,

服务器端是网站。通过使用Web浏览器、网络爬虫或者其它的工具,客户端发起

一个到服务器上指定端口(默认端口为80)的HTTP请求。我们称这个客户端叫

用户代理(user agent)。应答的服务器上存储着一些资源,比如HTML文件和图像。

我们称这个应答服务器为源服务器(origin server)。在用户代理和源服务器中间可

能存在多个中间层,比如代理,网关,或者隧道(tunnels)。

通常,由HTTP客户端发起一个请求,建立一个到服务器指定端口(默认是80

端口)的TCP连接。HTTP服务器则在那个端口监听客户端发送过来的请求。一

旦收到请求,服务器向客户端发回一个状态行,比如"HTTP/1.1 200 OK",和响应

的消息,消息的消息体可能是请求的文件、错误消息、或者其它一些信息。

HTTP 的主要特点如下:

1) 简单快速

HTTP本身既简单,又能有效地处理大量请求。在客户机与服务器连接后,客户机

必须传送的信息只是请求方法和路径。正是因为HTTP 简单,使得HTTP 服务器程序

规模小,而且简单。因此经由HTTP 的通信速度很快,与其它协议相比,时间开销小得

多。

2) 无连接

HTTP 是一个无连接协议。它的含义是限制每次连接只处理一个请求、客户机与服

务器连接后提交一个请求,在客户机接到应答后马上断开连接。使用这种无连接协议,

在没有请求时,服务器不会在那里闲等着,服务器更不会在完成一个请求后还把着原来

的请求不放。使用无连接协议就好像是写信,一旦写好信发出便没事了,对方回信有了

新信息,再写另一封信。而保持连接协议就跟打电话相似。双方轮番说许多话后才挂断。

在对话期间电话线一直被占用。对于无连接协议而言,服务器一方实现起来比较容易,

又能充分利用网上的资源。

3) 无状态

HTTP 是无状态的协议:每一次请求、应答的内容、状态及完成情况不作为历史数

据保留到下一阶段使用,它既是优点也是缺点。一方面,由于没有状态, 协议对事物

处理没有记忆能力,如果后续事物处理需要前面处理的有关信息,那么这些信息必须在

10 1

协议外面保存。缺少状态意味着所需要的前面信息必须重现, 势必导致每次连接要传

送较多的信息。另一方面,也正是由于缺少状态使得HTTP 累赘少,运行速度高,服务

器应答快。

4)内容协商

当服务器能够对客户的请求提供多种表示形式应答时,需要使用内容协商机制,使

Web服务器可以从中挑选出能满足用户要求的具有最适合表达形式的资源实体。因为很

多时候源服务器或提供缓存的中间服务器并不会有一个统一的最佳资源形式标准,而用

户端浏览器也不一定有能力处理所有的实体类型。

5)易于扩充

作为一个公开发布使用协议,HTTP 具有良好的可扩充性,它传输的已不仅仅是超

文本数据。在此基础上针对应用开发者的研究、开发要求,可以很容易地增加请求方法

和响应状态,运行于用户定制的系统之中。经过扩充的服务器,能够响应原有标准的浏

览器,也能够区别出用户自己开发的专用客户程序,做出相应的响应处理。

图1-1介绍了HTTP协议的通信过程

图1-1

1.3 HTTP会话及报文格式

基于HTTP协议的客户机访问包括4个过程,分别是:建立TCP套接字连接、发送

HTTP请求报文、接收HTTP应答报文和关闭TCP套接字连接:

1)创建TCP套接字连接

客户端与WEB服务器创建TCP套接字连接,其中WEB端服务器的地址可以通过

域名解析确定,WEB端的套接字侦听端口一般是80。

2) 发送HTTP请求报文

客户端向WEB服务端发送请求报文,HTTP协议的请求报文格式如表1-1所示:

10 1

请求消息 = 请求行(实体头信息)CRLF[实体内容]

请求行 = 方法 URL HTTP版本号 CRLF

方法 = GET|HEAD|POST|扩展方法

URL = 协议名称 + 宿主名 + 目录与文件名

表1-1

其中“CRLF”表示回车换行。“请求行”中的“方法”描述了对指定资源执行的动作,常

用的方法“GET”、“HEAD”和“POST”等3种,它们的含义如表1-2所示:

取值

GET

描述

从WEB服务器中获取对象,不同类型的对

象将获取不同的信息,比如:

· 文件类型对象,获取该文件的内容。

· 程序类型对象,获取该程序执行的结果。

· 数据库查询类型对象,获取该查询的结果。

要求服务器查找对象的元信息。

从客户端向WEB服务器发送数据。

表1-2

HEAD

POST

实体头信息中记载了报文的属性,利用这些信息可以实现客户端与WEB服务器之

间的请求或应答,它包括报文的数据类型、压缩方法、语言、长度、压缩方法、最后一

次修改时间、数据有效期等信息。

实体内容是报文传送的附加信息,一般供POST请求填写。

URL为“/upfile/media/3”的GET请求报文如表

1-3:

GET /upfile/media/3

Accept: */*

Accept-Language: zh-cn

Accept-Encoding: gzip, deflate

User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR

.50727)

Host:

Connection: Keep-Alive

表1-3

其中"/upfile/media/3"是URL信息,语句"ACCEPT:*/*"及其后的语句是"

实体头信息"。

10 1

3)接收HTTP应答报文

WEB服务器处理客户请求,并向客户机发送应答报文,HTTP协议的应答报文格式

为:

应答报文 = 状态行(实体头信息)CRLF [实体内容]

状态行 = HTTP版本号 状态码 原因叙述

状态码描述了WEB服务器执行客户机请求的状态信息,表1-4描述了HTTP的应

答码含义

取值

1xx

2xx

3xx

4xx

5xx

保留

成功接收,比如“200”表示处理成功

客户需进一步细化请求

客户错误,比如“404”表示访问指定资源不存在

服务器错误

表1-4

描述

表1-5为某个应答报文例子。

4)关闭TCP套接字连接

客户机与服务器双方关闭套接字连接,结束TCP/IP对话。

HTTP/1.1 200 OK

Connection: keep-alive

Date: Thu, 26 Jul 2007 14:00:02

GMTServer: Microsoft-IIS/6.0

X-Powered-By:

Content-Length: 190

Content-Type: text/html

Set-Cookie: ASPSESSIONIDSAATTCSQ=JOPPKDCAMHHBEOICJPGPBJOB; path=/

Cache-control: private

花的世界

10 1

为你所喜爱的花投上一票吧

投票测试

你选择了

梅花

表1-5

2 Windows套接字

2.1 什么是套接字

套接字(socket)是一种网络编程接口。它是对通信端点的一种抽象,提供了一种

套接字是通信的基石,一个套接字是通信的一端。在通信的任何一端上,用户可以

发送和接收数据的机制。

找到套接字及与其对应的一个套接字名字。一个正在被使用的套接字都有它的类型和与

其相关的进程。套接字存在于通讯域中。通讯域是为了处理一般的线程通过套接字通讯

而引进的一种抽象概念。套接字通常和同一个域中的套接字交换数据(数据交换也可能

穿越域的界限,但这时一定要执行某种解释程序)。Windows Sockets规范支持单一的通

讯域,即Internet域。各种进程使用这个域互相之间用Internet协议族来进行通讯

(Windows Sockets 1.1以上版本支持其他的域,例如Windows Sockets 2)。

用户目前可以使用两种套接字,它们是数据报套接字(Datagram Sockets)和流式套

接字(Stream Sockets),这两种套接字传输的数据类型是不同的,因而其使用方法和相

应的程序设计模型也是不同的。

数据报套接字(Datagram Sockets)提供了一种不可靠的、非连接的数据包(packet)

通信方式。这里“不可靠”的意思是指发送一个数据包不能保证被接收方接收,也不能保

证数据包按照发送顺序到达接收方。实际使用中,同一个分组数据报肯不止一次地被发

送,一般要等到接收方发回确认收到的消息才会停止发送。对于在TCP/IP上实现的

WinSock,数据包套接字使用用户数据报协议(UDP),不过WinSock 2也支持其他类型

的数据报协议。

数据报套接字一般用于一些轻载荷(lightly loaded)的局域网(LAN)上的计算机

之间的通信。在这种情况下,用户可能不会发现数据报有不被发送、没有按顺序到达或

被重复发送的情况。但是,用户的应用程序应该具备处理这些异常情况的能力。当用户

10 1

为非常复杂的网络,如Internet,编写应用程序时,就应该要考虑数据报套接字的不可

靠性。如果不能很好地处理这些异常情况,用户的应用程序可能会崩溃。

虽然数据报套接字存在“不可靠”性,但是它在发送数据报或记录型数据时仍然是很

流式套接字可以将数据按顺序无重复地发送到目的地,它提供的是一种可靠的面向

有用的,而且数据报套接字还提供了向多个目标发送广播数据包的能力。

连接的数据传输方式。不管是对单个的数据报、还是对数据包,流式套接字都提供了一

种流式数据传输。流式套接字使用传输控制协议(TCP)。当用户想发送大批量数据时

或想让发送的数据按顺序无重复地到达目的地时,使用流式套接字是最方便的。

流式套接字的使用与数据报套接字有一个很大的不同,在使用流式套接字传输数据

之前,必须在数据传输的发送和接收端之前建立连接,而在使用数据报套接字之前就不

必建立连接。在数据传输时,如果已经建立的连接断开,应用程序会被通知。

2.2 套接字规范

由于套接字广泛应用于网络编程,因此一个公众可以接受的套接字规范就十分有必

要了。套接字规范其实就是一套网络编程的接口,它包含了一系列的与套接字使用有关

的库函数,是一个面向网络程序编程的API。

最早的套接字规范是由Berkeley大学开发。这个规范是针对UNIX操作系统下的

TCP/IP协议实现的,为UNIX操作系统下不同计算机之间使用TCP/IP协议进行网络通

信编程提供了一个API。由于Berkeley大学最先在Sockets接口开发中工作,因此,这

个套接字规范一般称为Berkeley Sockets规范。

随着个人计算机的日益普及,Windows操作系统的用户与日俱增。Microsoft以

Berkeley Sockets规范为范例定义了一套Microsoft Windows下网络编程接口,它不仅包

含了人们所熟悉的Berkeley Sockets风格的库函数,也包含了一组针对Windows的扩展

库函数,以使程序员能充分地利用Windows消息驱动机制进行编程。

Windows Sockets规范(简称WinSock)本意在于提供给应用程序开发者一套简单的

API,并让各家网络软件供应商共同遵守。此外,在一个特定版本Windows的基础上,

Windows Sockets规范也定义了一个二进制接口(ABI),以此来保证应用Windows Sockets

API的应用程序能够在任何网络软件供应商的负荷Windows Sockets协议的实现上工作。

因此这份规范定义了应用程序开发者能够使用,并且网络软件供应商能够实现的一套库

函数调用和相关语义。

遵守这套Windows Sockets规范的网络软件,我们称之为Windows Sockets兼容的,

而Windows Sockets兼容实现的提供者,我们称之为Windows Sockets提供者。一个网

10 1

络软件供应商必须百分之百地实现Windows Sockets规范才能做到现有Windows Sockets

兼容。

任何能够与Windows Sockets兼容实现协同工作的应用程序就被认为是具有

Windows Sockets规范定义并记录了任何使用API与Internet协议族(IPS,通常我

Windows Sockets接口。我们称这种应用程序为Windows Sockets应用程序。

们指的是TCP/IP)连接,尤其要指出的是所有的Windows Sockets实现都支持流式套接

字和数据报套接字。

应用程序调用Windows Sockets的API实现相互之间的通讯。Windows Sockets又利

用下层的网络通讯协议功能和操作系统调用实现实际的通讯工作。他们之间的关系如图

2-1所示。

图2-1应用程序和Sockets的关系

2.3 Windows套接字的发展

在Windows下的各种网络编程接口中,Windows Sockets脱颖而出,越来越得到大

家的重视,这是因为Windows Sockets规范是一套开放的、支持多种协议的Windows下

的网络编程接口。从1991年的1.0版到1995年的2.0.8版,经过不断完善并在Intel、

Microsoft、Sun、SGI、Informix、Novell等公司的全力支持下,已成为Windows网络编

程的事实上的标准。

在Windows Sockets发展的几个版本中,其中几个标志性版本是:Windows Sockets

Windows Sockets 1.0代表了网络软件供应商和用户协会细致周到的工作结晶。

1.0,Windows Sockets 1.1和Windows Sockets2.0。

Windows Sockets 1.0 规范的发布是为了让网络软件供应商和应用程序开发者能够开始

10 1

建立各自负荷Windows Sockets标准的实现和应用程序。

Windows Sockets 1.1集成了Windows Sockets 1.0的准则与结构,仅在一些绝对必要

● 为了更加简单地得到主机名和地址,增加了gethostname()函数;

● Windows Sockets在DLL中保留了小于1000的序数,而对大于1000的序数则没

的地方作了改动。

有限制。这使Windows Sockets供应商可以再dll中加入自己的界面,而不用担心所选择

的序数会和Windows Sockets将来的版本冲突;

● 增加了WSAStartup()函数和WASCleanup()函数之间的关联,要求两个函数互相对

应,完善了意外关闭机制。这使得应用程序开发者和第三方DLL在使用WindowsSocker

实现时 不需要考虑其他程序对这套API的调用;

● 调整函数intr_addr()的返回类型,从in_addr结构改为了无符号长整型;这个改

变是为了适应不同的C编译器对返回类型为四字节的函数的不同处理方法;

● 把WSAAsyncSelect ()函数语义 从“边缘触发”改为“电平触发”。这种方式大

大地简化了应用程序对这个函数的调用;

● 改变了ioctlsocket ()函数中FIONBIO的语义。如果套接字还有未完成的

WSAAsyncSelect ()函数调用,该函数将失败返回;

● 为了符合RFC1122,在套接字选项中增加了TCP_NODELAY。

为了用户能够同时使用多个传输协议,在Windows Socket 2 中,结构有所改变。在

Windows Socket 1.1 中,软件开发商所提供的DLL实现了Windows Sockets 的API和

TCP/IP协议栈。Windows Sockets DLL和底层协议栈的接口是唯一而且独占的。Windows

Socket 2 改变了这种模型:它定义了一个Windows Sockets DLL 能够同时访问不同软件

开发商的多个底层协议栈。此外,Windows Sockets 2并不像Windows Sockets 1.1 仅支

持TCP/IP协议栈。

2.4 套接字的使用和WinSock API

在使用WinSock进行编程之前,必须对它进行初始化,使用WSAStartup()函数就可

以做到这一点。

一、初始化WinSock

WSAStartup()函数必须是应用程序或DLL调用的第一个Windows Sockets函数,它

允许应用程序或DLL指明Windows Sockets API 的版本号及获得特定Windows Sockets

实现的细节,应用程序或DLL只有在一次成功的WSAStartup()调用成功之后才能调用

进一步的Windows Sockets API函数。

10 1

Windows Sockets API函数的调用形式如下:

int WSAStartup(

WORD wVersionRequested,

LPWSADATA lpWSAData

);

其中,第一个参数sVersionRequested是一个WORD型数值,它指定了你的应用程

序要使用的WinSock规范的最高版本。其中,主板本号在低位字节,辅版本号在高位字

节。

第二个参数IpWSAData指定了一个指向WSADATA结构的指针。其中的wVersion

成员变量将返回用户的应用程序应该使用的版本号;而whighVersion 成员变量将返回

动态链接库(DLL)所支持的最高版本号;其他还包括WinSock具体开发商的信息的字

符串,一个进程最多可以使用的套接字个数,以及可发送的最大数据报的大小等有趣的

信息。

如果 或底层网络子系统没有被正确初始化或没有被找到,

WSAStartup()函数将返回WSASYSNOTREADY。此外这个函数允许用户的应用程序协

商使用某种版本的WinSock规范。如果这个版本比任何DLL支持的版本低,WSAStartup()

将返回WSAVERNOTSUPPORTED。

如果由WSADATA的数据成员wVersion返回的版本号不能被用户的应用程序接受,

用户应该调用WSACleanup()函数并退出应用程序,或找一个不同的来

尝试一下。

二、创建套接字

在初始化WinSock之后,就应该创建在通信中使用的套接字了,其方法就是调用

sockets()函数。

sockets()函数的调用形式如下:

SOCKET socket(

int af,

int type,

int protocol

);

其中,第一个参数af是一个地址描述。目前仅支持AF_INET格式,也就是说指定

了该套接字要使用ARPA Internet 地址格式。

第二个参数type指定新套接字的类型,采用SOCK STREAM,指定应用程序创建一

10 1

个数据报套接字;采用 SOCK_DGRAM,则创建一个流式套接字。

第三个参数protocol指定套接字所用的协议。如用户不想指定,可采用默认值0,

由程序本身根据地址格式和套接字类型,自动选择一个合适的协议。

如果socket()函数调用成功,他将返回一个新的套接字描述符,如果调用失败,socket()

将返回0,用户可以通过调用WSAGETLastError()函数来获得有关错误信息的具体信息。

三、服务器侦听连接和接受连接

用户在创建了套接字后,赋予了套接字一个地址。对于服务器应用程序来说,下一

步就是要有一种方法来建立与客户机的连接,为了能够探测到客户机的连接请求,服务

器需要调用listen()函数。

listen()函数的调用格式如下:

int listen(

);

其中第一个参数s是进行侦听的套接字的描述符;第二个参数backlog高速WinSock

应用程序能够接收多少个连接请求。

一旦将服务器的套接字设置成侦听状态,它就可以接收客户机的连接请求,在受到

请求后,实际的连接可以由accept()函数来完成。

accept()函数的调用格式如下:

SOCKET accept(

);

其中,第一个参数s是服务器应用程序中调用了listen()函数的那个侦听套接字的描

述符;第二、第三个参数用来返回所连接的客户机套接字的地址,地址试用了sockaddr

结构。

四、客户机请求连接

为了让服务器接受一个请求,客户机必须首先发送一个连接请求。用户可以通过调

用connect()函数来做到这一点。

connect()函数的调用格式如下:

int connect(

SOCKET s,

struct sockaddr FAR* addr,

int FAR* addrlen

SOCKET s,

int backlog

10 1

);

其中,第一个参数s是请求连接的套接字的描述符;第二个参数name用来指定用

户相连接的服务器端的套接字端口;第三个参数namelen指定地址的长度。

五、发送接收数据

建立连接之后,就可以进行数据传输了。调用send()函数可以用来发送数据,调用

recv()函数可以接收数据。

send()函数的调用格式如下:

int send(

);

其中,第一个从参数s是发送数据的套接字的描述符;第二个参数buf和第三个

参数len表示了要传输数据的缓冲区的指针和数据的长度;最后一个参数flags指定了该

函数的调用方式,若flags设为MSG_DONTROUTE,则WinSock在发送数据时不会使

用循环消息,如果设为MSG_OOB,则该函数用来发送带外数据。要注意的是,调用send()

函数时,要发送的数据长度不应该超过底层网络支持的最大数据包的大小。

为了从流式套接字中接收数据,可以使用recv()函数。

recv()函数的调用格式如下:

int recv(

);

该函数的参数与send()函数的参数意义相似,但buf指向用于存储接收数据的缓冲

区;flags参数可以被设置成MSG_OOB以用来接收带外数据,或设置成MSG_PEEK用来

向缓冲区填入接收到的数据,而且仍按照数据的输入顺序进行填充。

SOCKET s,

char FAR* buf,

int len,

int flags

SOCKET s,

const char FAR* buf,

int len,

int flags

SOCKET s,

const struct sockaddr FAR* name,

int namelen

10 1

六、关闭套接字

在完成对一个套接字的操作之后,用户应该使用closesocket()函数来将这个套接字关

闭。

closesocket()函数的调用形式如下:

int closesocket(

);

SOCKET s

3 多线程及断点续传技术

3.1 多线程的优点

线程是进程的一条执行路径,它包含独立的堆栈和CPU寄存器状态,每个线程共享

所有的进程资源,包括打开的文件、信号标识及动态分配的内存等。一个进程内的所有

线程使用同一个地址空间,而这些线程的执行由系统调度程序控制,调度程序决定哪个

线程可以及什么时候执行线程。线程有优先级别,优先权较低的线程必须等到优先权较

高的线程执行完毕后再执行。在多处理器的机器上,调度程序可将多个线程放到不同的

处理器上去运行,这样可使处理器任务平衡,并提高系统的运行效率。

传统操作系统中的单线程进程由进程控制块和用户地址空间,以及管理进程执行的

调用/返回行为的系统堆栈或用户堆栈构成。如果把进程的管理和执行相分离,进程作

为操作系统中进行保护和资源分配的单位,允许一个进程中包含有多个可并发执行的控

制流,这些控制流切换时不必通过进程调度,通信时可以直接借助于共享的内存区,这

就形成了多线程编程。

相比以前的单线程软件,一个合理的多线程结构软件的具有以下优点:

1.使用线程可以把占据长时间的程序中的任务放到后台去处理

2.用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,

可以弹出一个进度条来显示处理的进度

3.程序的运行速度可能加快

4.在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较

游泳了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。

5.最关键的一点,多线程可以让同一个程序的不同部分并发执行

如图3-1给出了单线程进程与多线程进程的模型

10 1

图3-1

3.2 多线程之间的互斥和同步

互斥是指对某一临界区如代码段或文件等进行访问或修改时,只能允许一个线程执

行,其他线程不能访问或修改。一般互斥主要通过对临界区进行加锁来实现的。如果没

有互斥,则临界区就可以被其他线程所拥有,此时某一线程在访问临界区资源时,可能

另一个线程正在修改它。对重要部分未实现互斥会引起很多问题,严重时会引起死锁,

造成程序崩溃。

所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,

同时其它线程也不能调用这个方法。多线程之间的同步就是指使隶属于同一进程的各线

程协调一致地工作。MFC提供了多种同步对象最常用的四种有:

●临界区(CCriticalSection)

●事件(CEvent)

●互斥量(CMutex)

●信号量(CSemaphore)

使用临界区(CCriticalSection)可以对重要的代码段进行加锁,以确保当有一个线

事件是一个允许一个线程在某种情况发生时,唤醒另外一个线程的同步对象。例如

在某些网络应用程序中,一个线程(记为A)负责监听通讯端口,另外一个线程(记为

B)负责更新用户数据。通过使用CEvent 类,线程A可以通知线程B何时更新用户数

据。每一个CEvent 对象可以有两种状态:有信号状态和无信号状态。线程监视位于其

中的CEvent 类对象的状态,并在相应的时候采取相应的操作。

互斥对象与临界区对象很像.其不同在于:互斥对象可以在进程间使用,而临界区对象

只能在同一进程的各线程间使用。当然,互斥对象也可以用于同一进程的各个线程间,

程在访问时,其他线程不能访问。

10 1

但是在这种情况下,使用临界区会更节省系统资源,更有效率。

当需要一个计数器来限制可以使用某个线程的数目时,可以使用“信号量”对象。

CSemaphore 类的对象保存了对当前访问某一指定资源的线程的计数值,该计数值是当

前还可以使用该资源的线程的数目。如果这个计数达到了零,则所有对这个CSemaphore

类对象所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或计数值不为零

时为止。一个线程被释放已访问了被保护的资源时,计数值减1;一个线程完成了对被

控共享资源的访问时,计数值增1。这个被CSemaphore 类对象所控制的资源可以同时

接受访问的最大线程数在该对象的构建函数中指定。

3.3 什么是断点续传技术

当在下载一个文件特别是比较大的文件时,如果由于某种原因如突然断线等使得下

载中断,如果用户使用的是IE自带的下载工具或者其他不支持断点续传的下载软件的

话,这就意味着你之前所花在下载该文件上的所有时间都白费,下次下载不得不从头开

始,并且还要准备再次被突然中断的风险。由此可见断点续传技术在下载软件中的作用。

断点续传就是如果下载中断,在重新建立连接后,跳过已经下载的部分,而只下裁

还没有下载的部分。在HTTP协议中,可以在请求报文头中加入Range段,来表示客户

机希望从何处继续下载。当然并不是所有的文件都可以支持断点续传的。如果在获得的

HTTP应答报文的应答码不是206,则说明该网站服务器设置为不支持断点续传,此时

只能依靠老办法,将其一次下载完成了。

4 下载工具的设计与实现

4.1 基本结构与数据流程图

图4-1是程序NetDownLoadMTR的层次结构,这些模块的主要功能如下:

多线程管理:利用标准线程库实现多线程机制,将下载任务分为若干子任务,为每

个子任务建立一个线程,并管理他们实现多线程同时连接下载。

HTTP下载:按照HTTP协议的报文格式及其规则与服务器通信。

断点存储:管理一个存放各线程下载进度的数据结构。

下载信息统计:计算下载文件名、下载总速度、下载总进度等,并在终端显示。

文件管理:负责本地文件的打开、读取、创建、清除等。

10 1

HTTP下载 断点存储

图4-1 程序各模块的层次结构

主控程序

下载信息统计 文件管理

多线程管理

图4-2是程序的主要数据流图。其中“接收下载任务”和“调度下载任务”在下载

信息汇总模块,用于初始化下载。

服务器

本地硬

图4-2 程序主要数据流图

用户

接收下载任务

调度下载任务

断点续传

多线程管理

HTTP下载 缓冲区

10 1

4.2 程序基本功能设计与实现

图4-3

10 1

按图4-3所示添加15个静态文本,4个编辑框,3个按钮(保留“确定”按钮为“下载”),

2个组框,1个RichEdit,1个复选框,1个进程控件。各控件属性如表4-1:

控件ID

IDC_STATIC

IDC_STATIC

IDC_STATIC

IDC_STATIC

IDC_STATIC

IDC_STATIC

IDC_STATIC_Speed

IDC_STATIC

IDC_STATIC_Status

IDC_STATIC_ Status 1

IDC_STATIC_ Status 2

IDC_STATIC_ Status 3

IDC_STATIC_ Status 4

IDC_STATIC_ Status 5

IDC_STATIC_ Status 6

IDC_STATIC_ Status 7

IDC_STATIC_ Status 8

IDC_EDIT_RemoteFileURL

IDC_EDIT_LocalFilePath

IDC_EDIT_FileName

IDC_EDIT_ThreadNumber

IDC_BUTTON_Browse

IDC_BUTTON_Stop

IDOK

IDC_EDIT_Log

IDC_CHECK_Force

IDC_PROGRESS1

表4-1

标题

任务下载

远程文件地址

保存目录

文件名

你可以拖动地址到此窗口!

线程数

000.00MB/S

各线程信息

主线程

线程1

线程2

线程3

线程4

线程5

线程6

线程7

线程8

浏览

暂停

下载

允许拖动

控件类型

组框

静态文本

静态文本

静态文本

静态文本

静态文本

静态文本

组框

静态文本

静态文本

静态文本

静态文本

静态文本

静态文本

静态文本

静态文本

静态文本

编辑框

编辑框

编辑框

编辑框

按钮

按钮

按钮

RichEdit

复选框

进程

10 1

控件ID

CNetDownMTRDlg

CNetDownMTRDlg

CNetDownMTRDlg

CNetDownMTRDlg

CNetDownMTRDlg

CNetDownMTRDlg

CNetDownMTRDlg

CNetDownMTRDlg

CNetDownMTRDlg

IDC_BUTTON_Browse

IDC_BUTTON_Stop

IDC_EDIT_RemoteFile

URL

IDC_EDIT_FileName

IDOK

IDC_EDIT_FileName

响应函数

DoDataExchange

OnDestroy

OnPaint

OnQueryDragicon

OnInitDialog

OnSysCommand

OnWM_DOWNLOAD_NOTIFY

On_WM_SHOWLOG

PreTranslateMessage

OnBUTTONBrowse

OnBUTTONStop

OnChangeEDITRemoteFileURL

OnKillfocusEDITFileName

OnOk

OnSetfocusEDITFileName

表4-2

消息

WM_DESTROY

WM_PAINT

WM_QUERYDRAGICON

WM_INITDIALOG

WM_SYSCOMMAND

WM_DOWNLOADNOTIFY

WM_SHOWLOG

PreTranslateMessage

BN_CLICKED

BN_CLICKED

EN_CHANGE

EN_KILLFOCUS

BN_CLICKED

EN_SETFOCUS

各主要控件的响应事件如表4-2:

程序的主要类有:

CDownloadMTR主要实现多线程下载和断点续传功能

CDownloadPub主要用于显示和保存下载的各方面信息

CDownloadHttp为CdownloadPub的派生类,用于分析HTTP服务器,建立任务并

初始化下载和格式化信息等功能

CNetDownMTRDlg实现对话框初始化

CSocketClient创建Socket对象并实现连接、断开等功能

Clog 用于日志信息的输出,格式化等

CgradientProgressCtrl实现渐变色进度条显示效果

ColeListDropTarget通过OLE拖动机制使窗口接收数据

CpublicFunction包含ChwDir和ChwDirEx两个类用于目录选择,另外定义了常用

操作宏,时间类型数据长度以及自定义的各种消息等全局函数和变量

CmemDC在内存中开辟一矩形区域,用于CgradientProgressCtrl类实现渐变色进度

条显示效果。

主程序的主要功能流程图如图4-4所示:

10 1

主界面

用户取

消下载

点击下载

参数

效?

创建信息统计对象

和多线程下载对象

以及空文件

断点续

传?

合并文件

下载

完成

下载

显示下载

时间等

图4-4 主要功能流程图

10 1

4.3 代码分析

下面介绍主要类中的主要功能函数及其函数功能:

CDownloadMTR的主要函数及其功能见下表4-3:(注:大部分函数参数已省略)

函数名

Get_TotalDownloadedSize()

功能描述

获取已下载的字节数,包括以前下载的和本次下

载的

Get_FileTotaleSize()

GetLocalFileNameByURL ( )

GetDownloadElapsedTime ()

获得要下载文件总大小

根据 URL 来获取本地保存的文件名。

获取下载所消耗的时间(毫秒),可用来计算下

载速度和推算剩余时间

StopDownload()

停止下载。将所有下载线程关闭,将下载对象删

除,文件关闭。

Download ()

SetThreadCount()

WaitForDownloadFinished ()

GetDownloadResult ()

AttemperDownloadTask ()

多线程断点续传下载一个文件

设置下载的线程数

等待下载结束

是否全部下载完成

为线程调度任务,为下载任务最繁重的对象减轻

负担

GetUndownloadMaxBytes ()

HandleDownloadFinished ()

找到剩余未下载的数量最大的那个对象编号

下载完成后进行的操作(删除与下载文件名相同

的文件,为文件改名,设置文件属性等)

AssignDownloadTask ()

ReadDownloadInfo ()

GetTempFilePath ()

StartMTRDownload ()

为每个线程分配下载任务

从下载信息文件中读取下载信息

得到临时数据保存的路径文件名

启动多线程下载,返回 0 表示失败,1表示成

功,2表示不用下载了,因为该文件已经下载过

CreateDownloadObjectAndDataMTR ()

CreateDownloadObject ( )

创建多线程下载使用的对象和数据缓冲

创建下载对象

表4-3 类CDownloadMTR的主要函数及其功能

10 1

CDownloadPub的主要函数及其功能见下表4-4:(注:大部分函数参数已省略)

函数名

ThreadIsRunning ()

GetDownloadObjectFileName ()

ResetVar()

GetUndownloadBytes ()

功能描述

获得线程状态

获取下载对象的文件名(带扩展名的)

重置下载信息,清空线程等。

获取尚未下载的字节数,写到文件中的和临时缓

冲里的都算是已经下载的。

Get_WillDownloadSize()

Set_WillDownloadSize ()

Get_DownloadedSize()

Get_TempSaveBytes()

GetRemoteFileName ()

Connect ()

GetRemoteSiteInfo ()

Download ()

Is_SupportResume ()

Get_ProtocolType ()

DownloadOnce()

GetRefererFromURL()

SaveDataToFile ( )

RecvDataAndSaveToFile()

Get_TimeLastModified()

Get_FileTotalSize()

Get_DownloadUrl ()

Is_DownloadSuccess()

Get_Thread_Handle()

OpenFileForSave()

获得要下载的字节数。

为线程分配要下载的字节数

获得已下载字节数

获得临时存储的字节数

获得远程文件名

连接服务器

获得远程站点信息(默认重试100次)

创建多线程下载文件

是否支持断点续传

获取协议类型

初始化下载

获得文件下载地址(不包括文件名)

保存下载的数据

从服务器接收数据并保存到文件中

获取最后修改时间

获取文件总大小

获得下载地址

下载是否成功

获得线程句柄

打开保存文件,更新文件中下载起始位置

表4-4 类CDownloadPub的主要函数及其功能

10 1

函数名

RequestHttpData ( )

ConvertHttpTimeString ()

功能描述

请求HTTP数据

将 HTTP 服务器表示的时间转换为 CTime 格

FindAfterFlagString()

ParseResponseString ( )

GetResponseCode ( )

SendRequest ()

GetRequestStr ( )

GetRemoteSiteInfo_Pro ()

获得相应信息

解析返回信息

获得返回代码

向服务器提交请求,并得到返回字符串

获取请求的字符串

获取远程站点信息,如:是否支持断点续传、

要下载的文件大小和创建时间等

表4-5 类CDownloadHttp的主要函数及其功能

类CDownloadHttp的主要函数及其功能见下表4-5:(注:大部分函数参数已省略)

由于类CsocketClient是由VC6.0自带类CSocket派生来的,为了方便说明类

CsocketClient的功能函数作用,现先介绍类Csocket的部分重要函数及其功能,见表4-6:

(注:大部分函数参数已省略)

函数名

Attach(SOCKET hSocket)

CancelBlockingCall()

Create()

FromHandle()

IsBlocking()

OnMessagePending()

功能描述

将hSocket句柄放到一个到CSocket对象

取消当前正在进行的闭锁调用

创建一个Socket对象,默认为流模式

返回一个到CSocket对象的指针

确定一个闭锁调用是否正在进行中

重载该成员函数可从Windows中查找特殊的消

息,并在程序的套接字中响应

表4-6 类Csocket的部分重要函数及其功能

10 1

类CsocketClient的主要函数及其功能见下表4-7:(注:大部分函数参数已省略)

函数名

Is_Connected()

CSocketClient ()

功能描述

获得连接状态

构造函数。默认结束模块为空,连接状态为未

连接,线程号为-1(主线程)

GetResponse()

GetResponseHistoryString ()

ClearResponseHistoryString ()

Disconnect()

Receive()

GetIPAndPortByPasvString()

获得返回代码

获得历史返回代码

清空历史返回代码

断开连接

接收数据

从类似 "(192,168,0,2,4,31)" 字符串中得到IP地

址和端口号

SetEventOfEndModule()

SendString()

Send()

Connect()

表4-7

常见事件结束模块

发送HTTP请求报文

发送数据

连接服务器

类CsocketClient的主要函数及其功能

除以上主要类的重要成员函数外,软件的实现还需要以下一些重要的全局函数,如

表4-8:

函数名

AddLogTextToEditCtrl()

ConvertStrToCTime()

功能描述

向大编辑控件添加日志信息

将一个表示日期的字符串(按年、月、日、时、

分、秒的顺序, 如"2001-08-09 18:03:30")转成

CTime 格式

CopyFileAppend()

CreateNullFile()

DbgLog()

FormatFileSize()

将一个文件追加拷贝到另一个文件末尾

创建一个空文件

向c盘debug文件输出任务信息

格式化表示文件大小的数字,如 2048 被格式

化为“2.00 k”

hwDeleteFolder()

删除一个文件夹,不管里面有没有文件,都会

10 1

被删除

Log()

MakeSureDirectory()

输出日志信息

确保目录存在,如果目录不存在就创建目录,可

以创建多层次的目录

ParseURL()

从URL里面拆分出Server、Object、协议类型

等信息

PartFileAndExtensionName()

PartFileAndPathByFullPath()

根据文件名来找它的文件名和扩展名

从一个完整的全路径名(包含文件名)中分离出

路径(没有文件名)和文件名

ReadDataFromFile()

SelectPathByCommonDlg()

StandardizationFileForPathName()

从文件中读取数据

从通用对话框中选择路径

准化路径或文件名,把不符合文件名命名规则

的字符替换成指定的字符

WriteDataToFile()

表4-8

保存数据到文件

重要的全局函数及其功能

4.4 主要功能实现算法

一、多线程下载的实现

多线程下载主要由两个函数来实现,首先调用类CDownloadMTR中的

AssignDownloadTask()函数为每个线程分配下载任务,然后对每个线程调用Download

(LPCTSTR lpszDownloadURL,LPCTSTR lpszSavePath,LPCTSTR lpszSaveOnlyFileName,

BOOL bForceDownload/*=FALSE*/)函数进行线程创建和下载。

具体代码如下:

BOOL CDownloadMTR::AssignDownloadTask()

{

ASSERT ( m_pDownloadPub_Info );

if ( !m_pDownloadPub_Info->Is_SupportResume() )

{

DeleteDownloadObjectAndDataMTR ();

Log ( L_WARNING, "站点 [%s] 不支持断点续传下载", m_pDownloadPub_Info->Get_ServerName()

10 1

);

DbgLog ( "任务分配如下:--------------------n" );

for ( int nIndex=0; nIndex

{

m_pDownloadCellInfo[nIndex].nWillDownloadStartPos = nWillDownloadStartPos;

}

// 文件大小未知,采用单线程

if ( m_pDownloadPub_Info->Get_FileTotalSize () <= 0 || !m_pDownloadPub_Info->Is_SupportResume() )

{

}

if ( !DownloadInfoIsValid() || !m_pDownloadPub_MTR || !m_pDownloadCellInfo )

{

}

ASSERT ( m_pDownloadPub_MTR && m_pDownloadCellInfo );

// 下载任务尚未分配

if ( !DownloadInfoIsValid() )

{

int nWillDownloadSize = -1, nWillDownloadStartPos = 0, nNoAssignSize = 0;

if ( m_pDownloadPub_Info->Get_FileTotalSize () > 0 )

{

}

nWillDownloadSize = m_pDownloadPub_Info->Get_FileTotalSize () / m_nThreadCount;

// 均分后剩下的部分,让第一个线程来承担下载

nNoAssignSize = m_pDownloadPub_Info->Get_FileTotalSize () % m_nThreadCount;

if ( !CreateDownloadObjectAndDataMTR () )

return FALSE;

if ( m_nThreadCount != 1 )

{

}

DeleteDownloadObjectAndDataMTR ();

SetThreadCount ( 1 );

10 1

DbgLog ( "线程.%d 从 %d(0x%08x) 下载到 %d(0x%08x) 共 %d(0x%08x) 字节n", nIndex,

m_pDownloadCellInfo[nIndex].nWillDownloadStartPos,

m_pDownloadCellInfo[nIndex].nWillDownloadSize = nWillDownloadSize;

if ( nIndex == 0 && m_pDownloadPub_Info->Get_FileTotalSize () > 0 )

{

}

m_pDownloadCellInfo[nIndex].nWillDownloadSize += nNoAssignSize;

m_pDownloadCellInfo[nIndex].nWillDownloadStartPos,

m_pDownloadCellInfo[nIndex].nWillDownloadStartPos+m_pDownloadCellInfo[nIndex].nWillDownloadSize,

m_pDownloadCellInfo[nIndex].nWillDownloadStartPos+m_pDownloadCellInfo[nIndex].nWillDownloadSize,

m_pDownloadCellInfo[nIndex].nWillDownloadSize,

m_pDownloadCellInfo[nIndex].nWillDownloadSize );

artPos,

m_pDownloadCellInfo[nIndex].nWillDownloadSize,

}

// 启动下载任务

for ( int nIndex=0; nIndex

{

if ( !m_pDownloadPub_MTR[nIndex].Download ( m_pDownloadCellInfo[nIndex].nWillDownloadSt

}

nWillDownloadStartPos += m_pDownloadCellInfo[nIndex].nWillDownloadSize;

m_pDownloadCellInfo[nIndex].nDownloadedSize ) )

}

}

m_adCount = m_nThreadCount;

return TRUE;

return FALSE;

表4-9 函数AssignDownloadTask()

10 1

BOOL CDownloadMTR::Download (

LPCTSTR lpszDownloadURL,

LPCTSTR lpszSavePath,

LPCTSTR lpszSaveOnlyFileName,

BOOL bForceDownload/*=FALSE*/ // 如果为 TRUE 表示强制性重新下载,以下载的部分

将会被删除,FALSE 表示断点续传

{

// 创建下载对象

if ( !( m_pDownloadPub_Info = CreateDownloadObject () ) )

{

}

// 设置下载对象的参数

Log ( L_ERROR, "创建下载对象失败!" );

return HandleDownloadFinished(ENUM_DOWNLOAD_RESULT_FAILED);

CString csServer, csObject;

USHORT nPort = 0;

if ( !ParseURL ( lpszDownloadURL, csServer, csObject, nPort, m_csProtocolType ) )

{

}

m_csDownloadURL = lpszDownloadURL;

Log ( L_ERROR, "下载地址 [%s]出错", lpszDownloadURL );

return FALSE;

if ( !HANDLE_IS_VALID(m_hEvtEndModule) )

return FALSE;

)

if ( !lpszSavePath || strlen(lpszSavePath) < 1 )

return FALSE;

m_csSavePath = lpszSavePath;

m_csSaveOnlyFileName = GET_SAFE_STRING(lpszSaveOnlyFileName);

m_bForceDownload = bForceDownload;

10 1

}

return TRUE;

// 创建一个下载线程

DWORD dwThreadId = 0;

m_hThread = CreateThread ( NULL, 0, ::ThreadProc_DownloadMTR, LPVOID(this), 0, &dwThreadId );

if ( !HANDLE_IS_VALID(m_hThread) )

{

}

Log ( L_WARNING, "创建下载线程失败!" );

return FALSE;

m_pDownloadPub_Info->m_pDownloadMTR = this;

m_pDownloadPub_Info->SetDownloadUrl ( lpszDownloadURL );

表4-10 函数Download()

二、断点续传的实现

该功能的实现主要通过三个结构体,以及类CDownloadMTR中SaveDownloadInfo ()

函数实现。结构体内容如下:

//

// 单个对象的下载信息

//

typedef struct _downloadcellinfo

{

int nWillDownloadStartPos;

int nWillDownloadSize;

// 要下载文件的开始位置

// 本次需要下载的大小,-1表示一直下载到文件尾

// 该线程已下载的大小 DWORD nDownloadedSize;

} t_DownloadCellInfo;

表4-11

10 1

// 下载信息

typedef struct _DownloadInfo

{

DWORD dwThreadCount; // 该文件由多少个线程在下载

} t_BaseDownInfo;

表4-12

typedef struct _DownloadNotifyPara

{

int nIndex;

UINT nNotityType;

LPVOID lpNotifyData;

LPVOID pDownloadMTR;

} t_DownloadNotifyPara;

表4-13

函数代码如下:

BOOL CDownloadMTR::SaveDownloadInfo ()

{

if ( !m_pDownloadPub_Info->Is_SupportResume() )

return TRUE;

CString csTempFileName = GetTempFilePath ();

BOOL bRet = FALSE;

CFile file;

TRY

{

if ( ( csTempFileName, CFile::modeCreate|

{

CFile::modeNoTruncate|

CFile::modeReadWrite|

CFile::typeBinary|

CFile::shareDenyNone ) )

10 1

if ( ( -(int)sizeof(t_BaseDownInfo), CFile::end ) == (int)(gth() -

sizeof(t_BaseDownInfo)) )

{

( &m_BaseDownInfo, sizeof(t_BaseDownInfo) );

if ( ( -GetDownloadInfoWholeSize(), CFile::end ) == int(gth() -

GetDownloadInfoWholeSize()) )

if ( !bRet ) Log ( L_WARNING, "保存下载信息出错! %s", hwFormatMessage ( GetLastError()/*针对之前调用的

}

CATCH( CFileException, e )

{

}

END_CATCH

if ( HANDLE_IS_VALID ( file.m_hFile ) )

();

e->Delete ();

bRet = FALSE;

}

}

{

}

( m_pDownloadCellInfo, m_nThreadCount*sizeof(t_DownloadCellInfo) );

bRet = TRUE;

api函数,用这个函数取得扩展错误信息*/ ) );

}

return bRet;

表4-14

限于篇幅有限,在此其他功能算法则不再一一介绍,详情请参见源代码。

10 1

5 总结

主要工作成果与感想:

本文主要研究了时下流行的下载软件中的关键技术--多线程下载和断点续传技术,

并分析了HTTP协议的发展及其特点和WinSock编程技术。最后再此基础上,本文提出

了基于HTTP协议的支持多线程和断点续传技术的下载工具NetDownMTR的基本框架,

在随后对其进行具体编码和实现的过程中,将上述三种技术结合在一起实现了文件下载

的功能。

本次毕业设计课题从开始设计到最终完成历时两个月。在这两个月中,我体会到软

件开发是一个有趣而又复杂的过程。在开发的过程中遇到过许许多多的难题,有过气馁、

沮丧甚至想到过退缩,但是通过询问老师、请教同学、查阅资料,不断地学习使我对项

目有了更深层次的认识并最终解决了问题。

实践是检验真理的唯一标准,当然也是检验学习成果的标准。通过本次毕业设计项

目的开发,我深深得了解到自己实践能力的不足,许多东西想起来觉得很容易,一旦要

将其用于实践却有种无处下手的感觉。另外,我还了解到软件开发不仅需要扎实的编程

基础,还要有软件工程的思想,一个好的程序员在开始编程前,就会认真思考分析项目

需求,做出程序框架甚至先完成任务设计书。在刚开始设计时,由于对项目的需求分析

不到位,使得在编程时走了很多弯路,吃了很多的亏。

下一步工作展望:

由于时间以及作者水平有限,本次成果只实现了HTTP协议上的多线程下载。目前网

络上的主流下载工具都同时支持多协议的下载,如:迅雷,FlashGet等都支持HTTP,FTP,

BT等下载方式。同时这些软件在支持上述功能的基础上,又添加了P2P技术和服务器以

及数据库支持,其中“迅雷”更是加入了强大的网络搜索功能,整合了大量零散的资源,

大大提高了速度。目前BT技术中己经大量采用了分布式哈希表(DHT)的技术,可以将

Peer信息进行分布式存储,以代替Trackerj及务器的功能,这样便可以消除BT协议中

对Traeker服务器的依赖。要想进一步提高NetDownMTR软件的下载速度,有必要向朝

上述方向进一步研究和改良。

10 1

致谢

不知不觉中,我便已经度过四年的本科生涯,这是一个充满激情与进步的四年,在

这四年里,我学到了许多科学知识和做人的道理。在本论文完成之际,谨此向四年来所

有帮助过我的人致以衷心的感谢和崇高的敬意!

此次毕业设计以及毕业论文的撰稿过程中,得到许多老师、同学以及朋友的关心,

在此表示衷心的感谢。我的指导老师覃颖老师,他是一位热心、认真、负责的老师,在

他的指导下我才得以解决一个个设计上的难题,最终顺利完成本次毕业设计。

由衷感谢我的同学梅月双、文明亮、杨银河、刘世启,他们开创性的研究思维拓展

同时,向一直支持我的家人表示深深的谢意,特别感谢我的父母在我身后对我始终

最后,衷心感谢为评阅本论文而付出辛勤劳动的各位专家和学者!

了我的学术视野,无数次的争论和探讨使我的能力有了长足的进展。

如一的默默支持。

10 1

参 考 文 献

[ 1 ] 毛光喜。多线程下载工具的开发与应用。计算机应用与软件,第23卷第7期

[ 2 ] 冯常青,师明珠。多线程文件断点续传。电脑编程技巧与维护,2006.8

[ 3 ] (美)Jim Beveridge & Robert Wiener 著,侯捷 译。Win32多线程程序设计。华中科技大学出

版社,2002

[ 4 ] 官章全,刘加明等。Visual C++.NET类库大全。电子工业大学出版社,2002

[ 5 ] 王爱民。多线程实例教程详解。清华大学出版社,2002

[ 6 ] 马展,李守勇等。Visual C++.NET网络与通信高级编程范例。清华大学出版社,2005

[ 7 ] 汤晟, 吴朝晖。 P2P—对等网络的未来。计算机应用研究,2004(1)

[ 8 ] 陈洪,刘双玉,杨玉华。P2P 技术发展与应用。计算机工程,2003.29(19)

[ 9 ] 行舟。断点续传和多线程下载(上)(下)。程序员,2002.02,03

[ 10 ] Marshall Cline,Greg Lomow,Mike Girou著,周远成 译。C++经典问答(第二版)。中国电力出

版社,2003

[ 11 ] 王险峰、刘宝宏。Windows环境下的多线程编程原理与应用。清华大学出版社,2002

[ 12 ] 陈增强,郭嘉琳,刘忠信,袁著祉。具有断点续传功能的文件传输系统的设计与关键技术。计

算机工程,2002.12

[ 13 ] 施炜,李铮,秦颖. Windows Sockets 规范及应用——Windows网络编程接口。 电子工业出版社,

1999

[ 14 ] 李培峰、朱巧明。Linux下支持续传的多线程下载工具的设计与实现。计算机工程与应用,2004.1

[ 15 ] 梅杓春,许世民。Windows环境下的Socket编程技术。计算机与通信,41998,(7)

[ 16 ] 蒋东兴。Wnidows Socket 程序设计指南。清华大学出版社,1995

[ 17 ] 丁展,刘海英等。Visual C++网络通信编程实用案例精选。 人民邮电出版社,2002

[ 18 ] 钟军, 汪晓平。Delphi网络通信协议分析与应用实现。人民邮电出版社,2002

[ 19 ] 李冠一,郑辉,韩维恒。基于WinSock实现Internet协议多线程连接的关键技术。计算机应用。

2001.21(12)

[ 20 ] 陶强,屈波,熊前兴。基于WinSock构建对等模式下断点续传系统的探讨。交通与计算机,2005.01

10 1

附录:

1. 程序代码

DownloadHttp.h

#include "DownloadPub.h"

class CDownloadHttp : public CDownloadPub

{

public:

CDownloadHttp();

virtual ~CDownloadHttp();

private:

//请求HTTP数据

BOOL RequestHttpData ( BOOL bGet, char *szTailData=NULL, int *pnTailSize=NULL );

// 将 HTTP 服务器表示的时间转换为 CTime 格式,如:Thus, 23 Apl 2009 14:29:53 GMT

CTime ConvertHttpTimeString ( CString csTimeGMT );

//获得相应信息

CString FindAfterFlagString(LPCTSTR lpszFoundStr, CString csOrg);

//解析返回信息

BOOL ParseResponseString ( CString csResponseString, OUT BOOL &bRetryRequest );

//获得返回代码

DWORD GetResponseCode ( CString csLineText );

// 向服务器提交请求,并得到返回字符串

BOOL SendRequest ( LPCTSTR lpszReq, OUT CString &csResponse, char *szTailData=NULL, int

*pnTailSize=NULL );

//获取请求的字符串

CString GetRequestStr ( BOOL bGet );

protected:

virtual BOOL DownloadOnce();

// 获取远程站点信息,如:是否支持断点续传、要下载的文件大小和创建时间等

virtual BOOL GetRemoteSiteInfo_Pro ();

};

#include "stdafx.h"

#include "NetDownMTR.h"

#include "DownloadHttp.h"

#ifdef _DEBUG

#undef THIS_FILE

static char THIS_FILE[]=__FILE__;

#define new DEBUG_NEW

#endif

void DownloadNotify ( int nIndex, UINT nNotityType, LPVOID lpNotifyData, LPVOID pDownloadMTR );

//////////////////////////////////////////////////////////////////////

// Construction/Destruction

//////////////////////////////////////////////////////////////////////

CDownloadHttp::CDownloadHttp()

{

}

CDownloadHttp::~CDownloadHttp()

{

10 1

}

BOOL CDownloadHttp::DownloadOnce()

{

// 不需要下载了

int nWillDownloadSize = Get_WillDownloadSize(); // 本次应该下载的字节数

int nDownloadedSize = Get_DownloadedSize (); // 已下载字节数

if ( nWillDownloadSize > 0 && nDownloadedSize >= nWillDownloadSize )

return DownloadEnd(TRUE);

if ( !CDownloadPub::DownloadOnce () )

return DownloadEnd(FALSE);

char szTailData[NET_BUFFER_SIZE] = {0};

int nTailSize = sizeof(szTailData);

if ( !RequestHttpData ( TRUE, szTailData, &nTailSize ) )

return DownloadEnd(FALSE);

// 从HTTP服务器中读取数据,并保存到文件中

return DownloadEnd ( RecvDataAndSaveToFile(m_SocketClient,szTailData, nTailSize) );

}

BOOL CDownloadHttp::RequestHttpData(BOOL bGet, char *szTailData/*=NULL*/, int *pnTailSize/*=NULL*/ )

{

int nTailSizeTemp = 0;

BOOL bRetryRequest = TRUE;

while ( bRetryRequest )

{

CString csReq = GetRequestStr ( bGet );

CString csResponse;

nTailSizeTemp = pnTailSize?(*pnTailSize):0;

if ( !SendRequest ( csReq, csResponse, szTailData, &nTailSizeTemp ) )

return FALSE;

CString csReferer_Old = m_csReferer;

CString csDownloadUrl_Old = m_csDownloadUrl;

CString csServer_Old = m_csServer;

CString csObject_Old = m_csObject;

USHORT nPort_Old = m_nPort;

CString csProtocolType_Old = m_csProtocolType;

if ( !ParseResponseString ( csResponse, bRetryRequest ) )

{

if ( !m_y () )

{

m_();

return FALSE;

}

m_csReferer = csReferer_Old;

m_csDownloadUrl = csDownloadUrl_Old;

m_csServer = csServer_Old;

m_csObject = csObject_Old;

m_nPort = nPort_Old;

m_csProtocolType = csProtocolType_Old;

m_csCookieFlag = "Flag=UUIISPoweredByUUSoft";

bRetryRequest = TRUE;

}

}

if ( pnTailSize )

*pnTailSize = nTailSizeTemp;

return TRUE;

}

10 1

//

// 获取远程站点信息,如:是否支持断点续传、要下载的文件大小和创建时间等

//

BOOL CDownloadHttp::GetRemoteSiteInfo_Pro()

{

BOOL bRet = FALSE;

if ( !CDownloadPub::GetRemoteSiteInfo_Pro() )

goto finished;

if ( !RequestHttpData ( TRUE ) )

goto finished;

bRet = TRUE;

finished:

return bRet;

}

//

//获取请求的字符串

//

CString CDownloadHttp::GetRequestStr(BOOL bGet)

{

CString strVerb;

if( bGet )

strVerb = _T("GET ");

else

strVerb = _T("HEAD ");

CString csReq, strAuth, strRange;

csReq = strVerb + m_csObject + " HTTP/1.1rn";

CString csPort;

if ( m_nPort != DEFAULT_HTTP_PORT )

( ":%d", m_nPort );

csReq += "Host: " + m_csServer + csPort + "rn";

csReq += "Accept: */*rn";

csReq += "Pragma: no-cachern";

csReq += "Cache-Control: no-cachern";

csReq += "User-Agent: "+m_csUserAgent+"rn";

if( m_y() )

{

m_csReferer = GetRefererFromURL ();

}

csReq += "Referer: "+m_csReferer+"rn";

csReq += "Connection: closern";

if ( !m_y() )

{

csReq += "Cookie: " + m_csCookieFlag + "rn";

}

// 指定要下载的文件范围

CString csEndPos;

int nWillDownloadStartPos = Get_WillDownloadStartPos (); // 开始位置

int nWillDownloadSize = Get_WillDownloadSize(); // 本次应该下载的字节数

int nDownloadedSize = Get_DownloadedSize (); // 已下载字节数

if ( nWillDownloadSize > 0 )

( "%d", nWillDownloadStartPos+nWillDownloadSize-1 );

ASSERT ( nWillDownloadSize < 0 || nDownloadedSize < nWillDownloadSize );

( _T("Range: bytes=%d-%srn"), nWillDownloadStartPos+nDownloadedSize, csEndPos );

10 1

csReq += strRange;

csReq += "rn";

return csReq;

}

//

// 向服务器提交请求,并得到返回字符串

//

BOOL CDownloadHttp::SendRequest(LPCTSTR lpszReq, CString &csResponse, char *szTailData/*=NULL*/, int

*pnTailSize/*=NULL*/ )

{

m_nect ();

if ( !Connect () ) return FALSE;

if ( !m_ring ( lpszReq ) )

{

return FALSE;

}

for ( int i=0; ; i++ )

{

char szRecvBuf[NET_BUFFER_SIZE] = {0};

int nReadSize = m_e ( szRecvBuf, sizeof(szRecvBuf) );

if ( nReadSize <= 0 )

{

Log ( L_WARNING, "线程(%d) 接收返回信息失败", m_nIndex );

return FALSE;

}

csResponse += szRecvBuf;

char *p = strstr ( szRecvBuf, "rnrn" );

if ( p )

{

if ( szTailData && pnTailSize && *pnTailSize > 0 )

{

p += 4;

int nOtioseSize = nReadSize - int( p - szRecvBuf );

*pnTailSize = MIN ( nOtioseSize, *pnTailSize );

memcpy ( szTailData, p, *pnTailSize );

}

#ifdef _DEBUG

int nPos = ( "rnrn", 0 );

CString csDump;

if ( nPos >= 0 ) csDump = ( nPos );

else csDump = csResponse;

Log ( L_NORMAL, "线程(%d) HTTP 服务器回复 :

rn<<<++++++++++++++++++++++++rn%srn<<<++++++++++++++++++++++++",

m_nIndex, csDump );

#endif

break;

}

}

return TRUE;

}

DWORD CDownloadHttp::GetResponseCode(CString csLineText)

{

wer ();

ASSERT ( ( "http/", 0 ) >= 0 );

int nPos = ( " ", 0 );

if ( nPos < 0 ) return 0;

CString csCode = ( nPos + 1 );

10 1

ft();

ght();

nPos = ( " ", 0 );

if ( nPos < 0 )

nPos = gth() - 1;

csCode = ( nPos );

return (DWORD)atoi(csCode); //转整数

}

BOOL CDownloadHttp::ParseResponseString ( CString csResponseString, OUT BOOL &bRetryRequest )

{

bRetryRequest = FALSE;

// 获取返回代码

CString csOneLine = GetOneLine ( csResponseString );

DWORD dwResponseCode = GetResponseCode ( csOneLine );

if ( dwResponseCode < 1 )

{

Log ( L_WARNING, "线程(%d) 接收到错误代码 : %s", m_nIndex, csOneLine );

return FALSE;

}

int nPos = 0;

// 请求文件被重定向

if( dwResponseCode >= 300 && dwResponseCode < 400 )

{

bRetryRequest = TRUE;

// 得到请求文件新的URL

CString csRedirectFileName = FindAfterFlagString ( "location:", csResponseString );

// 设置 Referer

m_csReferer = GetRefererFromURL ();

// 重定向到其他的服务器

nPos = ("://");

if ( nPos >= 0 )

{

m_csDownloadUrl = csRedirectFileName;

// 检验要下载的URL是否有效

if ( !ParseURL ( m_csDownloadUrl, m_csServer, m_csObject, m_nPort, m_csProtocolType ) )

{

Log ( L_WARNING, "线程(%d)定位媒体地址 [%s] 失败!", m_nIndex, m_csDownloadUrl );

return FALSE;

}

return TRUE;

}

// 重定向到本服务器的其他地方

e ( "", "/" );

// 重定向于根目录

if( csRedirectFileName[0] == '/' )

{

m_csObject = csRedirectFileName;

DownloadNotify ( -1, NOTIFY_TYPE_GOT_REMOTE_FILENAME,

(LPVOID)(LPCTSTR)(GetDownloadObjectFileName()), m_pDownloadMTR );

return TRUE;

}

// 定向于相对当前目录

int nParentDirCount = 0;

nPos = ( "../" );

10 1

while ( nPos >= 0 )

{

csRedirectFileName = (nPos+3);

nParentDirCount++;

nPos = ("../");

}

for (int i=0; i<=nParentDirCount; i++)

{

nPos = m_eFind('/');

if (nPos != -1)

m_csDownloadUrl = m_(nPos);

}

if ( ( "./", 0 ) == 0 )

( 0, 2 );

m_csDownloadUrl = m_csDownloadUrl+"/"+csRedirectFileName;

return ParseURL ( m_csDownloadUrl, m_csServer, m_csObject, m_nPort, m_csProtocolType );

}

// 请求被成功接收、理解和接受

else if( dwResponseCode >= 200 && dwResponseCode < 300 )

{

if ( m_nIndex == -1 ) // 主线程才需要获取文件大小的信息

{

// 获取 Content-Length

CString csDownFileLen = FindAfterFlagString ( "content-length:", csResponseString );

m_nFileTotalSize = (int) _ttoi( (LPCTSTR)csDownFileLen );

DownloadNotify ( -1, NOTIFY_TYPE_GOT_REMOTE_FILESIZE, (LPVOID)m_nFileTotalSize,

m_pDownloadMTR );

int nWillDownloadStartPos = Get_WillDownloadStartPos (); // 开始位置

int nWillDownloadSize = Get_WillDownloadSize(); // 本次应该下载的字节数

int nDownloadedSize = Get_DownloadedSize (); // 已下载字节数

if ( m_nFileTotalSize > 0 && nWillDownloadSize-nDownloadedSize > m_nFileTotalSize )

Set_WillDownloadSize ( m_nFileTotalSize-nDownloadedSize );

}

// 获取服务器文件的最后修改时间

CString csModifiedTime = FindAfterFlagString ( "last-modified:", csResponseString );

if ( !y() )

{

m_TimeLastModified = ConvertHttpTimeString(csModifiedTime);

}

if ( dwResponseCode == 206 ) // 支持断点续传

{

m_bSupportResume = TRUE;

}

else // 不支持断点续传

{

m_bSupportResume = FALSE;

}

return TRUE;

}

// Log ( L_WARNING, "(%d) Receive invalid code : %d", m_nIndex, dwResponseCode );

return FALSE;

}

CString CDownloadHttp::FindAfterFlagString(LPCTSTR lpszFoundStr, CString csOrg)

{

ASSERT ( lpszFoundStr && strlen(lpszFoundStr) > 0 );

CString csReturing, csFoundStr = GET_SAFE_STRING(lpszFoundStr);

10 1

wer (); //大写转小写

CString csOrgLower = csOrg;

wer ();

int nPos = ( csFoundStr );

if ( nPos < 0 ) return "";

csReturing = ( nPos + gth() );

nPos = ("rn");

if ( nPos < 0 ) return "";

csReturing = (nPos);

ft();

ght();

return csReturing;

}

//

// 将 HTTP 服务器表示的时间转换为 CTime 格式,如:Wed, 16 May 2007 14:29:53 GMT

//

CTime CDownloadHttp::ConvertHttpTimeString(CString csTimeGMT)

{

CString csYear, csMonth, csDay, csTime;

CTime tReturning = -1;

int nPos = ( ",", 0 );

if ( nPos < 0 || nPos >= gth()-1 )

return tReturning;

csTimeGMT = ( nPos + 1 );

ft(); ght ();

// 日

nPos = ( " ", 0 );

if ( nPos < 0 || nPos >= gth()-1 )

return tReturning;

csDay = ( nPos );

csTimeGMT = ( nPos + 1 );

ft(); ght ();

// 月

nPos = ( " ", 0 );

if ( nPos < 0 || nPos >= gth()-1 )

return tReturning;

csMonth = ( nPos );

int nMonth = GetMonthByShortStr ( csMonth );

ASSERT ( nMonth >= 1 && nMonth <= 12 );

( "%02d", nMonth );

csTimeGMT = ( nPos + 1 );

ft(); ght ();

// 年

nPos = ( " ", 0 );

if ( nPos < 0 || nPos >= gth()-1 )

return tReturning;

csYear = ( nPos );

csTimeGMT = ( nPos + 1 );

ft(); ght ();

// 时间

nPos = ( " ", 0 );

if ( nPos < 0 || nPos >= gth()-1 )

return tReturning;

csTime = ( nPos );

csTimeGMT = ( nPos + 1 );

10 1

CString csFileTimeInfo;

( "%s-%s-%s %s", csYear, csMonth, csDay, csTime );

ConvertStrToCTime ( fer(0), tReturning );

return tReturning;

}

****************************************************************************************************

****************************************************************************************************

DownloadMTR.h

#include "DownloadHttp.h"

#include

#define DEFAULT_THREAD_COUNT 4 // 默认下载一个文件所用的线程数

#define MAX_DOWNLOAD_THREAD_COUNT 16 // 下载一个文件最大使用的线程数

//

// 单个对象的下载信息

//

typedef struct _downloadcellinfo

{

int nWillDownloadStartPos; // 要下载文件的开始位置

int nWillDownloadSize; // 本次需要下载的大小,-1表示一直下载到文件尾

DWORD nDownloadedSize; // 该线程已下载的大小

} t_DownloadCellInfo;

//

// 下载信息

//

typedef struct _DownloadInfo

{

DWORD dwThreadCount; // 该文件由多少个线程在下载

} t_BaseDownInfo;

class CDownloadMTR

{

public:

int Get_TotalDownloadedSize_ThisTimes ();//获取本次下载字节数

int Get_TotalDownloadedSize(); //获取已下载的字节数,包括以前下载的和本次下载的

int Get_FileTotaleSize(); //获取文件总大小

static CString GetLocalFileNameByURL ( LPCTSTR lpszDownloadURL );// 根据 URL 来获取本地保存的文件名

void StopDownload(); //停止下载。将所有下载线程关闭,将下载对象删除,文件关闭

DWORD GetDownloadElapsedTime (); //获取下载所消耗的时间(毫秒),可用来计算下载速度和推算剩余

时间

BOOL Download ( // 多线程断点续传下载一个文件

LPCTSTR lpszDownloadURL,

LPCTSTR lpszSavePath,

LPCTSTR lpszSaveOnlyFileName,

BOOL bForceDownload=FALSE

);

BOOL SetThreadCount ( int nThreadCount );// 设置下载的线程数

CDownloadMTR(); //构造函数

virtual ~CDownloadMTR(); //析构函数

void Callback_SaveDownloadInfo ( int nIndex, int nDownloadedSize, int nSimpleSaveSize );

BOOL ThreadProc_DownloadMTR(); //线程执行

private:

int m_nTotalDownloadedSize_ThisTimes; // 表示这次启动下载任务以来总共下载的字节数

CCriticalSection m_CSFor_DownloadedData; //为已下载部分加互斥锁

void StandardSaveFileName (); //标准化下载文件名

BOOL m_bForceDownload; //是否为强制下载,是则重新下载,否则断点续传

10 1

HANDLE m_hThread; //下载线程

DWORD m_dwDownloadStartTime; //下载启动时间

int GetDownloadInfoWholeSize(); //获得所有下载信息的内存容量

int FindIndexByThreadHandle ( HANDLE hThread );//返回线程编号

ENUM_DOWNLOAD_RESULT WaitForDownloadFinished ();//等待下载结束

BOOL GetDownloadResult (); //是否全部下载完成

BOOL AttemperDownloadTask ( int nIndex ); //为编号为 nIndex 的对象调度任务,为下载任务最繁重的对象

减轻负担

int GetUndownloadMaxBytes ( int &nUndownloadBytes );//找到剩余未下载的数量最大的那个对象编号

BOOL HandleDownloadFinished ( ENUM_DOWNLOAD_RESULT eDownloadResult );// 下载完成后进行...

BOOL SaveDownloadInfo (); //保存下载信息

BOOL AssignDownloadTask (); //分配下载任务

BOOL DownloadInfoIsValid (); //确认下载信息是否有效,若无效重新分配

BOOL ReadDownloadInfo (); //从下载信息文件中读取下载信息

CString GetTempFilePath (); //得到临时数据保存的路径文件名

int StartMTRDownload (); //启动多线程下载,返回 0 表示失败,

//1表示成功,2表示不用下载了,因为该文件

已经下载过了

void DeleteDownloadObjectAndDataMTR(); //删除多线程下载使用的对象和数据缓冲

void DeleteDownloadObject_Info(); //删除取站点信息的下载对象

BOOL CreateDownloadObjectAndDataMTR (); //创建多线程下载使用的对象和数据缓冲

CDownloadPub* CreateDownloadObject ( int nCount=1 );//创建下载对象

void DeleteDownloadObject ( CDownloadPub *pDownloadPub );//删除下载对象

CString m_csSavePath, m_csSaveOnlyFileName, m_csSavePathFileName, m_csDownloadURL;//下载目录,下载文件

名,文件保存路径,下载地址

CString m_csProtocolType; //协议类型

int m_nThreadCount; // 线程数

CDownloadPub *m_pDownloadPub_Info; // 取站点信息对象

CDownloadPub *m_pDownloadPub_MTR; // 多线程下载对象

t_BaseDownInfo m_BaseDownInfo; // 下载基本信息,线程数等

t_DownloadCellInfo *m_pDownloadCellInfo; // 各个下载对象的参数

// 模块结束事件

HANDLE m_hEvtEndModule;

};

#include "stdafx.h"

#include "NetDownMTR.h"

#include "DownloadMTR.h"

#include

#ifdef _DEBUG

#undef THIS_FILE

static char THIS_FILE[]=__FILE__;

#define new DEBUG_NEW

#endif

void DownloadNotify ( int nIndex, UINT nNotityType, LPVOID lpNotifyData, LPVOID pDownloadMTR );

//////////////////////////////////////////////////////////////////////

// Construction/Destruction

//////////////////////////////////////////////////////////////////////

CDownloadMTR::CDownloadMTR()

: m_nThreadCount ( DEFAULT_THREAD_COUNT )

, m_pDownloadPub_MTR ( NULL )

, m_pDownloadPub_Info ( NULL )

, m_pDownloadCellInfo ( NULL )

, m_hThread ( NULL )

10 1

, m_bForceDownload ( FALSE )

, m_nTotalDownloadedSize_ThisTimes ( 0 )

{

memset ( &m_BaseDownInfo, 0, sizeof(t_BaseDownInfo) );//为下载任务分配所需内存

m_hEvtEndModule = ::CreateEvent ( NULL, TRUE, FALSE, NULL );

m_dwDownloadStartTime = GetTickCount();

}

CDownloadMTR::~CDownloadMTR()

{

StopDownload ();

}

//

// 设置下载的线程数

//

BOOL CDownloadMTR::SetThreadCount(int nThreadCount)

{

if ( nThreadCount <= 0 || nThreadCount > MAX_DOWNLOAD_THREAD_COUNT )

{

Log ( L_WARNING, "线程数 %d设置错误。范围 [%d-%d]", nThreadCount,

MAX_DOWNLOAD_THREAD_COUNT );

return FALSE;

}

if ( nThreadCount == m_nThreadCount )

return TRUE;

m_nThreadCount = nThreadCount;

return TRUE;

}

//

// 下载任务的线程函数

//

DWORD WINAPI ThreadProc_DownloadMTR( LPVOID lpParameter /* thread data*/ )

{

CDownloadMTR *pDownloadMTR = (CDownloadMTR*)lpParameter;

ASSERT ( pDownloadMTR );

pDownloadMTR->ThreadProc_DownloadMTR ();

TRACE ( "下载任务的线程函数执行完毕n" );

return TRUE;

}

BOOL CDownloadMTR::ThreadProc_DownloadMTR()

{

// 启动多线程下载任务

int nRet = StartMTRDownload ();

if ( nRet == 2 ) return HandleDownloadFinished(ENUM_DOWNLOAD_RESULT_SUCCESS);

if ( nRet == 0 ) return HandleDownloadFinished(ENUM_DOWNLOAD_RESULT_FAILED);

// 等待所有线程下载完成

ENUM_DOWNLOAD_RESULT eDownloadResult = WaitForDownloadFinished ();

if ( eDownloadResult == ENUM_DOWNLOAD_RESULT_SUCCESS && !GetDownloadResult () )

{

eDownloadResult = ENUM_DOWNLOAD_RESULT_FAILED;

}

return HandleDownloadFinished ( eDownloadResult );

}

//

1,

10 1

// 多线程断点续传下载一个文件

//

BOOL CDownloadMTR::Download (

LPCTSTR lpszDownloadURL,

LPCTSTR lpszSavePath,

LPCTSTR lpszSaveOnlyFileName,

BOOL bForceDownload/*=FALSE*/ // 如果为 TRUE 表示强制性重新下载,以下载的部分将会被

删除,FALSE 表示断点续传

)

{

if ( !HANDLE_IS_VALID(m_hEvtEndModule) )

return FALSE;

if ( !lpszSavePath || strlen(lpszSavePath) < 1 )

return FALSE;

m_csSavePath = lpszSavePath;

m_csSaveOnlyFileName = GET_SAFE_STRING(lpszSaveOnlyFileName);

m_bForceDownload = bForceDownload;

CString csServer, csObject;

USHORT nPort = 0;

if ( !ParseURL ( lpszDownloadURL, csServer, csObject, nPort, m_csProtocolType ) )

{

Log ( L_ERROR, "下载地址 [%s]出错", lpszDownloadURL );

return FALSE;

}

m_csDownloadURL = lpszDownloadURL;

// 创建下载对象

if ( !( m_pDownloadPub_Info = CreateDownloadObject () ) )

{

Log ( L_ERROR, "创建下载对象失败!" );

return HandleDownloadFinished(ENUM_DOWNLOAD_RESULT_FAILED);

}

// 设置下载对象的参数

m_pDownloadPub_Info->m_pDownloadMTR = this;

m_pDownloadPub_Info->SetDownloadUrl ( lpszDownloadURL );

// 创建一个下载线程

DWORD dwThreadId = 0;

m_hThread = CreateThread ( NULL, 0, ::ThreadProc_DownloadMTR, LPVOID(this), 0, &dwThreadId );

if ( !HANDLE_IS_VALID(m_hThread) )

{

Log ( L_WARNING, "创建下载线程失败!" );

return FALSE;

}

return TRUE;

}

//

// 创建下载对象

//

CDownloadPub* CDownloadMTR::CreateDownloadObject ( int nCount/*=1*/ )

{

if ( nCount < 1 ) return NULL;

CDownloadPub *pDownloadPub = NULL;

if ( m_eNoCase ( "http" ) == 0 )

{

pDownloadPub = (CDownloadPub*)new CDownloadHttp[nCount];

}

else return NULL;

10 1

return pDownloadPub;

}

//

// 删除下载对象

//

void CDownloadMTR::DeleteDownloadObject ( CDownloadPub *pDownloadPub )

{

if ( m_eNoCase ( "http" ) == 0 )

{

delete[] ( (CDownloadHttp*)pDownloadPub );

}

else delete[] pDownloadPub;

}

void Callback_SaveDownloadInfo ( int nIndex, int nDownloadedSize, int nSimpleSaveSize, WPARAM wParam )

{

CDownloadMTR *pDownloadMTR = (CDownloadMTR*)wParam;

ASSERT ( pDownloadMTR );

pDownloadMTR->Callback_SaveDownloadInfo ( nIndex, nDownloadedSize, nSimpleSaveSize );

}

//更新本次下载字节数

void CDownloadMTR::Callback_SaveDownloadInfo ( int nIndex, int nDownloadedSize, int nSimpleSaveSize )

{

if ( nIndex >= 0 && nIndex < m_nThreadCount )

{

m_pDownloadCellInfo[nIndex].nDownloadedSize = nDownloadedSize;

if ( nDownloadedSize > 0 )

{

m_CSFor_();

m_nTotalDownloadedSize_ThisTimes += nSimpleSaveSize;

m_CSFor_ ();

}

}

}

//

// 创建多线程下载使用的对象和数据缓冲

//

BOOL CDownloadMTR::CreateDownloadObjectAndDataMTR ()

{

DeleteDownloadObjectAndDataMTR ();

ASSERT ( !m_pDownloadPub_MTR && m_pDownloadPub_Info );

m_pDownloadPub_MTR = CreateDownloadObject ( m_nThreadCount );

// 设置多线程下载使用的对象的参数

if ( m_pDownloadPub_MTR )

{

for ( int nIndex=0; nIndex

{

m_pDownloadPub_MTR[nIndex].m_nIndex = nIndex;

m_pDownloadPub_MTR[nIndex].m_pDownloadMTR = this;

m_pDownloadPub_MTR[nIndex].Set_SaveDownloadInfo_Callback ( ::Callback_SaveDownloadInfo,

WPARAM(this) );

m_pDownloadPub_MTR[nIndex].SetDownloadUrl ( m_csDownloadURL );

if ( !m_pDownloadPub_MTR[nIndex].SetSaveFileName ( GetTempFilePath() ) )

return FALSE;

}

}

10 1

// 创建多线程下载使用的数据缓冲

ASSERT ( !m_pDownloadCellInfo );

m_pDownloadCellInfo = new t_DownloadCellInfo[m_nThreadCount];

if ( m_pDownloadCellInfo )

memset ( m_pDownloadCellInfo, 0, m_nThreadCount*sizeof(t_DownloadCellInfo) );

if ( m_pDownloadPub_MTR != NULL && m_pDownloadCellInfo != NULL )

return TRUE;

Log ( L_WARNING, "创建多线程下载对象或数据缓冲失败!" );

return FALSE;

}

//

// 删除多线程下载使用的对象和数据缓冲

//

void CDownloadMTR::DeleteDownloadObjectAndDataMTR()

{

if ( m_pDownloadPub_MTR )

{

DeleteDownloadObject ( m_pDownloadPub_MTR );

m_pDownloadPub_MTR = NULL;

}

if ( m_pDownloadCellInfo )

{

delete[] m_pDownloadCellInfo;

m_pDownloadCellInfo = NULL;

}

}

//

// 删除取站点信息的下载对象

//

void CDownloadMTR::DeleteDownloadObject_Info()

{

if ( m_pDownloadPub_Info )

{

DeleteDownloadObject ( m_pDownloadPub_Info );

m_pDownloadPub_Info = NULL;

}

}

//

// 启动多线程下载,返回 0 表示失败,1表示成功,2表示不用下载了,因为该文件已经下载过了

//

int CDownloadMTR::StartMTRDownload ()

{

m_dwDownloadStartTime = GetTickCount();

DownloadNotify ( -1, NOTIFY_TYPE_START_DOWNLOAD, (LPVOID)NULL, this );

// 先获取站点信息

ASSERT ( m_pDownloadPub_Info );

if ( !m_pDownloadPub_Info->GetRemoteSiteInfo () )

return 0;

DbgLog ( "要下载的文件大小是: %d 字节n", m_pDownloadPub_Info->Get_FileTotalSize () );

StandardSaveFileName ();

CFileStatus fileStatus;

if ( m_bForceDownload )

{

// 需要重新下载

10 1

::DeleteFile ( m_csSavePathFileName );

::DeleteFile ( GetTempFilePath() );

}

else

{

// 要保存的文件是否已经存在,且大小和创建时间一致,如果不是强制性下载,则不需要再下载了。

if ( CFile::GetStatus(m_csSavePathFileName,fileStatus) )

{

if (

(

fileStatus.m_e() - m_pDownloadPub_Info->Get_TimeLastModified() <=2 &&

m_pDownloadPub_Info->Get_TimeLastModified()-fileStatus.m_e() <=2

)

&&

fileStatus.m_size == m_pDownloadPub_Info->Get_FileTotalSize ()

&&

!m_bForceDownload

)

{

return 2;

}

}

}

BOOL bMustCreateNullFile = TRUE;

// 读取下载信息,如果能读到说明上次下载尚未完成

if ( !m_bForceDownload && m_pDownloadPub_Info->Is_SupportResume() )

{

if ( CFile::GetStatus(GetTempFilePath(),fileStatus) &&

fileStatus.m_size == m_pDownloadPub_Info->Get_FileTotalSize()+GetDownloadInfoWholeSize() )

{

if ( ReadDownloadInfo () )

bMustCreateNullFile = FALSE;

}

}

if ( bMustCreateNullFile )

{

int nFileSize = m_pDownloadPub_Info->Get_FileTotalSize();

int nTempFileSize = nFileSize+GetDownloadInfoWholeSize();

if ( nFileSize < 0 || !m_pDownloadPub_Info->Is_SupportResume() )

nTempFileSize = 0;

// 创建一个用来保存下载数据的空文件

if ( !CreateNullFile ( GetTempFilePath(), nTempFileSize ) )

return FALSE;

}

// 分配下载任务

if ( !AssignDownloadTask () )

{

Log ( L_WARNING, "分配任务失败!" );

return 0;

}

m_dwDownloadStartTime = GetTickCount();

return 1;

}

//

// 得到临时数据保存的路径文件名

//

CString CDownloadMTR::GetTempFilePath ()

10 1

{

ASSERT ( !m_y () );

CString csTempFileName;

( "%", m_csSavePathFileName );

::SetFileAttributes ( csTempFileName, FILE_ATTRIBUTE_ARCHIVE );

return csTempFileName;

}

//

// 分配下载任务

//

BOOL CDownloadMTR::AssignDownloadTask()

{

ASSERT ( m_pDownloadPub_Info );

if ( !m_pDownloadPub_Info->Is_SupportResume() )

{

DeleteDownloadObjectAndDataMTR ();

Log ( L_WARNING, "站点 [%s] 不支持断点续传下载", m_pDownloadPub_Info->Get_ServerName() );

}

// 文件大小未知,采用单线程

if ( m_pDownloadPub_Info->Get_FileTotalSize () <= 0 || !m_pDownloadPub_Info->Is_SupportResume() )

{

if ( m_nThreadCount != 1 )

{

DeleteDownloadObjectAndDataMTR ();

SetThreadCount ( 1 );

}

}

if ( !DownloadInfoIsValid() || !m_pDownloadPub_MTR || !m_pDownloadCellInfo )

{

if ( !CreateDownloadObjectAndDataMTR () )

return FALSE;

}

ASSERT ( m_pDownloadPub_MTR && m_pDownloadCellInfo );

// 下载任务尚未分配

if ( !DownloadInfoIsValid() )

{

int nWillDownloadSize = -1, nWillDownloadStartPos = 0, nNoAssignSize = 0;

if ( m_pDownloadPub_Info->Get_FileTotalSize () > 0 )

{

nWillDownloadSize = m_pDownloadPub_Info->Get_FileTotalSize () / m_nThreadCount;

// 均分后剩下的部分,让第一个线程来承担下载

nNoAssignSize = m_pDownloadPub_Info->Get_FileTotalSize () % m_nThreadCount;

}

DbgLog ( "任务分配如下:--------------------n" );

for ( int nIndex=0; nIndex

{

m_pDownloadCellInfo[nIndex].nWillDownloadStartPos = nWillDownloadStartPos;

m_pDownloadCellInfo[nIndex].nWillDownloadSize = nWillDownloadSize;

if ( nIndex == 0 && m_pDownloadPub_Info->Get_FileTotalSize () > 0 )

{

m_pDownloadCellInfo[nIndex].nWillDownloadSize += nNoAssignSize;

}

DbgLog ( "线程.%d 从 %d(0x%08x) 下载到 %d(0x%08x) 共 %d(0x%08x) 字节n", nIndex,

m_pDownloadCellInfo[nIndex].nWillDownloadStartPos,

m_pDownloadCellInfo[nIndex].nWillDownloadStartPos,

10 1

m_pDownloadCellInfo[nIndex].nWillDownloadStartPos+m_pDownloadCellInfo[nIndex].nWillDownloadSize,

m_pDownloadCellInfo[nIndex].nWillDownloadStartPos+m_pDownloadCellInfo[nIndex].nWillDownloadSize,

m_pDownloadCellInfo[nIndex].nWillDownloadSize,

m_pDownloadCellInfo[nIndex].nWillDownloadSize );

nWillDownloadStartPos += m_pDownloadCellInfo[nIndex].nWillDownloadSize;

}

}

// 启动下载任务

for ( int nIndex=0; nIndex

{

if ( !m_pDownloadPub_MTR[nIndex].Download ( m_pDownloadCellInfo[nIndex].nWillDownloadStartPos,

m_pDownloadCellInfo[nIndex].nWillDownloadSize, m_pDownloadCellInfo[nIndex].nDownloadedSize ) )

return FALSE;

}

m_adCount = m_nThreadCount;

return TRUE;

}

//

// 从下载信息文件中读取下载信息

//

BOOL CDownloadMTR::ReadDownloadInfo()

{

CString csTempFileName = GetTempFilePath ();

BOOL bRet = FALSE;

CFile file;

TRY

{

if ( ( csTempFileName, CFile::modeCreate|

CFile::modeNoTruncate|

CFile::modeReadWrite|

CFile::typeBinary|

CFile::shareDenyNone ) )

{

if ( ( -(int)sizeof(t_BaseDownInfo), CFile::end ) == (int)(gth() -

sizeof(t_BaseDownInfo)) )

{

if ( (UINT) ( &m_BaseDownInfo, sizeof(t_BaseDownInfo) ) == sizeof(t_BaseDownInfo) )

{

if ( (m_adCount > 0 && m_adCount <=

MAX_DOWNLOAD_THREAD_COUNT)&&

SetThreadCount ( m_adCount ) )

{

if ( CreateDownloadObjectAndDataMTR () )

{

if ( ( -GetDownloadInfoWholeSize(), CFile::end ) == int(gth() -

GetDownloadInfoWholeSize()) )

{

if ( ( m_pDownloadCellInfo,

sizeof(t_DownloadCellInfo)*m_nThreadCount ) == sizeof(t_DownloadCellInfo)*m_nThreadCount )

{

bRet = TRUE;

}

else

{

memset ( m_pDownloadCellInfo, 0,

sizeof(t_DownloadCellInfo)*m_nThreadCount );

10 1

}

}

}

}

}

}

}

}

CATCH( CFileException, e )

{

e->Delete ();

bRet = FALSE;

}

END_CATCH

if ( HANDLE_IS_VALID(file.m_hFile) )

();

return bRet;

}

BOOL CDownloadMTR::SaveDownloadInfo ()

{

if ( !m_pDownloadPub_Info->Is_SupportResume() )

return TRUE;

CString csTempFileName = GetTempFilePath ();

BOOL bRet = FALSE;

CFile file;

TRY

{

if ( ( csTempFileName, CFile::modeCreate|

CFile::modeNoTruncate|

CFile::modeReadWrite|

CFile::typeBinary|

CFile::shareDenyNone ) )

{

if ( ( -(int)sizeof(t_BaseDownInfo), CFile::end ) == (int)(gth() -

sizeof(t_BaseDownInfo)) )

{

( &m_BaseDownInfo, sizeof(t_BaseDownInfo) );

if ( ( -GetDownloadInfoWholeSize(), CFile::end ) == int(gth() -

GetDownloadInfoWholeSize()) )

{

( m_pDownloadCellInfo, m_nThreadCount*sizeof(t_DownloadCellInfo) );

bRet = TRUE;

}

}

}

}

CATCH( CFileException, e )

{

e->Delete ();

bRet = FALSE;

}

END_CATCH

if ( HANDLE_IS_VALID ( file.m_hFile ) )

();

if ( !bRet ) Log ( L_WARNING, "保存下载信息出错! %s", hwFormatMessage ( GetLastError()/*针对之前调用的

api函数,用这个函数取得扩展错误信息*/ ) );

return bRet;

10 1

}

// 下载完成后进行...

BOOL CDownloadMTR::HandleDownloadFinished(ENUM_DOWNLOAD_RESULT eDownloadResult)

{

CString csTempFileName;

CFileStatus fileStatus;

BOOL bRet = FALSE;

CFile file;

if ( eDownloadResult != ENUM_DOWNLOAD_RESULT_SUCCESS )

{

SaveDownloadInfo ();

goto Finished;

}

csTempFileName = GetTempFilePath ();// 得到临时数据保存的路径文件名

// 设置文件大小

if ( m_pDownloadPub_Info->Is_SupportResume() && m_pDownloadPub_Info->Get_FileTotalSize() > 0 )

{

TRY

{

( csTempFileName,

CFile::modeCreate|

CFile::modeNoTruncate|

CFile::modeReadWrite|

CFile::typeBinary|

CFile::shareDenyNone );

gth(m_pDownloadPub_Info->Get_FileTotalSize ());

bRet = TRUE;

}

CATCH( CFileException, e )

{

e->Delete ();

bRet = FALSE;

}

END_CATCH

if ( HANDLE_IS_VALID(file.m_hFile) )

();

if ( !bRet )

{

Log ( L_WARNING, "设置 [%s] 长度失败!", csTempFileName );

eDownloadResult = ENUM_DOWNLOAD_RESULT_FAILED;

goto Finished;

}

}

if ( _access(csTempFileName,04) == 0 )//临时文件可以访问

{

// 将文件改名

bRet = FALSE;

DeleteFile ( m_csSavePathFileName );//删除与下载文件名相同的文件

TRY

{

CFile::Rename ( csTempFileName, m_csSavePathFileName );

bRet = TRUE;

}

CATCH( CFileException, e )

{

e->Delete ();

bRet = FALSE;

10 1

}

END_CATCH

if ( !bRet )

{

Log ( L_WARNING, "为[%s]改名失败! %s", csTempFileName, hwFormatMessage(GetLastError()) );

eDownloadResult = ENUM_DOWNLOAD_RESULT_FAILED;

goto Finished;

}

// 设置文件属性,时间设置和服务器一致

bRet = FALSE;

if ( CFile::GetStatus(m_csSavePathFileName,fileStatus) )

{

fileStatus.m_mtime = m_pDownloadPub_Info->Get_TimeLastModified();

fileStatus.m_attribute = CFile::normal;

CFile::SetStatus ( m_csSavePathFileName, fileStatus );

bRet = TRUE;

}

if ( !bRet )

{

Log ( L_WARNING, "为文件[%s]设置属性失败! %s", csTempFileName,

hwFormatMessage(GetLastError()) );

eDownloadResult = ENUM_DOWNLOAD_RESULT_FAILED;

goto Finished;

}

}

Finished:

DownloadNotify ( -1, NOTIFY_TYPE_END_DOWNLOAD, (LPVOID)eDownloadResult, this );

return bRet;

}

BOOL CDownloadMTR::GetDownloadResult()

{

for ( int nIndex=0; nIndex

{

if ( !m_pDownloadPub_MTR[nIndex].Is_DownloadSuccess() )

return FALSE;

}

return TRUE;

}

//

// 下载信息是否有效

//

BOOL CDownloadMTR::DownloadInfoIsValid()

{

BOOL bValid = FALSE;

int nIndex = 0;

if ( !m_pDownloadCellInfo ) goto Invalid;

if ( m_adCount < 1 || m_adCount >

MAX_DOWNLOAD_THREAD_COUNT )

goto Invalid;

for ( nIndex=0; nIndex

{

if ( m_pDownloadCellInfo[nIndex].nWillDownloadSize > 0 )

{

bValid = TRUE;

break;

10 1

}

}

if ( !bValid ) goto Invalid;

return TRUE;

Invalid:

if ( m_pDownloadCellInfo )

memset ( m_pDownloadCellInfo, 0, m_nThreadCount*sizeof(t_DownloadCellInfo) );

memset ( &m_BaseDownInfo, 0, sizeof(t_BaseDownInfo) );

return FALSE;

}

//

// 找到剩余未下载的数量最大的那个对象编号

//

int CDownloadMTR::GetUndownloadMaxBytes( int &nUndownloadBytes )

{

nUndownloadBytes = 0;

int nMaxIndex = -1;

for ( int nIndex=0; nIndex

{

int nTempBytes = m_pDownloadPub_MTR[nIndex].GetUndownloadBytes ();

if ( nUndownloadBytes < nTempBytes )

{

nUndownloadBytes = nTempBytes;

nMaxIndex = nIndex;

}

}

return nMaxIndex;

}

//

// 为编号为 nIndex 的对象调度任务,为下载任务最繁重的对象减轻负担

//

BOOL CDownloadMTR::AttemperDownloadTask(int nIndex)

{

ASSERT ( m_pDownloadPub_MTR && m_pDownloadCellInfo );

if ( m_nThreadCount <= 1 || m_pDownloadCellInfo[nIndex].nWillDownloadSize == -1 )

return FALSE;

int nUndownloadBytes = 0;

int nIndex_Heavy = GetUndownloadMaxBytes ( nUndownloadBytes );

if ( nIndex_Heavy == -1 || nIndex_Heavy == nIndex )

return FALSE;

if ( m_pDownloadPub_MTR[nIndex_Heavy].ThreadIsRunning() && nUndownloadBytes < 100*1024 )

return FALSE;

ASSERT ( nIndex_Heavy >= 0 && nIndex_Heavy < m_nThreadCount );

ASSERT ( m_pDownloadPub_MTR[nIndex_Heavy].Get_WillDownloadStartPos() ==

m_pDownloadCellInfo[nIndex_Heavy].nWillDownloadStartPos );

DbgLog ( "下载对象.%d 帮 %d (%s) 减轻负担n", nIndex, nIndex_Heavy,

m_pDownloadPub_MTR[nIndex_Heavy].ThreadIsRunning()?"运行":"停止" );

// 给空闲下载对象分配新任务

m_pDownloadCellInfo[nIndex].nWillDownloadSize =

( m_pDownloadPub_MTR[nIndex_Heavy].ThreadIsRunning()?(nUndownloadBytes/2) : nUndownloadBytes );

m_pDownloadCellInfo[nIndex].nWillDownloadStartPos =

m_pDownloadPub_MTR[nIndex_Heavy].Get_WillDownloadStartPos() +

m_pDownloadPub_MTR[nIndex_Heavy].Get_WillDownloadSize() -

m_pDownloadCellInfo[nIndex].nWillDownloadSize;

m_pDownloadCellInfo[nIndex].nDownloadedSize = 0;

DbgLog ( "空闲下载对象.%d 分配新任务: %d(0x%08x) - %d(0x%08x) 共 %d(0x%08x)n",

10 1

nIndex,

m_pDownloadCellInfo[nIndex].nWillDownloadStartPos,

m_pDownloadCellInfo[nIndex].nWillDownloadStartPos,

m_pDownloadCellInfo[nIndex].nWillDownloadStartPos +

m_pDownloadCellInfo[nIndex].nWillDownloadSize,

m_pDownloadCellInfo[nIndex].nWillDownloadStartPos +

m_pDownloadCellInfo[nIndex].nWillDownloadSize,

m_pDownloadCellInfo[nIndex].nWillDownloadSize,

m_pDownloadCellInfo[nIndex].nWillDownloadSize );

// 启动空闲下载对象的下载任务

if ( m_pDownloadCellInfo[nIndex].nWillDownloadSize == 0 )

return FALSE;

m_pDownloadPub_MTR[nIndex].ResetVar ();

if ( !m_pDownloadPub_MTR[nIndex].Download ( m_pDownloadCellInfo[nIndex].nWillDownloadStartPos,

m_pDownloadCellInfo[nIndex].nWillDownloadSize, m_pDownloadCellInfo[nIndex].nDownloadedSize ) )

return FALSE;

// 减轻繁忙下载对象的任务

m_pDownloadCellInfo[nIndex_Heavy].nWillDownloadSize -= m_pDownloadCellInfo[nIndex].nWillDownloadSize;

m_pDownloadPub_MTR[nIndex_Heavy].Set_WillDownloadSize

( m_pDownloadCellInfo[nIndex_Heavy].nWillDownloadSize );

DbgLog ( "繁忙下载对象.%d 下载了 %d(0x%08x) 未完 %d(0x%08x) 字节,调整任务为: %d(0x%08x)

- %d(0x%08x) 共 %d(0x%08x)n",

nIndex_Heavy, m_pDownloadPub_MTR[nIndex_Heavy].Get_DownloadedSize(),

m_pDownloadPub_MTR[nIndex_Heavy].Get_DownloadedSize(),

nUndownloadBytes, nUndownloadBytes,

m_pDownloadCellInfo[nIndex_Heavy].nWillDownloadStartPos,

m_pDownloadCellInfo[nIndex_Heavy].nWillDownloadStartPos,

m_pDownloadCellInfo[nIndex_Heavy].nWillDownloadStartPos +

m_pDownloadCellInfo[nIndex_Heavy].nWillDownloadSize,

m_pDownloadCellInfo[nIndex_Heavy].nWillDownloadStartPos +

m_pDownloadCellInfo[nIndex_Heavy].nWillDownloadSize,

m_pDownloadCellInfo[nIndex_Heavy].nWillDownloadSize,

m_pDownloadCellInfo[nIndex_Heavy].nWillDownloadSize );

return TRUE;

}

//

// 等待下载结束

//

ENUM_DOWNLOAD_RESULT CDownloadMTR::WaitForDownloadFinished()

{

ASSERT ( HANDLE_IS_VALID(m_hEvtEndModule) );

int nCount = m_nThreadCount + 1;

ENUM_DOWNLOAD_RESULT eDownloadResult = ENUM_DOWNLOAD_RESULT_FAILED;

HANDLE *lpHandles = new HANDLE[nCount];

if ( !lpHandles ) goto End;

while ( TRUE )

{

nCount = 0;

for ( int nIndex=0; nIndex

{

HANDLE hThread = m_pDownloadPub_MTR[nIndex].Get_Thread_Handle ();

if ( HANDLE_IS_VALID(hThread) )

lpHandles[nCount++] = hThread;

}

lpHandles[nCount++] = m_hEvtEndModule;

if ( nCount == 1 )

{

10 1

if ( Get_TotalDownloadedSize() >= m_pDownloadPub_Info->Get_FileTotalSize() )

{

ASSERT ( Get_TotalDownloadedSize() == m_pDownloadPub_Info->Get_FileTotalSize() );

eDownloadResult = ENUM_DOWNLOAD_RESULT_SUCCESS;

}

else

eDownloadResult = ENUM_DOWNLOAD_RESULT_FAILED;

goto End;

}

//获得通知对象的索引

int nRet = (int)WaitForMultipleObjects ( nCount/*要等待的内核对象的数目*/,

lpHandles,// 句柄数组首地址

FALSE/*只要其中任何一个得到通知就可以返回*/,

INFINITE/*最大时间(等待超时)*/ ) -

WAIT_OBJECT_0;

// 某下载对象完成任务了

if ( nRet >= 0 && nRet < nCount-1 )

{

nIndex = FindIndexByThreadHandle ( lpHandles[nRet] );//通过线程句柄查找线程编号

if ( ( nIndex >= 0 && nIndex < m_nThreadCount ) )

{

if ( !m_pDownloadPub_MTR[nIndex].Is_DownloadSuccess() ||

!AttemperDownloadTask ( nIndex ) )

{

m_pDownloadPub_MTR[nIndex].Clear_Thread_Handle ();

}

}

else

{

eDownloadResult = ENUM_DOWNLOAD_RESULT_CANCEL;

goto End;

}

}

// 模块结束

else

{

eDownloadResult = ENUM_DOWNLOAD_RESULT_CANCEL;

goto End;

}

}

End:

// 等待所有下载线程结束

if ( eDownloadResult != ENUM_DOWNLOAD_RESULT_SUCCESS )

{

nCount = 0;

for ( int nIndex=0; nIndex

{

HANDLE hThread = m_pDownloadPub_MTR[nIndex].Get_Thread_Handle ();

if ( HANDLE_IS_VALID(hThread) )

lpHandles[nCount++] = hThread;

}

WaitForMultipleObjects ( nCount, lpHandles, TRUE, 500*1000 );

}

if ( lpHandles ) delete[] lpHandles;

return eDownloadResult;

}

//通过线程句柄查找线程编号

int CDownloadMTR::FindIndexByThreadHandle(HANDLE hThread)

{

10 1

for ( int nIndex=0; nIndex

{

HANDLE hThread_Temp = m_pDownloadPub_MTR[nIndex].Get_Thread_Handle ();

if ( HANDLE_IS_VALID(hThread_Temp) && hThread_Temp == hThread )

return nIndex;

}

return -1;

}

int CDownloadMTR::GetDownloadInfoWholeSize()

{

return ( sizeof(t_DownloadCellInfo)*m_nThreadCount + sizeof(t_BaseDownInfo) );

}

//

// 获取下载所消耗的时间(毫秒),可用来计算下载速度和推算剩余时间

//

DWORD CDownloadMTR::GetDownloadElapsedTime()

{

return (GetTickCount() - m_dwDownloadStartTime);

}

//

// 停止下载。将所有下载线程关闭,将下载对象删除,文件关闭

//

void CDownloadMTR::StopDownload()

{

if ( HANDLE_IS_VALID(m_hEvtEndModule) )

{

::SetEvent ( m_hEvtEndModule );//将m_hEvtEndModule设置为一直发信号状态

}

// 设置多线程下载使用的对象的参数

if ( m_pDownloadPub_MTR )

{

for ( int nIndex=0; nIndex

{

m_pDownloadPub_MTR[nIndex].StopDownload ();

}

}

if ( m_pDownloadPub_Info )

{

m_pDownloadPub_Info->StopDownload ();

}

if ( HANDLE_IS_VALID(m_hThread) )

{

WaitForThreadEnd ( m_hThread,100*1000 );

CLOSE_HANDLE ( m_hThread )

}

DeleteDownloadObjectAndDataMTR ();

DeleteDownloadObject_Info ();

CLOSE_HANDLE ( m_hEvtEndModule );

}

void CDownloadMTR::StandardSaveFileName ()

{

ASSERT ( m_gth() > 0 );//若下载目录为空,则出错

StandardizationPathBuffer ( m_fer(MAX_PATH), MAX_PATH );//标准化下载目录缓冲

m_eBuffer ();//释放多余内存

10 1

MakeSureDirectory ( m_csSavePath ); //确保目录存在,如果目录不存在就创建目录,可以创建多层次的目录

char szOnlyFileName_NoExt_User[MAX_PATH] = {0};

char szExtensionName_User[MAX_PATH] = {0};

// 如果用户指定了新的保存文件名,就用新的。

if ( m_gth() > 0 )

{

CString csFileNameByURL = GetLocalFileNameByURL ( m_csDownloadURL );

if ( eNoCase(m_csSaveOnlyFileName) != 0 )

{

PartFileAndExtensionName ( m_csSaveOnlyFileName, szOnlyFileName_NoExt_User, MAX_PATH,

szExtensionName_User, MAX_PATH );

}

}

CString csExtensionName_Remote;

CString csFileName_Remote = m_pDownloadPub_Info->GetDownloadObjectFileName

( &csExtensionName_Remote );

if ( strlen(szOnlyFileName_NoExt_User) > 0 )

{

if ( strlen(szExtensionName_User) < 1 )

STRNCPY_CS ( szExtensionName_User, csExtensionName_Remote );

m_ ( "%s%s.%s", StandardizationFileForPathName(m_csSavePath,FALSE),

StandardizationFileForPathName(szOnlyFileName_NoExt_User,TRUE),

StandardizationFileForPathName(szExtensionName_User,TRUE) );

}

else

{

m_ ( "%s%s", StandardizationFileForPathName(m_csSavePath,FALSE),

StandardizationFileForPathName(csFileName_Remote,TRUE) );

}

}

//

// 根据 URL 来获取本地保存的文件名

//

CString CDownloadMTR::GetLocalFileNameByURL ( LPCTSTR lpszDownloadURL )

{

if ( !lpszDownloadURL || strlen(lpszDownloadURL) < 1 )

return "";

char szOnlyPath[MAX_PATH] = {0};

char szOnlyFileName[MAX_PATH] = {0};

if ( !PartFileAndPathByFullPath ( lpszDownloadURL, szOnlyFileName, MAX_PATH, szOnlyPath, MAX_PATH ) )

return "";

return szOnlyFileName;

}

//

// 获取文件大小

//

int CDownloadMTR::Get_FileTotaleSize()

{

if ( !m_pDownloadPub_Info ) return -1;

return m_pDownloadPub_Info->Get_FileTotalSize ();

}

//

// 获取已下载的字节数,包括以前下载的和本次下载的

//

int CDownloadMTR::Get_TotalDownloadedSize()

{

10 1

if ( !m_pDownloadPub_Info ) return -1;

int nTotalUndownloadBytes = 0;

for ( int nIndex=0; nIndex

{

nTotalUndownloadBytes += m_pDownloadPub_MTR[nIndex].GetUndownloadBytes();

}

int nFileSize = m_pDownloadPub_Info->Get_FileTotalSize();

if ( nFileSize < 1 ) return -1;

// 文件大小减去未完成的,就是已下载的

return ( nFileSize - nTotalUndownloadBytes );

}

//获取本次下载的字节数

int CDownloadMTR::Get_TotalDownloadedSize_ThisTimes()

{

m_CSFor_ ();

int nTotalDownloadedSize_ThisTimes = m_nTotalDownloadedSize_ThisTimes;

m_CSFor_ ();

return nTotalDownloadedSize_ThisTimes;

}

****************************************************************************************************

****************************************************************************************************

DownloadPub.h

#include "SocketClient.h"

#include

#include

//缺省的重试次数

const UINT DEFAULT_RETRY_MAX = 10;

#define SLEEP_RETURN_Down(x)

{

if ( ::WaitForSingleObject ( m_hEvtEndModule, x ) == WAIT_OBJECT_0 )

return DownloadEnd(FALSE);

}

enum

{

NOTIFY_TYPE_GOT_REMOTE_FILENAME, // 取得远程站点文件名, 当被下载的文件

//被重定向时才发送该通知,lpNotifyData 为

//LPCTSTR 类型的文件名字符串指针

NOTIFY_TYPE_GOT_REMOTE_FILESIZE, // 取得远程站点文件大小,lpNotifyData 为

//int 类型的文件大小

NOTIFY_TYPE_START_DOWNLOAD, // 开始下载,lpNotifyData 无用

NOTIFY_TYPE_END_DOWNLOAD, // 结束下载,lpNotifyData 为

//ENUM_DOWNLOAD_RESULT 类型的下载

结果

NOTIFY_TYPE_WILL_DOWNLOAD_SIZE, // 本次需要下载的大小,lpNotifyData 为

//int 类型的需要下载的大小

NOTIFY_TYPE_THREAD_DOWNLOADED_SIZE, // 某线程已下载的数据大小,

lpNotifyData 为

//int 类型的已下载的大小

};

typedef struct _DownloadNotifyPara

{

int nIndex;

UINT nNotityType;

LPVOID lpNotifyData;

LPVOID pDownloadMTR;

10 1

} t_DownloadNotifyPara;

typedef CArray t_Ary_DownloadNotifyPara;

// 消息通知回调函数

typedef void (*FUNC_DownloadNotify) ( t_DownloadNotifyPara *pDownloadNotifyPara, WPARAM wParam );

typedef void (*FUNC_SaveDownloadInfo) ( int nIndex, int nDownloadedSize, int nSimpleSaveSize, WPARAM wParam );

// 缺省端口号

#define DEFAULT_HTTP_PORT 80

#define DEFAULT_HTTPS_PORT 443

#define DEFAULT_SOCKS_PORT 6815

typedef enum _DownloadResult

{

ENUM_DOWNLOAD_RESULT_SUCCESS,

ENUM_DOWNLOAD_RESULT_FAILED,

ENUM_DOWNLOAD_RESULT_CANCEL,

} ENUM_DOWNLOAD_RESULT;

class CDownloadPub

{

public:

BOOL ThreadIsRunning (); //线程正在运行

CString GetDownloadObjectFileName ( CString *pcsExtensionName=NULL );//获取下载对象的文件名(带扩展名

的)

void Clear_Thread_Handle(); //关闭线程

void ResetVar(); //重置下载信息,清空线程等

int GetUndownloadBytes (); //获取尚未下载的字节数,写到文件中的和临时缓冲里的都算是已经

下载的

BOOL ThreadProc_Download(); // 下载任务的线程函数

BOOL SetSaveFileName ( LPCTSTR lpszSaveFileName );//变量m_csSaveFileName设置为保存文件名

int Get_WillDownloadSize(); //获得要下载的字节数

void Set_WillDownloadSize ( int nWillDownloadSize );//为编号为nIndex线程分配要下载的字节数

int Get_DownloadedSize(); //获得已下载字节数

void Set_DownloadedSize ( int nDownloadedSize );//编号为nIndex线程已下载字节数

int Get_TempSaveBytes(); //获得临时存储的字节数

void Set_TempSaveBytes ( int nTempSaveBytes );//设置临时存储字节数

int m_nIndex; //线程编号

CString GetRemoteFileName (); //获得远程文件名

BOOL SetDownloadUrl ( LPCTSTR lpszDownloadUrl );//将变量m_csDownloadUrl设置为下载地址

virtual BOOL Connect (); //连接状态

BOOL GetRemoteSiteInfo (); //获得远程站点信息(默认重试100次)

CDownloadPub(); //构造函数

virtual ~CDownloadPub(); //析构函数

void SetReferer(LPCTSTR lpszReferer); // 设置Referer

void SetUserAgent(LPCTSTR lpszUserAgent);// 设置UserAgent

// 设置保存下载信息回调函数

void Set_SaveDownloadInfo_Callback ( FUNC_SaveDownloadInfo Proc_SaveDownloadInfo, WPARAM wParam );

// 创建线程下载文件

virtual BOOL Download (

int nWillDownloadStartPos, // 要下载文件的开始位置

int nWillDownloadSize, // 本次需要下载的大小,-1表示一直下载到文件尾

int nDownloadedSize // 已下载的字节数,指完全写到文件中的字节数

);

BOOL Is_SupportResume () { return m_bSupportResume; } //是否支持断点续传

CString Get_ProtocolType () { return m_csProtocolType; } //获取协议类型

time_t Get_TimeLastModified() { return m_e(); }//获取最后修改时间

int Get_FileTotalSize() { return m_nFileTotalSize; } //获取文件总大小

10 1

CString Get_DownloadUrl () { return m_csDownloadUrl; } //获得下载地址

BOOL Is_DownloadSuccess() { return m_bDownloadSuccess; } //下载是否成功

HANDLE Get_Thread_Handle() { return m_hThread; } //获得线程句柄

int Get_WillDownloadStartPos() { return m_nWillDownloadStartPos; }//获得下载起始位置

CString Get_ServerName() { return m_csServer; } //获得服务器名

void StopDownload (); //停止下载

LPVOID m_pDownloadMTR; //多线程断点续传模块指针

protected:

virtual BOOL GetRemoteSiteInfo_Pro(); //获得远程服务器信息

virtual BOOL DownloadOnce(); //开始下载

CString GetRefererFromURL(); //获得文件下载地址(不包括文件名)

int SaveDataToFile ( char *data, int size ); //保存下载的数据

//从服务器接收数据并保存到文件中

virtual BOOL RecvDataAndSaveToFile(CSocketClient &SocketClient,char *szTailData=NULL, int nTailSize=0);

BOOL DownloadEnd ( BOOL bRes ); // 下载结束

CFile m_file;

HANDLE m_hEvtEndModule; // 模块结束事件

CSocketClient m_SocketClient; // 连接服务器的 Socket

CString m_csDownloadUrl; // 待下载URL

CString m_csSaveFileName; //文件保存名

FUNC_SaveDownloadInfo m_Proc_SaveDownloadInfo; // 保存下载信息的回调函数

WPARAM m_wSaveDownloadInfo_Param;

BOOL m_bSupportResume; // 是否支持断点续传

HANDLE m_hThread;

// 文件以及下载大小

int m_nFileTotalSize; // 文件总的大小,-1表示未知文件大小

int m_nWillDownloadStartPos; // 要下载文件的开始位置

int m_nWillDownloadSize; // 本次需要下载的大小,-1表示不知道文件大

小,所以下载到服务器关闭连接为止

CCriticalSection m_CSFor_WillDownloadSize; // 访问 m_nWillDownloadSize 变量的互斥锁

int m_nTempSaveBytes; // 存放在临时缓冲中的字节数

CCriticalSection m_CSFor_TempSaveBytes; // 访问 m_nTempSaveBytes 变量的互斥锁

int m_nDownloadedSize; // 已下载的字节数,指完全写到文件中的字节

数,不包含临时缓冲里的数据

CCriticalSection m_CSFor_DownloadedSize; // 访问 m_nDownloadedSize 变量的互斥锁

CTime m_TimeLastModified; // 文件日期(远程文件的信息)

// Referer

CString m_csReferer;

CString m_csCookieFlag;

// UserAgent

CString m_csUserAgent;

// 下载过程中所用的变量

CString m_csProtocolType; // 所使用的传输协议:http、ftp等

CString m_csServer;

CString m_csObject;

CString m_csFileName;

USHORT m_nPort; //unsigned short

10 1

private:

BOOL OpenFileForSave();

BOOL m_bDownloadSuccess;

};

int Base64Encode(LPCTSTR lpszEncoding, CString &strEncoded);

int Base64Decode(LPCTSTR lpszDecoding, CString &strDecoded);

//地址解析成功

BOOL ParseURL(LPCTSTR lpszURL,CString& strServer,CString& strObject,USHORT& nPort, CString &csProtocolType);

//

void Set_DownloadNotify_Callback ( FUNC_DownloadNotify Proc_DownloadNotify, WPARAM wParam );

void SetRetryTimes ( int nRetryTimes ); //设置重试次数

#include "stdafx.h"

#include "NetDownMTR.h"

#include "DownloadPub.h"

// for PathFindExtension () / PathFindFileName ()

#include "Shlwapi.h"

#pragma comment ( lib, "" )

#ifdef _DEBUG

#undef THIS_FILE

static char THIS_FILE[]=__FILE__;

#define new DEBUG_NEW

#endif

// 下载数据保存的临时缓冲大小

#define TEMP_SAVE_BUFFER_SIZE (10*NET_BUFFER_SIZE)

// 当下载的数据达到这个数的时候才保存到文件中

#define WRITE_TEMP_SAVE_MIN_BYTES(TEMP_SAVE_BUFFER_SIZE - 2*NET_BUFFER_SIZE)

void DownloadNotify ( int nIndex, UINT nNotityType, LPVOID lpNotifyData, LPVOID pDownloadMTR );

// 重试多少次

int g_nRetryTimes = 100;

void SetRetryTimes ( int nRetryTimes )

{

g_nRetryTimes = nRetryTimes;

}

//////////////////////////////////////////////////////////////////////

// Construction/Destruction

//////////////////////////////////////////////////////////////////////

CDownloadPub::CDownloadPub()

{

m_hThread = NULL;

m_TimeLastModified = -1;

m_csDownloadUrl = _T("");

m_csSaveFileName = _T("");

m_Proc_SaveDownloadInfo = NULL;

m_wSaveDownloadInfo_Param = NULL;

m_bSupportResume = FALSE;

m_nFileTotalSize = -1;

m_csReferer = _T("");

m_csUserAgent = _T("YSL-HttpDown/2.0");

m_csProtocolType = "http";

m_csServer = _T("");

m_csObject = _T("");

m_csFileName = _T("");

m_nPort = DEFAULT_HTTP_PORT ;

10 1

m_nIndex = -1;

m_csCookieFlag = _T("");

ResetVar ();

m_hEvtEndModule = ::CreateEvent ( NULL, TRUE, FALSE, NULL );

m_pDownloadMTR = NULL;

}

void CDownloadPub::ResetVar()

{

Clear_Thread_Handle ();

m_nWillDownloadStartPos = 0;

m_nWillDownloadSize = -1;

m_nDownloadedSize = 0;

m_nTempSaveBytes = 0;

m_bDownloadSuccess = FALSE;

}

CDownloadPub::~CDownloadPub()

{

StopDownload ();

Clear_Thread_Handle ();

}

void CDownloadPub::StopDownload ()

{

m_BlockingCall ();

if ( HANDLE_IS_VALID(m_hEvtEndModule) )

{

::SetEvent ( m_hEvtEndModule );

WaitForThreadEnd ( m_hThread,30*1000 );

CLOSE_HANDLE ( m_hEvtEndModule );

m_hEvtEndModule = NULL;

Clear_Thread_Handle ();

}

}

// 设置Referer

void CDownloadPub::SetReferer(LPCTSTR lpszReferer)

{

if( lpszReferer != NULL )

m_csReferer = lpszReferer;

else

m_csReferer = _T("");

}

// 设置UserAgent

void CDownloadPub::SetUserAgent(LPCTSTR lpszUserAgent)

{

m_csUserAgent = lpszUserAgent;

if( m_y())

m_csUserAgent = _T("YSL-HttpDown/2.0");

}

//

// 设置保存下载信息回调函数

//

void CDownloadPub::Set_SaveDownloadInfo_Callback ( FUNC_SaveDownloadInfo Proc_SaveDownloadInfo, WPARAM

wParam )

{

m_Proc_SaveDownloadInfo = Proc_SaveDownloadInfo;

m_wSaveDownloadInfo_Param = wParam;

10 1

}

//

// 下载任务的线程函数

//

DWORD WINAPI ThreadProc_Download(

LPVOID lpParameter // thread data

)

{

CDownloadPub *pDownloadPub = (CDownloadPub*)lpParameter;

ASSERT ( pDownloadPub );

return pDownloadPub->ThreadProc_Download ();

}

BOOL CDownloadPub::ThreadProc_Download()

{

BOOL bRet = FALSE;

for ( int i=0; i

{

if ( DownloadOnce () )

{

bRet = TRUE;

goto finished;

}

SLEEP_RETURN_Down ( 5*1000 );

}

finished:

TRACE ( "(%d) 线程结束 %s", m_nIndex, bRet?"成功":"失败" );

return DownloadEnd ( bRet );

}

BOOL CDownloadPub::DownloadOnce()

{

// 打开文件

if ( !OpenFileForSave () )

return FALSE;

// 连接到服务器

if ( !m__Connected () )

{

if ( !Connect () )

return FALSE;

}

return TRUE;

}

//

// 创建线程下载文件

//

BOOL CDownloadPub::Download (

int nWillDownloadStartPos, // 要下载文件的开始位置

int nWillDownloadSize, // 本次需要下载的大小,-1表示一直下载到文件尾

int nDownloadedSize // 已下载的字节数,指完全写到文件中的字节数

)

{

if ( nWillDownloadSize == 0 ) return TRUE;

// 设置下载参数

m_nWillDownloadStartPos = nWillDownloadStartPos;

Set_WillDownloadSize ( nWillDownloadSize );

10 1

if ( m_nFileTotalSize > 0 && Get_WillDownloadSize() > m_nFileTotalSize )

{

Set_WillDownloadSize ( m_nFileTotalSize );

}

Set_DownloadedSize ( nDownloadedSize );

// 创建一个下载线程

DWORD dwThreadId = 0;

m_hThread = CreateThread ( NULL, 0, ::ThreadProc_Download, LPVOID(this), 0, &dwThreadId );

if ( !HANDLE_IS_VALID(m_hThread) )

{

Log ( L_WARNING, "创建线程(%d)失败!", m_nIndex );

return FALSE;

}

return TRUE;

}

//

// 下载结束

//

BOOL CDownloadPub::DownloadEnd(BOOL bRes)

{

m_bDownloadSuccess = bRes;

m_nect ();

if ( HANDLE_IS_VALID ( m_file.m_hFile ) )

{

m_ ();

}

TRACE ( "(%d) %s 结束一次下载n", m_nIndex, bRes?"成功":"!!! 失败 !!!" );

return bRes;

}

BOOL CDownloadPub::Connect()

{

if ( !HANDLE_IS_VALID(m_hEvtEndModule) )

return FALSE;

if ( m_y() )

{

Log ( L_WARNING, "线程(%d)请输入下载地址!", m_nIndex );

return FALSE;

}

m_ntOfEndModule ( m_hEvtEndModule );

m_SocketClient.m_nIndex = m_nIndex;

// 连接到服务器

if ( !m_t ( m_csServer, m_nPort ) )

return FALSE;

Log ( L_NORMAL, "线程(%d) 连接服务器 [%s:%d] 成功!", m_nIndex, m_csServer, m_nPort );

return TRUE;

}

BOOL CDownloadPub::SetDownloadUrl(LPCTSTR lpszDownloadUrl)

{

if ( !lpszDownloadUrl ) return FALSE;

m_csDownloadUrl = lpszDownloadUrl;

// 检验要下载的URL是否为空

m_ft();//清理地址栏左侧空格区

m_ght();//右侧空格区

10 1

if( m_y() )

return FALSE;

// 检验要下载的URL是否有效

if ( !ParseURL(m_csDownloadUrl, m_csServer, m_csObject, m_nPort, m_csProtocolType))

{

TRACE(_T("解析地址: %s 失败!n"), m_csDownloadUrl);

return FALSE;

}

return TRUE;

}

//

// 从服务器接收数据并保存到文件中

//

BOOL CDownloadPub::RecvDataAndSaveToFile(CSocketClient &SocketClient,char *szTailData/*=NULL*/, int

nTailSize/*=0*/)

{

int nDownloadedSize = Get_DownloadedSize();

if ( szTailData && nTailSize > 0 )

{

nDownloadedSize = SaveDataToFile ( szTailData, nTailSize );

if ( nDownloadedSize < 0 )

{

return FALSE;

}

}

char szRecvBuf[NET_BUFFER_SIZE] = {0}, *szTempSaveBuf = new char[TEMP_SAVE_BUFFER_SIZE];

if ( !szTempSaveBuf )

{

Log ( L_ERROR, "为线程(%d) 分配内存失败!", m_nIndex );

return FALSE;

}

m_nTempSaveBytes = 0;

while ( TRUE )

{

BOOL bDownloadFinished = FALSE;

int nReadSize = 0;

int nTempSaveBytes = Get_TempSaveBytes ();

int nRecvTotalBytes = nDownloadedSize+nTempSaveBytes; // 保存在文件中的字节数加上临时缓冲中的字

节数就是总共接收到字节数

int nWillDownloadSize = Get_WillDownloadSize ();

if (

// 从字节数判断,本次下载已经完成了。

( nWillDownloadSize > 0 && nRecvTotalBytes >= nWillDownloadSize )

||

// 模块结束事件有信号,要结束了。

( ::WaitForSingleObject ( m_hEvtEndModule, 1 ) == WAIT_OBJECT_0 )

)

{

bDownloadFinished = TRUE;

}

else

{

int nRecvBytesThisTimes = sizeof(szRecvBuf);

if ( nWillDownloadSize > 0 )

nRecvBytesThisTimes = nWillDownloadSize - nRecvTotalBytes;

ASSERT ( nRecvBytesThisTimes >= 0 );

nRecvBytesThisTimes = MIN ( nRecvBytesThisTimes, sizeof(szRecvBuf) );

10 1

nReadSize = e ( szRecvBuf, nRecvBytesThisTimes );

// 读不到数据了,所以认为下载已经完成

if ( nReadSize <= 0 )

{

if ( nWillDownloadSize <= 0 )

{

bDownloadFinished = TRUE;

}

}

else

{

// TRACE ( "对象.%d, 收到 %d 字节,我的任务是 %d (0x%08x)字节n",

// m_nIndex, nReadSize, nWillDownloadSize , nWillDownloadSize ); //w

}

}

// 先将数据保存到临时缓冲中

if ( nReadSize > 0 )

{

nReadSize = MIN ( nReadSize, TEMP_SAVE_BUFFER_SIZE-nTempSaveBytes );

memcpy ( szTempSaveBuf+nTempSaveBytes, szRecvBuf, nReadSize );

nTempSaveBytes += nReadSize;

ASSERT ( nTempSaveBytes < TEMP_SAVE_BUFFER_SIZE );

}

// 当下载已完成或者收到的数据超过一定数量时才保存到文件中

if ( bDownloadFinished || nTempSaveBytes >= WRITE_TEMP_SAVE_MIN_BYTES )

{

// 保存文件失败,下载也应该终止

nDownloadedSize = SaveDataToFile ( szTempSaveBuf, nTempSaveBytes );

if ( nDownloadedSize < 0 )

{

break;

}

nTempSaveBytes = 0;

}

Set_TempSaveBytes ( nTempSaveBytes );

if ( bDownloadFinished )

{

ASSERT ( (Get_WillDownloadSize () > 0 && nDownloadedSize >= Get_WillDownloadSize ()) ||

(nReadSize <= 0 && Get_WillDownloadSize () <= 0) ||

(::WaitForSingleObject ( m_hEvtEndModule, 1 ) == WAIT_OBJECT_0) );

break;

}

}

if ( szTempSaveBuf ) delete[] szTempSaveBuf;

szTempSaveBuf = NULL;

BOOL bRes = FALSE;

int nWillDownloadSize = Get_WillDownloadSize ();

if ( nWillDownloadSize != -1 )

{

if ( nDownloadedSize >= nWillDownloadSize )

{

bRes = TRUE;

}

}

else if ( nDownloadedSize > 0 )

{

bRes = TRUE;

}

10 1

return bRes;

}

int CDownloadPub::SaveDataToFile(char *data, int size)

{

ASSERT ( HANDLE_IS_VALID ( m_file.m_hFile ) );

if ( !data || size < 0 ) return -1;

int nDownloadedSize = -1;

// 保存下载的数据

ASSERT ( HANDLE_IS_VALID(m_file.m_hFile) );

BOOL bRet = TRUE;

TRY

{

m_ ( data, size );

}

CATCH( CFileException, e )

{

e->Delete ();

bRet = FALSE;

}

END_CATCH

if ( !bRet )

{

Log ( L_WARNING, "线程(%d)将数据写入本地文件 [%s] 失败!", m_nIndex, m_eName() );

}

else

{

nDownloadedSize = Get_DownloadedSize();

nDownloadedSize += size;

Set_DownloadedSize ( nDownloadedSize );

if ( m_Proc_SaveDownloadInfo )

m_Proc_SaveDownloadInfo ( m_nIndex, nDownloadedSize, size, m_wSaveDownloadInfo_Param );

}

if ( !bRet ) return -1;

return nDownloadedSize;

}

BOOL CDownloadPub::GetRemoteSiteInfo_Pro()

{

return TRUE;

}

BOOL CDownloadPub::GetRemoteSiteInfo ()

{

for ( int i=0; i

{

if ( GetRemoteSiteInfo_Pro () )

{

m_nect ();

return TRUE;

}

SLEEP_RETURN_Down ( 5*1000 );

}

m_nect ();

return FALSE;

}

//

10 1

//获得远程文件名

//

CString CDownloadPub::GetRemoteFileName()

{

int nPos = m_eFind ( '/' );//从末端开始查找“/”

if ( nPos <= 0 ) return m_csObject;

return m_ ( nPos+1 );//“/”到末端即为文件名

}

//

// 从URL里面拆分出Server、Object、协议类型等信息,其中 Object 里的值是区分大小写的,否则有些网站可能会

下载不了

//

BOOL ParseURL(LPCTSTR lpszURL, CString &strServer, CString &strObject,USHORT& nPort, CString

&csProtocolType)

{

if ( !lpszURL || strlen(lpszURL) < 1 ) return FALSE;

CString csURL_Lower(lpszURL);

csURL_ft();

csURL_ght();

csURL_e ( "", "/" );

CString csURL = csURL_Lower;

csURL_wer ();

// 清除数据

strServer = _T("");

strObject = _T("");

nPort = 0;

int nPos = csURL_("://");

if( nPos == -1 )

{

csURL_ ( 0, "" );

( 0, "" );

nPos = 4;

}

csProtocolType = csURL_ ( nPos );

csURL_Lower = csURL_( gth()+3 );

csURL = ( gth()+3 );

nPos = csURL_('/');

if ( nPos == -1 )

return FALSE;

strObject = (nPos);

CString csServerAndPort = csURL_(nPos);

// 查找是否有端口号,站点服务器域名一般用小写

nPos = (":");

if( nPos == -1 )

{

strServer = csServerAndPort;

nPort = DEFAULT_HTTP_PORT;

}

else

{

strServer = ( nPos );

csServerAndPort = ( nPos+1 );

nPort = (USHORT)_ttoi((LPCTSTR)csServerAndPort);

}

return TRUE;

}

10 1

void CDownloadPub::Set_DownloadedSize(int nDownloadedSize)

{

m_CSFor_ ();

m_nDownloadedSize = nDownloadedSize;

m_CSFor_ ();

DownloadNotify ( m_nIndex, NOTIFY_TYPE_THREAD_DOWNLOADED_SIZE, (LPVOID)nDownloadedSize,

m_pDownloadMTR );

}

int CDownloadPub::Get_DownloadedSize()

{

int nDownloadedSize = 0;

m_CSFor_ ();

nDownloadedSize = m_nDownloadedSize;

m_CSFor_ ();

return nDownloadedSize;

}

void CDownloadPub::Set_TempSaveBytes(int nTempSaveBytes)

{

m_CSFor_ ();

m_nTempSaveBytes = nTempSaveBytes;

m_CSFor_ ();

}

int CDownloadPub::Get_TempSaveBytes()

{

int nTempSaveBytes = 0;

m_CSFor_ ();

nTempSaveBytes = m_nTempSaveBytes;

m_CSFor_ ();

return nTempSaveBytes;

}

void CDownloadPub::Set_WillDownloadSize(int nWillDownloadSize)

{

m_CSFor_ ();

m_nWillDownloadSize = nWillDownloadSize;

m_CSFor_ ();

DownloadNotify ( m_nIndex, NOTIFY_TYPE_WILL_DOWNLOAD_SIZE, (LPVOID)m_nWillDownloadSize,

m_pDownloadMTR );

}

int CDownloadPub::Get_WillDownloadSize()

{

int nWillDownloadSize = 0;

m_CSFor_ ();

nWillDownloadSize = m_nWillDownloadSize;

m_CSFor_ ();

return nWillDownloadSize;

}

BOOL CDownloadPub::SetSaveFileName(LPCTSTR lpszSaveFileName)

{

if ( !lpszSaveFileName ) return FALSE;

m_csSaveFileName = lpszSaveFileName;

if ( m_y() )

return FALSE;

return TRUE;

10 1

}

//

// 获取尚未下载的字节数,写到文件中的和临时缓冲里的都算是已经下载的

//

int CDownloadPub::GetUndownloadBytes()

{

// 总共需要下载的字节数减去已经下载的字节数

return Get_WillDownloadSize () - ( Get_DownloadedSize () + Get_TempSaveBytes () );

}

BOOL CDownloadPub::OpenFileForSave()

{

ASSERT ( !m_y() );

// 打开本地文件

if ( HANDLE_IS_VALID ( m_file.m_hFile ) )

{

m_ ();

}

BOOL bRet = FALSE;

TRY

{

if ( m_ ( m_csSaveFileName,

CFile::modeCreate|CFile::modeNoTruncate|CFile::modeReadWrite|CFile::typeBinary|CFile::shareDenyNone ) )

{

int nWillDownloadStartPos = Get_WillDownloadStartPos ();

nWillDownloadStartPos += Get_DownloadedSize ();

if ( m_ ( nWillDownloadStartPos, CFile::begin ) == nWillDownloadStartPos )

bRet = TRUE;

}

}

CATCH( CFileException, e )

{

e->Delete ();

bRet = FALSE;

}

END_CATCH

if ( !bRet )

{

Log ( L_WARNING, "线程(%d) 打开文件 [%s] 失败。 %s", m_nIndex,

m_csSaveFileName, ::hwFormatMessage ( GetLastError() ) );

}

return bRet;

}

void CDownloadPub::Clear_Thread_Handle()

{

CLOSE_HANDLE ( m_hThread );

}

// 消息通知的回调函数

FUNC_DownloadNotify f_Proc_DownloadNotify = NULL;

WPARAM f_wDownloadNotify_Param = NULL;

//

// 设置通知回调函数

//

void Set_DownloadNotify_Callback ( FUNC_DownloadNotify Proc_DownloadNotify, WPARAM wParam )

{

f_Proc_DownloadNotify = Proc_DownloadNotify;

f_wDownloadNotify_Param = wParam;

10 1

}

void DownloadNotify ( int nIndex, UINT nNotityType, LPVOID lpNotifyData, LPVOID pDownloadMTR )

{

ASSERT ( pDownloadMTR );

t_DownloadNotifyPara DownloadNotifyPara = {0};

= nIndex;

yType = nNotityType;

fyData = lpNotifyData;

oadMTR = pDownloadMTR;

if ( f_Proc_DownloadNotify )

f_Proc_DownloadNotify ( &DownloadNotifyPara, f_wDownloadNotify_Param );

}

//

// 获取下载对象的文件名(带扩展名的)

//

CString CDownloadPub::GetDownloadObjectFileName(CString *pcsExtensionName)

{

ASSERT ( !m_y() );

CString csOnlyPath, csOnlyFileName, csExtensionName;//光路径(无文件名),光文件名(无路径),扩展名

if ( !PartPathAndFileAndExtensionName ( m_csObject, &csOnlyPath, &csOnlyFileName, &csExtensionName ) )

return "";

if ( pcsExtensionName ) *pcsExtensionName = csExtensionName;

if ( !y() )

{

csOnlyFileName += ".";

csOnlyFileName += csExtensionName;

}

return csOnlyFileName;

}

CString CDownloadPub::GetRefererFromURL()

{

int nPos = m_eFind ( '/' );

if ( nPos < 0 ) return "";

return m_ ( nPos );

}

BOOL CDownloadPub::ThreadIsRunning()

{

if ( !HANDLE_IS_VALID(m_hThread) )

return FALSE;

return ( WaitForSingleObject ( m_hThread, 0 ) != WAIT_OBJECT_0 );

}

****************************************************************************************************

****************************************************************************************************

NetDownMTRDlg.h

#include "DownloadMTR.h"

#include "GradientProgressCtrl.h"

#include "OleListDropTarget.h"

#include "resource.h"

#define WM_DOWNLOAD_NOTIFY (WM_USER+0x100)

/////////////////////////////////////////////////////////////////////////////

// CNetDownMTRDlg dialog

class CNetDownMTRDlg : public CDialog

{

// Construction

public:

10 1

void AddURL ( LPCTSTR lpszURL );

BOOL Add_DownloadNotifyPara ( t_DownloadNotifyPara *pDownloadNotifyPara );

t_Ary_DownloadNotifyPara m_Ary_DownloadNotifyPara;

CCriticalSection m_CSFor_Ary_DownloadNotifyPara;

COleListDropTarget m_OleTarget;

void DeleteInstance_DownloadMTR();

void GetCtrlValue(); //获得各控件值

CNetDownMTRDlg(CWnd* pParent = NULL); // standard constructor

// Dialog Data

//{{AFX_DATA(CNetDownMTRDlg)

enum { IDD = IDD_NETDOWNMTR_DIALOG };

CGradientProgressCtrl m_progress;

//}}AFX_DATA

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CNetDownMTRDlg)

public:

virtual BOOL PreTranslateMessage(MSG* pMsg);

protected:

virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

HICON m_hIcon;

// Generated message map functions

//{{AFX_MSG(CNetDownMTRDlg)

virtual BOOL OnInitDialog();

afx_msg void OnSysCommand(UINT nID,LPARAM lParam);

afx_msg void OnPaint();

afx_msg HCURSOR OnQueryDragIcon();

virtual void OnOK();

afx_msg void OnBUTTONBrowse();

afx_msg void OnDestroy();

afx_msg void OnChangeEDITRemoteFileURL();

afx_msg void OnBUTTONStop();

afx_msg void OnKillfocusEDITFileName();

afx_msg void OnSetfocusEDITFileName();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

private:

void ReadSettingInfoAndSetCtrlValue ( UINT nCtrlID, LPCTSTR lpszDefaultValue );

void SetCtrlValue ();

CString GetCtrlValueAndSaveSettingInfo(UINT nCtrlID);

private:

LRESULT OnWM_SHOWLOG(WPARAM wParam, LPARAM lParam);

void SetFileNameEditSel();

void EnableSomeWindow();

BOOL m_bDownloadIsStart;

void Callback_DownloadNotify ( t_DownloadNotifyPara *pDownloadNotifyPara );

void Get_Ary_DownloadNotifyPara ( t_Ary_DownloadNotifyPara &Ary_DownloadNotifyPara );

LRESULT OnWM_DOWNLOAD_NOTIFY ( WPARAM wParam, LPARAM lParam );

CDownloadMTR* CreateInstance_DownloadMTR ();

void OutStatusString ( int nIndex, LPCTSTR lpszText );

CDownloadMTR *m_pDownloadMTR;

BOOL m_bFileNameGetFocus;

};

#include "stdafx.h"

10 1

#include "NetDownMTR.h"

#include "NetDownMTRDlg.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog

{

public:

CAboutDlg();

// Dialog Data

//{{AFX_DATA(CAboutDlg)

enum { IDD = IDD_ABOUTBOX };

//}}AFX_DATA

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CAboutDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

//{{AFX_MSG(CAboutDlg)

virtual BOOL OnInitDialog();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)

{

//{{AFX_DATA_INIT(CAboutDlg)

//}}AFX_DATA_INIT

}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CAboutDlg)

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)

//{{AFX_MSG_MAP(CAboutDlg)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CNetDownMTRDlg dialog

CNetDownMTRDlg::CNetDownMTRDlg(CWnd* pParent /*=NULL*/) : CDialog(CNetDownMTRDlg::IDD, pParent)

, m_pDownloadMTR ( NULL )

, m_bDownloadIsStart ( FALSE )

, m_bFileNameGetFocus ( FALSE )

{

//{{AFX_DATA_INIT(CNetDownMTRDlg)

10 1

//}}AFX_DATA_INIT

// Note that LoadIcon does not require a subsequent DestroyIcon in Win32

m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

m_Ary_e ( 0, 10*sizeof(t_DownloadNotifyPara) );

}

void CNetDownMTRDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX); //更新信息

//{{AFX_DATA_MAP(CNetDownMTRDlg)

DDX_Control(pDX, IDC_PROGRESS1, m_progress);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CNetDownMTRDlg, CDialog)

//{{AFX_MSG_MAP(CNetDownMTRDlg)

ON_WM_SYSCOMMAND()

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

ON_BN_CLICKED(IDC_BUTTON_Browse, OnBUTTONBrowse)

ON_WM_DESTROY()

ON_EN_CHANGE(IDC_EDIT_RemoteFileURL, OnChangeEDITRemoteFileURL)

ON_BN_CLICKED(IDC_BUTTON_Stop, OnBUTTONStop)

ON_EN_KILLFOCUS(IDC_EDIT_FileName, OnKillfocusEDITFileName)

ON_EN_SETFOCUS(IDC_EDIT_FileName, OnSetfocusEDITFileName)

ON_MESSAGE(WM_SHOWLOG,OnWM_SHOWLOG)

ON_MESSAGE(WM_DOWNLOAD_NOTIFY,OnWM_DOWNLOAD_NOTIFY)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CNetDownMTRDlg message handlers

BOOL CNetDownMTRDlg::OnInitDialog()

{

CDialog::OnInitDialog();

// Add "" menu item to system menu.

// IDM_ABOUTBOX must be in the system command range.

ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);

ASSERT(IDM_ABOUTBOX < 0xF000);

CMenu* pSysMenu = GetSystemMenu(FALSE);

if (pSysMenu != NULL)

{

CString strAboutMenu;

ring(IDS_ABOUTBOX);

if (!y())

{

pSysMenu->AppendMenu(MF_SEPARATOR);

pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);

}

}

// Set the icon for this dialog. The framework does this automatically

// when the application's main window is not a dialog

SetIcon(m_hIcon, TRUE); // Set big icon

SetIcon(m_hIcon, FALSE); // Set small icon

Set_RecvLogMsgHwnd ( GetSafeHwnd() );

10 1

SetCtrlValue (); //将对应的控件值设置为默认值

EnableSomeWindow (); //若开始下载则启动/禁止相应控件

BOOL success = m_er(this);

if(!success )

MessageBox("Ole Register Drop Target Failed");

m_ent(this);

// 总在最前

#ifndef _DEBUG

CRect rect;

GetWindowRect(&rect);

SetWindowPos(&wndTopMost, , , (), (), SWP_NOMOVE);

#endif

return TRUE; // return TRUE unless you set the focus to a control

}

void CNetDownMTRDlg::OnSysCommand(UINT nID,LPARAM lParam)

{

if ((nID & 0xFFF0) == IDM_ABOUTBOX)

{

CAboutDlg dlgAbout;

l();

}

else

{

CDialog::OnSysCommand(nID, lParam);

}

}

// If you add a minimize button to your dialog, you will need the code below

// to draw the icon. For MFC applications using the document/view model,

// this is automatically done for you by the framework.

void CNetDownMTRDlg::OnPaint()

{

if (IsIconic())

{

CPaintDC dc(this); // device context for painting

SendMessage(WM_ICONERASEBKGND, (WPARAM) eHdc(), 0);

// Center icon in client rectangle

int cxIcon = GetSystemMetrics(SM_CXICON);

int cyIcon = GetSystemMetrics(SM_CYICON);

CRect rect;

GetClientRect(&rect);

int x = (() - cxIcon + 1) / 2;

int y = (() - cyIcon + 1) / 2;

// Draw the icon

on(x, y, m_hIcon);

}

else

{

CDialog::OnPaint();

}

}

// The system calls this to obtain the cursor to display while the user drags

10 1

// the minimized window.

HCURSOR CNetDownMTRDlg::OnQueryDragIcon()

{

return (HCURSOR) m_hIcon;

}

void Callback_DownloadNotify ( t_DownloadNotifyPara *pDownloadNotifyPara, WPARAM wParam )

{

CNetDownMTRDlg *pNetDownMTRDlg = (CNetDownMTRDlg*)wParam;

ASSERT ( pNetDownMTRDlg );

pNetDownMTRDlg->Add_DownloadNotifyPara ( pDownloadNotifyPara );

//将消息放入到窗口线程的消息队列

pNetDownMTRDlg->PostMessage ( WM_DOWNLOAD_NOTIFY, WPARAM(NULL), LPARAM(NULL) );

}

LRESULT CNetDownMTRDlg::OnWM_DOWNLOAD_NOTIFY(WPARAM wParam, LPARAM lParam)

{

t_Ary_DownloadNotifyPara Ary_DownloadNotifyPara;

Get_Ary_DownloadNotifyPara ( Ary_DownloadNotifyPara );

for ( int i=0; i

{

t_DownloadNotifyPara &DownloadNotifyPara = Ary_ (i);

Callback_DownloadNotify ( &DownloadNotifyPara );

}

return TRUE;

}

//

//消息回调函数

//

void CNetDownMTRDlg::Callback_DownloadNotify(t_DownloadNotifyPara *pDownloadNotifyPara)

{

ASSERT ( pDownloadNotifyPara );

CString csStatus;

switch ( pDownloadNotifyPara->nNotityType )

{

case NOTIFY_TYPE_GOT_REMOTE_FILENAME:

{

OutStatusString ( pDownloadNotifyPara->nIndex, (LPCTSTR)pDownloadNotifyPara->lpNotifyData );

break;

}

case NOTIFY_TYPE_GOT_REMOTE_FILESIZE:

{

int nFileSize = (int)pDownloadNotifyPara->lpNotifyData;

( "文件大小为 : %s bytes", FormatFileSize(nFileSize) );

OutStatusString ( pDownloadNotifyPara->nIndex, csStatus );

ASSERT ( nFileSize ==

( (CDownloadMTR*)pDownloadNotifyPara->pDownloadMTR )->Get_FileTotaleSize() );

m_ge32 ( 0, nFileSize );

break;

}

case NOTIFY_TYPE_START_DOWNLOAD:

{

OutStatusString ( pDownloadNotifyPara->nIndex, _T("开始下载") );

break;

}

case NOTIFY_TYPE_END_DOWNLOAD:

{

m_bDownloadIsStart = FALSE;

EnableSomeWindow ();

OutStatusString ( pDownloadNotifyPara->nIndex, _T("下载结束") );

ENUM_DOWNLOAD_RESULT eDownloadResult =

10 1

(ENUM_DOWNLOAD_RESULT)(int)pDownloadNotifyPara->lpNotifyData;

CString csMsg, csElapseTime;

ASSERT ( pDownloadNotifyPara->pDownloadMTR );

( "%d ms",

( (CDownloadMTR*)pDownloadNotifyPara->pDownloadMTR )->GetDownloadElapsedTime() );

if ( eDownloadResult == ENUM_DOWNLOAD_RESULT_SUCCESS )

{

csMsg = "下载成功";

}

else if ( eDownloadResult == ENUM_DOWNLOAD_RESULT_FAILED )

{

csMsg = "下载失败";

}

else if ( eDownloadResult == ENUM_DOWNLOAD_RESULT_CANCEL )

{

csMsg = "用户取消下载";

}

else

{

ASSERT ( FALSE );

}

csMsg += ", 下载用时 : " + csElapseTime;

Log ( (eDownloadResult==ENUM_DOWNLOAD_RESULT_SUCCESS)?L_NORMAL:L_ERROR, "%s",

csMsg );

AfxMessageBox ( csMsg,

(eDownloadResult==ENUM_DOWNLOAD_RESULT_SUCCESS)?MB_ICONINFORMATION:MB_ICONWARNING );

break;

}

case NOTIFY_TYPE_WILL_DOWNLOAD_SIZE:

{

( "要下载字节数为 : %d bytes", (int)pDownloadNotifyPara->lpNotifyData );

OutStatusString ( pDownloadNotifyPara->nIndex, csStatus );

break;

}

case NOTIFY_TYPE_THREAD_DOWNLOADED_SIZE:

{

( "已下载字节数为 : %d bytes", (int)pDownloadNotifyPara->lpNotifyData );

OutStatusString ( pDownloadNotifyPara->nIndex, csStatus );

int nFileSize = ( (CDownloadMTR*)pDownloadNotifyPara->pDownloadMTR )->Get_FileTotaleSize();

CDownloadMTR *pDownloadMTR = ( (CDownloadMTR*)pDownloadNotifyPara->pDownloadMTR );

ASSERT ( pDownloadMTR );

int nTotalDownloadedSize = pDownloadMTR->Get_TotalDownloadedSize ();

if ( nFileSize > 0 && nTotalDownloadedSize > 0 )

{

if ( nTotalDownloadedSize <= nFileSize )

{

m_ ( nTotalDownloadedSize );

m_date ();

}

// 计算下载速度

int nTotalDownloadedSize_ThisTimes = pDownloadMTR->Get_TotalDownloadedSize_ThisTimes ();

int nElapsedTime = pDownloadMTR->GetDownloadElapsedTime ();

if ( nElapsedTime > 5*1000 || ( nElapsedTime > 2*1000 && nTotalDownloadedSize_ThisTimes >

1024*10 ) )

{

double dSpeed = (double)nTotalDownloadedSize_ThisTimes / ((double)nElapsedTime /

1000.0);

CString csSpeed = FormatFileSize ( dSpeed );

csSpeed += "/S";

SetDlgItemText ( IDC_STATIC_Speed, csSpeed );

10 1

}

}

break;

}

}

}

//

//点击下载按钮事件

//

void CNetDownMTRDlg::OnOK()

{

CString csRemoteFileURL = GetCtrlValueAndSaveSettingInfo(IDC_EDIT_RemoteFileURL);

//如果地址为空则提示并焦点设置为地址框

if ( y () )

{

Log ( L_WARNING, "下载地址不能为空!" );

GetDlgItem(IDC_EDIT_RemoteFileURL)->SetFocus ();

return;

}

CString csLocalFilePath = GetCtrlValueAndSaveSettingInfo(IDC_EDIT_LocalFilePath);

if ( y() )

{

Log ( L_WARNING, "保存目录不能为空!" );

GetDlgItem(IDC_EDIT_LocalFilePath)->SetFocus ();

return;

}

CString csFileName = GetCtrlValueAndSaveSettingInfo(IDC_EDIT_FileName);

if ( y() )

{

Log ( L_WARNING, "文件名不能为空!" );

GetDlgItem(IDC_EDIT_FileName)->SetFocus ();

return;

}

///设置通知回调函数

Set_DownloadNotify_Callback ( ::Callback_DownloadNotify, WPARAM(this) );

//初始化多线程下载模块

if ( !CreateInstance_DownloadMTR () )

{

Log ( L_WARNING, "创建下载例程失败!" );

return;

}

m_bDownloadIsStart = TRUE;

EnableSomeWindow (); //启用/禁止相关控件

m_ ( 0 ); //进度条设置为0

//从线程数文本框读取并设置线程数量

m_pDownloadMTR->SetThreadCount ( atoi(GetCtrlValueAndSaveSettingInfo(IDC_EDIT_ThreadNumber)) );

//开始下载

m_pDownloadMTR->Download ( csRemoteFileURL, csLocalFilePath, csFileName,FALSE );

//大编辑控件置空

SetDlgItemText ( IDC_EDIT_Log, "" );

}

//

//点击浏览按钮事件

//

void CNetDownMTRDlg::OnBUTTONBrowse()

{

10 1

//从通用对话框中选择路径

SelectPathByCommonDlg ( this, IDC_EDIT_LocalFilePath );

}

void CNetDownMTRDlg::SetCtrlValue()

{

ReadSettingInfoAndSetCtrlValue ( IDC_EDIT_RemoteFileURL, "" );//将对应的控件值设置为默认值

ReadSettingInfoAndSetCtrlValue ( IDC_EDIT_LocalFilePath, "" );

ReadSettingInfoAndSetCtrlValue ( IDC_EDIT_FileName, "" );

ReadSettingInfoAndSetCtrlValue ( IDC_EDIT_ThreadNumber, "4" );

( (CButton*)GetDlgItem(IDC_CHECK_Force) )->SetCheck ( AfxGetApp()->GetProfileInt ( "Setting",

"IDC_CHECK_Force", 0 ) );

}

void CNetDownMTRDlg::GetCtrlValue()

{

GetCtrlValueAndSaveSettingInfo ( IDC_EDIT_RemoteFileURL );

GetCtrlValueAndSaveSettingInfo ( IDC_EDIT_LocalFilePath );

GetCtrlValueAndSaveSettingInfo ( IDC_EDIT_FileName );

GetCtrlValueAndSaveSettingInfo ( IDC_EDIT_ThreadNumber );

AfxGetApp()->WriteProfileInt ( "Setting", "IDC_CHECK_Force",

( (CButton*)GetDlgItem(IDC_CHECK_Force) )->GetCheck () );

}

void CNetDownMTRDlg::ReadSettingInfoAndSetCtrlValue(UINT nCtrlID, LPCTSTR lpszDefaultValue)

{

CString csEntry;

( "%d", nCtrlID );

SetDlgItemText ( nCtrlID, AfxGetApp()->GetProfileString ( "Setting", csEntry, lpszDefaultValue ) );

}

CString CNetDownMTRDlg::GetCtrlValueAndSaveSettingInfo(UINT nCtrlID)

{

CString csEntry, csValue;

( "%d", nCtrlID );

GetDlgItemText ( nCtrlID, csValue );

ft(); ght ();

AfxGetApp()->WriteProfileString ( "Setting", csEntry, csValue );

return csValue;

}

void CNetDownMTRDlg::OnDestroy()

{

CDialog::OnDestroy();

DeleteInstance_DownloadMTR ();

GetCtrlValue ();

}

//

//若地址改变,则相应的改变地址变量和文件名

//

void CNetDownMTRDlg::OnChangeEDITRemoteFileURL()

{

CString csFileURL;

GetDlgItemText ( IDC_EDIT_RemoteFileURL, csFileURL );

ft(); ght ();

CString csFileNameByURL = CDownloadMTR::GetLocalFileNameByURL ( csFileURL );

SetDlgItemText ( IDC_EDIT_FileName, csFileNameByURL );

}

10 1

void CNetDownMTRDlg::OutStatusString(int nIndex, LPCTSTR lpszText)

{

if ( nIndex != -1 && (nIndex < 0 || nIndex > 7) )

return;

nIndex ++;

UINT nID = IDC_STATIC_Status + nIndex;

SetDlgItemText ( nID, GET_SAFE_STRING(lpszText) );

}

//初始化多线程下载模块

CDownloadMTR* CNetDownMTRDlg::CreateInstance_DownloadMTR()

{

DeleteInstance_DownloadMTR ();

m_pDownloadMTR = new CDownloadMTR;

return m_pDownloadMTR;

}

void CNetDownMTRDlg::DeleteInstance_DownloadMTR()

{

if ( m_pDownloadMTR )

delete m_pDownloadMTR;

m_pDownloadMTR = NULL;

}

void CNetDownMTRDlg::OnBUTTONStop()

{

//停止下载

if ( m_pDownloadMTR )

m_pDownloadMTR->StopDownload ();

}

//

//添加下载信息对象

//

BOOL CNetDownMTRDlg::Add_DownloadNotifyPara(t_DownloadNotifyPara *pDownloadNotifyPara)

{

if ( !pDownloadNotifyPara ) return FALSE;

t_DownloadNotifyPara DownloadNotifyPara = {0};

memcpy ( &DownloadNotifyPara, pDownloadNotifyPara, sizeof(t_DownloadNotifyPara) );

m_CSFor_Ary_ ();

BOOL bRet = FALSE;

TRY

{

m_Ary_ ( DownloadNotifyPara );

bRet = TRUE;

}

CATCH ( CMemoryException, e )

{

e->Delete ();

bRet = FALSE;

}

END_CATCH

m_CSFor_Ary_ ();

if ( !bRet ) Log ( L_ERROR, "内存容量不足" );

return bRet;

}

void CNetDownMTRDlg::Get_Ary_DownloadNotifyPara(t_Ary_DownloadNotifyPara &Ary_DownloadNotifyPara)

10 1

{

m_CSFor_Ary_ ();

Ary_ ( m_Ary_DownloadNotifyPara );

m_Ary_All ();

m_CSFor_Ary_ ();

}

//若开始下载则启动/禁止相应控件

void CNetDownMTRDlg::EnableSomeWindow()

{

GetDlgItem(IDC_EDIT_RemoteFileURL)->EnableWindow ( !m_bDownloadIsStart );

GetDlgItem(IDC_EDIT_LocalFilePath)->EnableWindow ( !m_bDownloadIsStart );

GetDlgItem(IDC_BUTTON_Browse)->EnableWindow ( !m_bDownloadIsStart );

GetDlgItem(IDC_EDIT_FileName)->EnableWindow ( !m_bDownloadIsStart );

GetDlgItem(IDC_EDIT_ThreadNumber)->EnableWindow ( !m_bDownloadIsStart );

GetDlgItem(IDOK)->EnableWindow ( !m_bDownloadIsStart );

GetDlgItem(IDC_CHECK_Force)->EnableWindow ( !m_bDownloadIsStart );

GetDlgItem(IDC_BUTTON_Stop)->EnableWindow ( m_bDownloadIsStart );

}

void CNetDownMTRDlg::AddURL ( LPCTSTR lpszURL )

{

SetDlgItemText ( IDC_EDIT_RemoteFileURL, GET_SAFE_STRING(lpszURL) );

}

BOOL CAboutDlg::OnInitDialog()

{

CDialog::OnInitDialog();

// SetDlgItemText ( IDC_EDIT1, NOTE_DownloadMTR );

return TRUE; // return TRUE unless you set the focus to a control

// EXCEPTION: OCX Property Pages should return FALSE

}

void CNetDownMTRDlg::SetFileNameEditSel()

{

CEdit* pEdit = (CEdit*)GetDlgItem(IDC_EDIT_FileName);

CString csText;

pEdit->GetWindowText ( csText );

int nPos = eFind ( '.' );

if ( nPos >= 0 )

{

pEdit->SetSel ( 0, 0 );

pEdit->SetSel ( 0, nPos );

}

}

BOOL CNetDownMTRDlg::PreTranslateMessage(MSG* pMsg)

{

if ( WM_LBUTTONDOWN == pMsg->message || WM_LBUTTONUP == pMsg->message )

{

if ( !m_bFileNameGetFocus )

{

CEdit* pEdit = (CEdit*)GetDlgItem(IDC_EDIT_FileName);

CRect rc;

pEdit->GetWindowRect ( &rc );

if ( ct (pMsg->pt) )

{

GetDlgItem(IDC_EDIT_FileName)->SetFocus ();

SetFileNameEditSel ();

m_bFileNameGetFocus = TRUE;

return TRUE;

10 1

}

}

}

return CDialog::PreTranslateMessage(pMsg);

}

void CNetDownMTRDlg::OnKillfocusEDITFileName()

{

m_bFileNameGetFocus = FALSE;

}

void CNetDownMTRDlg::OnSetfocusEDITFileName()

{

SetFileNameEditSel ();

}

LRESULT CNetDownMTRDlg::OnWM_SHOWLOG(WPARAM wParam, LPARAM lParam)

{

UINT nLevel = wParam;

CStringArray StrAryLog;

CUIntArray UIntAryLevel;

GetLastLogStrAry ( StrAryLog, UIntAryLevel );

ASSERT ( e() == e() );

for ( int i=0; i

{

CString csLogText = ( i );

AddLogTextToEditCtrl ( (CRichEditCtrl*)GetDlgItem (IDC_EDIT_Log), csLogText, (i) );

}

return 0L;

}

****************************************************************************************************

****************************************************************************************************

SocketClient.h

#include

#include "PublicFunction.h"

#include "Log.h"

#define NET_BUFFER_SIZE 4096 // 默认缓冲大小

class CSocketClient : public CSocket

{

// Attributes

public:

BOOL Is_Connected() { return m_bConnected; }

// Operations

public:

CSocketClient ();

virtual ~CSocketClient();

CString GetDigitStrAtHead ( LPCTSTR lpszStr );

int GetResponse ( CString *pcsResponseStr=NULL, BOOL bBlock=TRUE );

BOOL GetResponse ( int nVerifyCode, CString *pcsResponseStr=NULL );

CString GetResponseHistoryString () { return m_csResponseHistoryString; }

void ClearResponseHistoryString () { m_(); }

// Overrides

public:

int m_nIndex;

void Disconnect();

int Receive ( char *szBuf, int size, BOOL bBlock=TRUE );

BOOL GetIPAndPortByPasvString ( LPCTSTR lpszPasvString, OUT CString &csIP, OUT USHORT &nPort );

10 1

void SetEventOfEndModule ( HANDLE hEvtEndModule );

BOOL Send ( char *data, int size );

BOOL SendString ( LPCTSTR lpszData, ... );

BOOL Connect ( LPCTSTR lpszHost, USHORT nPort );

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CSocketClient)

//}}AFX_VIRTUAL

// Generated message map functions

//{{AFX_MSG(CSocketClient)

// NOTE - the ClassWizard will add and remove member functions here.

//}}AFX_MSG

// Implementation

protected:

virtual BOOL ConnectHelper(const SOCKADDR* lpSockAddr, int nSockAddrLen);

private:

BOOL PumpMessagesMy(UINT uStopFlag);

CString m_csResponseHistoryString;

HANDLE m_hEvtEndModule;

BOOL m_bConnected;

};

#include "stdafx.h"

#include "NetDownMTR.h"

#include "SocketClient.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

#pragma comment(lib, "")

#define _afxSockThreadState AfxGetModuleThreadState()

#define _AFX_SOCK_THREAD_STATE AFX_MODULE_THREAD_STATE

#define WM_SOCKET_NOTIFY 0x0373

#define WM_SOCKET_DEAD 0x0374

/////////////////////////////////////////////////////////////////////////////

// CSocketClient

CSocketClient::CSocketClient ()

: m_hEvtEndModule ( NULL )

, m_bConnected ( FALSE )

, m_nIndex ( -1 )

{

}

CSocketClient::~CSocketClient()

{

}

// Do not edit the following lines, which are needed by ClassWizard.

#if 0

BEGIN_MESSAGE_MAP(CSocketClient, CSocket)

//{{AFX_MSG_MAP(CSocketClient)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

#endif // 0

10 1

/////////////////////////////////////////////////////////////////////////////

// CSocketClient member functions

CString CSocketClient::GetDigitStrAtHead ( LPCTSTR lpszStr )

{

if ( !lpszStr ) return "";

CString csStr = lpszStr;

ft(); ght();

CString csDigitStr;

for ( int i=0; isdigit ( (int)csStr[i] ); i++ )

{

csDigitStr += csStr[i];

}

return csDigitStr;

}

//

// return : ------------------------------------------

// > 0 - 回应代码

// = 0 - 未读到数据

// = -1 - 网络出错

//

int CSocketClient::GetResponse ( CString *pcsResponseStr/*=NULL*/, BOOL bBlock/*=TRUE*/ )

{

if ( pcsResponseStr ) *pcsResponseStr = "";

ASSERT ( m_hSocket != INVALID_SOCKET );

CString csOneLine = GetOneLine ( m_csResponseHistoryString );

if ( y () )

{

char szRecvBuf[NET_BUFFER_SIZE] = {0};

int nRet = Receive ( szRecvBuf, sizeof(szRecvBuf), bBlock );

if ( nRet <= 0 )

{

if ( nRet < 0 ) Log ( L_WARNING, "(%d) Receive response data failed", m_nIndex );

return nRet;

}

m_csResponseHistoryString += szRecvBuf;

csOneLine = GetOneLine ( m_csResponseHistoryString );

if ( y () ) return -1;

}

if ( pcsResponseStr ) *pcsResponseStr = csOneLine;

CString csRetCode = GetDigitStrAtHead ( csOneLine );

Log ( L_NORMAL, "(%d) FTP server response: %s", m_nIndex, csOneLine );

return atoi ( csRetCode );

}

BOOL CSocketClient::GetResponse ( int nVerifyCode, CString *pcsResponseStr/*=NULL*/ )

{

CString csResponseStr;

int nResponseCode = GetResponse ( &csResponseStr );

if ( pcsResponseStr ) *pcsResponseStr = csResponseStr;

if ( nResponseCode != nVerifyCode )

{

CString csMsg;

( "Receive error response code : %s", csResponseStr );

return FALSE;

}

return TRUE;

}

10 1

BOOL CSocketClient::SendString(LPCTSTR lpszData, ...)

{

// 格式化

char szDataBuf[NET_BUFFER_SIZE] = {0};

va_list va;

va_start (va, lpszData);

_vsnprintf ( szDataBuf, sizeof(szDataBuf)-1, (const char*)lpszData, va);

va_end(va);

Log ( L_VERBOSE, "线程 (%d) 发送HTTP请

rn------------------------>>>rn%s------------------------>>>rn",

m_nIndex, szDataBuf );

return Send ( szDataBuf, strlen(szDataBuf) );

}

BOOL CSocketClient::Send(char *data, int size)

{

ASSERT ( m_hEvtEndModule && m_hEvtEndModule != INVALID_HANDLE_VALUE );

ASSERT ( m_hSocket != INVALID_SOCKET );

if ( !data || size < 1 ) return TRUE;

int nRemainBytes = size;

int nSentTotalBytes = 0;

int nSendFailedCount = 0;

while ( nRemainBytes > 0 )

{

int nSentBytes = CSocket::Send ( data+nSentTotalBytes, nRemainBytes );

if ( nSentBytes < 0 )

{

nSendFailedCount ++;

if ( nSendFailedCount > 10 )

{

Log ( L_WARNING, "(%d) Send net data block failed", m_nIndex );

m_bConnected = FALSE;

return FALSE;

}

else

{

SLEEP_RETURN ( 100 );

}

}

else

{

nRemainBytes -= nSentBytes;

nSentTotalBytes += nSentBytes;

nSendFailedCount = 0;

}

}

return TRUE;

}

void CSocketClient::SetEventOfEndModule(HANDLE hEvtEndModule)

{

m_hEvtEndModule = hEvtEndModule;

ASSERT ( m_hEvtEndModule && m_hEvtEndModule != INVALID_HANDLE_VALUE );

}

// 从类似 "(192,168,0,2,4,31)" 字符串中得到IP地址和端口号

//

求报文:

10 1

BOOL CSocketClient::GetIPAndPortByPasvString(LPCTSTR lpszPasvString, OUT CString &csIP, OUT USHORT &nPort)

{

if ( !lpszPasvString ) return FALSE;

char *p = strchr ( lpszPasvString, '(' );

if ( !p ) return FALSE;

CString csPasvStr = p+1, csTemp;

int nPosStart = 0, nPosEnd = 0;

int nMultiple = 0, nMantissa = 0;

for ( int i=0; ; i++ )

{

nPosEnd = ( ",", nPosStart );

if ( nPosEnd < 0 )

{

if ( i == 5 )

{

nPosEnd = ( ")", nPosStart );

csTemp = ( nPosStart, nPosEnd-nPosStart );

nMantissa = atoi ( csTemp );

break;

}

else return FALSE;

}

csTemp = ( nPosStart, nPosEnd-nPosStart );

ft(); ght();

if ( i < 4 )

{

if ( !y () ) csIP += ".";

csIP += csTemp;

}

else if ( i == 4 )

{

nMultiple = atoi ( csTemp );

}

else return FALSE;

nPosStart = nPosEnd + 1;

}

nPort = nMultiple*256 + nMantissa;

return TRUE;

}

//

// return : -----------------------------------------------------------

// >= 0 - 收到的字节数

// -1 - 失败

//

int CSocketClient::Receive(char *szBuf, int size, BOOL bBlock/*=TRUE*/)

{

if ( !szBuf || size < 0 ) return -1;

int nReadSize = 0;

if ( bBlock )

{

nReadSize = CSocket::Receive ( szBuf, size );

if ( nReadSize <= 0 ) nReadSize = -1;

}

else

{

nReadSize = CAsyncSocket::Receive ( szBuf, size );

if ( nReadSize < 0 )

{

if ( WSAEWOULDBLOCK == GetLastError () )

nReadSize = 0;

10 1

else

nReadSize = -1;

}

}

if ( nReadSize == -1 )

{

m_bConnected = FALSE;

}

return nReadSize;

}

void CSocketClient::Disconnect()

{

CancelBlockingCall ();

Close ();

m_bConnected = FALSE;

}

BOOL CSocketClient::Connect(LPCTSTR lpszHost, USHORT nPort)

{

if ( !lpszHost || strlen(lpszHost) <= 0 || nPort < 1 )

return FALSE;

Log ( L_VERBOSE, "线程(%d) 正在连接 [%s:%d] ...", m_nIndex, lpszHost, nPort );

Close ();

if ( !Create () ) return FALSE;

m_bConnected = CSocket::Connect ( lpszHost, nPort );

if ( !m_bConnected )

Log ( L_WARNING, "线程(%d) 连接 [%s:%d] 失败", m_nIndex, lpszHost, nPort );

return m_bConnected;

}

//

// 改写这个虚函数,可以在调用 CancelBlockingCall() 函数时立即终止连接

//

BOOL CSocketClient::ConnectHelper(const SOCKADDR *lpSockAddr, int nSockAddrLen)

{

if (!CAsyncSocket::ConnectHelper(lpSockAddr, nSockAddrLen))

{

if (GetLastError() == WSAEWOULDBLOCK)

{

while (PumpMessagesMy(FD_CONNECT))

{

if (m_nConnectError != -1)

{

WSASetLastError(m_nConnectError);

return (m_nConnectError == 0);

}

}

}

return FALSE;

}

return TRUE;

}

BOOL CSocketClient::PumpMessagesMy(UINT uStopFlag)

{

// The same socket better not be blocking in more than one place.

ASSERT(m_pbBlocking == NULL);

_AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;

10 1

ASSERT(pState->m_hSocketWindow != NULL);

BOOL bBlocking = TRUE;

m_pbBlocking = &bBlocking;

CWinThread* pThread = AfxGetThread();

// This is not a timeout in the WinSock sense, but more

// like a WM_KICKIDLE to keep message pumping alive

UINT nTimerID = ::SetTimer(pState->m_hSocketWindow, 1, m_nTimeOut, NULL);

if (nTimerID == 0)

AfxThrowResourceException();

BOOL bPeek = TRUE;

while (bBlocking)

{

TRY

{

MSG msg;

if ( ::PeekMessage(&msg, pState->m_hSocketWindow,

WM_SOCKET_NOTIFY, WM_SOCKET_DEAD, PM_REMOVE) )

{

if (e == WM_SOCKET_NOTIFY && (SOCKET) == m_hSocket)

{

if (WSAGETSELECTEVENT() == FD_CLOSE)

{

break;

}

if (WSAGETSELECTEVENT() == uStopFlag)

{

if (uStopFlag == FD_CONNECT)

m_nConnectError = WSAGETSELECTERROR();

break;

}

}

if ( != 0 || != 0)

CSocket::AuxQueueAdd(e, , );

bPeek = TRUE;

}

else if ( ::PeekMessage(&msg, pState->m_hSocketWindow,

WM_TIMER, WM_TIMER, PM_REMOVE) )

{

break;

}

if (bPeek && ::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))

{

if (OnMessagePending())

{

// allow user-interface updates

pThread->OnIdle(-1);

}

else

{

bPeek = FALSE;

}

}

else

{

// no work to do -- allow CPU to sleep

10 1

}

}

SLEEP_RETURN ( 100 );

bPeek = TRUE;

}

}

CATCH_ALL(e)

{

TRACE0("Error: caught exception in PumpMessage - continuing.n");

e->Delete ();

bPeek = TRUE;

}

END_CATCH_ALL

::KillTimer(pState->m_hSocketWindow, nTimerID);

if (!bBlocking)

{

WSASetLastError(WSAEINTR);

return FALSE;

}

m_pbBlocking = NULL;

::PostMessage(pState->m_hSocketWindow, WM_SOCKET_NOTIFY, 0, 0);

return TRUE;