实现自己的音乐搜索软件(三)

实现自己的音乐搜索软件(一)

 

实现自己的音乐搜索软件(二)

 

 

前面介绍了实现自己的搜索模块,我们已经能够从互联网上搜索到自己想要的音乐的地址了,所以这一篇文章主要介绍实现自己的下载的模块。对于下载模块,也没有做的很复杂,毕竟对于象迅雷那种很强劲的下载工具也不是很了解,所以只能在功能上模仿一个形似。

 

 


 

 

一 下载模块功能

 

上图右侧显示了下载模块的基本功能,支持多任务下载,并且支持断点续传。所以可以进行暂停,取消等控制。而下载并没有使用多线程下载,因为相对要复杂一些,所以目前只是单线程下载。对于MP3这种小文件来说速度还是不错的。

 

对于下载模块多任务实现,没有使用异步方法,或者是建立线程池去控制。而是使用了基于事件的异步模型。在.NET中基于此模型实现的控件就是BackGroundWork,他能很好的解决子线程和UI主线程间的通信,并且使用起来非常方便。但是他最大的问题是,他是以一个控件实现的,并且不支持多任务,所以我们想要使用他的话,每一个任务都需要使用一个控件,并且如果像编写一个与UI无关的下载模块,却引人一个UI控件,总是不太好,所以我们下载的核心就是自己实现一个可以支持多任务的BackGroundWork模块,用来管理下载。除了下载管理模块之外,我们还需要具体的下载功能以及下载后的文件管理功能。

 

.

.

 

二 下载模块结构

如上图,下载模块一共有3个文件,其中DownloadCore.cs是对HttpWebRequest的包装主要负责下载,支持断线续传;DownloadManagement.cs是模块的核心,负责下载任务的调度,控制和管理,整个功能是模仿BackGroundWork进行,但是支持多任务控制,而且支持调度控制;FileManager.cs文件是对下载文件的管理,负责文件重名,下载临时文件等处理。

 

DownloadManagement在使用方面和BackGroundWork一样,使用时注册DoWork,ProgressChanged,RunWorkerCompleted3个事件。在DoWork事件对应的方法中,调用DownloadCore对象进行下载;在ProgressChanged事件对应的方法中更新下载进度和状态,而在RunWorkerCompleted3事件触发时,调用FileManager对象,管理文件。

 

.

.

三 下载管理的实现

对于下载管理DownloadManagement,是参照BackGroundWork实现的,不过他可以支持多任务,并且可以对多任务之间进行管理。在了解本对象实现之前,最好能对BackGroundWork的实现有一定了解,可以参考我之前的文章BackgroundWork的内部实现

 

1 多任务的支持

相对于BackGroundWork,我们支持了多任务,所以必须在内部有一张表来维护这些任务。从而能对这些任务进行调度,而且还必须有一个位置的值作为Task的key,来唯一标识一个任务。

 

 

以上是类中定义的一些变量,可以看到,我们定义了3个哈希表,用来保存等待下载和正在下载的任务,而另一个管理同步操作的列表。同时定义了3个同步锁,保证多个任务能同时正确的操作哈希表。而哈希表的key使用的是GUID,这样就能保证唯一性,每个下载任务都有一个GUID。

 

前面两个哈希表比较好理解,存放的对象是DownloadMusicTask,而后一个用来管理同步操作的哈希表存放的是AsyncOperation对象。在前面的BackgroundWork的内部实现的文章中有介绍过这个对象,他是BackGroundWork的核心,用来和UI线程进行通信的。他具体的实现细节前面文章介绍过了,这就就不在说了。但是BackGroundWork是单任务的,所以只存在一个AsyncOperation对象,而我们这里支持多任务,所以必须有多个AsyncOperation对象。

 

您的类应该在每个任务开始时调用 AsyncOperationManager.CreateOperation,为每个异步任务获取 AsyncOperation 对象。为了使客户端能够区分不同的异步任务,AsyncOperationManager.CreateOperation 采用一个参数作为客户端提供的独一无二的标记,这个就是 UserSuppliedState 属性。然后客户端代码便可使用该属性来标识当前引发进度或完成事件的特定异步任务。

 

以上是MSDN对他使用的解释,所以我们在创建对象时,把任务的GUID传递给了他,保证了唯一性,而在BackGroudWork中传递的是null,因为只有一个对象。

 

我们的的多任务下载就是以此为基础进行的。每个任务都有一个GUID,当需要与UI交互时,我们根据GUID从哈希表中查找到相应的AsyncOperation对象,然后调用Post或者PostOperationCompleted方法和UI进行通信。

.

2 基于事件的异步实现

我们这里的实现,基本和BackGroudWork一致,不过相对于多任务增加了一些功能。微软也建议对于更复杂的异步应用程序,请考虑实现一个符合基于事件的异步模式的类。

 

参考:《使用基于事件的异步模式进行多线程编程》http://msdn.microsoft.com/zh-cn/library/hkasytyf(v=VS.90).aspx

 

基于事件的异步模式可以采用多种形式,具体取决于某个特定类支持的操作的复杂程度。最简单的类可能只有一个方法名称Async 方法和一个对应的方法名称Completed 事件。更复杂的类可能有若干个方法名称Async 方法(每种方法都有一个对应的方法名称Completed 事件),以及这些方法的同步版本。这些类分别支持各种异步方法的取消、进度报告和增量结果。

 

以上是我们的DownloadManagement 类中定义的事件成员。为了简便,我们使用了BackgroundWork使用的委托原型。当然也可以自己定义事件的委托类型以及参数类型。随后我们定义了3个事件发生时通知订阅者的虚方法。在这里我们并没有显示的去实现事件的订阅与注销,因为我们这里事件很少,而且也不太关注CLR实现的事件潜在的同步问题,以为使用对象自己作为同步锁,对于大多数程序并不是一个问题。

 

最后我们就需要定义三个触发事件的方法,在开始下载,下载中以及下载完成时触发。但是我们要注意到一个问题。我们这里定义了三个事件,注册这些事件的方法应该都是UI中的方法。而我们DownloadManagement本质是使用多线程。所以就存在一个子线程修改UI控件的问题,这是不允许的,所以我们需要使用前面提到的AsyncOperation对象,把这些方法发送到UI线程执行。而在我们这里,就是要把这3个触发事件的方法,发送到UI线程执行。所以我们需要利用委托来包装这3个方法。

 

所以我们定义了和这3个方法有关的委托,并在构造函数中和相应的方法进行绑定。其中WorkerThreadStartDelegate是我们自定义的委托类型,因为开始下载的方法需要在一个新的线程中执行,使用了委托的Invoke方法。而另外两个方法和SendOrPostCallback委托绑定,此委托是系统定义的:表示在消息即将被调度到同步上下文时要调用的方法

.

 

3 实现异步方法

 我们这里只实现了异步方法,而没有实现同步方法,因为单线程下载是没有意义的。对于我们来说,这里有开始下载,停止下载,取消下载,报告下载进度这几个异步的方法。并且这几个方法是public的,共外部调用。先看看整个下载功能开始的方法

整个方法比较简单,传入一个下载任务对象,检查次任务是否已经存在,如果不存在就新建一个对应的AsyncOperation对象,并添加到哈希表中。根据目前任务数量决定是进入下载队列还是等待队列。如果可以下载,我们使用了threadStart.BeginInvoke 方法让这个任务在一个新的线程中运行,这也是我们这个管理类的本质,仍旧是使用多线程。

以上就是接受到任务后,在新的任务线程中执行的方法。他的作用是管理整个下载过程。首先是触发了注册的DoWork时间的方法,我们这里也就是实际的下载方法。总的来说,这里就是让DownloadCore对象的下载方法在一个新的线程运行。当下载完成后,需要触发RunWorkerCompleted事件,并把注册的方法发送到UI线程执行。这里可以看到使用了AsyncOperation对象的PostOperationCompleted方法,并把operationCompleted委托作为参数传递了过去。

接下来就是进度报告的异步方法。使用了AsyncOperation对象的Post方法,并把progressReporter委托传递了过去。

.

 

4 异步方法的暂停和取消

在多线程和基于APM的异步模型中,最麻烦的就是暂停一个异步操作。对于基于事件的异步模型来说,应该尽量支持取消操作,而且在取消时要出发RunWorkerCompleted事件,这是微软的建议。

 

在实现上,是对于每个任务都有两个标志IsCancle 和 IsStop ,我们的工作也就是设置这两个标志的状态。这里有一个问题,我们这个模块很难用在下载以外的地方,因为很难知道如何去结束一个异步操作。这也是为什么BackgroundWork不支持取消的原因。所以我们这里设置的标志位,在下载时会使用到,如果标志位为true,则从正在运行的程序中退出。从前面可以知道,下载结束时,会触发RunWorkerCompleted事件。 对于没有在下载的任务,取消时直接触发RunWorkerCompleted事件。

.

 

5 总结

以上就是实现的基于事件的异步模型,从整个模型可以看出,他的功能就是利用委托,启动一个新的线程,运行注册到类事件上的方法,而事件并不需要知道运行的是什么方法。对于报道进度,这里只是负责把数据和注册到时间的方法发送到正确的线程中,结束时也是一样。所以基于事件的异步模型更像是一个邮差。接受数据和方法,并投递到订阅者那里,如果处理这些数据,用什么方法处理,都是订阅者的事情。这样以来,就简化了我们使用多线程时一切都要自己控制,代码也更清晰,逻辑中基本看不到多线程处理的恒基。

.

.

四  文件下载和文件管理

文件下载,使用的是HttpWebRequest对象。整个过程和网页请求是一样的,不同的是相对于网页,MP3文件较大,所以我们在读取请求的流时是循环读取的。而且在保存下载数据时也要考虑到暂停和同名等情况。

 

 

以上就是下载的实现方式,主要有几点,一个是断点续传,一个是报告进度,另一个就是计算下载速度。

1 断点续传

断点续传的功能实际是利用了HttpWebRequest的AddRange方法来实现断点续传。下载时我们把文件存入到一个.mus后缀的临时文件中。当暂停一个下载任务时,这个任务并没有从前面的DownloadManagement的哈希表中删除,只是标志位设置为了stop。

 

当继续下载时,首先从硬盘上读取这个文件,如果存在,则获得文件大小,并设置到HttpWebRequest的AddRange方法中,这样就能从指定的位置请求数据,实现断点续传。另外要注意的是,断点续传时,HttpWebResponse.ContentLength返回的只是剩余数据的大小,而不是全部数据的大小。如果弄错会导致下载的文件变小,播放的MP3就是乱的了。

.

 

2 进度报告和速度

在HttpWebResponse请求成功之后就马上进行了报告,这样做是为了让UI获得要下载的文件的大小数据,以及准备下载的状态。在下载中,利用已下载大小和文件大小来进行循环,每次从流中读取1024个字节。没读取一次回报一下进度,当然可以自己去控制回报进度的频率。在下载的过程中可能会出现错误,在出现错误时,也进行了进度汇报,这样前台就能知道此时下载的状态。

 

对于计算速度,就是用下载字节数除下载的时间。但是问题就是,如果读取1024个字节就计算一次,可能很不准确,因为读1024自己的时间很短,而且这样更新太快,用户也看不清楚。所以这里我采用了每400ms计算一次速度,实际的效果还是不错的。

.

 

3 文件管理

我们在下载时,文件名为歌曲的名字,所以下载多个同名音乐时很可能出现同名的状况。通用的情况就是给后面的文件加上(1),(2)这样的后缀。而在下载过程中,文件是存放到扩展名为.mus的文件中。当下载完成后,重名为对应的音乐格式后缀。而在文件取消和下载失败时,也需要对文件进行删除等操作。

 

 

这里也考虑了在下载是使用一个后缀为mus的文件存放数据,另一个后缀为cfg的文件存放下载信息,比如把DownloadMusicTask对象序列化存入到文件中,这样,即便在下载暂停,程序关闭之后,在下一次打开程序是,还能再次进行下载。而且对于多线程下载时,每个下载块的管理也是很有必要的。但是目前并没有这样做。不过实现起来没有什么难度。

 

.

.

 

五 总结

这一篇主要介绍了实现的下载模块,实际我想介绍的核心就是基于事件的异步模型。所以其他和下载相关的一些地方就介绍的很少或者没有提及。目前比较忙,没有时间继续写,程序实现的功能也基本就是目前介绍的这些。 当然还是用了WMP的插件,实现了在线播放。并且还写了一个网络状态实时检查的功能,但这里都没有做更多的介绍。

 

在后面计划尝试进行歌词的实时显示功能,不过不知道是否有时间。因为进去工作比较忙,在开发一个手机平台上的msn客户端,也打算马上写一系列MSN协议分析的文章。所以时间就很难说了。

 

源代码SVN:http://code.google.com/p/cmusicsearch/

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于层架构的音乐网站是采用一种将应用程序分为个不同层级的软件开发模式,包括用户界面层(UI层),业务逻辑层(BLL层)以及数据访问层(DAL层)。 在音乐网站的用户界面层,我们可以设计和开发各种功能齐全且用户友好的网页界面,包括注册和登录页面、音乐排行榜、搜索功能以及用户个人信息管理等。这一层主要负责与用户的交互,接收和处理用户的请求,并将这些请求传递给业务逻辑层。 业务逻辑层是音乐网站的核心部分,它负责处理各种业务逻辑,包括音乐的上传和下载、播放列表的管理、歌曲推荐以及用户的喜好分析等。该层主要负责对用户的请求进行处理,并通过调用数据访问层从数据库中获取和更新数据。 数据访问层是连接数据库的桥梁,在这一层中,我们可以设计和实现各种与数据库交互的功能,包括数据的插入、删除和更新等。该层主要负责与数据库的交互,并根据业务逻辑层的需求提供相应的数据。 通过采用层架构,音乐网站可以实现应用程序的分层和模块化开发,使得各个层级之间的功能和责任清晰,易于维护和扩展。同时,这种架构还可以提高系统的可靠性和安全性,减少不同功能的耦合性,方便团队协作开发。 在开发基于层架构的音乐网站时,我们需要先设计和实现数据库,然后根据业务需求设计和开发业务逻辑层和用户界面层,最后再实现数据访问层与数据库的交互。这种开发流程可以提高开发效率和代码质量,同时也为以后的系统功能迭代和维护提供了便利。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值