socket http文件下载器c语言实现
2016-02-29 18:52
1206 查看
socket是网络编程的基石, 本文介绍如何使用c语言使用socket实现一个http文件下载器.
下载分为以下几个过程
解析出下载地址中的域名和文件名
通过域名获取服务器的IP地址
与目标服务器建立连接
构建http请求头并将其发送到服务器
等待服务器响应然后接收响应头
解析响应头, 判断返回码, 分离开响应头, 并且响应的正文内容以字节形式写入文件, 正文内容与头部用两个\n\r分开
具体实现完全可以通过代码看明白, 只需要看main函数和download函数即可, 其他函数都不是核心
完整的实现(Linux平台)
一下代码是单线程执行, 所以我把线程加进去感觉有点多此一举, 算是描述多线程下载吧, 这里补上没有使用线程的代码并能显示下载速度的代码 https://github.com/xanarry/http-downloader/blob/master/http_downloader.c
执行过程, 注意使用了线程编译时要加 -lpthread
这仅仅实现的文件的单线程下载, http的请求头中有个Content-Length属性, 可以通过这个属性轻松实现断点下载和多线程下载. 如下是实现思路:
断点下载
在新的下载之前, 发送两次请求, 第一次请求中我们子读取响应头中的头部, 得到文件长度, 用该长度与本地的文件长度做比较, 如果长度不一致, 那么再发送第二次请求, 在第二次请求中我们给定下载内容的起点和终点, 返回的字节依次写入本地文件末尾就OK了
多线程下载
假如分3个线程下载, 文件的大小是100kb, 第一个线程下线0-33kb, 保存到临时文件part1, 第二线程下载34-66kb, 保存到临时文件part2, 第三个线程下载67-100kb, 保存到文件part3, 最后再把三个临时文件按顺序合并到一起就完成下载了
下载分为以下几个过程
解析出下载地址中的域名和文件名
通过域名获取服务器的IP地址
与目标服务器建立连接
构建http请求头并将其发送到服务器
等待服务器响应然后接收响应头
解析响应头, 判断返回码, 分离开响应头, 并且响应的正文内容以字节形式写入文件, 正文内容与头部用两个\n\r分开
具体实现完全可以通过代码看明白, 只需要看main函数和download函数即可, 其他函数都不是核心
完整的实现(Linux平台)
一下代码是单线程执行, 所以我把线程加进去感觉有点多此一举, 算是描述多线程下载吧, 这里补上没有使用线程的代码并能显示下载速度的代码 https://github.com/xanarry/http-downloader/blob/master/http_downloader.c
#include <stdio.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <fcntl.h> #include <unistd.h> #include <netdb.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <pthread.h> #include <sys/time.h> struct resp_header//保持相应头信息 { int status_code;//HTTP/1.1 '200' OK char content_type[128];//Content-Type: application/gzip long content_length;//Content-Length: 11683079 char file_name[256]; }; struct resp_header resp;//全剧变量以便在多个进程中使用 void parse_url(const char *url, char *domain, int *port, char *file_name) { /*通过url解析出域名, 端口, 以及文件名*/ int j = 0; int start = 0; *port = 80; char *patterns[] = {"http://", "https://", NULL}; for (int i = 0; patterns[i]; i++) if (strncmp(url, patterns[i], strlen(patterns[i])) == 0) start = strlen(patterns[i]); //解析域名, 这里处理时域名后面的端口号会保留 for (int i = start; url[i] != '/' && url[i] != '\0'; i++, j++) domain[j] = url[i]; domain[j] = '\0'; //解析端口号, 如果没有, 那么设置端口为80 char *pos = strstr(domain, ":"); if (pos) sscanf(pos, ":%d", port); //删除域名端口号 for (int i = 0; i < (int)strlen(domain); i++) { if (domain[i] == ':') { domain[i] = '\0'; break; } } //获取下载文件名 j = 0; for (int i = start; url[i] != '\0'; i++) { if (url[i] == '/') { if (i != strlen(url) - 1) j = 0; continue; } else file_name[j++] = url[i]; } file_name[j] = '\0'; } struct resp_header get_resp_header(const char *response) { /*获取响应头的信息*/ struct resp_header resp; char *pos = strstr(response, "HTTP/"); if (pos) sscanf(pos, "%*s %d", &resp.status_code);//返回状态码 pos = strstr(response, "Content-Type:");//返回内容类型 if (pos) sscanf(pos, "%*s %s", resp.content_type); pos = strstr(response, "Content-Length:");//内容的长度(字节) if (pos) sscanf(pos, "%*s %ld", &resp.content_length); return resp; } void get_ip_addr(char *domain, char *ip_addr) { /*通过域名得到相应的ip地址*/ struct hostent *host = gethostbyname(domain); if (!host) { ip_addr = NULL; return; } for (int i = 0; host->h_addr_list[i]; i++) { strcpy(ip_addr, inet_ntoa( * (struct in_addr*) host->h_addr_list[i])); break; } } void progressBar(long cur_size, long total_size) { /*用于显示下载进度条*/ float percent = (float) cur_size / total_size; const int numTotal = 50; int numShow = (int)(numTotal * percent); if (numShow == 0) numShow = 1; if (numShow > numTotal) numShow = numTotal; char sign[51] = {0}; memset(sign, '=', numTotal); printf("\r%.2f%%\t[%-*.*s] %.2f/%.2fMB", percent * 100, numTotal, numShow, sign, cur_size / 1024.0 / 1024.0, total_size / 1024.0 / 1024.0); fflush(stdout); if (numShow == numTotal) printf("\n"); } void * download(void * socket_d) { /*下载文件函数, 放在线程中执行*/ int client_socket = *(int *) socket_d; int length = 0; int mem_size = 4096;//mem_size might be enlarge, so reset it int buf_len = mem_size;//read 4k each time int len; //创建文件描述符 int fd = open(resp.file_name, O_CREAT | O_WRONLY, S_IRWXG | S_IRWXO | S_IRWXU); if (fd < 0) { printf("Create file failed\n"); exit(0); } char *buf = (char *) malloc(mem_size * sizeof(char)); //从套接字中读取文件流 while ((len = read(client_socket, buf, buf_len)) != 0 && length < resp.content_length) { write(fd, buf, len); length += len; progressBar(length, resp.content_length); } if (length == resp.content_length) printf("Download successful ^_^\n\n"); } int main(int argc, char const *argv[]) { /* test url: 1. https://nodejs.org/dist/v4.2.3/node-v4.2.3-linux-x64.tar.gz 2. http://img.ivsky.com/img/tupian/pre/201312/04/nelumbo_nucifera-009.jpg */ char url[2048] = "127.0.0.1"; char domain[64] = {0}; char ip_addr[16] = {0}; int port = 80; char file_name[256] = {0}; if (argc == 1) { printf("Input a valid URL please\n"); exit(0); } else strcpy(url, argv[1]); puts("1: Parsing url..."); parse_url(url, domain, &port, file_name); if (argc == 3) strcpy(file_name, argv[2]); puts("2: Get ip address..."); get_ip_addr(domain, ip_addr); if (strlen(ip_addr) == 0) { printf("can not get ip address\n"); return 0; } puts("\n>>>>Detail<<<<"); printf("URL: %s\n", url); printf("DOMAIN: %s\n", domain); printf("IP: %s\n", ip_addr); printf("PORT: %d\n", port); printf("FILENAME: %s\n\n", file_name); //设置http请求头信息 char header[2048] = {0}; sprintf(header, \ "GET %s HTTP/1.1\r\n"\ "Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n"\ "User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537(KHTML, like Gecko) Chrome/47.0.2526Safari/537.36\r\n"\ "Host:%s\r\n"\ "Connection:close\r\n"\ "\r\n"\ ,url, domain); //printf("%s\n%d", header, (int) strlen(header)); //创建套接字 int client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (client_socket < 0) { printf("invalid socket descriptor: %d\n", client_socket); exit(-1); } //创建地址结构体 struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(ip_addr); addr.sin_port = htons(port); //连接服务器 puts("3: Connect server..."); int res = connect(client_socket, (struct sockaddr *) &addr, sizeof(addr)); if (res == -1) { printf("connect failed, return: %d\n", res); exit(-1); } puts("4: Send request...");//向服务器发送下载请求 write(client_socket, header, strlen(header)); int mem_size = 4096; int length = 0; int len; char *buf = (char *) malloc(mem_size * sizeof(char)); char *response = (char *) malloc(mem_size * sizeof(char)); //每次单个字符读取响应头信息, 仅仅读取的是响应部分的头部, 后面单独开线程下载 while ((len = read(client_socket, buf, 1)) != 0) { if (length + len > mem_size) { //动态内存申请, 因为无法确定响应头内容长度 mem_size *= 2; char * temp = (char *) realloc(response, sizeof(char) * mem_size); if (temp == NULL) { printf("realloc failed\n"); exit(-1); } response = temp; } buf[len] = '\0'; strcat(response, buf); //找到响应头的头部信息, 两个"\n\r"为分割点 int flag = 0; for (int i = strlen(response) - 1; response[i] == '\n' || response[i] == '\r'; i--, flag++); if (flag == 4) break; length += len; } //printf("\n>>>>Response header:<<<<\n%s", response); resp = get_resp_header(response); strcpy(resp.file_name, file_name); printf("5: Start thread to download...\n"); /*开新的线程下载文件*/ pthread_t download_thread; pthread_create(&download_thread, NULL, download, (void *) &client_socket); pthread_join(download_thread, NULL); return 0; }
执行过程, 注意使用了线程编译时要加 -lpthread
xanarry@ThinkPad:~/t$ gcc socket\ http下载器.c -std=c99 -lpthread xanarry@ThinkPad:~/t$ ./a.out https://nodejs.org/dist/v4.2.3/node-v4.2.3-linux-x64.tar.gz 1: Parsing url... 2: Get ip address... >>>>Detail<<<< URL: https://nodejs.org/dist/v4.2.3/node-v4.2.3-linux-x64.tar.gz DOMAIN: nodejs.org IP: 104.20.22.46 PORT: 80 FILENAME: node-v4.2.3-linux-x64.tar.gz 3: Connect server... 4: Send request... 5: Start thread to download... 3.11% [== ] 0.35/11.14MB
这仅仅实现的文件的单线程下载, http的请求头中有个Content-Length属性, 可以通过这个属性轻松实现断点下载和多线程下载. 如下是实现思路:
断点下载
在新的下载之前, 发送两次请求, 第一次请求中我们子读取响应头中的头部, 得到文件长度, 用该长度与本地的文件长度做比较, 如果长度不一致, 那么再发送第二次请求, 在第二次请求中我们给定下载内容的起点和终点, 返回的字节依次写入本地文件末尾就OK了
多线程下载
假如分3个线程下载, 文件的大小是100kb, 第一个线程下线0-33kb, 保存到临时文件part1, 第二线程下载34-66kb, 保存到临时文件part2, 第三个线程下载67-100kb, 保存到文件part3, 最后再把三个临时文件按顺序合并到一起就完成下载了
相关文章推荐
- java-模拟tomcat服务器
- Linux socket 初步
- 如何组织构建多文件 C 语言程序(二)
- Scrapy的架构介绍
- 发布一个自己做的jsp博客系统
- 如何写好 C main 函数
- java socket 注意的地方
- java socket 注意的地方
- 提供个 全免杀海洋2006asp木马 下载
- 高手写的Tracer-Flash代码调试类代码下载
- Vista 防火墙 Vista Firewall Control v1.0.11 下载
- 国外Lightbox v2.03.3 最新版 下载
- 火影漫画下载助手 下载
- USBkill U盘防火墙 v3.0 下载
- 腾讯 Tencent Traveler v3.4 下载
- 查杀软件 360安全卫士 v3.2.1.1001 下载
- Kaspersky 6.0.2.666 MP2 nct Release+汉化补丁 下载
- Symantec Norton Ghost v12.0 Retail ISO 多国语言版 下载
- 电脑硬件分析Ultimate Boot CD v4.1.1 下载
- 花香盈路7.6商业美化版 下载