寫在前面的話:也沒有作過源碼分析,確實拿到了源碼以後之前就只是看看源碼裏面坐着寫的那些很給力的工具類之類的,好比之前看zermoq,czmq, lighthttpd這些大神級源碼,我大都仍是停留在看看他們寫的工具類,好比:hash, map, list, arraylist等等這些工具集拿來而後用在我本身的工程裏面,這麼着確實給個人工做帶來了不少的方便,可是也是凸顯出我對不少的源碼其實並未深刻其中的。html
此次來到上海了,工做不是很忙,因此仍是想專心下來,看看全部的源碼,提升本身的能力爲主要目標,源碼分析每一個人有每一個人的看源碼的方式,其實我看源碼的方式就比着網上的大神來講仍是比較low的,但願看個人文檔的不要嫌棄纔對哈,呵呵,有些的不對的地方,或者不太符合各位的看源碼習慣的同仁,但願給我提提意見,我後面也按照各位看源碼的方式閱讀試試,是否是更有效率一點。linux
我看源碼其實就分3步:git
1:看一下源碼的大體的結構,這一步,我其實就看看,OK源碼裏面有工具類,有業務類,源碼主次分的很清楚,就好了windows
2:把全部更業務相關的工具類所有通讀一遍,這一步,我就只單純的看源碼裏面的工具類,好比log, list等等,這些東西反正跟這個工程業務來講關係不是很大,拿出來之後咱們本身能夠在咱們本身的工程裏面也能使用。數組
3:有了上面工具類看完以後,開始看工程業務類的代碼,OK這就是主線了,看完了基本上整個工程就至關於有了靈魂,把第二步的工具也都串聯起來了。緩存
此次源碼分析我就先拿wget來下手了,爲何呢?隨手從本身的Source Insight工程裏面搞了一個出來了,也沒什麼目的,哈哈!服務器
好了,廢話不說了,那首先得說說wget的做用cookie
一、下載整個http或者ftp站點。app
二、斷點續傳。dom
三、批量下載。
四、選擇性的下載。
五、密碼和認證。
六、利用代理服務器進行下載。
目前分析的這一版的wget是1.9是支持ssl的,不用再使用curl去下載了。
下面正式開始源碼分析過程;
先組織一下文件結構吧:
打開wget1.9/src目錄,
工具類的.C文件以下
url.c+url.h : url工具類
hash.c+hash.h : hash表類
gnu-md5.c + gnu-md5.h : md5處理類
無依賴文件
Options.h, sysdep.h, log.c , snprintf.c , alloca.c
ansi2knr.c, cmpt.c
剩下的文件就是wget整個工程都會用到基礎類庫+業務處理類,這些文件之間大都會相互依賴,到了那些地方我再一一說明也不遲,先從最簡單,無任何依賴的文件開始看,化繁爲簡。
全文就一個結構體,很清晰
struct options
{
int verbose;/* 冗長模式(這是缺省設置)? */
int quiet;/* 安靜模式(沒有輸出) */ 是否安靜
int ntry;/* 設定最大嘗試連接次數(0 表示無限制). */
int retry_connrefused;/* 鏈接拒絕重試. */
int background;/* 是否是工做在後臺*/
int kill_longer;/* 超過指定的content-length」 是夠拒收消息*/
int ignore_length; /* 是否忽視「content-length」*/
int recursive;/* 是否遞歸*/
int spanhost;/*當遞歸時轉到外部主機*/
int relative_only;/* 僅僅跟蹤相對連接*/
int no_parent;/* 不要追溯到父目錄*/
int reclevel;/* 最大遞歸深度 (inf 或 0 表明無窮).*/
int dirstruct;/* 強制建立目錄*/
int no_dirstruct;/* 不建立目錄*/
int cut_dirs;/* 忽略cut_dirs層遠程目錄*/
int add_hostdir;/* 是否增長主機目錄*/
int noclobber;/* 不要覆蓋存在的文件或使用.#前綴*/
char *dir_prefix;/* 將文件保存到目錄 PREFIX/...*/
char *lfilename;/* 日誌文件 */
char *input_filename;/* input文件名 */
int force_html;/* 把輸入文件看成HTML格式文件對待? */
int spider;/* 不下載任何東西 */
char **accepts;/* 分號分隔的被接受擴展名的列表 */
char **rejects;/* 分號分隔的不被接受的擴展名的列表 */
char **excludes;/* 不被包含目錄的列表*/
char **includes;/* 容許目錄的列表 */
char **domains;/* 分號分隔的被接受域的列表 */
char **exclude_domains; /*分號分隔的不被接受的域的列表*/
int dns_cache;/* 是否是緩存dns */
char **follow_tags; /* 分號分隔的被跟蹤的HTML標籤的列表. */
char **ignore_tags; /* 分號分隔的被忽略的HTML標籤的列表*/
int follow_ftp;/* 跟蹤HTML文檔中的FTP連接*/
int retr_symlinks;/* 在遞歸的時候,將連接指向文件(而不是目錄)*/
char *output_document;/* documents被打印的輸出文件*/
int od_known_regular;/* 輸出文檔是否是一個可操做的普通文件,i.e. not `-' or a device file. */
FILE *dfp;/* 輸出文檔的文件指針 */
int always_rest;/* Always use REST. */
char *ftp_acc;/* FTP 用戶名 */
char *ftp_pass;/* FTP 密碼 */
int netrc;/* 是否讀取 .netrc文件 */
int ftp_glob;/* 打開或關閉文件名的 globbing機制*/
int ftp_pasv;/* 使用被動傳輸模式 (缺省值). */
char *http_user;/* HTTP 用戶名 */
char *http_passwd;/* HTTP 密碼 */
char *user_header;/* 在headers中插入字符串 STRING*/
int http_keep_alive;/* 是否關閉 HTTP活動連接 (永遠連接).*/
int use_proxy;/* 是否使用代理 */
int allow_cache;/* 容許服務端緩存? */
char *http_proxy, *ftp_proxy, *https_proxy;
char **no_proxy;
char *base_href; /*將URL做爲在-F -i參數指定的文件中出現的相對連接的前綴*/
char *progress_type;/* p設定進程條標記. */
char *proxy_user; /*設定代理的用戶名爲 USER*/
char *proxy_passwd; /*設定代理的密碼爲 PASS*/
double read_timeout;/* 讀寫 超時. */
double dns_timeout;/* DNS超時. */
double connect_timeout;/* 鏈接超時. */
int random_wait;/* 在下載之間等待0...2*WAIT秒*/
double wait;/* 兩次嘗試之間間隔SECONDS秒 */
double waitretry;/* 在從新連接之間等待1...SECONDS秒 */
int use_robots;/* 關注 robots.txt? */
long limit_rate;/* 限制下載速度 */
LARGE_INT quota;/* 下載和存儲的最大文件大小 */
int numurls;/* 成功下載的url的數量*/
int server_response;/* 是否打印服務端的response? */
int save_headers;/* 保存HTTP頭到文件 */
#ifdef ENABLE_DEBUG
int debug;/* Debugging on/off */
#endif
int timestamping;/* 不要從新下載文件除非比本地文件新 */
int backup_converted;/* 在轉換文件X以前,將之備份爲 X.orig*/
int backups;/* Are numeric backups made? */
char *useragent;/* 設定代理的名稱爲 AGENT而不是 Wget/VERSION. */
char *referer;/* 在HTTP請求中包含 `Referer: URL'頭 */
int convert_links;/* 轉換非相對連接爲相對連接 */
int remove_listing;/* 是否不移走 `.listing'文件 */
int htmlify;/* Do we HTML-ify the OS-dependent
listings? */
char *dot_style;
long dot_bytes;/* How many bytes in a printing dot. */
int dots_in_line;/* How many dots in one line. */
int dot_spacing;/* How many dots between spacings. */
int delete_after;/* 文件下載後是否被刪除 */
int html_extension;/* 將全部text/html文檔以.html擴展名保存*/
int page_requisites;/* 下載顯示HTML文件的全部圖片 * 遞歸下載中的包含和不包含(accept/reject) */
char *bind_address;/* 指定本地使用地址(主機名或IP,當本地有多個IP或名字時使用). */
#ifdef HAVE_SSL
char *sslcadir;/* CA 目錄 (hash files) */
char *sslcafile;/* CA File to use */
char *sslcertfile;/* 可選客戶端證書. */
char *sslcertkey;/* 可選客戶端證書的KEYFILE */
int sslcerttype;/* 0 = PEM / 1=ASN1 (DER) */
int sslcheckcert;/* 0 do not check / 1 check server cert */
char *sslegdsock; /* optional socket of the egd daemon */
int sslprotocol;/* 0 = auto / 1 = v2 / 2 = v3 / 3 = TLSv1 */
#endif /* HAVE_SSL */
int cookies;/*使用 cookies.*/
char *cookies_input;/*在開始會話前從文件 FILE中加載cookie*/
char *cookies_output;/*在會話結束後將 cookies保存到 FILE文件中*/
char *post_data;/* POST query string */
char *post_file_name;/* File to post */
enum {
restrict_unix,
restrict_windows
} restrict_files_os;/* file name restriction ruleset. */
int restrict_files_ctrl;/* non-zero if control chars in URLs
are restricted from appearing in
generated file names. */
int strict_comments;/* whether strict SGML comments are
enforced. */
};
extern struct options opt;
Sysdep.h文件這個文件主要定義了一些各類系統之間差異的東西,好比windows系統,aix,sunos, OS/2系統缺乏或者應該定義的東西;好比Linux libc5沒有MAP_FAILED這個宏的定義;SunOS系統沒有提供strstr,strtok這個函數的定義等扥,還有一切比較舊的編譯器Watcom須要自定義malloc.h文件之類的。
僅說明一下下面代碼
#ifndef HAVE_U_INT32_T
# if SIZEOF_INT == 4
typedef unsigned int u_int32_t;
# else
# if SIZEOF_LONG == 4
typedef unsigned long u_int32_t;
# else
# if SIZEOF_SHORT == 4
typedef unsigned short u_int32_t;
# else
#error "Cannot determine a 32-bit type"
# endif
# endif
上面代碼提供了有些平臺不提供u_int32_t的定義。儘管關於整數的大小是跟平臺無關的,一些代碼也是須要32比特的整數類型的,這樣的代碼應該用u_int32_t(除了gnu-md5.[ch], 對於可移植性和跨平臺它用本身的檢測)
Safe-ctype.[ch]文件解析
這是一個標準庫 <ctype.h>的兼容的可替代版本,有下面的特性:
- 實現了全部的C99要求的isxxx()函數
- 實現了一些解析類C語言很是有用的字符類
- 沒有改變依賴於當前環境的特徵
- 對於signed或者unsigned char之間的全部值表現都正確
爲了不衝突,頭文件定義isxxx函數都是大寫的,例如,ISALPHA == isalpha
Wget.h -- 工程頭文件
這個文件包含了一些在其餘地方不合適的定義,同時還有一些有用的include,好比討厭的TIME_H
這個頭文件仍是挺重要的,下面我一面翻譯而後一面相信解釋一下,這個文件的理解對後面不少源碼文件理解都帶有很大的關聯性。
/*查看是否是編譯的時候打開了debug的開關*/
#ifndef ENABLE_DEBUG
# define NDEBUG
#endif
/**下面這個PARAMS我想不少人不是很清楚,簡單說明一下
* 就簡單記住,這個PARAMS宏是爲了代碼的可移植性
* 由於早期的C語言聲明函數的時候都不辦函參數,是直接寫在實現中* 的, 確定有人見過下面的函數實現
* int test()
* int a, b;
* { return a - b; }
* OK ,固然如今C語言編譯器都沒有這樣的,這麼僅僅爲了兼容老的 *編譯器
*/
#ifndef PARAMS
# if PROTOTYPES
# define PARAMS(args) args
# else
# define PARAMS(args) ()
# endif
#endif
/**
* 下面的宏判斷僅僅是爲了作程序的國際化和本地化使用
* 對就是這個函數gettext,有須要的能夠本身查看一個linux
* 國際化和本地化的資料
*/
#ifdef HAVE_NLS
# define _(string) gettext (string)
# ifdef HAVE_LIBINTL_H
# include <libintl.h>
# endif /* HAVE_LIBINTL_H */
#else /* not HAVE_NLS */
# define _(string) string
#endif /* not HAVE_NLS */
下面的宏也是一個gettext的用於const字符串的無操做版本,其實就是直接返回了這個string
#define N_(string) (string)
OK ,下面是關於debug消息本地化的一個筆記
翻譯:你會發現DEBUGP消息也是有意被標記爲可翻譯的,緣由以下:
1:debug消息不是給用戶看的,而是給開發者看的,所以debug消息更多的是應該是源碼註釋而不是直接程序裏面輸出
2:debug消息有不少,但大多數是隨機並且瑣碎的,會有不少無用功
3:最後,debug消息對我來講是wget的問題的一個線索,若是我獲得了一個debug的消息是一個我都不理解的語言,那仍是算了,語言都是一個問題了!
#define DO_NOTHING do {} while (0)
/* 你看看下面的宏定義,就是說,若是你打開了debug開關,你是用DEBUGP宏傳入X參數,就會把X打印出來,相反啥也不作 */
#ifdef ENABLE_DEBUG
# define DEBUGP(x) do { if (opt.debug) { debug_logprintf x; } } while (0)
#else /* not ENABLE_DEBUG */
# define DEBUGP(x) DO_NOTHING
#endif /* not ENABLE_DEBUG */
/**
* 該__attribute__屬性能夠給被聲明的函數加上相似printf或者scanf的* 特徵,它可使編譯器檢查函數聲明和函數實際調用參數之間的格式化字* 符串是否匹配。該功能十分有用,尤爲是處理一些很難發現的bug。
*/
#ifdef __GNUC__
# define GCC_FORMAT_ATTR(a, b) __attribute__ ((format (printf, a, b)))
#else /* not __GNUC__ */
# define GCC_FORMAT_ATTR(a, b)
#endif /* not __GNUC__ */
/*下面的枚舉和函數都是來自log.c,可是由於它處處使用,因此定義在這了。我給你們框起來,這麼好看*/
下面這個枚舉說明了日誌的級別 enum log_options { LOG_VERBOSE, LOG_NOTQUIET, LOG_NONVERBOSE, LOG_ALWAYS };
/** *<stdarg.h>這個頭文件我以爲你們應該知道這傢伙定義了什麼* 了,對,就是,va_start, va_arg,va_end這三個可變參數宏,就是* 爲了函數的可變參數存在的 * logprintf 第2個參數是格式化字符串,從第3個參數開始格式* 化參數 * debug_logprintf 第一個參數就是格式化字符串 */ #ifdef HAVE_STDARG_H void logprintf PARAMS ((enum log_options, const char *, ...)) GCC_FORMAT_ATTR (2, 3); void debug_logprintf PARAMS ((const char *, ...)) GCC_FORMAT_ATTR (1, 2); #else /* not HAVE_STDARG_H */ void logprintf (); void debug_logprintf (); #endif /* not HAVE_STDARG_H */
下面就是一些日誌處理函數了
void logputs PARAMS ((enum log_options, const char *)); void logflush PARAMS ((void)); void log_set_flush PARAMS ((int)); int log_set_save_context PARAMS ((int)); |
/*下面的一些就是來自於utils.c裏面的,這些也是處處使用,也給放到wget.h這個頭文件裏面來了*/
#ifndef DEBUG_MALLOC /*下面的宏是沒有開啓debug_malloc開關使用的內存操做*/ #define xmalloc xmalloc_real #define xrealloc xrealloc_real #define xstrdup xstrdup_real #define xfree free
void *xmalloc_real PARAMS ((size_t)); void *xrealloc_real PARAMS ((void *, size_t)); char *xstrdup_real PARAMS ((const char *));
#else /* DEBUG_MALLOC */ /*下面的宏是開啓debug_malloc開關使用的內存操做,能看到宏後面都帶上了debug字段*/ #define xmalloc(s) xmalloc_debug (s, __FILE__, __LINE__) #define xfree(p) xfree_debug (p, __FILE__, __LINE__) #define xrealloc(p, s) xrealloc_debug (p, s, __FILE__, __LINE__) #define xstrdup(p) xstrdup_debug (p, __FILE__, __LINE__)
void *xmalloc_debug PARAMS ((size_t, const char *, int)); void xfree_debug PARAMS ((void *, const char *, int)); void *xrealloc_debug PARAMS ((void *, size_t, const char *, int)); char *xstrdup_debug PARAMS ((const char *, const char *, int)); |
/* wget日誌文件的名稱 */
#define DEFAULT_LOGFILE "wget-log"
#define MD5_HASHLEN 16
OK, 寫到這裏了,系統有關的一些東西也差很少都解釋了,下面就是一些頗有用的宏定義,後面的代碼中會用的到的,你能夠把這些宏定義弄走,你之後寫代碼說不定也用上了,不少人都說宏定義很差,不少的C語言教程裏面也說,宏和goto是頗有爭議性的東西,可是我以爲存在就有理由,不少大師級的任務寫出來的宏定義看起來仍是很爽的,好比說ttl模板,看起來很吃力,可是也比C++的boost宏模板生成寫的要好得多了,呵呵,我的謬論了!
/* 看一個字符串是否是僅僅是一個連字符 */
#define HYPHENP(x) (*(x) == '-' && !*((x) + 1))
/* 最小值宏 */
#define MINVAL(x, y) ((x) < (y) ? (x) : (y))
/*16進制轉10進制,X必須知足isxdigit*/
#define XDIGIT_TO_NUM(x) ((x) < 'A' ? (x) - '0' : TOUPPER (x) - 'A' + 10)
/*轉換ascii碼16進制X,Y的序列到0-255的數值*/
#define X2DIGITS_TO_NUM(h1, h2) ((XDIGIT_TO_NUM (h1) << 4) + XDIGIT_TO_NUM (h2))
/*把一個[0,16)範圍的數轉換成16進制的ascii表示,A-F大寫*/
#define XNUM_TO_DIGIT(x) ("0123456789ABCDEF"[x])
/*把一個[0,16)範圍的數轉換成16進制的ascii表示,A-F小寫*/
#define XNUM_TO_digit(x) ("0123456789abcdef"[x])
/* 返回已經初始化的數值元素的數量
For example:
static char a[] = "foo"; -- countof(a) == 4 (for terminating \0)
int a[5] = {1, 2}; -- countof(a) == 5
char *a[] = { -- countof(a) == 3
"foo", "bar", "baz"
}; */
#define countof(array) (sizeof (array) / sizeof (*(array)))
#define alloca_array(type, size) ((type *) alloca ((size) * sizeof (type)))
/*拷貝以beg開始,end接收的數據到一個從新分配的以’\0’結尾的內存區, place是out的*/
#define BOUNDED_TO_ALLOCA(beg, end, place) do {\
const char *BTA_beg = (beg);\
int BTA_len = (end) - BTA_beg;\
char **BTA_dest = &(place);\
*BTA_dest = alloca (BTA_len + 1);\
memcpy (*BTA_dest, BTA_beg, BTA_len);\
(*BTA_dest)[BTA_len] = '\0';\
} while (0)
/* Return non-zero if string bounded between BEG and END is equal to
STRING_LITERAL. The comparison is case-sensitive. */
/*比較beg開始,end結尾的字符串和string_literal的大小,返回非0相等,這種比較是大小寫敏感的,是內存數據的比較*/
#define BOUNDED_EQUAL(beg, end, string_literal)\
((end) - (beg) == sizeof (string_literal) - 1\
&& !memcmp ((beg), (string_literal),\
sizeof (string_literal) - 1))
/* 這個跟上面的同樣,就是比較的時候不區分大小寫*/
#define BOUNDED_EQUAL_NO_CASE(beg, end, string_literal)\
((end) - (beg) == sizeof (string_literal) - 1\
&& !strncasecmp ((beg), (string_literal),\
sizeof (string_literal) - 1))
/* 其實就是strdup的宏實現 */
#define STRDUP_ALLOCA(ptr, str) do {\
(ptr) = (char *)alloca (strlen (str) + 1);\
strcpy ((ptr), (str));\
} while (0)
/* 若是你想避開任意大小的限制,又不須要一個全動態的數組,假設BASEVAR指向一個type類型的malloced數組,SIZEVAR是數組大小,下面的宏就是 realloc BASEVAR這個數組的,若是你傳入的NEEDED_SIZE數值比SIZEVAR 大,爲了確保每一個元素攤還常量時間,從新分配的數組是之前的2倍,*/
#define DO_REALLOC(basevar, sizevar, needed_size, type)do\
{\
/* Avoid side-effectualness. */\
long do_realloc_needed_size = (needed_size);\
long do_realloc_newsize = 0;\
while ((sizevar) < (do_realloc_needed_size)) {\
do_realloc_newsize = 2*(sizevar);\
if (do_realloc_newsize < 32)\
do_realloc_newsize = 32;\
(sizevar) = do_realloc_newsize;\
}\
if (do_realloc_newsize)\
basevar = (type *)xrealloc (basevar, do_realloc_newsize * sizeof (type));\
} while (0)
/* 釋放一個非空指針 */
#define FREE_MAYBE(foo) do { if (foo) xfree (foo); } while (0)
extern const char *exec_name;
/* Document type ("dt") flags */
enum
{
TEXTHTML = 0x0001,/* document is of type text/html
or application/xhtml+xml */
RETROKF = 0x0002,/* retrieval was OK */
HEAD_ONLY = 0x0004,/* only send the HEAD request */
SEND_NOCACHE = 0x0008,/* send Pragma: no-cache directive */
ACCEPTRANGES = 0x0010,/* Accept-ranges header was found */
ADDED_HTML_EXTENSION = 0x0020 /* added ".html" extension due to -E */
};
/*下面的枚舉是基本錯誤類型,錯誤報告的細節通常沒用應該被簡化*/
typedef enum
{...} uerr_t;
typedef unsigned char boolean;
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
/* So we can say strcmp(a, b) == EQ rather than strcmp(a, b) == 0 or
the really awful !strcmp(a, b). */
#define EQ 0
/* 正無窮遞歸,文檔寫了一大堆,我的感受然並卵,就記住它的定義就好了*/
#define INFINITE_RECURSION -1
#define CONNECT_ERROR(x) ((x) == ECONNREFUSED && !opt.retry_connrefused\
? CONREFUSED : CONERROR)
好了,wget.h這個文件咱們就基本分析完了,其實我就我的理解加翻譯了一下,總結一下wget.h這個頭文件的整體:
1:定義了一些系統兼容性的問題
2:日誌打印的聲明
3:調試函數的聲明
4:內存操做的聲明
5:一些頗有用的宏定義
6:一些後面會使用到的枚舉定義
其實就是:宏,宏,宏,編譯器和各類系統間兼容的問題(Sysdep.h)都是在這個頭文件裏面都包括了,這是第一次的源碼剖析,若是有不恰當和說不明白的地方但願指出來