計算機網絡實驗代碼與文件可見github:計算機網絡實驗整理
實驗名稱 HTTP 代理服務器的設計與實現
實驗目的:
熟悉並掌握 Socket 網絡編程的過程與技術;深入理解 HTTP 協議,
掌握 HTTP 代理服務器的基本工作原理;掌握 HTTP 代理服務器設計與
編程實現的基本技能。
實驗內容:
(1) 設計並實現一個基本 HTTP 代理服務器。 要求在指定端口(例如8080) 接收來自客戶的 HTTP 請求並且根據其中的 URL 地址訪問該地址所指向的 HTTP 服務器(原服務器), 接收 HTTP 服務器的響應報文,並將響應報文轉發給對應的客戶進行瀏覽。
(2) 設計並實現一個支持 Cache 功能的 HTTP 代理服務器。要求能緩存原服務器響應的對象,並能夠通過修改請求報文(添加 if-modified-since頭行),向原服務器確認緩存對象是否是最新版本。
(3) 擴展 HTTP 代理服務器,支持如下功能:
a) 網站過濾:允許/不允許訪問某些網站;
b) 用戶過濾:支持/不支持某些用戶訪問外部網站;
c) 網站引導:將用戶對某個網站的訪問引導至一個模擬網站(釣魚)
實驗過程:
以文字描述、實驗結果截圖等形式闡述實驗過程,必要時可附相應的代碼截圖或以附件形式提交。
一.Socket 編程的客戶端和服務器端主要步驟
客戶端:
- 根據目的服務器IP地址和端口號創建套接字,連接服務器
- 發送請求報文
- 接受返回報文
- 關閉連接
服務器端: - 創建套接字,綁定IP地址和端口號,監聽端口
- 從連接隊列中取出一個連接請求,三次握手創建連接
- 接收請求報文
- 發送響應報文
- 關閉當前連接,繼續監聽端口
二.HTTP 代理服務器的基本原理
代理服務器,俗稱“FQ軟件”,允許一個網絡終端(一般為客戶端)通過這個服務與另一個網絡終端(一般為服務器)進行非直接的連接代理服務器在指定端口(例如 8080) 監聽瀏覽器的訪問請求(需要在客戶端瀏覽器進行相應的設置), 接收到瀏覽器對遠程網站的瀏覽請求時,代理服務器開始在代理服務器的緩存中檢索URL對應的對象(網頁、圖像等對象),找到對象文件后,提取該對象文件的最新被修改時間;代理服務器程序在客戶的請求報文首部插入<If-Modified-Since: 對象文件的最新被修改時間>,並向原 Web 服務器轉發修改后的請求報文。 如果代理服務器沒有該對象的緩存,則會直接向原服務器轉發請求報文,並將原服務器返回的響應直接轉發給客戶端,同時將對象緩存到代理服務器中。代理服務器程序會根據緩存的時間、大小和提取記錄等對緩存進行清理。
三.HTTP 代理服務器的程序流程圖
四.實現 HTTP 代理服務器的關鍵技術及解決方案
4.1 關鍵技術:代理服務器基本功能實現
解決方案:這一部分主要實現的功能就是上面第三部分中流程圖中的全部內容,主要使用到的函數簡單介紹如下:
Socket():根據選用的服務建立套接字,本實驗使用的網絡層協議是TCP
Bind():將一個本地地址與一個創建好的套接字綁定,使用這個函數意味着代理服務器端的套接字已經可以開始准備開始工作了
Listen():監聽端口的連接請求,在使用這個函數之后套接字進入監聽模式
Accept():在一個套接字處接受連接請求,注意和listen的區別,在accept()函數起作用之后意味着代理服務器和客戶端之間的連接已經建立起來了。
Connect():建立socket連線
Send():發送報文(本實驗中使用的時候主要是向目標服務器發送請求報文,向客戶端發送響應報文)
Recv():接收報文
接下來簡單介紹代理服務器的工作流程:
- 首先初始化套接字。使用socket函數創建一個套接字,並利用bind函數將套接字和代理服務器的本地地址綁定,在本實驗中根據實驗指導書,將代理服務器的IP地址設置為“127.0.0.1”,將端口設置為10240。在這一步完成之后套接字初始化完成,使用listen函數,套接字進入監聽模式,等待客戶端發送的連接請求。
- 進入監聽模式之后使用accept函數對於每個來到的請求進行接收,在本實驗中為了保證效率,所以對於每一個連接都創建一個單獨的進程來提供服務
- 當連接建立之后,使用recv函數接收客戶端發送的HTTP報文,解析出其中的目的服務器的相關信息,並使用connect函數與目的服務器建立連接,使用send函數將HTTP請求報文發送給目的服務器。
- 等待目的服務器返回響應報文,使用send函數將響應報文發送給客戶端。如果前述過程一切正常,表示代理服務器的任務已經完成。
- 處理完成之后,等待200ms之后,關閉該線程,清理緩存,可以繼續處理接下來的連接請求。
4.2 關鍵技術:實現代理服務器緩存
解決方案:代理服務器緩存技術在選做內容中相對是比較復雜的,實現過程主要如下: - 創建一個cache數組用來存儲用戶訪問過的服務器中的數據
- 當客戶第一次訪問某個服務器中的數據的時候,代理服務器將返回報文中的內容緩存下來,保存到本地。在本實驗中由於是直接在程序中存儲,因此設置的存儲容量比較小。
- 如果在cache數組中沒有查找到代理服務器已經緩存過服務器的內容,和2同操作;如果查詢到已經有緩存,需要解決這個緩存是否已經不是目的服務器上最新的內容了。因此本實驗中解析客戶端發送的HTTP請求報文,加入一個“If-Modified-Since”,並加上緩存中的最后修改時間,以此詢問目的服務器是否在這段時間內發生了更改,如果目的服務器返回狀態碼304,說明代理服務器緩存的就是最新的,那么就將緩存返回給客戶端;如果返回狀態碼200,表示已經更新,那么將更新過后的響應內容發送給客戶端,並更新緩存。
4.3 關鍵技術:網站過濾
解決方案:在代理服務器段對於客戶發送的請求報文進行解析,提取出其要訪問的url,如果和已知需要過濾的網站一致,直接關閉套接字,結束這次連接,代碼片段如下:
if (strstr(httpHeader->url, invalid_website[0]) != NULL)
2. {
3. printf("\n=====================\n");
4. printf("--------該網站已被屏蔽!----------\n");
5. goto error;
6. }
4.4 關鍵技術:用戶過濾
解決方案:本實驗實現過程中是實現了只能從限定的部分用戶才能訪問,因此只需要在建立連接的時候比對請求連接的客戶端IP地址是否和限定的IP地址一致,如果一致允許繼續執行程序;如果不一致直接斷開連接。代碼片段如下:
if (!strcmp(restrict_host[0], inet_ntoa(addr_in.sin_addr)) && button)
2. {
3. printf("該用戶訪問受限\n");
4. continue;
5. }
4.5 關鍵技術:網站引導
解決方案:檢測客戶端發送的HTTP請求報文,如果發現訪問的網址是要被釣魚的網址,則將該網址引導到其他網站(釣魚目的網址),通過更改 HTTP 頭部字段的 url和主機名來實現,部分代碼如下:
if (strstr(httpHeader->url, fishing_src) != NULL)
2. {
3. printf("\n=====================\n");
4. printf("-------------已從源網址:%s 轉到 目的網址 :%s ----------------\n", fishing_src, fishing_dest);
5. memcpy(httpHeader->host, fishing_dest_host, strlen(fishing_dest_host) + 1);
6. memcpy(httpHeader->url, fishing_dest, strlen(fishing_dest));
7. }
實驗結果:
(1)首先在實驗開始之前設置瀏覽器連接代理服務器的IP地址和端口,如下圖所示:
(2)接着是HTTP代理服務器基本功能實現,連接的網址為:http://www.hit.edu.cn,顯示結果為:
(3)接着測試支持cache的HTTP代理服務器的功能,訪問網址為:http://jwts.hit.edu.cn,第一次訪問結果:
第二次訪問結果如下:
觀察目的服務器段返回的報文可以知道,訪問的網站中內容和緩存的內容是一致的,因此直接將緩存的內容返回給客戶端。
(4)擴展HTTP代理服務器:
(a)網站過濾,不允許訪問http://www.hit.edu.cn,可以知道我們上述測試中這網站是可以正常訪問的,而當開啟了網站過濾功能之后結果如下:
可以發現網站已經無法訪問,而且在控制台中輸出了我們預期的網站被屏蔽的提示信息。
(b)用戶過濾,將本機地址屏蔽,訪問地址為http://jwts.hit.edu.cn/,經上述測試,如果不使用屏蔽的時候訪問結果正常,而當開啟了用戶過濾之后可以發現結果如下,已經無法訪問,且在控制台輸出相應的提示信息:
(c)網站引導:將網址 http://today.hit.edu.cn/ 的訪問引導到網址 http://jwts.hit.edu.cn/ ,運行結果如圖:
可以觀察到網站被引導到了http://jwts.hit.edu.cn/,在控制台也輸出了相關的提示信息,證明引導成功。
問題討論:
對實驗過程中的思考問題進行討論或回答。
(1)對於指導書中的參考代碼報錯的一些思考:a)報錯的第一個點就是#include "stdafx.h"這一行代碼報錯,解決方式就是直接去掉即可。錯誤的原因是沒有這個頭文件,而這個頭文件的作用經查詢資料可知,Windows和MFC的include文件都非常大,即使有一個快速的處理程序,編譯程序也要花費相當長的時間來完成工作。由於每個.CPP文件都包含相同的include文件,為每個.CPP文件都重復處理這些文件耗時很長,因此stdafx.h可以視為一個包含所有需要include的頭文件,在編譯開始的時候先把這些內容編譯好,之后在編譯每一個cpp文件的時候就閱讀編譯好的結果即可。b)報錯的第二個比較大的點就是代碼中使用了goto,而且在調用goto語句之后還定義了隨機變量,大部分編譯器都會認為這種行為是危險的,而在VS2019中可以通過對其進行簡單設置去除這種錯誤,經查閱資料,其余一些編譯器需要將所有的隨機變量都定義在調用goto之前(事實上這樣才是真正安全的,VS2019的處理方式只是強制略過這種錯誤檢測)。
(2)對於使用代理服務器訪問一些網站的時候存在圖片無法加載或者加載的很慢的情況,猜測是因為對於代理服務器來說,HTTP發送報文的時候是不采用流水的,因此中間需要多次發送和接受過程,而且還經過了代理服務器這一中介,可能使得速度更慢,因此在現在的HTTP報文傳送中猜測大多使用了流水線形式或其他形式加速發送報文。
心得體會:
結合實驗過程和結果給出實驗的體會和收獲。
本次實驗讓我對於socket編程的基本函數和基本操作有了認識,同時也了解了HTTP代理服務器的基本原理,對於HTTP請求和響應過程有了更深刻的認識。同時通過一些杜宇代理服務器的擴展,對於網站引導和屏蔽的最簡單的實現方式有了認識,對於socket編程產生了濃厚興趣。
1. #include <stdio.h>
2. #include <Windows.h>
3. #include <process.h>
4. #include <string.h>
5. #pragma comment(lib,"Ws2_32.lib")
6. #define MAXSIZE 65507 //發送數據報文的最大長度
7. #define HTTP_PORT 80 //http 服務器端口
8. #define DATELENGTH 50 //時間字節數
9. #define CACHE_NUM 50 //定義最大緩存數量
10. //Http 重要頭部數據
11. struct HttpHeader {
12. char method[4]; // POST 或者 GET,注意有些為 CONNECT,本實驗暫不考慮
13. char url[1024]; // 請求的 url
14. char host[1024]; // 目標主機
15. char cookie[1024 * 10]; //cookie
16. HttpHeader() {
17. ZeroMemory(this, sizeof(HttpHeader));
18. }
19. };
20. //因為不做外部存儲,所以為了節省空間,cache存儲的時候
21. //去掉Http頭部信息中的cookie
22. struct cacheHttpHead {
23. char method[4]; // POST 或者 GET,注意有些為 CONNECT,本實驗暫不考慮
24. char url[1024]; // 請求的 url
25. char host[1024]; // 目標主機
26. cacheHttpHead() {
27. ZeroMemory(this, sizeof(cacheHttpHead));
28. }
29. };
30. //代理服務器緩存技術
31. struct CACHE {
32. cacheHttpHead httpHead;
33. char buffer[MAXSIZE];//儲存報文返回內容
34. char date[DATELENGTH];//緩存內容的最后修改時間
35. CACHE() {
36. ZeroMemory(this->buffer, MAXSIZE);
37. ZeroMemory(this->date, DATELENGTH);
38. }
39. };
40. CACHE cache[CACHE_NUM];//緩存地址
41. int cache_index = 0;//記錄當前應該將緩存放在哪個位置
42.
43. BOOL InitSocket();
44. void ParseHttpHead(char* buffer, HttpHeader* httpHeader);
45. BOOL ConnectToServer(SOCKET* serverSocket, char* host);
46. unsigned int __stdcall ProxyThread(LPVOID lpParameter);
47. int isInCache(CACHE* cache, HttpHeader httpHeader);//尋找緩存中是否存在,如果存在返回index,不存在返回-1
48. BOOL httpEqual(cacheHttpHead http1, HttpHeader http2);//判斷兩個http報文是否相同,主要用來判斷緩存和報文是否相同
49. void changeHTTP(char* buffer, char* date);//用於修改HTTP報文
50. //代理相關參數
51. SOCKET ProxyServer;
52. sockaddr_in ProxyServerAddr;
53. const int ProxyPort = 10240;
54. //由於新的連接都使用新線程進行處理,對線程的頻繁的創建和銷毀特別浪費資源
55. //可以使用線程池技術提高服務器效率
56. //const int ProxyThreadMaxNum = 20;
57. //HANDLE ProxyThreadHandle[ProxyThreadMaxNum] = {0};
58. //DWORD ProxyThreadDW[ProxyThreadMaxNum] = {0};
59. struct ProxyParam {
60. SOCKET clientSocket;
61. SOCKET serverSocket;
62. };
63. //選做功能參數定義
64. bool button =true;//取true的時候表示開始運行選做功能
65. //禁止訪問網站
66. char* invalid_website[10] = { "http://www.hit.edu.cn" };
67. const int invalid_website_num = 1;//有多少個禁止網站
68. //釣魚網站
69. char* fishing_src = "http://today.hit.edu.cn";//釣魚網站原網址
70. char* fishing_dest = "http://jwes.hit.edu.cn";//釣魚網站目標網址
71. char* fishing_dest_host = "jwts.hit.edu.cn";//釣魚目的地址主機名
72. //限制訪問用戶
73. char* restrict_host[10] = { "127.0.0.1" };
74. int main(int argc, char* argv[])
75. {
76. printf("代理服務器正在啟動\n");
77. printf("初始化...\n");
78. if (!InitSocket()) {
79. printf("socket 初始化失敗\n");
80. return -1;
81. }
82. printf("代理服務器正在運行,監聽端口 %d\n", ProxyPort);
83. SOCKET acceptSocket = INVALID_SOCKET;
84. ProxyParam* lpProxyParam;
85. HANDLE hThread;
86. DWORD dwThreadID;
87. sockaddr_in addr_in;
88. int addr_len = sizeof(SOCKADDR);
89. //代理服務器不斷監聽
90. while (true) {
91. acceptSocket = accept(ProxyServer, (SOCKADDR*)&addr_in, &(addr_len));
92. lpProxyParam = new ProxyParam;
93. if (lpProxyParam == NULL) {
94. continue;
95. }
96. //受限用戶,與列表中匹配上的都無法訪問
97. if (strcmp(restrict_host[0], inet_ntoa(addr_in.sin_addr)) && button)//注意比較之前將網絡二進制的數字轉換成網絡地址
98. {
99. printf("該用戶訪問受限\n");
100. continue;
101. }
102. lpProxyParam->clientSocket = acceptSocket;
103. hThread = (HANDLE)_beginthreadex(NULL, 0,
104. &ProxyThread, (LPVOID)lpProxyParam, 0, 0);
105. CloseHandle(hThread);
106. Sleep(200);
107. }
108. closesocket(ProxyServer);
109. WSACleanup();
110. return 0;
111. }
112. //************************************
113. // Method: InitSocket
114. // FullName: InitSocket
115. // Access: public
116. // Returns: BOOL
117. // Qualifier: 初始化套接字
118. //************************************
119. BOOL InitSocket() {
120. //加載套接字庫(必須)
121. WORD wVersionRequested;
122. WSADATA wsaData;
123. //套接字加載時錯誤提示
124. int err;
125. //版本 2.2
126. wVersionRequested = MAKEWORD(2, 2);
127. //加載 dll 文件 Scoket 庫
128. err = WSAStartup(wVersionRequested, &wsaData);
129. if (err != 0) {
130. //找不到 winsock.dll
131. printf("加載 winsock 失敗,錯誤代碼為: %d\n", WSAGetLastError());
132. return FALSE;
133. }
134. //if中的語句主要用於比對是否是2.2版本
135. if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
136. {
137. printf("不能找到正確的 winsock 版本\n");
138. WSACleanup();
139. return FALSE;
140. }
141. //創建的socket文件描述符基於IPV4,TCP
142. ProxyServer = socket(AF_INET, SOCK_STREAM, 0);
143. if (INVALID_SOCKET == ProxyServer) {
144. printf("創建套接字失敗,錯誤代碼為: %d\n", WSAGetLastError());
145. return FALSE;
146. }
147. ProxyServerAddr.sin_family = AF_INET;
148. ProxyServerAddr.sin_port = htons(ProxyPort);//整型變量從主機字節順序轉變成網絡字節順序,轉換為大端法
149. ProxyServerAddr.sin_addr.S_un.S_addr = INADDR_ANY;//泛指本機也就是表示本機的所有IP,多網卡的情況下,這個就表示所有網卡ip地址的意思
150. if (bind(ProxyServer, (SOCKADDR*)&ProxyServerAddr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
151. printf("綁定套接字失敗\n");
152. return FALSE;
153. }
154. if (listen(ProxyServer, SOMAXCONN) == SOCKET_ERROR) {
155. printf("監聽端口%d 失敗", ProxyPort);
156. return FALSE;
157. }
158. return TRUE;
159. }
160. //************************************
161. // Method: ProxyThread
162. // FullName: ProxyThread
163. // Access: public
164. // Returns: unsigned int __stdcall
165. // Qualifier: 線程執行函數
166. // Parameter: LPVOID lpParameter
167. //************************************
168. unsigned int __stdcall ProxyThread(LPVOID lpParameter) {
169. char Buffer[MAXSIZE];
170. char* CacheBuffer;
171. ZeroMemory(Buffer, MAXSIZE);
172. SOCKADDR_IN clientAddr;
173. int length = sizeof(SOCKADDR_IN);
174. int recvSize;
175. int ret;
176. recvSize = recv(((ProxyParam
177. *)lpParameter)->clientSocket, Buffer, MAXSIZE, 0);//接收到報文
178. if (recvSize <= 0) {
179. goto error;
180. }
181. HttpHeader* httpHeader = new HttpHeader();
182. CacheBuffer = new char[recvSize + 1];
183. ZeroMemory(CacheBuffer, recvSize + 1);
184. memcpy(CacheBuffer, Buffer, recvSize);
185. //處理HTTP頭部
186. ParseHttpHead(CacheBuffer, httpHeader);
187. //處理禁止訪問網站
188. if (strstr(httpHeader->url, invalid_website[0]) != NULL && button)
189. {
190. printf("\n=====================\n");
191. printf("--------該網站已被屏蔽!----------\n");
192. goto error;
193. }
194. //處理釣魚網站
195. if (strstr(httpHeader->url, fishing_src) != NULL && button)
196. {
197. printf("\n=====================\n");
198. printf("-------------已從源網址:%s 轉到 目的網址 :%s ----------------\n", fishing_src, fishing_dest);
199. //修改HTTP報文
200. memcpy(httpHeader->host, fishing_dest_host, strlen(fishing_dest_host) + 1);
201. memcpy(httpHeader->url, fishing_dest, strlen(fishing_dest));
202. }
203. delete CacheBuffer;
204. //連接目標主機
205. if (!ConnectToServer(&((ProxyParam
206. *)lpParameter)->serverSocket, httpHeader->host)) {
207. goto error;
208. }
209. printf("代理連接主機 %s 成功\n", httpHeader->host);
210.
211. int index = isInCache(cache, *httpHeader);
212. //如果在緩存中存在
213. if (index > -1)
214. {
215. char* cacheBuffer;
216. char Buf[MAXSIZE];
217. ZeroMemory(Buf, MAXSIZE);
218. memcpy(Buf, Buffer, recvSize);
219. //插入"If-Modified-Since: "
220. changeHTTP(Buf, cache[index].date);
221. printf("-------------------請求報文------------------------\n%s\n", Buf);
222. ret = send(((ProxyParam
223. *)lpParameter)->serverSocket, Buf, strlen(Buf)+1, 0);
224. recvSize = recv(((ProxyParam
225. *)lpParameter)->serverSocket, Buf, MAXSIZE, 0);
226. printf("------------------Server返回報文-------------------\n%s\n", Buf);
227. if (recvSize <= 0) {
228. goto error;
229. }
230. char* No_Modified = "304";
231. //沒有改變,直接返回cache中的內容
232. if (!memcmp(&Buf[9], No_Modified, strlen(No_Modified)))
233. {
234. ret = send(((ProxyParam
235. *)lpParameter)->clientSocket, cache[index].buffer, strlen(cache[index].buffer) + 1, 0);
236. printf("將cache中的緩存返回客戶端\n");
237. printf("============================\n");
238. goto error;
239. }
240. }
241. //將客戶端發送的 HTTP 數據報文直接轉發給目標服務器
242. ret = send(((ProxyParam*)lpParameter)->serverSocket, Buffer, strlen(Buffer)
243. + 1, 0);
244. //等待目標服務器返回數據
245. recvSize = recv(((ProxyParam
246. *)lpParameter)->serverSocket, Buffer, MAXSIZE, 0);
247. if (recvSize <= 0) {
248. goto error;
249. }
250. //以下部分將返回報文加入緩存
251. //從服務器返回報文中解析時間
252. char* cacheBuffer2 = new char[MAXSIZE];
253. ZeroMemory(cacheBuffer2, MAXSIZE);
254. memcpy(cacheBuffer2, Buffer, MAXSIZE);
255. char* delim = "\r\n";
256. char date[DATELENGTH];
257. char* nextStr;
258. ZeroMemory(date, DATELENGTH);
259. char* p = strtok_s(cacheBuffer2, delim, &nextStr);
260. bool flag = false;//表示是否含有修改時間報文
261. //不斷分行,直到分出具有修改時間的那一行
262. while (p)
263. {
264. if (p[0] == 'L')//找到Last-Modified:那一行
265. {
266. if (strlen(p) > 15)
267. {
268. char header[15];
269. ZeroMemory(header, sizeof(header));
270. memcpy(header, p, 14);
271. if (!(strcmp(header, "Last-Modified:")))
272. {
273. memcpy(date, &p[15], strlen(p) - 15);
274. flag = true;
275. break;
276. }
277. }
278. }
279. p = strtok_s(NULL, delim, &nextStr);
280. }
281. if (flag)
282. {
283. if (index > -1)//說明已經有內容存在,只要改一下時間和內容
284. {
285. memcpy(&(cache[index].buffer), Buffer, strlen(Buffer));
286. memcpy(&(cache[index].date), date, strlen(date));
287. }
288. else//第一次訪問,需要完全緩存
289. {
290. memcpy(&(cache[cache_index % CACHE_NUM].httpHead.host), httpHeader->host,strlen(httpHeader->host));
291. memcpy(&(cache[cache_index % CACHE_NUM].httpHead.method), httpHeader->method, strlen(httpHeader->method));
292. memcpy(&(cache[cache_index % CACHE_NUM].httpHead.url), httpHeader->url, strlen(httpHeader->url));
293. memcpy(&(cache[cache_index % CACHE_NUM].buffer), Buffer, strlen(Buffer));
294. memcpy(&(cache[cache_index % CACHE_NUM].date), date, strlen(date));
295. cache_index++;
296. }
297. }
298. //將目標服務器返回的數據直接轉發給客戶端
299. ret = send(((ProxyParam
300. *)lpParameter)->clientSocket, Buffer, sizeof(Buffer), 0);
301. //錯誤處理
302. error:
303. printf("關閉套接字\n");
304. Sleep(200);
305. closesocket(((ProxyParam*)lpParameter)->clientSocket);
306. closesocket(((ProxyParam*)lpParameter)->serverSocket);
307. delete lpParameter;
308. _endthreadex(0);
309. return 0;
310. }
311. //************************************
312. // Method: ParseHttpHead
313. // FullName: ParseHttpHead
314. // Access: public
315. // Returns: void
316. // Qualifier: 解析 TCP 報文中的 HTTP 頭部
317. // Parameter: char * buffer
318. // Parameter: HttpHeader * httpHeader
319. //************************************
320. void ParseHttpHead(char* buffer, HttpHeader* httpHeader) {
321. char* p;
322. char* ptr;
323. const char* delim = "\r\n";
324. p = strtok_s(buffer, delim, &ptr);//提取第一行
325. printf("%s\n", p);
326. if (p[0] == 'G') {//GET 方式
327. memcpy(httpHeader->method, "GET", 3);
328. memcpy(httpHeader->url, &p[4], strlen(p) - 13);
329. }
330. else if (p[0] == 'P') {//POST 方式
331. memcpy(httpHeader->method, "POST", 4);
332. memcpy(httpHeader->url, &p[5], strlen(p) - 14);
333. }
334. printf("%s\n", httpHeader->url);
335. p = strtok_s(NULL, delim, &ptr);
336. while (p) {
337. switch (p[0]) {
338. case 'H'://Host
339. memcpy(httpHeader->host, &p[6], strlen(p) - 6);
340. break;
341. case 'C'://Cookie
342. if (strlen(p) > 8) {
343. char header[8];
344. ZeroMemory(header, sizeof(header));
345. memcpy(header, p, 6);
346. if (!strcmp(header, "Cookie")) {
347. memcpy(httpHeader->cookie, &p[8], strlen(p) - 8);
348. }
349. }
350. break;
351. default:
352. break;
353. }
354. p = strtok_s(NULL, delim, &ptr);
355. }
356. }
357. //************************************
358. // Method: ConnectToServer
359. // FullName: ConnectToServer
360. // Access: public
361. // Returns: BOOL
362. // Qualifier: 根據主機創建目標服務器套接字,並連接
363. // Parameter: SOCKET * serverSocket
364. // Parameter: char * host
365. //************************************
366. BOOL ConnectToServer(SOCKET* serverSocket, char* host) {
367. sockaddr_in serverAddr;
368. serverAddr.sin_family = AF_INET;
369. serverAddr.sin_port = htons(HTTP_PORT);
370. HOSTENT* hostent = gethostbyname(host);
371. if (!hostent) {
372. return FALSE;
373. }
374. in_addr Inaddr = *((in_addr*)*hostent->h_addr_list);
375. serverAddr.sin_addr.s_addr = inet_addr(inet_ntoa(Inaddr));//將一個將網絡地址轉換成一個長整數型數
376. *serverSocket = socket(AF_INET, SOCK_STREAM, 0);
377. if (*serverSocket == INVALID_SOCKET) {
378. return FALSE;
379. }
380. if (connect(*serverSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr))
381. == SOCKET_ERROR) {
382. closesocket(*serverSocket);
383. return FALSE;
384. }
385. return TRUE;
386. }
387. BOOL httpEqual(cacheHttpHead http1, HttpHeader http2)
388. {
389. if (strcmp(http1.method, http2.method))return false;
390. if (strcmp(http1.host, http2.host))return false;
391. if (strcmp(http1.url, http2.url))return false;
392. return true;
393. }
394. int isInCache(CACHE* cache, HttpHeader httpHeader)
395. {
396. int index = 0;
397. for (; index < CACHE_NUM; index++)
398. {
399. if (httpEqual(cache[index].httpHead, httpHeader))return index;
400. }
401. return -1;
402. }
403. void changeHTTP(char* buffer, char* date)
404. {
405. //此函數在HTTP中間插入"If-Modified-Since: "
406. const char* strHost = "Host";
407. const char* inputStr = "If-Modified-Since: ";
408. char temp[MAXSIZE];
409. ZeroMemory(temp, MAXSIZE);
410. char* pos = strstr(buffer, strHost);//找到Host位置
411. int i = 0;
412. //將host與之后的部分寫入temp
413. for (i = 0; i < strlen(pos); i++) {
414. temp[i] = pos[i];
415. }
416. *pos = '\0';
417. while (*inputStr != '\0') { //插入If-Modified-Since字段
418. *pos++ = *inputStr++;
419. }
420. while (*date != '\0') {
421. *pos++ = *date++;
422. }
423. *pos++ = '\r';
424. *pos++ = '\n';
425. //將host之后的字段復制到buffer中
426. for (i = 0; i < strlen(temp); i++) {
427. *pos++ = temp[i];
428. }
429. }