• c语言实现http下载功能,显示进度条和下载速率


    1. #include <stdio.h>//printf
    2. #include <string.h>//字符串处理
    3. #include <sys/socket.h>//套接字
    4. #include <arpa/inet.h>//ip地址处理
    5. #include <fcntl.h>//open系统调用
    6. #include <unistd.h>//write系统调用
    7. #include <netdb.h>//查询DNS
    8. #include <stdlib.h>//exit函数
    9. #include <sys/stat.h>//stat系统调用获取文件大小
    10. #include <sys/time.h>//获取下载时间
    11. //下载地址 http://dldir1.qq.com/qqfile/qq/QQ7.9/16638/QQ7.9.exe
    12. struct HTTP_RES_HEADER//保持相应头信息
    13. {
    14. int status_code;//HTTP/1.1 '200' OK
    15. char content_type[128];//Content-Type: application/gzip
    16. long content_length;//Content-Length: 11683079
    17. };
    18. void parse_url(const char *url, char *host, int *port, char *file_name)
    19. {
    20. /*通过url解析出域名, 端口, 以及文件名*/
    21. int j = 0;
    22. int start = 0;
    23. *port = 80;
    24. char *patterns[] = {"http://", "https://", NULL};
    25. for (int i = 0; patterns[i]; i++)//分离下载地址中的http协议
    26. if (strncmp(url, patterns[i], strlen(patterns[i])) == 0)
    27. start = strlen(patterns[i]);
    28. //解析域名, 这里处理时域名后面的端口号会保留
    29. for (int i = start; url[i] != '/' && url[i] != '\0'; i++, j++)
    30. host[j] = url[i];
    31. host[j] = '\0';
    32. //解析端口号, 如果没有, 那么设置端口为80
    33. char *pos = strstr(host, ":");
    34. if (pos)
    35. sscanf(pos, ":%d", port);
    36. //删除域名端口号
    37. for (int i = 0; i < (int)strlen(host); i++)
    38. {
    39. if (host[i] == ':')
    40. {
    41. host[i] = '\0';
    42. break;
    43. }
    44. }
    45. //获取下载文件名
    46. j = 0;
    47. for (int i = start; url[i] != '\0'; i++)
    48. {
    49. if (url[i] == '/')
    50. {
    51. if (i != strlen(url) - 1)
    52. j = 0;
    53. continue;
    54. }
    55. else
    56. file_name[j++] = url[i];
    57. }
    58. file_name[j] = '\0';
    59. }
    60. struct HTTP_RES_HEADER parse_header(const char *response)
    61. {
    62. /*获取响应头的信息*/
    63. struct HTTP_RES_HEADER resp;
    64. char *pos = strstr(response, "HTTP/");
    65. if (pos)//获取返回代码
    66. sscanf(pos, "%*s %d", &resp.status_code);
    67. pos = strstr(response, "Content-Type:");
    68. if (pos)//获取返回文档类型
    69. sscanf(pos, "%*s %s", resp.content_type);
    70. pos = strstr(response, "Content-Length:");
    71. if (pos)//获取返回文档长度
    72. sscanf(pos, "%*s %ld", &resp.content_length);
    73. return resp;
    74. }
    75. void get_ip_addr(char *host_name, char *ip_addr)
    76. {
    77. /*通过域名得到相应的ip地址*/
    78. struct hostent *host = gethostbyname(host_name);//此函数将会访问DNS服务器
    79. if (!host)
    80. {
    81. ip_addr = NULL;
    82. return;
    83. }
    84. for (int i = 0; host->h_addr_list[i]; i++)
    85. {
    86. strcpy(ip_addr, inet_ntoa( * (struct in_addr*) host->h_addr_list[i]));
    87. break;
    88. }
    89. }
    90. void progress_bar(long cur_size, long total_size, double speed)
    91. {
    92. /*用于显示下载进度条*/
    93. float percent = (float) cur_size / total_size;
    94. const int numTotal = 50;
    95. int numShow = (int)(numTotal * percent);
    96. if (numShow == 0)
    97. numShow = 1;
    98. if (numShow > numTotal)
    99. numShow = numTotal;
    100. char sign[51] = {0};
    101. memset(sign, '=', numTotal);
    102. printf("\r%.2f%%[%-*.*s] %.2f/%.2fMB %4.0fkb/s", percent * 100, numTotal, numShow, sign, cur_size / 1024.0 / 1024.0, total_size / 1024.0 / 1024.0, speed);
    103. fflush(stdout);
    104. if (numShow == numTotal)
    105. printf("\n");
    106. }
    107. unsigned long get_file_size(const char *filename)
    108. {
    109. //通过系统调用直接得到文件的大小
    110. struct stat buf;
    111. if (stat(filename, &buf) < 0)
    112. return 0;
    113. return (unsigned long) buf.st_size;
    114. }
    115. void download(int client_socket, char *file_name, long content_length)
    116. {
    117. /*下载文件函数*/
    118. long hasrecieve = 0;//记录已经下载的长度
    119. struct timeval t_start, t_end;//记录一次读取的时间起点和终点, 计算速度
    120. int mem_size = 8192;//缓冲区大小8K
    121. int buf_len = mem_size;//理想状态每次读取8K大小的字节流
    122. int len;
    123. //创建文件描述符
    124. int fd = open(file_name, O_CREAT | O_WRONLY, S_IRWXG | S_IRWXO | S_IRWXU);
    125. if (fd < 0)
    126. {
    127. printf("文件创建失败!\n");
    128. exit(0);
    129. }
    130. char *buf = (char *) malloc(mem_size * sizeof(char));
    131. //从套接字流中读取文件流
    132. long diff = 0;
    133. int prelen = 0;
    134. double speed;
    135. while (hasrecieve < content_length)
    136. {
    137. gettimeofday(&t_start, NULL ); //获取开始时间
    138. len = read(client_socket, buf, buf_len);
    139. write(fd, buf, len);
    140. gettimeofday(&t_end, NULL ); //获取结束时间
    141. hasrecieve += len;//更新已经下载的长度
    142. //计算速度
    143. if (t_end.tv_usec - t_start.tv_usec >= 0 && t_end.tv_sec - t_start.tv_sec >= 0)
    144. diff += 1000000 * ( t_end.tv_sec - t_start.tv_sec ) + (t_end.tv_usec - t_start.tv_usec);//us
    145. if (diff >= 1000000)//当一个时间段大于1s=1000000us时, 计算一次速度
    146. {
    147. speed = (double)(hasrecieve - prelen) / (double)diff * (1000000.0 / 1024.0);
    148. prelen = hasrecieve;//清零下载量
    149. diff = 0;//清零时间段长度
    150. }
    151. progress_bar(hasrecieve, content_length, speed);
    152. if (hasrecieve == content_length)
    153. break;
    154. }
    155. }
    156. int main(int argc, char const *argv[])
    157. {
    158. /* 命令行参数: 接收两个参数, 第一个是下载地址, 第二个是文件的保存位置和名字, 下载地址是必须的, 默认下载到当前目录
    159. * 示例: ./download http://www.baidu.com baidu.html
    160. */
    161. char url[2048] = "127.0.0.1";//设置默认地址为本机,
    162. char host[64] = {0};//远程主机地址
    163. char ip_addr[16] = {0};//远程主机IP地址
    164. int port = 80;//远程主机端口, http默认80端口
    165. char file_name[256] = {0};//下载文件名
    166. if (argc == 1)
    167. {
    168. printf("您必须给定一个http地址才能开始工作\n");
    169. exit(0);
    170. }
    171. else
    172. strcpy(url, argv[1]);
    173. puts("1: 正在解析下载地址...");
    174. parse_url(url, host, &port, file_name);//从url中分析出主机名, 端口号, 文件名
    175. if (argc == 3)
    176. {
    177. printf("\t您已经将下载文件名指定为: %s\n", argv[2]);
    178. strcpy(file_name, argv[2]);
    179. }
    180. puts("2: 正在获取远程服务器IP地址...");
    181. get_ip_addr(host, ip_addr);//调用函数同访问DNS服务器获取远程主机的IP
    182. if (strlen(ip_addr) == 0)
    183. {
    184. printf("错误: 无法获取到远程服务器的IP地址, 请检查下载地址的有效性\n");
    185. return 0;
    186. }
    187. puts("\n>>>>下载地址解析成功<<<<");
    188. printf("\t下载地址: %s\n", url);
    189. printf("\t远程主机: %s\n", host);
    190. printf("\tIP 地 址: %s\n", ip_addr);
    191. printf("\t主机PORT: %d\n", port);
    192. printf("\t 文件名 : %s\n\n", file_name);
    193. //设置http请求头信息
    194. char header[2048] = {0};
    195. sprintf(header, \
    196. "GET %s HTTP/1.1\r\n"\
    197. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n"\
    198. "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537(KHTML, like Gecko) Chrome/47.0.2526Safari/537.36\r\n"\
    199. "Host: %s\r\n"\
    200. "Connection: keep-alive\r\n"\
    201. "\r\n"\
    202. ,url, host);
    203. puts("3: 创建网络套接字...");
    204. int client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    205. if (client_socket < 0)
    206. {
    207. printf("套接字创建失败: %d\n", client_socket);
    208. exit(-1);
    209. }
    210. //创建IP地址结构体
    211. struct sockaddr_in addr;
    212. memset(&addr, 0, sizeof(addr));
    213. addr.sin_family = AF_INET;
    214. addr.sin_addr.s_addr = inet_addr(ip_addr);
    215. addr.sin_port = htons(port);
    216. //连接远程主机
    217. puts("4: 正在连接远程主机...");
    218. int res = connect(client_socket, (struct sockaddr *) &addr, sizeof(addr));
    219. if (res == -1)
    220. {
    221. printf("连接远程主机失败, error: %d\n", res);
    222. exit(-1);
    223. }
    224. puts("5: 正在发送http下载请求...");
    225. write(client_socket, header, strlen(header));//write系统调用, 将请求header发送给服务器
    226. int mem_size = 4096;
    227. int length = 0;
    228. int len;
    229. char *buf = (char *) malloc(mem_size * sizeof(char));
    230. char *response = (char *) malloc(mem_size * sizeof(char));
    231. //每次单个字符读取响应头信息
    232. puts("6: 正在解析http响应头...");
    233. while ((len = read(client_socket, buf, 1)) != 0)
    234. {
    235. if (length + len > mem_size)
    236. {
    237. //动态内存申请, 因为无法确定响应头内容长度
    238. mem_size *= 2;
    239. char * temp = (char *) realloc(response, sizeof(char) * mem_size);
    240. if (temp == NULL)
    241. {
    242. printf("动态内存申请失败\n");
    243. exit(-1);
    244. }
    245. response = temp;
    246. }
    247. buf[len] = '\0';
    248. strcat(response, buf);
    249. //找到响应头的头部信息
    250. int flag = 0;
    251. for (int i = strlen(response) - 1; response[i] == '\n' || response[i] == '\r'; i--, flag++);
    252. if (flag == 4)//连续两个换行和回车表示已经到达响应头的头尾, 即将出现的就是需要下载的内容
    253. break;
    254. length += len;
    255. }
    256. struct HTTP_RES_HEADER resp = parse_header(response);
    257. printf("\n>>>>http响应头解析成功:<<<<\n");
    258. printf("\tHTTP响应代码: %d\n", resp.status_code);
    259. if (resp.status_code != 200)
    260. {
    261. printf("文件无法下载, 远程主机返回: %d\n", resp.status_code);
    262. return 0;
    263. }
    264. printf("\tHTTP文档类型: %s\n", resp.content_type);
    265. printf("\tHTTP主体长度: %ld字节\n\n", resp.content_length);
    266. printf("7: 开始文件下载...\n");
    267. download(client_socket, file_name, resp.content_length);
    268. printf("8: 关闭套接字\n");
    269. if (resp.content_length == get_file_size(file_name))
    270. printf("\n文件%s下载成功! ^_^\n\n", file_name);
    271. else
    272. {
    273. remove(file_name);
    274. printf("\n文件下载中有字节缺失, 下载失败, 请重试!\n\n");
    275. }
    276. shutdown(client_socket, 2);//关闭套接字的接收和发送
    277. return 0;
    278. }

    运行效果:

  • 相关阅读:
    网站服务器是什么意思?它的用途有哪些?
    自定义表格标签
    数据结构算法之链表指针LinkList
    CSS基础入门笔记(选择器)
    关于类和对象超级初级小白知识
    Valine表白动态心跳源码
    Snipaste安装及使用方法
    凸度(bulge)是AutoCAD 中独有的概念
    读取nii数据,转为volume.dat二进制数据
    【网页设计】基于HTML+CSS+JavaScript学生网上报到系统响应式网站
  • 原文地址:https://blog.csdn.net/buhuidage/article/details/134237426