原创博客,转载请联系博主!linux
本项目已托管到本人Git远程库:https://github.com/yue9944882/Snowgit
项目目标 github
Major Functionality编程
开发环境: CentOS7-Qt4服务器
实现一个基于LINUX的多线程下载器,功能上仿造迅雷,主要有以下几个功能:网络
(仅限HTTP协议)多线程下载远程资源数据结构
(暂停/继续功能)断点续传多线程
项目主要技术 架构
Major Technique异步
POSIX线程及其协做
TCP协议套接字编程
Qt界面实现
Qt 信号槽机制SIGNAL/SLOT
// Linux信号处理
项目构思
Major Architecture
贯穿整个使用过程的Qt的主界面对全局的若干个队列进行操做,从而实现系统协做,其过程当中使用若个同步锁调配线程之间的协做与竞争。
为何这么设计?
这么设计最大的缘由是为了实现POSIX线程与Qt封装类之间的互动,POSIX线程是基于LINUX下的C语言实现的,其调用建立的入口必须是C-style声明的函数,若是直接将这些函数声明为Qt控件继承类内静态函数会破坏其封装性(我的实践证实,这么作也是不可行的)。通过几回完全失败以后,这个方法也是目前很少可行解决办法之一。
为何使用POSIX线程?
Qt是可移植的项目环境,若是使用linux下独有的FORK/VFORK系列函数,会局限程序运行环境,POSIX标准下的线程更通用,更普遍。至于为何没有使用QThread,我只能说不想用- -|||
下图所示是总体模块之间的联系:
网络及动态显示控件部分架构 以下图所示:
逻辑任务线程不直接参与下载,附属线程封装进逻辑任务中,对其余任务不可见,动态控件队列随着用户的操做长度和顺序会不断发生改变,而逻辑下载任务队列只会不断生长,而且经过索引与动态控件一对一对应。
Qt主界面应用类架构 分别在与之对应的 *.ui 文件中定义,这里暂不赘述,各个类之间的沟通是经过Qt的SIGNAL/SLOT 信号槽机制完成的。
POSIX锁类架构 以下图所示:
--- Declaration ------ global.h :extern声明外部全局变量
| |
| --- global_t.h :C风格结构体定义,主要用于POSIX线程传参
| |
| --- global_f.h :extern声明外部全局函数
| |
| --- missionbar.h missioncheck.h :C++风格声明动态控件
| |
| --- mainwindow.h newdialog.h :C++风格声明静态控件
|
|
|
--- Defination ----- global.cpp :全局变量定义
| |
| --- global_f.cpp :POSIX线程及日志系统- C风格函数定义
| |
| --- main.cpp :程序入口,程序环境初始化..
| |
| --- *.cpp :Qt库继承类定义
|
|
--- Makefile ------ SnowLINUX.pro :QMake 脚本
|
--- Makefile :自动化编译脚本
代码实现
Implementation
网络下载部分
1. TCP套接字:
因为多线程下载器须要保证文件的完整性,咱们选择TCP协议下的套接字进行下载,每次下载首先发送一个HTTP-HEAD请求获得文件的长度和完整名称而且声明不使用gzip格式压缩,不然没法多线程写入!获得了文件完整的长度以后,根据线程数量为文件划分段落,而后由若干个POSIX线程使用随机上长的端口号和服务器80号端口进行TCP链接,再使用Linux内核提供的接口进行下载,每一个任务维护一个更新写入进程锁,以更新当前进度/速度的实时信息,并竞争全局锁刷新全局统计变量。
2. 无锁数据结构:
使用Linux的原子文件读/写操做而不是标准文件操做,以保证多线程写入的原子性和完整性,pwrite/pread函数是咱们的最终选择,因为读写原子性,文件描述符不须要锁类保护同步性。
3. 日志系统
日志系统本来的设计是经过Linux-signal库进行定时的任务日志更新,可是这样给CPU带来了太多额外的任务消耗,下载的速度也会形成不一样程度减小,更重要的是这样实现会破坏Qt继承类的封装性,由于signal_handler风格的函数必须是全局函数。咱们采用的是”单次”日志,在用户有须要的时候触发日志记录系统,相关函数见 global_f.cpp中的:
Init_log(),write_log(),read_log()系列函数,日志格式暂不赘述,为纯ASCii编码文件。
Qt类与全局锁部分
1. 全局竞争锁:
时间锁( timeMutex )是为了主界面定时调用函数在获取任务队列中不断异步更新进度的任务的执行时间,时间锁是为了保证主界面和动态控件之间数据的同步,其中动态控件的数据由动态控件封装的锁保证其内部POSIX线程刷新数据的同步性。
表锁(tableMutex) 是为了保证咱们在进行删除/重启任务而致使全局表修改的同时,程序不会由于定时刷新曲线而错误访问任务表致使的程序崩溃。
状态锁( finishMutex ) 是为了防止动态控件和任务表之间相互经过记录对方索引而相互访问时表内容修改致使的程序崩溃。
2. 全局协做锁:
在启动新任务的时候弹出的小QDialog窗口是个独立的窗口,其内部空间的SIGNAL分别绑定到了主界面的槽(SLOT)和动态控件的槽中,然而两边的槽默认是同时进行调用的,而咱们必需要求其顺序,否则程序会崩溃。例如,在咱们选择文件路径后开始下载任务,咱们须要先调用主界面的槽来更新表作预备工做,而后才能建立动态控件及下载任务.
程序操做
Demostration
操做方式很是简明: