socket http檔案下載器c語言實現
socket是網路程式設計的基石, 本文介紹如何使用c語言使用socket實現一個http檔案下載器.
下載分為以下幾個過程
- 解析出下載地址中的域名和檔名
- 通過域名獲取伺服器的IP地址
- 與目標伺服器建立連線
- 構建http請求頭並將其傳送到伺服器
- 等待伺服器響應然後接收響應頭
- 解析響應頭, 判斷返回碼, 分離開響應頭, 並且響應的正文內容以位元組形式寫入檔案, 正文內容與頭部用兩個\n\r分開
具體實現完全可以通過程式碼看明白, 只需要看main函式和download函式即可, 其他函式都不是核心
完整的實現(Linux平臺)
#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
[email protected]:~/t$ gcc socket\ http下載器.c -std=c99 -lpthread
[email protected]:~/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, 最後再把三個臨時檔案按順序合併到一起就完成下載了
相關推薦
socket http檔案下載器c語言實現
socket是網路程式設計的基石, 本文介紹如何使用c語言使用socket實現一個http檔案下載器. 下載分為以下幾個過程 解析出下載地址中的域名和檔名 通過域名獲取伺服器的IP地址 與目標伺服器建立連線 構建http請求頭並將其傳送到伺服器 等待
Socket介面原理及用C#語言實現
首先從原理上解釋一下采用Socket介面的網路通訊,這裡以最常用的C/S模式作為範例,首先,服務端有一個程序(或多個程序)在指定的埠等待客戶來連線,服務程式等待客戶的連線資訊,一旦連線上之後,就可以按設計的資料交換方法和格式進行資料傳輸。客戶端在需要的時刻發出向服務端的連
C語言實現http的下載
/************************************************************ Copyright (C), 2016, Leon, All Rights Reserved. FileName: download.c coding: UTF-8 Descripti
高效能網路程式設計總結及《TCP/IP Sockets程式設計(C語言實現) (第2版)》 程式碼下載(連結以及檔案打包)
這篇文章將試圖說明應用程式如何接收網路上傳送過來的TCP訊息流,由於篇幅所限,暫時忽略ACK報文的回覆和接收視窗的滑動。 為了快速掌握本文所要表達的思想,我們可以帶著以下問題閱讀: 1、應用程式呼叫read、recv等方法時,socket套接字可以設定為阻塞或者非阻塞,這兩種方式是如何工作的? 2、
基於RTOS的c語言實現http檔案上傳
本實驗為了減少程式碼量,使用了封裝比較完善的http庫,本文主要講述http檔案上傳的主要要求。 一、分析http關鍵頭部資訊 為了分析http header,我們通過chrome得到上傳檔案時的http資訊: 通過上面的截圖我們可以發現,關鍵頭部
用C語言實現websocket服務器
sockaddr extend ++i set strlen ner ace == perl Websocket Echo Server Demo 背景 嵌入式設備的應用開發大都依靠C語言來完成,我去研究如何用c語言實現websocket服務器也是為了在嵌入式設備中實現一個
c語言實現簡單web服務器
tps gate choices found lte expect inf tro condition 1http簡單介紹http超文本傳輸協議:host主機地址:port端口/urlhost會被DNS服務器 解析成IP地址,所以有時候可以直接用域名,http默認訪問80端
C語言實現Socket簡單通信
簡單 置0 tin led AS accep sin ive receive 服務端 #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h
[原始碼和報告分享]基於C語言實現的檔案系統
2 需求說明 2.1 基本要求 l 設計並實現一個目錄列表函式(無須支援選項,如ls -a、ls -l等),用來顯示當前目錄下包含的檔案資訊 l 設計並實現一個改變目錄函式(無須處理路徑名,如../../directoryName等), 用來把當前目錄切換
Linux下用c語言實現發送http請求 方式可以Get或者Post例程參考
sockaddr select sleep online 創建 線程終止 index -s lse [1].[代碼] Linux下用c語言實現發送http請求 方式可以Get或者Post 跳至 [1] ? 1 2 3 4 5 6 7 8 9 10 11 12 1
Linux下用c語言實現傳送http請求 方式可以Get或者Post例程參考
[1].[程式碼] Linux下用c語言實現傳送http請求 方式可以Get或者Post 跳至 [1] ? 1 2
C++:C語言實現HTTP的GET和POST請求例程參考 C++:C語言實現HTTP的GET和POST請求
C++:C語言實現HTTP的GET和POST請求 閱讀目錄 HTTP請求和IP/TCP 實現GET請求 實現POST請求: 參考: 回到頂部
C語言 實現讀取檔案,並統計每個字元出現的個數
/***************** 實現讀取檔案,並統計每個字元出現的個數 *****************/ #include <stdio.h> #include <stdlib.h> unsigned long file_size;
Linux下用c語言實現傳送http請求
前言 在linux下,使用socket進行程式設計,需要到伺服器上進行獲取資料,伺服器使用的php程式設計,需要使用http的方式進行獲取資料。 程式碼 #include <stdio.h> #include <string.h&
Java實現HTTP檔案下載
java 中使用代理伺服器的問題求教 :http://www.linuxsir.org/bbs/archive/index.php/t-188774.html 序言 許多使用者可能會遇到這樣的情況:在網站上發現一個很好的資源,但是這個資源是分成了很多個檔案存放的,如果想把它儲存到本地,只有靠使用者點選另
JAVA 實現 HTTP檔案下載
需求: 在工程中,放置WORD模板檔案供使用者下載 功能:頁面提供下載連結,使用者點選連結後,實現檔案下載。 理解:剛開始以為直接用超連結指向檔案即可,或用js實現,後來發現都不行。 實現:最後在網上搜了一個實現HTTP檔案下載的java類,只要呼叫如下程式碼即可。 下面程
串列埠傳檔案(非終端串列埠,自己定協議下位機部分C語言實現)
之前專案中有個地方要實現一個功能,就是通過非終端串列埠傳輸檔案,這裡將其作為一個小功能模組,簡單的介紹一下自己是如何實現的!SecureCRT超級終端上可以通過lrz等工具通過命令傳輸檔案這個就不介紹,之前的部落格也有寫過怎麼編譯移植使用這個工具,這次是自己來編碼實現通過非
Java servlet 簡單實現http檔案下載斷點續傳功能
斷點續傳,聽上去似乎是個比較高階的話題,本文只講述一下http版的斷點續傳,其他協議的大家可以自行研究。 http協議中,服務端實現斷點續傳首先需要讀取客戶端傳送的Range頭資訊,比如“Range: bytes=12583394-”這個就是指原來正在下載的檔案需要從第12
java 實現 http 檔案下載
package com.easemob.server.example.httpclient.utils; import java.io.File; import java.io.FileOutputStream; import java.io.IOExcepti
[原始碼和文件分享]基於WinInet實現的HTTP檔案下載
背景 之前寫過的網路資料傳輸的小程式都是基於Socket去寫的,所以,如果要用Socket傳輸資料到網站,還需要根據域名獲取伺服器的IP地址,然後再建立連線,傳輸資料。雖然,Socket也可以實現網路傳輸,但是,總感覺不是很方便。所以,後來隨著知識面的拓展,瞭解到Windows還專門提供了Win