CSAPP Lab -- Proxy Lab
來自專欄 Computer Science學習筆記4 人贊了文章
準備
這是最後一個lab,要求實現一個並發Web代理。相比本書前半部分的lab,這個任務很容易完成,不需要苦思冥想。讀過最後三章的內容,就可以開始了。需要的知識點有:
- client-server模型
- 簡單的socket編程
- Web伺服器基礎和HTTP協議基礎
- 多線程並發編程,讀者-寫者模型
- Cache相關知識(可選)
先去官網下載所需的材料,一定要下載Writeup,它會提供很大的幫助。閱讀Writeup,可以了解到一些實驗相關的信息:
- Web代理是一個Web伺服器和瀏覽器之間的中間程序。瀏覽器和代理連接,然後代理再將請求轉發給伺服器。伺服器的相應,也由代理返回給瀏覽器。
port-for-user.pl
用來自動生成可用埠(沒啥用,可以自己隨便選個比較大的,基本都能用)- 附贈一個tiny web伺服器,用來進行測試
- 實驗分為三步。第一步實現基本的轉發,第二步實現並發,第三步加入cache功能
- 可以用
curl
這個工具來進行測試,例如:curl -v --proxy http://localhost:15214 http://localhost:15213/home.html
就是向代理http://localhost:15214
發送請求,得到後面那個uri的資源
弄清實驗要求後,就可以構思大致的思路了。類似於web伺服器,只是接受client的request後,解析一下,然後將信息轉發給對應的伺服器,等伺服器響應後,將responce信息從伺服器對應的描述符中讀取出來,再寫入和client對應的描述符就完成了。
完整代碼:
xiebei1108/csapp_codePart1: 簡單實現
HTTP request中包含請求行和請求頭,所以我們可以設計對應的數據結構,方便存儲解析後的數據:
typedef struct { char host[HOSTNAME_MAX_LEN]; char port[PORT_MAX_LEN]; char path[MAXLINE];} ReqLine;typedef struct { char name[HEADER_NAME_MAX_LEN]; char value[HEADER_VALUE_MAX_LEN];} ReqHeader;
請求行實際上是一個map
,但是C語言實現比較麻煩,就直接用了一個數組來保存。
回想一下web伺服器的實現,main()
函數裡面bind
套接字、listen
一個埠、accept
等待連接,然後再處理這個描述符。這個過程完成就和代理一樣,可以直接套用,不同的只是處理函數:
int main(int argc, char **argv) { int listenfd, *connfd; pthread_t tid; char hostname[MAXLINE], port[MAXLINE]; socklen_t clientlen; struct sockaddr_storage clientaddr; if (argc != 2) { fprintf(stderr, "usage: %s <port>
", argv[0]); exit(1); } init_cache(); listenfd = Open_listenfd(argv[1]); while (1) { clientlen = sizeof(clientaddr); connfd = Malloc(sizeof(int)); *connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen); Getnameinfo((SA *) &clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0); printf("Accepted connection from (%s, %s)
", hostname, port); Pthread_create(&tid, NULL, thread, connfd); //處理函數,這個是create了一個新線程 }}
在創建新線程後,它會自動去執行thread()
函數,然後thread()
函數會去調用主要的處理函數doit()
。如果不看並發實現部分的代碼,doit()
函數中的邏輯大概就是:parse_request
得到解析後的請求行和請求頭,然後send_to_server()
,讓他去連接對應的伺服器,向伺服器發送請求。和伺服器建立連接後,返回的信息會在描述符connfd
中,也就是send_to_server()
函數的返回值。然後再把信息從connfd
中讀取出來,直接寫進客戶端對應的描述符fd
就行了。
void doit(int fd) { char buf[MAXLINE], uri[MAXLINE], object_buf[MAX_OBJECT_SIZE]; int total_size, connfd; rio_t rio; ReqLine request_line; ReqHeader headers[20]; int num_hds, n; parse_request(fd, &request_line, headers, &num_hds); strcpy(uri, request_line.host); strcpy(uri+strlen(uri), request_line.path); if (reader(fd, uri)) { fprintf(stdout, "%s from cache
", uri); fflush(stdout); return; } total_size = 0; connfd = send_to_server(&request_line, headers, num_hds); Rio_readinitb(&rio, connfd); while ((n = Rio_readlineb(&rio, buf, MAXLINE))) { Rio_writen(fd, buf, n); strcpy(object_buf + total_size, buf); total_size += n; } if (total_size < MAX_OBJECT_SIZE) { writer(uri, object_buf); } Close(connfd);}
解析部分的代碼很簡單,就不解釋了:
void parse_request(int fd, ReqLine *linep, ReqHeader *headers, int *num_hds) { char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE]; rio_t rio; Rio_readinitb(&rio, fd); if (!Rio_readlineb(&rio, buf, MAXLINE)) return; //parse request line sscanf(buf, "%s %s %s", method, uri, version); parse_uri(uri, linep); //parse request headers *num_hds = 0; Rio_readlineb(&rio, buf, MAXLINE); while(strcmp(buf, "
")) { headers[(*num_hds)++] = parse_header(buf); Rio_readlineb(&rio, buf, MAXLINE); }}void parse_uri(char *uri, ReqLine *linep) { if (strstr(uri, "http://") != uri) { fprintf(stderr, "Error: invalid uri!
"); exit(0); } uri += strlen("http://"); char *c = strstr(uri, ":"); *c =