七 PROXY LAB

這個(gè)LAB 是上完CMU CSAPP的21-25 LECTURE之后硼补,就可以做了。
csapp 課程觀看地址:https://search.bilibili.com/all?keyword=csapp&from_source=banner_search
lab 6 下載地址: http://csapp.cs.cmu.edu/3e/labs.html
選擇PROXY LAB拇勃, 點(diǎn)擊SELF-STUDY HANDOUT

恭喜你四苇,已經(jīng)來到了最后一個(gè)LAB。我的系列也已經(jīng)到了尾聲方咆。紀(jì)念這一個(gè)月來的努力月腋。把自己所有的CODE,放到了GITHUB。
https://github.com/yixuaz/csapp-labs

這次的作業(yè)主要分三個(gè)部分(詳情參見WRITE-UP http://csapp.cs.cmu.edu/3e/proxylab.pdf ):

Sequential Proxy: 接收客戶端發(fā)送的 HTTP 請(qǐng)求榆骚,解析之后向目標(biāo)服務(wù)器轉(zhuǎn)發(fā)片拍,獲得響應(yīng)之后再轉(zhuǎn)發(fā)回客戶端
Concurrent Proxy: 在第一步的基礎(chǔ)上,支持多線程
Cache Web Objects: 使用 LRU 緩存單獨(dú)的對(duì)象妓肢,而不是整個(gè)頁面

PART 1

第一部分捌省,我的思考筆記如下。
第一步碉钠,看懂TINY SERVER(HANDOUT里贈(zèng)送)的代碼纲缓。 就大概知道如何寫一個(gè)SERVER。

第二步喊废,根據(jù)WRITE-UP 4 Part I: Implementing a sequential web proxy
大概需要做如下編程工作色徘。服務(wù)器端接受請(qǐng)求,解析GET http://www.cmu.edu/hub/index.html HTTP/1.1 轉(zhuǎn)換為 GET /hub/index.html HTTP/1.0, 同時(shí)拿到HOST 和 PORT操禀,代理服務(wù)器自己作為CLIENT向目標(biāo)發(fā)送HTTP 1.0請(qǐng)求.

header 部分褂策,先全部保持不變,隨后改4個(gè)值颓屑,
分別為
Host: www.cmu.edu
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3
Connection: close
Proxy-Connection: close

轉(zhuǎn)發(fā)送后斤寂,把接受到的信息再作為代理服務(wù)器的輸出,向原客戶端轉(zhuǎn)發(fā)揪惦。
第一部分就大功告成遍搞。

第三步 代碼實(shí)現(xiàn)

3.1 抄TINY SERVER的框架,把一些常量定義掉

#include <stdio.h>
#include "csapp.h"
/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400

/* You won't lose style points for including this long line in your code */
static const char *user_agent_hdr = "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3\r\n";
static const char *conn_hdr = "Connection: close\r\n";
static const char *prox_hdr = "Proxy-Connection: close\r\n";


void doit(int fd);
void clienterror(int fd, char *cause, char *errnum, 
         char *shortmsg, char *longmsg);
void parse_uri(char *uri,char *hostname,char *path,int *port);
void build_requesthdrs(rio_t *rp, char *newreq, char *hostname);
void *thread(void *vargp);


int main(int argc, char **argv)
{
    int listenfd, *connfd;
    pthread_t tid;
    char hostname[MAXLINE], port[MAXLINE];
    socklen_t clientlen;
    struct sockaddr_storage clientaddr;

    
    /* Check command line args */
    if (argc != 2) {
    fprintf(stderr, "usage: %s <port>\n", argv[0]);
    exit(1);
    }
    signal(SIGPIPE, SIG_IGN);

    listenfd = Open_listenfd(argv[1]);
    while (1) {
        printf("listening..\n");
    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)\n", hostname, port);
        Pthread_create(&tid, NULL, thread, connfd);                              
    }
}
/* Thread routine */
void *thread(void *vargp)
{
    int connfd = *((int *)vargp);
    Pthread_detach(pthread_self());
    Free(vargp);
    doit(connfd);                                             
    Close(connfd);  
    return NULL;
}

/*
 * doit - handle one HTTP request/response transaction
 */
/* $begin doit */
void doit(int client_fd) 
{
    int endserver_fd;
    char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
    rio_t from_client, to_endserver;
    /*store the request line arguments*/
    char hostname[MAXLINE],path[MAXLINE];//path eg  /hub/index.html 
    int port;

    /* Read request line and headers */
    Rio_readinitb(&from_client, client_fd);

    if (!Rio_readlineb(&from_client, buf, MAXLINE))  
        return;
    sscanf(buf, "%s %s %s", method, uri, version);       
    if (strcasecmp(method, "GET")) {                     
        clienterror(client_fd, method, "501", "Not Implemented",
                    "Proxy Server does not implement this method");
        return;
    }

    //parse uri then open a clientfd
    
    parse_uri(uri, hostname, path, &port);
    char port_str[10];
    sprintf(port_str, "%d", port);
    endserver_fd = Open_clientfd(hostname, port_str);
    if(endserver_fd<0){
        printf("connection failed\n");
        return;
    }
    Rio_readinitb(&to_endserver, endserver_fd);

    char newreq[MAXLINE]; //for end server http req headers
    //set up first line eg.GET /hub/index.html HTTP/1.0
    sprintf(newreq, "GET %s HTTP/1.0\r\n", path); 
    build_requesthdrs(&from_client, newreq, hostname);
    
    Rio_writen(endserver_fd, newreq, strlen(newreq)); //send client header to real server
    int n;
    while ((n = Rio_readlineb(&to_endserver, buf, MAXLINE))) {//real server response to buf
        //printf("proxy received %d bytes,then send\n",n);
        Rio_writen(client_fd, buf, n);  //real server response to real client
    }
    
}
/* $end doit */



/*
 * clienterror - returns an error message to the client
 */
/* $begin clienterror */
void clienterror(int fd, char *cause, char *errnum, 
         char *shortmsg, char *longmsg) 
{
    char buf[MAXLINE], body[MAXBUF];

    /* Build the HTTP response body */
    sprintf(body, "<html><title>Proxy Error</title>");
    sprintf(body, "%s<body bgcolor=""ffffff"">\r\n", body);
    sprintf(body, "%s%s: %s\r\n", body, errnum, shortmsg);
    sprintf(body, "%s<p>%s: %s\r\n", body, longmsg, cause);
    sprintf(body, "%s<hr><em>The Proxy Web server</em>\r\n", body);

    /* Print the HTTP response */
    sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Content-type: text/html\r\n");
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Content-length: %d\r\n\r\n", (int)strlen(body));
    Rio_writen(fd, buf, strlen(buf));
    Rio_writen(fd, body, strlen(body));
}
/* $end clienterror */

3.2 實(shí)現(xiàn)2個(gè)輔助函數(shù)

在寫PARSE URI方法前器腋,我們得回顧下C 的STR的用法
https://www.cs.cmu.edu/~213/activities/cbootcamp/cbootcamp_s19.pdf

void parse_uri(char *uri,char *hostname,char *path,int *port) {
    *port = 80;
    //uri http://www.cmu.edu/hub/index.html
    char* pos1 = strstr(uri,"http://");
    if (pos1 == NULL) {
        pos1 = uri;
    } else pos1 += 2;

    //printf("parse uri pos1 %s\n",pos1);//pos1 www.cmu.edu/hub/index.html

    char* pos2 = strstr(pos1,":");
    /*pos1 www.cmu.edu:8080/hub/index.html, pos2 :8080/hub/index.html */
    if (pos2 != NULL) {
        *pos2 = '\0'; //pos1 www.cmu.edu/08080/hub/index.html
        strncpy(hostname,pos1,MAXLINE);
        sscanf(pos2+1,"%d%s",port,path); //pos2+1 8080/hub/index.html
        *pos2 = ':';
    } else {
        pos2 = strstr(pos1,"/");//pos2 /hub/index.html
        if (pos2 == NULL) {/*pos1 www.cmu.edu*/
            strncpy(hostname,pos1,MAXLINE);
            strcpy(path,"");
            return;
        }
        *pos2 = '\0';
        strncpy(hostname,pos1,MAXLINE);
        *pos2 = '/';
        strncpy(path,pos2,MAXLINE);
    }

}
void build_requesthdrs(rio_t *rp, char *newreq, char *hostname, char* port) {
    //already have sprintf(newreq, "GET %s HTTP/1.0\r\n", path);
    char buf[MAXLINE];

    while(Rio_readlineb(rp, buf, MAXLINE) > 0) {          
    if (!strcmp(buf, "\r\n")) break;
        if (strstr(buf,"Host:") != NULL) continue;
        if (strstr(buf,"User-Agent:") != NULL) continue;
        if (strstr(buf,"Connection:") != NULL) continue;
        if (strstr(buf,"Proxy-Connection:") != NULL) continue;

    sprintf(newreq,"%s%s", newreq,buf);
    }
    sprintf(newreq, "%sHost: %s:%s\r\n",newreq, hostname,port);
    sprintf(newreq, "%s%s%s%s", newreq, user_agent_hdr,conn_hdr,prox_hdr);
    sprintf(newreq,"%s\r\n",newreq);
}

3.3 測(cè)試

image.png

第一部分40分拿齊了溪猿。


image.png

PART 2

首先閱讀ECHO MULTI THREAD的代碼
http://www.cs.cmu.edu/afs/cs/academic/class/15213-f18/www/lectures/23-concprog.pdf

隨后就根據(jù)PPT里的思路 用多線程的方式實(shí)現(xiàn)。


image.png

image.png

進(jìn)行測(cè)試


image.png

依然PART 2

本來想些PART3了纫塌,但是突然發(fā)現(xiàn)诊县,有2個(gè)HINT,我都沒察覺到措左,我用過他們依痊。隨后就打算試試看自己的PROXY的健壯性,發(fā)現(xiàn)用瀏覽器測(cè)試怎披,連百度都上不去呀胸嘁。


image.png

隨后根據(jù)這篇博客,和一版新的HINT 對(duì)我的代碼進(jìn)行優(yōu)化
https://www.keblog.me/2014/12/writing-proxy-lab-csapp/

image.png

image.png

依然PART 2.1 修改CSAPP.C做錯(cuò)誤保護(hù)

這里一律注釋掉


image.png

如果有錯(cuò)凉逛,一律return 0


image.png

設(shè)置方法


image.png

開PROXY SERVER前


image.png

開之后
image.png

依然PART 2.2 測(cè)試有沒有File Descriptor泄漏

下面紅框的性宏,不應(yīng)該存在,看來我有FD沒有做釋放状飞。


image.png

在DOIT里面補(bǔ)上這個(gè)


image.png

PART 3

要實(shí)現(xiàn)CACHE的方法毫胜,
決定使用數(shù)組的方法蝌借,為了不浪費(fèi)空間,決定采用分級(jí)數(shù)組的思想指蚁。(和MALLOC LAB很想)
因?yàn)樽畲缶彺鎸?duì)象是100KB菩佑, 一共有1M的緩存空間。
我可以用5個(gè)100KB (500 KB)
25 KB 可以用12個(gè)凝化。(300 KB)
隨后10KB 可以用10個(gè)稍坯。 (100KB)
還有5KB的用20個(gè),(100 KB)
1 KB 用 20個(gè)(20 KB)
100B的 用40個(gè) (4KB)

第一步 定義數(shù)據(jù)結(jié)構(gòu)

//cache.h
#include "csapp.h"
#include <sys/time.h>

#define TYPES 6
extern const int cache_block_size[];
extern const int cache_cnt[];

typedef struct cache_block{
    char* url;
    char* data;
    int datasize;
    int64_t time;
    pthread_rwlock_t rwlock;
} cache_block;

typedef struct cache_type{
    cache_block *cacheobjs;  
    int size;
} cache_type;


cache_type caches[TYPES];

//intialize cache with malloc
void init_cache();
//if miss cache return 0, hit cache write content to fd
int read_cache(char* url, int fd);
//save value to cache
void write_cache(char* url, char* data, int len);
//free cache
void free_cache();

第二步 實(shí)現(xiàn)方法

這里我們用了讀者寫者模式搓劫,并且根據(jù)提示瞧哟。不用嚴(yán)格的按照LRU。這是什么意思的枪向,其實(shí)就是暗示我們?cè)谧x的時(shí)候勤揩,需要去更新時(shí)間錯(cuò),如果有別的線程也在更新同一個(gè)CACHE BLOCK秘蛔。呢么就按照那個(gè)為準(zhǔn)陨亡,TRY失敗了不必強(qiáng)求。


image.png
//cache.c
#include "cache.h"

const int cache_block_size[] = {102, 1024, 5120 ,10240,25600, 102400};
const int cache_cnt[] = {40,20,20,10,12,5};
int64_t currentTimeMillis();

void init_cache()
{
    int i = 0;
    for (; i < TYPES; i++) {
        caches[i].size = cache_cnt[i];
        caches[i].cacheobjs 
              = (cache_block *)malloc(cache_cnt[i] * sizeof(cache_block));
        cache_block *j = caches[i].cacheobjs;
        int k;
        for (k = 0; k < cache_cnt[i]; j++, k++) {
            j->time = 0;
            j->datasize = 0;
            j->url = malloc(sizeof(char) * MAXLINE);
            strcpy(j->url,"");
            j->data = malloc(sizeof(char) * cache_block_size[i]);
            memset(j->data,0,cache_block_size[i]);
            pthread_rwlock_init(&j->rwlock,NULL);
        }
    }
}

void free_cache() {
    int i = 0;
    for (; i < TYPES; i++) {
        cache_block *j = caches[i].cacheobjs;
        int k;
        for (k = 0; k < cache_cnt[i]; j++, k++) {
            free(j->url);
            free(j->data);
            pthread_rwlock_destroy(&j->rwlock);
        }
        free(caches[i].cacheobjs);
    }
}

int read_cache(char *url,int fd){
    
    int tar = 0, i = 0;
    cache_type cur;
    cache_block *p;
    printf("read cache %s \n", url);
    for (; tar < TYPES; tar++) {
        cur = caches[tar];
        p = cur.cacheobjs;
        for(i=0;i < cur.size; i++,p++){
            if(p->time != 0 && strcmp(url,p->url) == 0) break;
        }
        if (i < cur.size) break;     
    }

    if(i == cur.size){
        printf("read cache fail\n");
        return 0;
    }
    pthread_rwlock_rdlock(&p->rwlock);
    if(strcmp(url,p->url) != 0){
        pthread_rwlock_unlock(&p->rwlock);
        return 0;
    }
    pthread_rwlock_unlock(&p->rwlock);
    if (!pthread_rwlock_trywrlock(&p->rwlock)) {
        p->time = currentTimeMillis();
        pthread_rwlock_unlock(&p->rwlock); 
    }
    pthread_rwlock_rdlock(&p->rwlock);
    Rio_writen(fd,p->data,p->datasize);
    pthread_rwlock_unlock(&p->rwlock);
    printf("read cache successful\n");
    return 1;
}

void write_cache(char *url, char *data, int len){
    int tar = 0;
    for (; tar < TYPES && len > cache_block_size[tar]; tar++) ;
    printf("write cache %s %d\n", url, tar);
    /* find empty block */
    cache_type cur = caches[tar];
    cache_block *p = cur.cacheobjs, *pt;
    int i;
    for(i=0;i < cur.size;i++,p++){
        if(p->time == 0){
            break;
        }
    }
    /* find last visited */
    int64_t min = currentTimeMillis();
    if(i == cur.size){
        for(i=0,pt = cur.cacheobjs;i<cur.size;i++,pt++){
            if(pt->time <= min){
                min = pt->time;
                p = pt;
            }
        }
    }
    pthread_rwlock_wrlock(&p->rwlock);
    p->time = currentTimeMillis();
    p->datasize = len;
    memcpy(p->url,url,MAXLINE);
    memcpy(p->data,data,len);
    pthread_rwlock_unlock(&p->rwlock);
    printf("write Cache\n");
}

int64_t currentTimeMillis() {
  struct timeval time;
  gettimeofday(&time, NULL);
  int64_t s1 = (int64_t)(time.tv_sec) * 1000;
  int64_t s2 = (time.tv_usec / 1000);
  return s1 + s2;
}

第三步 整合進(jìn)現(xiàn)有CODE

3.1 修改MAKE FILE


image.png

3.2 增加INIT CACHE


image.png

image.png

image.png

第四步 測(cè)試

image.png

用瀏覽器測(cè)試前深员,需要BAN掉瀏覽器自帶的CACHE负蠕。


image.png

這里我訪問的是
http://home.baidu.com/home/index/contact_us

里面會(huì)加載很多資料,試了幾次倦畅,基本都CACHE下來了遮糖。


image.png

測(cè)試是否內(nèi)存泄漏

valgrind --leak-check=full --show-leak-kinds=all ./proxy 45161

只有一個(gè)我的代碼無法控制的。


image.png
image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末叠赐,一起剝皮案震驚了整個(gè)濱河市欲账,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌芭概,老刑警劉巖赛不,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異谈山,居然都是意外死亡俄删,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門奏路,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人臊诊,你說我怎么就攤上這事鸽粉。” “怎么了抓艳?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵触机,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng)儡首,這世上最難降的妖魔是什么片任? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮蔬胯,結(jié)果婚禮上对供,老公的妹妹穿的比我還像新娘。我一直安慰自己氛濒,他們只是感情好产场,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著舞竿,像睡著了一般京景。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上骗奖,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天确徙,我揣著相機(jī)與錄音,去河邊找鬼执桌。 笑死米愿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鼻吮。 我是一名探鬼主播育苟,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼椎木!你這毒婦竟也來了违柏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤香椎,失蹤者是張志新(化名)和其女友劉穎漱竖,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體畜伐,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡馍惹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了玛界。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片万矾。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖慎框,靈堂內(nèi)的尸體忽然破棺而出良狈,到底是詐尸還是另有隱情,我是刑警寧澤笨枯,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布遇西,位于F島的核電站,受9級(jí)特大地震影響严嗜,放射性物質(zhì)發(fā)生泄漏粱檀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一漫玄、第九天 我趴在偏房一處隱蔽的房頂上張望茄蚯。 院中可真熱鬧,春花似錦称近、人聲如沸第队。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凳谦。三九已至,卻和暖如春衡未,著一層夾襖步出監(jiān)牢的瞬間尸执,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國打工缓醋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留如失,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓送粱,卻偏偏與公主長(zhǎng)得像褪贵,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子抗俄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355