CSAPP Lab08——Proxy Lab完成思路

news2025/1/17 23:26:12

蓝色的思念

突然演变成了阳光的夏天

空气中的温暖不会很遥远

——被风吹过的夏天

完整代码见:CSAPP/proxylab-handout at main · SnowLegend-star/CSAPP (github.com)

Q:计算机网络中port的作用是什么?

A:在计算机网络中,"port"(端口)是用于标识网络通信中的特定应用程序或服务的数字标识符。端口是网络通信的重要组成部分,它允许多个应用程序在同一台计算机上共享网络连接,并确保正确地将数据传递给目标应用程序。

端口通常与 IP 地址结合使用,构成了网络通信中的目标地址。IP 地址用于标识网络中的设备,而端口则用于标识设备上的具体应用程序或服务

端口的作用包括:

  1. 标识应用程序或服务:每个应用程序或服务都可以绑定到一个或多个端口上,通过端口号来唯一标识。
  2. 实现多路复用:通过使用端口,多个应用程序可以在同一台计算机上共享网络连接。这意味着同一时间内,可以同时进行多个不同应用程序的通信,每个应用程序使用不同的端口。
  3. 提供通信路由:在网络通信中,端口号帮助路由数据到正确的目标应用程序。当数据包到达目标设备后,根据目标端口号将数据包分发给相应的应用程序或服务。

总之,端口在计算机网络中起着非常重要的作用,它们允许多个应用程序共享网络连接,并确保数据被正确地路由到目标应用程序或服务。


运行课本样例code的方法 

今天突然想把《CSAPP》书上的例子跑一下,结果还是经典的报错。本来以前运行过书上的示例代码,当时就是报错。折腾半天无果索性放弃了。现在想到书都快看完了还没跑成功过示例代码,我心血来潮决定好好研究下到底怎么执行。

想不到还不好搜,找半天才看到一个可行的方案,原来编译书上的代码时都得和csapp.c进行联合编译。因为好多函数都在csapp.c里面重写了,如果不带上这个文件就会有各种报错,看似是没引入头文件导致库函数无法使用,实则是因为当前文件用到的函数其实是在csapp.c内部重新实现了的,只不过csapp.c内部编写的函数命名方式和库函数神似,有点以假乱真。

至此,终于是成功运行了书上的示例代码。


 

一不小心又在proxy lab上磨了好久,主要是一开始对怎么测试proxy感到很疑惑,就去b站上搜了下,结果发现了个讲得很好的up。遂跟着这个up把10~12章重新温习了一番,收获颇多。由于up主的github上只有通过最后一次实验的记录,我又研究半天怎么获得github的历史提交记录,最后也是成功掌握了这个技巧。

言归正传,下面讲讲我完成这个lab的历程。

首先,总体思路图大致如下

 

1、Proxy先与Client进行通信,建立连接并接收并分析来自Client的http-request

2、Proxy把处理好的http-request发送给Server

3、Server发送response给Proxy

4、Proxy将response转发给Client

Part I: Implementing a sequential web proxy

第一部分要求我们实现一个简单的sequential proxy,类似的代码在书上随处可见,tiny.c就是一个很好的模仿对象。它的大致流程如下:

1、在main()里面创建一个监听描述符listenfd,然后在while的循环体里不断尝试与客户端进行连接connfd=Accept(listenfd)。连接成功后将connfd传入请求处理函数handleRequest()中。

2、进入handRequest()后,分别创建好两个I/O缓冲区rio_Proxy2Client和rio_Proxy2Server,Proxy可以利用这两个不同的缓冲区分别和Client与Server进行读写操作。

GET http://www.cmu.edu:8080/hub/index.html HTTP/1.1

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

3、利用rio_Proxy2Client读入http-request,并把这个请求分割成writeup要求的几部分,即method、url、version;其中url又可以分割为hostName、port和fielName。

4、处理完request的第一行后,开始处理后序的四个request header,少哪个header就自己补上哪个header。

5、 Proxy利用刚才得到的hostName和port,调用clientfd=Open_clientfd()冒充一个Client与Server建立连接。

6、Proxy利用clientfd和rio_Proxy2Server,先把从Client处得到的http-request发送给Server,然后不断读入Server回复的response。Proxy在读入response的同时,又利用connfd把它们发送给Client。

7、还得加上一个如果method不是“GET”的错误处理函数,直接照着书上的敲一遍或者把tiny.c的那个clientError()复制过来就行。

第一部分的代码难度并不高,核心操作就是要对字符串进行各种处理。

part I的实现如下
 

#include <stdio.h>
#include"csapp.h"
#include"sbuf.h"

/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400
#define HTTP_PREFIX "http://"

/* 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";

void handleRequest(int fd);
void parseRequest(char* buf, char* host, char* port, char* method, char* url, char* version, char* fileName);
void clientError(int fd, char* cause, char* errnum, char* errmsg, char* errmsg_datail);
int readAndFormatRequestHeader(rio_t* rio, char* clientRequest, char* Host, char* port,
                                char* method, char* url, char* version, char* filename);

//和tiny.c里面的那个差不多
void clientError(int fd, char* cause, char* errnum, char* errmsg, char* errmsg_datail){
    char buf[MAXLINE];

    //打印HTTP的响应头
    sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, errmsg);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Content-type: text/html\r\n\r\n");
    Rio_writen(fd, buf, strlen(buf));

    //打印HTTP响应的主体
    sprintf(buf, "<html><title>Tiny Error</title>");
    Rio_writen(fd,buf,strlen(buf));
    sprintf(buf, "<body bgcolor=""ffffff"">\r\n");
    Rio_writen(fd,buf,strlen(buf));
    sprintf(buf, "%s: %s\r\n", errmsg, errmsg);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "<p>%s: %s\r\n", errmsg_datail, cause);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "<hr><em>The Tiny Web Server</em>\r\n");
    Rio_writen(fd, buf, strlen(buf));
}

void handleRequest(int fd){
    //处理各种定位的指针
    char* pos=NULL;

    //分割http请求的参数
    char buf[MAXLINE],method[MAXLINE],uri[MAXLINE],version[MAXLINE],fileName[MAXLINE];

    //客户端的几个请求头的主体
    char clientRequest[MAXLINE];
    char hostName[MAXLINE],port[MAXLINE];

    //proxy-client和proxy-server的IO
    rio_t rio_Proxy2Client,rio_Proxy2Server;

    //Step1: proxy读入来自client的请求
    Rio_readinitb(&rio_Proxy2Client,fd);
    if(Rio_readlineb(&rio_Proxy2Client,buf,MAXLINE)==0){
        //这个请求是空的
        printf("Oops! empty request\n");
        return ;
    }

    //如果http的版本是1.1,处理成1.0
    if((pos=strstr(buf,"HTTP/1.1"))!=NULL){
        buf[pos-buf-1+strlen("HTTP/1.1")]='0';
    }

    //Step2: 分割请求
    parseRequest(buf, hostName, port, method, uri, version, fileName);

    //判断请求是否有效
    if(strcasecmp(method,"GET")!=0){
        clientError(fd, method, "501", "Not Implement", "Tiny Does not implement this method");
        return ;
    }

    int rv=readAndFormatRequestHeader(&rio_Proxy2Client, clientRequest, hostName, port, method, uri, version, fileName);
    if(rv==0){
        return ;
    }

    //Step3: tiny server和proxy建立连接
    int clientfd=Open_clientfd(hostName, port);

    Rio_readinitb(&rio_Proxy2Server, clientfd);
    Rio_writen(rio_Proxy2Server.rio_fd, clientRequest, strlen(clientRequest));

    //Step4: 从tiny server读入response,并且把它发送给client
    printf("The Proxy is ready to relay the response\n");

    char tinyResponse[MAXLINE];
    int n;

    while((n=Rio_readlineb(&rio_Proxy2Server, tinyResponse, MAXLINE))!=0){
        Rio_writen(fd, tinyResponse, n);
    }
}


    //client request is like this
    //GET http://www.cmu.edu/hub/index.html HTTP/1.1
void parseRequest(char* buf, char* host, char* port, char* method, char* url, char* version, char* fileName){
    sscanf(buf,"%s %s %s", method, url, version);
    //method = "GET", url = "http://localhost:15213/home.html", version = "HTTP1.0"

    char* host_pos =strstr(url,HTTP_PREFIX)+strlen(HTTP_PREFIX);    //主机名开始的位置
    char* port_pos =strstr(host_pos,":");                           //端口开始的位置
    char* slash_pos=strstr(host_pos,"/");                           //suffix开始的位置
   
    //判断url有没有带端口号,如果没带就是默认端口80
    if(port_pos==NULL){ //没带端口号
        strcpy(port,"80");
        strncmp(host,host_pos,slash_pos-host_pos);      
    }
    else{
        strncpy(host,host_pos,port_pos-host_pos);
        strncpy(port,port_pos+1,slash_pos-port_pos-1);
    }

    strcpy(fileName,slash_pos);
    printf("HostName: %s",host);
    printf("Port: %s",port);
    printf("fileName: %s",fileName);
}

int readAndFormatRequestHeader(rio_t* rio, char* clientRequest, char* Host, char* port,
                                char* method, char* url, char* version, char* fileName){

    int UserAgent=0, Connection=0, proxyConnection=0, hostInfo=0;
    char buf[MAXLINE/2];
    int n;
    char* findpos;

    sprintf(clientRequest, "GET %s HTTP/1.0\r\n",fileName);

    n=Rio_readlineb(rio, buf, MAXLINE);
    printf("receive buf %s\n", buf);
    printf("n = %d", n);

    while(strcmp("\r\n",buf)!=0&&n!=0){
        strcat(clientRequest, buf);
        printf("receive buf %s\n", buf);
        
        //判断要求的四个请求头是否存在
        if((findpos=strstr(buf, "User-Agent:"))!=NULL){
            UserAgent=1;
        }
        if((findpos=strstr(buf,"Proxy-Connection:"))!=NULL){
            proxyConnection=1;
        }
        if((findpos=strstr(buf,"Connection"))!=NULL){
            Connection=1;
        }
        if((findpos=strstr(buf, "Host"))!=NULL){
            hostInfo=1;
        }

        n=Rio_readlineb(rio, buf ,MAXLINE);
    }

    if(n==0){
        return 0;
    }


    //如果缺失了这四个头部,则进行添加    
    if(hostInfo==0){
        sprintf(buf, "Host: %s\r\n", Host);
        strcat(clientRequest, buf);
    }

    if(UserAgent==0){
        strcat(clientRequest, user_agent_hdr);
    }

    if(Connection==0){
        sprintf(buf, "Connection: close\r\n");
        strcat(clientRequest, buf);
    }

    if(proxyConnection==0){
        sprintf(buf, "Proxy-Connection: close\r\n");
        strcat(clientRequest, buf);
    }

    //添加最后的空行
    strcat(clientRequest,"\r\n");
    return 1;
}

int main(int argc,char** argv){
    if(argc!=2){
        unix_error("proxy usage: ./proxy <port>");
    }

    int listenfd=Open_listenfd(argv[1]);
    struct sockaddr_storage clientaddr;
    char hostName[MAXLINE], port[MAXLINE];

    while(1){
        socklen_t clientlen=sizeof(struct sockaddr_storage);
        int connfd=Accept(listenfd,(SA*) &clientaddr,&clientlen);

        Getnameinfo((SA*) &clientaddr,clientlen,hostName,MAXLINE,port,MAXLINE,0);

        handleRequest(connfd);
        Close(connfd);
    }
    return 0;
}

Part II

书上给出了三种并发编程的方式:

1、基于进程

2、基于I/O多路复用

3、基于线程

不得不说基于线程是最为通俗易懂的,而且书上还有一个基于线程进行并发编程的例子“echoservert-pre.c”。结合这份代码,在Part I的基础上稍作改动即可。

对了,书上还要求在main()函数里面加入Signal(SIGPIPE, SIG_IGN)来屏蔽SIGPIPE信号。我一开始并没有加这个顺利通过了lab,(:

part II实现如下
 

#include <stdio.h>
#include"csapp.h"
#include"sbuf.h"

/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400
#define HTTP_PREFIX "http://"
#define NTHREADS 4
#define SBUFSIZE 16

sbuf_t sbuf;    //shared buffer of connected descriptor
void *thread(void *vargp);

/* 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";

void handleRequest(int fd);
void parseRequest(char* buf, char* host, char* port, char* method, char* url, char* version, char* fileName);
void clientError(int fd, char* cause, char* errnum, char* errmsg, char* errmsg_datail);
int readAndFormatRequestHeader(rio_t* rio, char* clientRequest, char* Host, char* port,
                                char* method, char* url, char* version, char* filename);

//和tiny.c里面的那个差不多
void clientError(int fd, char* cause, char* errnum, char* errmsg, char* errmsg_datail){
    char buf[MAXLINE];

    //打印HTTP的响应头
    sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, errmsg);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Content-type: text/html\r\n\r\n");
    Rio_writen(fd, buf, strlen(buf));

    //打印HTTP响应的主体
    sprintf(buf, "<html><title>Tiny Error</title>");
    Rio_writen(fd,buf,strlen(buf));
    sprintf(buf, "<body bgcolor=""ffffff"">\r\n");
    Rio_writen(fd,buf,strlen(buf));
    sprintf(buf, "%s: %s\r\n", errmsg, errmsg);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "<p>%s: %s\r\n", errmsg_datail, cause);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "<hr><em>The Tiny Web Server</em>\r\n");
    Rio_writen(fd, buf, strlen(buf));
}

void handleRequest(int fd){
    //处理各种定位的指针
    char* pos=NULL;

    //分割http请求的参数
    char buf[MAXLINE],method[MAXLINE],uri[MAXLINE],version[MAXLINE],fileName[MAXLINE];

    //客户端的几个请求头的主体
    char clientRequest[MAXLINE];
    char hostName[MAXLINE],port[MAXLINE];

    //proxy-client和proxy-server的IO
    rio_t rio_Proxy2Client,rio_Proxy2Server;

    //Step1: proxy读入来自client的请求
    Rio_readinitb(&rio_Proxy2Client,fd);
    if(Rio_readlineb(&rio_Proxy2Client,buf,MAXLINE)==0){
        //这个请求是空的
        printf("Oops! empty request\n");
        return ;
    }

    //如果http的版本是1.1,处理成1.0
    if((pos=strstr(buf,"HTTP/1.1"))!=NULL){
        buf[pos-buf-1+strlen("HTTP/1.1")]='0';
    }

    //Step2: 分割请求
    parseRequest(buf, hostName, port, method, uri, version, fileName);

    //判断请求是否有效
    if(strcasecmp(method,"GET")!=0){
        clientError(fd, method, "501", "Not Implement", "Tiny Does not implement this method");
        return ;
    }

    int rv=readAndFormatRequestHeader(&rio_Proxy2Client, clientRequest, hostName, port, method, uri, version, fileName);
    if(rv==0){
        return ;
    }

    //Step3: tiny server和proxy建立连接
    int clientfd=Open_clientfd(hostName, port);

    Rio_readinitb(&rio_Proxy2Server, clientfd);
    Rio_writen(rio_Proxy2Server.rio_fd, clientRequest, strlen(clientRequest));

    //Step4: 从tiny server读入response,并且把它发送给client
    printf("The Proxy is ready to relay the response\n");

    char tinyResponse[MAXLINE];
    int n;

    while((n=Rio_readlineb(&rio_Proxy2Server, tinyResponse, MAXLINE))!=0){
        Rio_writen(fd, tinyResponse, n);
    }
}


    //client request is like this
    //GET http://www.cmu.edu/hub/index.html HTTP/1.1
void parseRequest(char* buf, char* host, char* port, char* method, char* url, char* version, char* fileName){
    sscanf(buf,"%s %s %s", method, url, version);
    //method = "GET", url = "http://localhost:15213/home.html", version = "HTTP1.0"

    char* host_pos =strstr(url,HTTP_PREFIX)+strlen(HTTP_PREFIX);    //主机名开始的位置
    char* port_pos =strstr(host_pos,":");                           //端口开始的位置
    char* slash_pos=strstr(host_pos,"/");                           //suffix开始的位置
   
    //判断url有没有带端口号,如果没带就是默认端口80
    if(port_pos==NULL){ //没带端口号
        strcpy(port,"80");
        strncmp(host,host_pos,slash_pos-host_pos);      
    }
    else{
        strncpy(host,host_pos,port_pos-host_pos);
        strncpy(port,port_pos+1,slash_pos-port_pos-1);
    }

    strcpy(fileName,slash_pos);
    printf("HostName: %s",host);
    printf("Port: %s",port);
    printf("fileName: %s",fileName);
}

int readAndFormatRequestHeader(rio_t* rio, char* clientRequest, char* Host, char* port,
                                char* method, char* url, char* version, char* fileName){

    int UserAgent=0, Connection=0, proxyConnection=0, hostInfo=0;
    char buf[MAXLINE/2];
    int n;
    char* findpos;

    sprintf(clientRequest, "GET %s HTTP/1.0\r\n",fileName);

    n=Rio_readlineb(rio, buf, MAXLINE);
    printf("receive buf %s\n", buf);
    printf("n = %d", n);

    while(strcmp("\r\n",buf)!=0&&n!=0){
        strcat(clientRequest, buf);
        printf("receive buf %s\n", buf);
        
        //判断要求的四个请求头是否存在
        if((findpos=strstr(buf, "User-Agent:"))!=NULL){
            UserAgent=1;
        }
        if((findpos=strstr(buf,"Proxy-Connection:"))!=NULL){
            proxyConnection=1;
        }
        if((findpos=strstr(buf,"Connection"))!=NULL){
            Connection=1;
        }
        if((findpos=strstr(buf, "Host"))!=NULL){
            hostInfo=1;
        }

        n=Rio_readlineb(rio, buf ,MAXLINE);
    }

    if(n==0){
        return 0;
    }


    //如果缺失了这四个头部,则进行添加    
    if(hostInfo==0){
        sprintf(buf, "Host: %s\r\n", Host);
        strcat(clientRequest, buf);
    }

    if(UserAgent==0){
        strcat(clientRequest, user_agent_hdr);
    }

    if(Connection==0){
        sprintf(buf, "Connection: close\r\n");
        strcat(clientRequest, buf);
    }

    if(proxyConnection==0){
        sprintf(buf, "Proxy-Connection: close\r\n");
        strcat(clientRequest, buf);
    }

    //添加最后的空行
    strcat(clientRequest,"\r\n");
    return 1;
}

int main(int argc,char** argv){
    if(argc!=2){
        unix_error("proxy usage: ./proxy <port>");
    }

    int listenfd=Open_listenfd(argv[1]), i;
    pthread_t tid;
    struct sockaddr_storage clientaddr;
    char hostName[MAXLINE], port[MAXLINE];
    sbuf_init(&sbuf, SBUFSIZE);

    for(i=0;i<NTHREADS;i++){    //创建工作线程
        Pthread_create(&tid, NULL, thread, NULL);
    }

    while(1){
        socklen_t clientlen=sizeof(struct sockaddr_storage);
        int connfd=Accept(listenfd,(SA*) &clientaddr,&clientlen);
        //Getnameinfo((SA*)&clientaddr, clientlen, hostName, MAXLINE, port, MAXLINE, 0);
        sbuf_insert(&sbuf, connfd);
    }
    return 0;
}

void *thread(void *vargp){
    pthread_detach(pthread_self());
    while(1){
        int connfd=sbuf_remove(&sbuf);
        handleRequest(connfd);
        Close(connfd);
    }
}

 

Part III

最后一部分写的我汗流浃背了。

这一部分要求我们给Proxy添加cache。对于cache的读取方式,可以参照第一类“读者-写者”问题。我一开始也并没有反应过来可以在此引入“读者-写者”模式,还是在网上参考了其他人的解决方案才隐约发现得这么做的哈哈哈。

其实可以直接把和cache相关的代码写在proxy.c里面,就是会让proxy.c更为臃肿一点。但我一想书上都给出sbuf.c和sbuf.h了,我也模仿着写一个cache.c和cache.h岂不美哉?正式这个决定让我后序改bug改得一头大包。我先在proxy.c中定义了缓存cache_t cache,然后进行参数传递在cache.c中修改cache。由于我对全局变量跨文件的处理方式一知半解,但还是硬着头皮写了下去。

后面仔细思考了下,关于“全局变量跨文件的处理”其实有两种方案。

       方案一:我直接cache_t cache写入cache.h当中,而不是写在proxy.c。这样只需要在cache.c和proxy.c中直接引入“cache.h”就可以直接用cache了,但是这样有个弊端———有且仅有一个cache供proxy.c使用,如果还有个proxy2.c也要用到cache,那就没得用了。还是治标不治本。

       方案二:用指针传递来在cahce.c中修改proxy.c定义的cache_t cache。这方法其实不难,我最后也是采用的这个方法。坏就坏在昨晚写的代码简直就是一坨,好像用了指针传递又好像没用,我就在这一坨东西上反复修改,然后越陷越深。理清了思路之后问题就迎刃而解了。

还有几个让我困惑的小问题:

①malloc问题

void cache_init(cache_t *cache) {
    // cache=(cache_t*)Malloc(sizeof(cache_t));
    cache->cache_Item_Using = 0;
}

在这里我开始准备模仿sbuf.c给cache分配个空间再说,结果测试的时候一直报错“Segmentation fault”,搞得我一度以为是malloc出了问题。最后把参数传递的问题解决后发现这里的malloc可用可不用,由于cache_t cache是全局变量,它的生命周期和进行同步,故没必要多此一举再在堆上给它分配空间了。

②url问题

     printf("=====The length of url is: %d====\n",(int)sizeof(url));
    // strncpy(item->url,url,sizeof(url));  西八,这个bug害得我好苦啊
    strncpy(item->url,url,MAXLINE);

在解决缓存的问题后,我发现cache部分的得分依然是0昏。初步判断是url的匹配有问题,测试了一番后发现一个十分怪异的情况——存入的url长度并不能用sizeof(url),实际长度好像比sizeof(url)还要长。

 

 

把每次存进cache的item->url打印出来一看果然如此,于是最后直接假设url的长度是MAXLINE,这样就可以通过测试了

 

Q:例如char* url=“http://localhost:7710/”

那sizeof(url)是输出8还是字符串本身的长度22呢?

A:    在这种情况下,char* url = "http://localhost:7710/"中的url是一个指向字符的指针,指向字符串常量"http://localhost:7710/"的首地址。因此,使用sizeof(url)将返回指针的大小,而不是字符串的长度。

在大多数平台上,指针的大小通常是机器字长的大小。在64位系统上,指针通常是8个字节,因此sizeof(url)将返回8。

要获取字符串本身的长度,您可以使用strlen函数,如下所示:

#include <stdio.h>

#include <string.h>

int main() {

char *url = "http://localhost:7710/";

size_t length = strlen(url);

printf("Length of the string: %zu\n", length); // 这里将输出字符串的长度,即22

return 0;

}

这段代码将输出字符串的长度,即22。

但是,由于strlen(str)不会包括结尾的‘\0’,所以得用strncpy(item->url,url,strlen(url)+1)

至此,真相大白。 

csche.c如下
 

#include "csapp.h"
#include "cache.h"

void initializeCache(cache_t* cache){
    cache->head = Malloc(sizeof(*(cache->head)));
    cache->head->flag = '@';
    cache->head->prev = NULL;
    cache->head->next = NULL;

    cache->tail = Malloc(sizeof(*(cache->tail)));
    cache->tail->flag = '@';
    cache->tail->prev = NULL;
    cache->tail->next = NULL;

    /* construct the doubly linked list */
    cache->head->next = cache->tail;
    cache->tail->prev = cache->head;

    cache->nitems = 0;
}

cache.h实现如下

#include "csapp.h"
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400

typedef struct _obj_t{
    char flag;
    char uri[100];
    char respHeader[1024];
    char respBody[MAX_OBJECT_SIZE];
    int respHeaderLen;
    int respBodyLen;
    struct _obj_t* prev;
    struct _obj_t* next;
}obj_t;

typedef struct _cache_t{
    obj_t* head;
    obj_t* tail;    
    int nitems;
}cache_t;

//write to cache
//read cache
//search cache

void initializeCache(cache_t* );

写入到cache的实现如下

/*
 * This function is guarded by Write Lock, thus is thread safe
 * assume head is the newest part, we evict the last part
 * if possible
 */
void writeToCache(obj_t* obj){
    /* step1: check current capacity, if full ,delete one */
    while(obj->respBodyLen + cacheSize > MAX_CACHE_SIZE && cache.head->next != cache.tail){
        obj_t* last = cache.tail->prev;
        last->next->prev = last->prev;
        last->prev->next = last->next;

        last->next = NULL;
        last->prev = NULL;
        Free(last);
    }

    /* step2: add into the cache */
    //mount the current obj into cache
    obj->next = cache.head->next;
    obj->prev = cache.head;
    cache.head->next->prev = obj;
    cache.head->next       = obj;
    cacheSize += obj->respBodyLen;
}

 

 读取cahce条目的实现如下

obj_t* readItem(char* targetURI, int clientfd){
    P(&mutex);
    readcnt++;
    if(readcnt == 1){
        P(&W);
    }
    V(&mutex);

    /***** reading section starts *****/
    obj_t* cur = cache.head->next;
    rio_t rio;
    Rio_readinitb(&rio, clientfd);
    while(cur->flag != '@'){
        if(strcmp(targetURI, cur->uri) == 0){
            return cur;
        }

        cur = cur->next;
    }


    /***** reading section ends *****/
    P(&mutex);
    readcnt--;
    if(readcnt == 0){
        V(&W);
    }
    V(&mutex);

    return NULL;
}

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1790606.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

下载安装Grafana 监控mysql和Linux主机

下载地址:https://grafana.com/grafana/download [rootlocalhost ~]# wget https://dl.grafana.com/oss/release/grafana-7.2.0- 1.x86_64.rpm 安装 [rootlocalhost ~]# yum install grafana-7.2.0-1.x86_64.rpm -y启动服务 [rootlocalhost ~]# systemctl enable --now grafa…

AST 在前端开发中的应用与实践:从代码分析到自动化转换

抽象语法树&#xff08;AST&#xff0c;Abstract Syntax Tree&#xff09;在前端开发中有着广泛的应用。它是编译器和工具链的核心组件&#xff0c;使得代码分析、转换、优化等操作成为可能。在前端开发中&#xff0c;AST 主要用于代码编译和转译、代码优化、代码分析、代码格式…

C语言数字全排列生成器

前言 从0开始记录我的学习历程&#xff0c;我会尽我所能&#xff0c;写出最最大白话的文章&#xff0c;希望能够帮到你&#xff0c;谢谢。 提示&#xff1a;文章作者为初学者&#xff0c;有问题请评论指正&#xff0c;感谢。 这个代码的功能是生成并打印出从1到N的所有整数的…

Allegro-开店指南

开店指南 Allegro企业账户注册流程 Allegro注册流程分成两个主要阶段: 第一创建您的账户&#xff0c;第二激活您账户的销售功能。完成两个阶段&#xff0c;才能在Allegro进行销售。 中国企业应该入驻Business account&#xff08;企业账户&#xff09;。 第二阶段&#xff…

nginx中配置ssl证书(宝塔面板)

首先申请一个SSL证书&#xff0c;这里我申请的joyssl的免费证书。提交订单申请后&#xff0c;按照页面提示在域名解析中将CNAME和记录值配置好。 比如我用的阿里云&#xff0c; 这是好后&#xff0c;需要等几分钟&#xff0c;然后域名检验成功。 然后点击joyssl的左侧菜单的“证…

【新书上市】图像画质算法与底层视觉技术

图书主页&#xff1a;https://book.douban.com/subject/36895899/ 购买链接&#xff1a;https://item.jd.com/10105601481762.html 内容介绍 本书主要介绍了图像画质相关的各类底层视觉任务及其相关算法&#xff0c;重点讲解了去噪、超分辨率、去雾、高动态范围、图像合成与图…

conflicting types for 错误问题

操作系统真象还原中&#xff0c;第十一章出现的问题&#xff1a; 怎样编译都会出现一个conflicting types for ’xxx‘的错误 出现这个错误的原因&#xff1a; 头文件声明和定义参数稍有不同 头文件中声明 void Hanlder(const char * buf); 在定义时写作 void Hanlder(char…

双指针法 ( 三数之和 )

题目 &#xff1a;给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复…

嵌入式 Linux LED 驱动开发实验学习

I.MX6U-ALPHA 开发板上的 LED 连接到 I.MX6ULL 的 GPIO1_IO03 这个引脚上&#xff0c;进行这个驱动开发实验之前&#xff0c;需要了解下地址映射。 地址映射 MMU 全称叫做 MemoryManage Unit&#xff0c;也就是内存管理单元。在老版本的 Linux 中要求处理器必须有 MMU&#x…

Amazon云计算AWS(一)

目录 一、基础存储架构Dynamo&#xff08;一&#xff09;Dynamo概况&#xff08;二&#xff09;Dynamo架构的主要技术 二、弹性计算云EC2&#xff08;一&#xff09;EC2的基本架构&#xff08;二&#xff09;EC2的关键技术&#xff08;三&#xff09;EC2的安全及容错机制 提供的…

NXP i.MX8系列平台开发讲解 - 3.14 Linux 之Power Supply子系统(二)

专栏文章目录传送门&#xff1a;返回专栏目录 Hi, 我是你们的老朋友&#xff0c;主要专注于嵌入式软件开发&#xff0c;有兴趣不要忘记点击关注【码思途远】 目录 1. 前言 2. 芯片简介 2. 系统原理设计 2. 设备树相关 本文实操是基于Android11 系统下i.MX8MQ环境下&#x…

DKTCDR:Domain-Oriented Knowledge Transfer for Cross-Domain Recommendation

Domain-Oriented Knowledge Transfer for Cross-Domain Recommendation IEEE(CCF B.SCI 1)-Guoshuai Zhao, Xiaolong Zhang, Hao Tang, Jialie Shen, and Xueming Qian-2024 思路 在CDR中,构建连接两个域的桥梁是实现跨域推荐的基础。然而现在的CDR方法往往在连接两个域时忽…

STM32-- GPIO->EXTI->NVIC中断

一、NVIC简介 什么是 NVIC &#xff1f; NVIC 即嵌套向量中断控制器&#xff0c;全称 Nested vectored interrupt controller 。它 是内核的器件&#xff0c;所以它的更多描述可以看内核有关的资料。M3/M4/M7 内核都是支持 256 个中断&#xff0c;其中包含了 16 个系统中…

调用smc为什么就能直接切换到ATF?

快速链接: . &#x1f449;&#x1f449;&#x1f449;Trustzone/TEE/安全 面试100问-目录 &#x1f448;&#x1f448;&#x1f448; 付费专栏-付费课程 【购买须知】:联系方式-加入交流群 ----联系方式-加入交流群 个人博客笔记导读目录(全部) 背景 插播一个小插曲&#…

图片的dpi数值怎么修改?快速在线改图片dpi的操作技巧

在网上报名或者上传个人证件照片时&#xff0c;经常会对图片dpi数值有所要求&#xff0c;需要按照要求将图修改图片dpi到规定数值才可以正常上传&#xff0c;有很多人都对这个问题的感到非常的困扰&#xff0c;那么有什么方法能够快速在线改图片分辨率的dpi数值呢&#xff1f; …

WPF 依赖属性原理、 附加属性

依赖属性如何节约内存 MSDN中给出了下面几种应用依赖属性的场景&#xff1a; 希望可在样式中设置属性。 希望属性支持数据绑定。 希望可使用动态资源引用设置属性。 希望从元素树中的父元素自动继承属性值。 希望属性可进行动画处理。 希望属性系统在属性系统、环境或用户…

Wpf 使用 Prism 开发MyToDo应用程序

MyToDo 是使用 WPF &#xff0c;并且塔配Prism 框架进行开发的项目。项目中进行了前后端分离设计&#xff0c;客户端所有的数据均通过API接口获取。适合新手入门学习WPF以及Prism 框架使用。 首页统计以及点击导航到相关模块功能待办事项增删改查功能备忘录增删改查功能登录注册…

【Python编程】【Jupyter Notebook】启动时报错:no available port could be found

一、报错描述 在Jupyter Notebook中编写程序&#xff0c;无法运行&#xff0c;提示由于没有可供监听的端口&#xff0c;无法启动Jupyter服务器&#xff0c;如下图所示&#xff1a; 二、原因分析 通过报错信息&#xff0c;猜测大概是由于网络环境的原因。首先&#xff0c;关闭…

智能体应用开发:构建各类垂直领域的ai智能体应用

最近在做个类似的项目&#xff0c;有用到这方面的知识&#xff0c;顺便做一些记录和笔记吧&#xff0c;希望能帮到大家了解智能体应用开发 目录 引言 AI原生应用的兴起 智能体在AI中的角色 实现原理详解 机器学习基础 数据管理与关联数据库 数据结构 Embedding 检索方…

开放式耳机哪个牌子好?2024年度热门机型推荐榜单分享!

随着音乐技术的不断革新&#xff0c;开放式耳机已成为音乐发烧友们的首选。从最初的简单音质&#xff0c;到如今的高清解析&#xff0c;开放式耳机不断进化。音质纯净&#xff0c;佩戴舒适&#xff0c;无论是街头漫步还是家中细细静听&#xff0c;都能带给你身临其境的音乐体验…