一 摘要
JWebFileTrans是一款基于socket的网络文件传输小程序,目前支持从HTTP站点下载文件,后续会增加ftp站点下载、断点续传、多线程下载等功能。其代码已开源到github上面,下载网址是JWebFileTrans的github链接 。
注:转载请注明博客原始链接,最近发现有人盗用我的博客却设置为他的原创。
二 下载功能演示截图
笔者分别用3个链接做了下载测试,分别是apache tomcat镜像、apache hbase 华中科大镜像以及著名下载工具快车的官网下载链接,链接如下:
- http://www-us.apache.org/dist/tomcat/tomcat-8/v8.5.11/bin/apache-tomcat-8.5.11-fulldocs.tar.gz
- http://mirrors.hust.edu.cn/apache/hbase/stable/hbase-1.2.4-bin.tar.gz
- http://mirrors.hust.edu.cn/apache/hbase/stable/hbase-1.2.4-src.tar.gz
- http://www.flashget.com/apps/flashget3.7.0.1222cn.exe
下面几个截图分别是下载完后的文件、解压后的文件、浏览文件、运行快车安装程序的截图
在下文笔者会列出源代码,需要注意的是,笔者在源代码里面定义了断点续传的数据结构,以及处理断点续传文件的函数,但是实际上当前JWebFileTrans并不支持断点续传,这个功能会在后续的更新中提供。
三:基本思路
本文所涉及到的主要技术点分别是:Http协议、TCP传输协议、socket编程技术。虽然涉及到HTTP/TCP协议,但是本文并不需要了解这些协议的具体细节,我们只需要知道其中主要的几个特性,以及几个socket编程接口便可以实现一个网络文件下载程序。
在HTTP协议中,有几个颇为耳熟能详的命令:Head、GET、POST、DELETE等等。本文所涉及到的主要是HEAD和GET. 假设HTTP服务端存储了一些文件供客户端下载,那么用户发送一个HEAD命令给服务端,服务端便会返回一个响应消息给客服端。响应消息里面会包含一系列字段,其中一个比较重要的字段就是‘感兴趣’的那个文件的大小,这个大小是以字节为单位的。HEAD命令以及相应的服务端发来的响应消息都是有一定的格式的。网上有大量的介绍文章,此处笔者就不再赘述。HEAD命令得到的响应消息只包含消息头,不包含‘感兴趣’的那个文件的具体内容数据。
GET命令与HEAD命令的区别在于,服务端除了发送消息的头部外,紧跟着头部还会发送‘感兴趣’的那个文件的内容。但是文件的尺寸有可能非常大,比如好几个G,这样的话,如果用GET命令来请求服务端传输这个文件的数据,显然是非常‘不优雅’的。很难想象服务端一个响应消息一下传输几个G的数据。大家不用担心,GET命令可以用于设置告诉服务端‘我’期望获得文件的某一小段内容,比如:第1000个字节到第2000个字节。
于是我们可以先用HEAD命令来获得文件的尺寸,假设为file_size, 然后我们设置每次下载文件的一小段,假设这一小段的字节数是one_piece,那么我们向服务端请求file_size/one_piece次就可以获得文件的全部内容了。当然file_size并不一定是one_piece的整数倍,此是后话,下文源代码部分会处理这种情况。
前文说的HEAD命令,GET命令,那么怎么使用呢?没错socket系列接口函数就可以解决这个问题。socket是一套网络编程接口,面向应用层它支持IPV4、IPV6协议族,面向传输层它支持TCP、UDP等传输协议。socket使用socket描述符来表示客服端-服务端的链接,使用connect()接口来与服务端建立连接,使用send()等接口向服务端发送消息,使用recv()等接收服务端发来的响应消息。如果读者对这些概念不是太熟悉的话,可以去网上搜索一下,笔者在写JWebFileTrans的时候也是在网上搜索资料来学习的。
有了HEAD、GET命令以及socket系列接口函数后,相信读者脑海中已经有了一个大致的下载程序框架了。那么就让我们一起来看看这些技术点如何通过代码的方式来转换为一个迷你下载工具的。
四:JWebFileTrans代码实现
1. 下载链接解析,前文中我们做测试的有几个链接,比如:http://mirrors.hust.edu.cn/apache/hbase/stable/hbase-1.2.4-src.tar.gz",我们需要从这个链接里面解析出四个元素,url部分:mirrors.hust.edu.cn(抱歉笔者一直分不清楚应该是URL还是URI);port端口部分:这个链接省略了端口号,默认http端口号是80,完整的网址应该在edu.cn后加:80;路径部分:/apache/hbase/stable/;文件名:hbase-1.2.4-src.tar.gz
1 int Http_link_parse(char *link, char **url, char **port, char **path, char **file_name){ 2 3 /** 4 ** check argument 5 */ 6 if(NULL==link){ 7 printf("Http_link_parse: argument error, please provide correct link "); 8 exit(0); 9 } 10 11 char *url_begin=NULL; 12 char *url_end=NULL; 13 14 url_begin=strstr(link,"http://"); 15 if(NULL==url_begin){ 16 printf("Http_link_parse: not valid http link "); 17 exit(0); 18 } 19 20 url_begin=url_begin+7; 21 22 int link_length=strlen(link); 23 24 int i=0; 25 for(i=link_length;i>=7;i--){ 26 if('/'!=link[i]){ 27 continue; 28 } 29 else{ 30 break; 31 } 32 } 33 34 int j=0; 35 for(j=7;j<link_length;j++){ 36 if('/'!=link[j]){ 37 continue; 38 }else{ 39 break; 40 } 41 } 42 43 if(j>=link_length){ 44 printf("Http_link_parse: Http link path not intact "); 45 exit(0); 46 } 47 48 if(i<7){ 49 printf("Http_link_parse: Http link path not intact "); 50 exit(0); 51 } 52 char *path_begin=&(link[j]); 53 int path_length=link_length-j; 54 55 char *colon=strstr(url_begin,":"); 56 char *port_begin=NULL; 57 int url_length=0; 58 int port_length=0; 59 if(NULL==colon){ 60 61 *port="80";//default http port 62 url_end=&(link[j]); 63 url_length=url_end-url_begin; 64 65 }else{ 66 67 port_length=&(link[i])-colon-1; 68 port_begin=colon+1; 69 70 url_length=colon-url_begin; 71 72 } 73 74 char *file_name_tmp=&(link[i])+1; 75 int file_length=(link_length-1)-i; 76 77 *url=(char *)malloc(sizeof(char)*(url_length+1)); 78 if(port_length!=0){ 79 *port=(char *)malloc(sizeof(char)*(port_length+1)); 80 if(NULL==*port){ 81 printf("Http_link_parsed: malloc failed "); 82 exit(0); 83 } 84 memcpy(*port,port_begin,port_length); 85 (*port)[port_length]='