Proxy lab

news2024/9/22 10:04:50

CSAPP Proxy Lab

本实验需要实现一个web代理服务器,实现逐步从迭代到并发,到最终的具有缓存功能的并发代理服务器。

Web 代理是充当 Web 浏览器和终端服务器之间的中间人的程序。浏览器不是直接联系终端服务器获取网页,而是联系代理,代理将请求转发到终端服务器。当终端服务器回复代理时,代理将回复发送给浏览器。

本实验共三个部分,具体要求如下:

  • 在本实验的第一部分,您将设置代理以接受传入连接、读取和解析请求、将请求转发到 Web 服务器、读取服务器的响应并将这些响应转发到相应的客户端。第一部分将涉及学习基本的 HTTP 操作以及如何使用套接字编写通过网络连接进行通信的程序。
  • 在第二部分中,您将升级代理以处理多个并发连接。这将向您介绍如何处理并发,这是一个重要的系统概念。
  • 在第三部分也是最后一部分,您将使用最近访问的 Web 内容的简单主内存缓存将缓存添加到您的代理。

Part I

实现迭代Web代理,首先是实现一个处理HTTP/1.0 GET请求的基本迭代代理。开始时,我们的代理应侦听端⼝上的传⼊连接,端⼝号将在命令行中指定。建⽴连接后,您的代理应从客⼾端读取整个请求并解析请求。它应该判断客户端是否发送了⼀个有效的 HTTP 请求;如果是这样,它就可以建⽴自⼰与适当的 Web 服务器的连接,然后请求客⼾端指定的对象。最后,您的代理应读取服务器的响应并将其转发给客⼾端。

我们先将tiny.c中的基本框架复制过来,移除不需要的函数,保留doit,parse_uri,clienterror即可,其他还用不到,接下来我们需要修改的是doitparse_uridoit应该做的事如下:

  1. 读取客户端的请求行,判断其是否是GET请求,若不是,调用clienterror向客户端打印错误信息。
  2. parse_uri调用解析uri,提取出主机名,端口,路径信息。
  3. 代理作为客户端,连接目标服务器。
  4. 调用build_request函数构造新的请求报文new_request
  5. 将请求报文build_request发送给目标服务器。
  6. 接受目标服务器的数据,并将其直接发送给源客户端。

代码如下:

#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 *proxy_hdr = "Proxy-Connection: close\r\n";

void doit(int fd);
void parse_uri(char *uri, char *hostname, char *path, int *port);
void build_request(rio_t *real_client, char *new_request, char *hostname, char *port);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);

int main(int argc, char **argv) {
    int listenfd, connfd;
    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);
    }

    listenfd = Open_listenfd(argv[1]);
    while (1) {
        clientlen = sizeof(clientaddr);
        connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
        Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0);
        printf("Accepted connection from (%s, %s)\n", hostname, port);
        doit(connfd);
        Close(connfd);
    }
}

void doit(int fd) {
    int real_server_fd;
    char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
    char hostname[MAXLINE], path[MAXLINE];
    rio_t rio_client, rio_server;
    int port;

    /* Read request line and headers */
    Rio_readinitb(&rio_client, fd);                         // 初始化rio内部缓冲区
    if (!Rio_readlineb(&rio_client, buf, MAXLINE)) return;  // 读到0个字符,return
    // 请求行: GET http://www.cmu.edu/hub/index.html HTTP/1.1
    sscanf(buf, "%s %s %s", method, uri, version);

    if (strcasecmp(method, "GET")) {
        clienterror(fd, method, "501", "Not Implemented", "Tiny does not implement this method");
        return;
    }

    // 解析uri
    parse_uri(uri, hostname, path, &port);
    char port_str[10];
    sprintf(port_str, "%d", port);
    // 代理作为客户端,连接目标服务器
    real_server_fd = Open_clientfd(hostname, port_str);

    Rio_readinitb(&rio_server, real_server_fd);  // 初始化rio
    char new_request[MAXLINE];
    sprintf(new_request, "GET %s HTTP/1.0\r\n", path);
    build_request(&rio_client, new_request, hostname, port_str);

    // 向目标服务器发送http报文
    Rio_writen(real_server_fd, new_request, strlen(new_request));

    int char_nums;
    // 从目标服务器读到的数据直接发送给客户端
    while ((char_nums = Rio_readlineb(&rio_server, buf, MAXLINE))) Rio_writen(fd, buf, char_nums);
}

void parse_uri(char *uri, char *hostname, char *path, int *port) {
    *port = 80;  // 默认端口
    char *ptr_hostname = strstr(uri, "//");
    //  http://hostname:port/path
    if (ptr_hostname)
        ptr_hostname += 2;  // 绝对uri
    else
        ptr_hostname = uri;  // 相对uri,相对url不包含"http://"或"https://"等协议标识符

    char *ptr_port = strstr(ptr_hostname, ":");
    if (ptr_port) {
        // 字符串ptr_hostname需要以'\0'为结尾标记
        *ptr_port = '\0';
        strncpy(hostname, ptr_hostname, MAXLINE);

        sscanf(ptr_port + 1, "%d%s", port, path);
    } else {  // uri中没有端口号
        char *ptr_path = strstr(ptr_hostname, "/");
        if (ptr_path) {
            strncpy(path, ptr_path, MAXLINE);
            *ptr_path = '\0';
            strncpy(hostname, ptr_hostname, MAXLINE);
        } else {
            strncpy(hostname, ptr_hostname, MAXLINE);
            strcpy(path, "");
        }
    }
}
void build_request(rio_t *real_client, char *new_request, char *hostname, char *port) {
    char temp_buf[MAXLINE];

    // 获取client的请求报文
    while (Rio_readlineb(real_client, temp_buf, MAXLINE) > 0) {
        if (strstr(temp_buf, "\r\n")) break;  // end

        // 忽略以下几个字段的信息
        if (strstr(temp_buf, "Host:")) continue;
        if (strstr(temp_buf, "User-Agent:")) continue;
        if (strstr(temp_buf, "Connection:")) continue;
        if (strstr(temp_buf, "Proxy Connection:")) continue;

        sprintf(new_request, "%s%s", new_request, temp_buf);
        printf("%s\n", new_request);
        fflush(stdout);
    }
    sprintf(new_request, "%sHost: %s:%s\r\n", new_request, hostname, port);
    sprintf(new_request, "%s%s%s%s", new_request, user_agent_hdr, conn_hdr, proxy_hdr);
    sprintf(new_request, "%s\r\n", new_request);
}

void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg) {
    char buf[MAXLINE];

    /* Print the HTTP response headers */
    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\r\n");
    Rio_writen(fd, buf, strlen(buf));

    /* Print the HTTP response body */
    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", errnum, shortmsg);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "<p>%s: %s\r\n", longmsg, cause);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "<hr><em>The Tiny Web server</em>\r\n");
    Rio_writen(fd, buf, strlen(buf));
}

若程序出现错误,printf大法依然是定位错误的好方法。此外可以通过使用curl来模拟操作。需要注意的是需要先运行proxy和tiny再运行curl,tiny就相当于一个目标服务器,curl则相当于一个客户端。

在这里插入图片描述

Part II

接下来我们需要改变上面的程序,使其可以处理多个并发请求,这里使用多线程来实现并发服务器。具体如下:

  • Accept之后通过创建新的线程来完成doit函数。
  • 注意:由于并发导致的竞争,所以需要注意connfd传入的形式,这里选择将每个已连接描述符分配到它自己的动态分配的内存块。

代码如下,只需要在Part I 基础上略作修改即可。

#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 *proxy_hdr = "Proxy-Connection: close\r\n";

void *doit(void *vargp);
void parse_uri(char *uri, char *hostname, char *path, int *port);
void build_request(rio_t *real_client, char *new_request, char *hostname, char *port);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);
int main(int argc, char **argv) {
    int listenfd, *connfd;
    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);
    }

    listenfd = Open_listenfd(argv[1]);

    pthread_t tid;
    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)\n", hostname, port);

        Pthread_create(&tid, NULL, doit, connfd);
    }
}

void *doit(void *vargp) {
    int fd = *((int *)vargp);
    Free(vargp);
    Pthread_detach(Pthread_self());

    int real_server_fd;
    char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
    char hostname[MAXLINE], path[MAXLINE];
    rio_t rio_client, rio_server;
    int port;

    /* Read request line and headers */
    Rio_readinitb(&rio_client, fd);                         // 初始化rio内部缓冲区
    if (!Rio_readlineb(&rio_client, buf, MAXLINE)) return;  // 读到0个字符,return
    // 请求行: GET http://www.cmu.edu/hub/index.html HTTP/1.1
    sscanf(buf, "%s %s %s", method, uri, version);

    if (strcasecmp(method, "GET")) {
        clienterror(fd, method, "501", "Not Implemented", "Tiny does not implement this method");
        return;
    }

    // 解析uri
    parse_uri(uri, hostname, path, &port);
    char port_str[10];
    sprintf(port_str, "%d", port);
    // 代理作为客户端,连接目标服务器
    real_server_fd = Open_clientfd(hostname, port_str);

    Rio_readinitb(&rio_server, real_server_fd);  // 初始化rio
    char new_request[MAXLINE];
    sprintf(new_request, "GET %s HTTP/1.0\r\n", path);
    build_request(&rio_client, new_request, hostname, port_str);

    // 向目标服务器发送http报文
    Rio_writen(real_server_fd, new_request, strlen(new_request));

    int char_nums;
    // 从目标服务器读到的数据直接发送给客户端
    while ((char_nums = Rio_readlineb(&rio_server, buf, MAXLINE))) Rio_writen(fd, buf, char_nums);

    Close(fd);
}

void parse_uri(char *uri, char *hostname, char *path, int *port) {
    *port = 80;  // 默认端口
    char *ptr_hostname = strstr(uri, "//");
    //  http://hostname:port/path
    if (ptr_hostname)
        ptr_hostname += 2;  // 绝对uri
    else
        ptr_hostname = uri;  // 相对uri,相对url不包含"http://"或"https://"等协议标识符

    char *ptr_port = strstr(ptr_hostname, ":");
    if (ptr_port) {
        // 字符串ptr_hostname需要以'\0'为结尾标记
        *ptr_port = '\0';
        strncpy(hostname, ptr_hostname, MAXLINE);

        sscanf(ptr_port + 1, "%d%s", port, path);
    } else {  // uri中没有端口号
        char *ptr_path = strstr(ptr_hostname, "/");
        if (ptr_path) {
            strncpy(path, ptr_path, MAXLINE);
            *ptr_path = '\0';
            strncpy(hostname, ptr_hostname, MAXLINE);
        } else {
            strncpy(hostname, ptr_hostname, MAXLINE);
            strcpy(path, "");
        }
    }
}
void build_request(rio_t *real_client, char *new_request, char *hostname, char *port) {
    char temp_buf[MAXLINE];

    // 获取client的请求报文
    while (Rio_readlineb(real_client, temp_buf, MAXLINE) > 0) {
        if (strstr(temp_buf, "\r\n")) break;  // end

        // 忽略以下几个字段的信息
        if (strstr(temp_buf, "Host:")) continue;
        if (strstr(temp_buf, "User-Agent:")) continue;
        if (strstr(temp_buf, "Connection:")) continue;
        if (strstr(temp_buf, "Proxy Connection:")) continue;

        sprintf(new_request, "%s%s", new_request, temp_buf);
        printf("%s\n", new_request);
        fflush(stdout);
    }
    sprintf(new_request, "%sHost: %s:%s\r\n", new_request, hostname, port);
    sprintf(new_request, "%s%s%s%s", new_request, user_agent_hdr, conn_hdr, proxy_hdr);
    sprintf(new_request, "%s\r\n", new_request);
}

void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg) {
    char buf[MAXLINE];

    /* Print the HTTP response headers */
    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\r\n");
    Rio_writen(fd, buf, strlen(buf));

    /* Print the HTTP response body */
    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", errnum, shortmsg);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "<p>%s: %s\r\n", longmsg, cause);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "<hr><em>The Tiny Web server</em>\r\n");
    Rio_writen(fd, buf, strlen(buf));
}

Part III

第三部分需要添加缓存web对象的功能。根据实验文档的要求我们需要实现对缓存实现读写者问题,且缓存的容量有限,当容量不足是,要按照类似LRU算法进行驱逐。我们先定义缓存的结构,这里使用的是双向链表,选择这个数据结构的原因在于LRU算法的需求,链尾即使最近最少使用的web对象。关于缓存的定义以及相关操作如下:

struct cache {
    char *url;
    char *content;  // web object,这里只是简单的将目标服务器发来的数据进行保存
    struct cache *prev;
    struct cache *next;
};

struct cache *head = NULL;
struct cache *tail = NULL;


/* 创建缓存节点 */
struct cache *create_cacheNode(char *url, char *content) {
    struct cache *node = (struct cache *)malloc(sizeof(struct cache));

    int len = strlen(url);
    node->url = (char *)malloc(len * sizeof(char));
    strncpy(node->url, url, len);

    len = strlen(content);
    node->content = (char *)malloc(len * sizeof(char));
    strncpy(node->content, content, len);

    node->prev = NULL;
    node->next = NULL;

    return node;
}
/* 将节点添加到缓存头部 */
void add_cacheNode(struct cache *node) {
    node->next = head;
    node->prev = NULL;
    if (head != NULL) {
        head->prev = node;
    }
    head = node;
    if (tail == NULL) {
        tail = node;
    }
    total_size += strlen(node->content) * sizeof(char);
}
/* 删除缓存尾部节点 */
void delete_tail_cacheNode() {
    if (tail != NULL) {
        total_size -= strlen(tail->content) * sizeof(char);
        Free(tail->content);
        Free(tail->url);
        struct cache *tmp = tail;
        tail = tail->prev;
        Free(tmp);

        if (tail != NULL) {
            tail->next = NULL;
        } else {
            head = NULL;
        }
    }
}
/* 移动缓存节点到头部 */
void move_cacheNode_to_head(struct cache *node) {
    if (node == head) {
        return;
    } else if (node == tail) {
        tail = tail->prev;
        tail->next = NULL;
    } else {
        node->prev->next = node->next;
        node->next->prev = node->prev;
    }
    node->prev = NULL;
    node->next = head;
    head->prev = node;
    head = node;
}
/* 获取缓存数据 */
char *get_cacheData(char *url) {
    struct cache *node = head;
    while (node != NULL) {
        if (strcmp(node->url, url) == 0) {
            move_cacheNode_to_head(node);
            return node->content;
        }
        node = node->next;
    }
    return NULL;
}

此外还需要实现读者写者问题,为此定义了如下几个相关变量:

int readcnt = 0;     // 目前读者数量
sem_t mutex_read_cnt, mutex_content;
void init() {
    Sem_init(&mutex_content, 0, 1);
    Sem_init(&mutex_read_cnt, 0, 1);
}

同时,定义了readerwriter函数作为读者和写者。

  • int reader(int fd, char *url);其内调用get_cacheData检查是否缓存命中,若是,则将所缓存的数据通过fd发送给客户端,否则返回0表示缓存未命中。
  • void writer(char **url*, char **content*);缓存未命中后,与之前一样进行代理服务,从目标服务器接收数据后发送到客户端,如果web object的大小符号要求的话,再调用writer将接收的数据进行缓存。

总代码如下:

#include <stdio.h>

#include "csapp.h"

/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400

char *content_tmp[MAX_OBJECT_SIZE];
/* 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 *proxy_hdr = "Proxy-Connection: close\r\n";

int total_size = 0;  // 缓存中有效载荷总大小
int readcnt = 0;     // 目前读者数量
sem_t mutex_read_cnt, mutex_content;

struct cache {
    char *url;
    char *content;  // web 对象,这里只是简单的将目标服务器发来的数据进行保存
    struct cache *prev;
    struct cache *next;
};

struct cache *head = NULL;
struct cache *tail = NULL;

// 缓存操作辅助函数
char *get_cacheData(char *url);
void move_cacheNode_to_head(struct cache *node);
void delete_tail_cacheNode();
void add_cacheNode(struct cache *node);
struct cache *create_cacheNode(char *url, char *content);

void writer(char *url, char *content);
int reader(int fd, char *url);
void init();

void *doit(void *vargp);
void parse_uri(char *uri, char *hostname, char *path, int *port);
void build_request(rio_t *real_client, char *new_request, char *hostname, char *port);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);

int main(int argc, char **argv) {
    init();
    int listenfd, *connfd;
    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);
    }

    listenfd = Open_listenfd(argv[1]);

    pthread_t tid;
    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)\n", hostname, port);

        Pthread_create(&tid, NULL, doit, connfd);
    }
}

void *doit(void *vargp) {
    int fd = *((int *)vargp);
    Free(vargp);
    Pthread_detach(Pthread_self());

    int real_server_fd;
    char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
    char hostname[MAXLINE], path[MAXLINE], url[MAXLINE];
    rio_t rio_client, rio_server;
    int port;

    /* Read request line and headers */
    Rio_readinitb(&rio_client, fd);                         // 初始化rio内部缓冲区
    if (!Rio_readlineb(&rio_client, buf, MAXLINE)) return;  // 读到0个字符,return
    // 请求行: GET http://www.cmu.edu/hub/index.html HTTP/1.1
    sscanf(buf, "%s %s %s", method, uri, version);

    if (strcasecmp(method, "GET")) {
        clienterror(fd, method, "501", "Not Implemented", "Tiny does not implement this method");
        return;
    }

    // 解析uri
    parse_uri(uri, hostname, path, &port);
    strncpy(url, hostname, MAXLINE);
    strncat(url, path, MAXLINE);

    if (reader(fd, url)) {
        Close(fd);
        return 1;
    }
    // 缓存未命中------

    char port_str[10];
    sprintf(port_str, "%d", port);
    // 代理作为客户端,连接目标服务器
    real_server_fd = Open_clientfd(hostname, port_str);

    Rio_readinitb(&rio_server, real_server_fd);  // 初始化rio
    char new_request[MAXLINE];
    sprintf(new_request, "GET %s HTTP/1.0\r\n", path);
    build_request(&rio_client, new_request, hostname, port_str);

    // 向目标服务器发送http报文
    Rio_writen(real_server_fd, new_request, strlen(new_request));

    int char_nums;
    // 从目标服务器读到的数据直接发送给客户端
    int len = 0;
    content_tmp[0] = '\0';
    while ((char_nums = Rio_readlineb(&rio_server, buf, MAXLINE))) {
        len += char_nums;
        if (len <= MAX_OBJECT_SIZE) strncat(content_tmp, buf, char_nums);
        Rio_writen(fd, buf, char_nums);
    }

    if (len <= MAX_OBJECT_SIZE) {
        writer(url, content_tmp);
    }
    Close(fd);  //--------------------------------------------------------
}

void parse_uri(char *uri, char *hostname, char *path, int *port) {
    *port = 80;  // 默认端口
    char *ptr_hostname = strstr(uri, "//");
    //  http://hostname:port/path
    if (ptr_hostname)
        ptr_hostname += 2;  // 绝对uri
    else
        ptr_hostname = uri;  // 相对uri,相对url不包含"http://"或"https://"等协议标识符

    char *ptr_port = strstr(ptr_hostname, ":");
    if (ptr_port) {
        // 字符串ptr_hostname需要以'\0'为结尾标记
        *ptr_port = '\0';
        strncpy(hostname, ptr_hostname, MAXLINE);

        sscanf(ptr_port + 1, "%d%s", port, path);
    } else {  // uri中没有端口号
        char *ptr_path = strstr(ptr_hostname, "/");
        if (ptr_path) {
            strncpy(path, ptr_path, MAXLINE);
            *ptr_path = '\0';
            strncpy(hostname, ptr_hostname, MAXLINE);
        } else {
            strncpy(hostname, ptr_hostname, MAXLINE);
            strcpy(path, "");
        }
    }
}
void build_request(rio_t *real_client, char *new_request, char *hostname, char *port) {
    char temp_buf[MAXLINE];

    // 获取client的请求报文
    while (Rio_readlineb(real_client, temp_buf, MAXLINE) > 0) {
        if (strstr(temp_buf, "\r\n")) break;  // end

        // 忽略以下几个字段的信息
        if (strstr(temp_buf, "Host:")) continue;
        if (strstr(temp_buf, "User-Agent:")) continue;
        if (strstr(temp_buf, "Connection:")) continue;
        if (strstr(temp_buf, "Proxy Connection:")) continue;

        sprintf(new_request, "%s%s", new_request, temp_buf);
        printf("%s\n", new_request);
        fflush(stdout);
    }
    sprintf(new_request, "%sHost: %s:%s\r\n", new_request, hostname, port);
    sprintf(new_request, "%s%s%s%s", new_request, user_agent_hdr, conn_hdr, proxy_hdr);
    sprintf(new_request, "%s\r\n", new_request);
}
void init() {
    Sem_init(&mutex_content, 0, 1);
    Sem_init(&mutex_read_cnt, 0, 1);
}
/* 创建缓存节点 */
struct cache *create_cacheNode(char *url, char *content) {
    struct cache *node = (struct cache *)malloc(sizeof(struct cache));

    int len = strlen(url);
    node->url = (char *)malloc(len * sizeof(char));
    strncpy(node->url, url, len);

    len = strlen(content);
    node->content = (char *)malloc(len * sizeof(char));
    strncpy(node->content, content, len);

    node->prev = NULL;
    node->next = NULL;

    return node;
}
/* 将节点添加到缓存头部 */
void add_cacheNode(struct cache *node) {
    node->next = head;
    node->prev = NULL;
    if (head != NULL) {
        head->prev = node;
    }
    head = node;
    if (tail == NULL) {
        tail = node;
    }
    total_size += strlen(node->content) * sizeof(char);
}
/* 删除缓存尾部节点 */
void delete_tail_cacheNode() {
    if (tail != NULL) {
        total_size -= strlen(tail->content) * sizeof(char);
        Free(tail->content);
        Free(tail->url);
        struct cache *tmp = tail;
        tail = tail->prev;
        Free(tmp);

        if (tail != NULL) {
            tail->next = NULL;
        } else {
            head = NULL;
        }
    }
}
/* 移动缓存节点到头部 */
void move_cacheNode_to_head(struct cache *node) {
    if (node == head) {
        return;
    } else if (node == tail) {
        tail = tail->prev;
        tail->next = NULL;
    } else {
        node->prev->next = node->next;
        node->next->prev = node->prev;
    }
    node->prev = NULL;
    node->next = head;
    head->prev = node;
    head = node;
}
/* 获取缓存数据 */
char *get_cacheData(char *url) {
    struct cache *node = head;
    while (node != NULL) {
        if (strcmp(node->url, url) == 0) {
            move_cacheNode_to_head(node);
            return node->content;
        }
        node = node->next;
    }
    return NULL;
}

int reader(int fd, char *url) {
    int find = 0;
    P(&mutex_read_cnt);
    readcnt++;
    if (readcnt == 1)  // first in
        P(&mutex_content);
    V(&mutex_read_cnt);

    char *content = get_cacheData(url);
    if (content) {  // 命中
        Rio_writen(fd, content, strlen(content));
        find = 1;
    }

    P(&mutex_read_cnt);
    readcnt--;
    if (readcnt == 0)  // last out
        V(&mutex_content);
    V(&mutex_read_cnt);

    return find;
}
void writer(char *url, char *content) {
    P(&mutex_content);
    while (total_size + strlen(content) * sizeof(char) > MAX_CACHE_SIZE) {
        delete_tail_cacheNode();
    }
    struct cache *node = create_cacheNode(url, content_tmp);
    add_cacheNode(node);
    V(&mutex_content);
}
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg) {
    char buf[MAXLINE];

    /* Print the HTTP response headers */
    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\r\n");
    Rio_writen(fd, buf, strlen(buf));

    /* Print the HTTP response body */
    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", errnum, shortmsg);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "<p>%s: %s\r\n", longmsg, cause);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "<hr><em>The Tiny Web server</em>\r\n");
    Rio_writen(fd, buf, strlen(buf));
}

总结

测试结果如下,顺利拿下满分。

在这里插入图片描述

本实验和上一个malloclab实验就不是一个级别的,可以说此实验是很简单的,也就比datalab略难一下。由于本身也有一些web服务器的学习经验,所以做起来还是比较轻松的,但此实验无疑新手练习多线程和并发的好实验。

至此CSAPP的最后一个lab也完成了,一共8个,除开malloclab得了98分外,其他7个实验均拿下满分。全部lab见这里。

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

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

相关文章

关系型数据库的三大范式

一、简而言之 1、是什么&#xff1f; 三大范式是针对关系型数据库的一种数据库设计规范&#xff0c;使数据库设计符合约定的规范要求。 2、为什么要符合该规范&#xff1f; 为了建立冗余较小、结构合理的数据库。 3、三大范式内容的简单理解&#xff08;Normal Form&#…

2023美赛D题:可持续发展目标

以下内容全部来自人工翻译&#xff0c;仅供参考。 文章目录背景要求术语表文献服务背景 联合国制定了17个可持续发展目标&#xff08;SDGs&#xff09;。实现这些目标最终将改善世界上许多人的生活。这些目标并不相互独立&#xff0c;因此&#xff0c;一些目标的积极进展常常…

2023美国大学生数学建模竞赛选题建议

总的来说&#xff0c;这次算是美赛环境题元年&#xff0c;以往没有这么多环境题目&#xff0c;大部分题目都是开放度相当高的题目。C君认为的难度&#xff1a;D>C>AE>BF&#xff0c;开放度&#xff1a;DF>ABE>C。A题 遭受旱灾的植物群落这次A题为环境类题目&…

【技术分享】在RK3568上如何烧录MAC

本次我们使用的是触觉智能基于RK3568研发的IDO-EVB3568来给大家演示如何烧录MAC。 这款开发板拥有四核A55&#xff0c;主频高达2.0G&#xff0c;支持高达8GB高速LPDDR4&#xff0c;1T算力NPU &#xff0c;4K H.265硬解码&#xff0c;4K HDMI2.0显示输出&#xff0c;支持双通…

AMEPD SSD1680 调试记录

AMEPD Active Martix Electrophoretic Display&#xff0c;有源矩阵电泳显示屏。就是电纸书那种屏&#xff0c;调试效果使用感受和我的Kindle差不多。屏幕参数屏幕 IC为SSD1680122*250&#xff0c;单bit控制&#xff0c;1为白&#xff0c;0为黑逐行刷新&#xff0c;一个字节8bi…

JavaScript 浏览器中执行

本章节为大家介绍如何在浏览器上进行 JavaScript 代码的运行与调试。目前的主流浏览器有谷歌的Chrome&#xff08;使用blink内核&#xff09;&#xff0c;微软的edge&#xff08;使用chromium内核&#xff0c;这是一款谷歌提供的开源浏览器内核&#xff09;和IE&#xff08;使用…

记录锁,间隙锁,插入意向锁,临键锁兼容关系

插入意向锁是什么&#xff1f; 注意&#xff01;插入意向锁名字里虽然有意向锁这三个字&#xff0c;但是它并不是意向锁&#xff0c;它属于行级锁&#xff0c;是一种特殊的间隙锁。 在MySQL的官方文档中有以下重要描述&#xff1a; An Insert intention lock is a type of gap…

羊了个羊游戏开发教程3:卡牌拾取和消除

本文首发于微信公众号&#xff1a; 小蚂蚁教你做游戏。欢迎关注领取更多学习做游戏的原创教程资料&#xff0c;每天学点儿游戏开发知识。嗨&#xff01;大家好&#xff0c;我是小蚂蚁。终于要写第三篇教程了&#xff0c;中间拖的时间有点儿长&#xff0c;以至于我的好几位学员等…

2023美赛C题思路数据代码分享

文章目录赛题思路2023年美国大学生数学建模竞赛选题&论文一、关于选题二、关于论文格式三、关于论文提交四、论文提交流程注意不要手滑美赛C题思路数据代码【最新】赛题思路 (赛题出来以后第一时间在CSDN分享) 最新进度在文章最下方卡片&#xff0c;加入获取一手资源 202…

【小西】同步咪咕订单给咪咕方(写接口给第三方)

同步咪咕订单给咪咕方前言思路实现1、定义请求体和响应信息MiGuOrderSyncReqMiGuOrderSyncResp2、nacos定义好咪咕相关配置信息3、同步咪咕参数配置4、MiGuOrderSyncControl5、MiGuOrderSyncService6、MiGuOrderSyncServiceImplCreateAscIISignUtil 生成参数 字典排序 签名Hmac…

数据分析:消费者数据分析

数据分析&#xff1a;消费者数据分析 作者&#xff1a;AOAIYI 创作不易&#xff0c;如果觉得文章不错或能帮助到你学习&#xff0c;记得点赞收藏评论一下哦 文章目录数据分析&#xff1a;消费者数据分析一、前言二、数据准备三、数据预处理四、个体消费者分析五、用户消费行为总…

【CMake】CMake构建C++代码(一)

在Linux开发过程中&#xff0c;难免会用到CMake来构建你的代码。本文将说明如何构建自己的代码&#xff0c;将自己的代码变为共享库&#xff0c;共其他代码使用。 文章目录在Linux开发过程中&#xff0c;难免会用到CMake来构建你的代码。本文将说明如何构建自己的代码&#xff…

R语言Ternary包绘制三元图、RGB三色空间分布图的方法

本文介绍基于R语言中的Ternary包&#xff0c;绘制三元图&#xff08;Ternary Plot&#xff09;的详细方法&#xff1b;其中&#xff0c;我们就以RGB三色分布图为例来具体介绍。 三元图可以从三个不同的角度反应数据的特征&#xff0c;因此在很多领域都得以广泛应用&#xff1b;…

2023美赛F题思路数据代码分享

文章目录赛题思路2023年美国大学生数学建模竞赛选题&论文一、关于选题二、关于论文格式三、关于论文提交四、论文提交流程注意不要手滑美赛F题思路数据代码【最新】赛题思路 (赛题出来以后第一时间在CSDN分享) 最新进度在文章最下方卡片&#xff0c;加入获取一手资源 202…

MySQL 索引 (只要能看完)(一篇就够了)

文章目录前言一、MySQL索引介绍1.1 索引的类别1.2 索引的创建原则二、索引的管理和使用2.1 制造实验数据2.2 explain 使用说明2.3 创建索引2.3.1 基于创建表时建立索引2.3.2 基于已创建好的表创建索引2.4 删除索引2.5 聚集索引和二级索引2.5.1 聚集索引2.5.2 二级索引&#xff…

【python知识】win10下如何用python将网页转成pdf文件

一、说明 本篇记录一个自己享用的简单工具。在大量阅读网上文章中&#xff0c;常常遇到一个专题对应多篇文章&#xff0c;用浏览器的收藏根本不够。能否见到一篇文章具有搜藏价值&#xff0c;就转到线下&#xff0c;以备日后慢慢消化吸收。这里终于找到一个办法&#xff0c;将在…

【IIC子系统之读取温湿度】

IIC子系统之读取温湿度IIC总线协议主机读取一个字节主机发送一个字节设备树编写IIC设备驱动层API编写程序读取温湿度应用层驱动读取温湿度函数解析头文件IIC总线协议 1.I2C总线是PHLIPS公司在八十年代初推出的一种串行的半双工同步总线&#xff0c;主要用于连接整体电路。 1&a…

面试准备知识点与总结——(基础篇)

目录Java基础Java面向对象有哪些特征ArrayList和LinkedList有什么区别高并发的集合有哪些问题迭代器的fail-fast和fail-safeArrayList底层扩容机制HashMap面试合集解答设计模式单例设计模式哪些地方体现了单例模式Java基础 Java面向对象有哪些特征 Java面向对象有三大特征&am…

Win10显示dds及tga缩略图

整理之前做游戏MOD时收集的模型资源,3D游戏模型的贴图文件格式基本都是dds或tga的,毕竟无损压缩、支持嵌入MipMap、带透明通道、可以被GPU硬解balabala...道理我都懂但这俩玩意系统根本直接查看不了,就算装上专门的看图软件或插件,文件夹视图下也没有缩略图预览,只能一个个点开…

SQL查询的优化:如何调整SQL查询的性能

查询优化是在合理利用系统资源和性能指标的基础上&#xff0c;定义最有效、最优化的方式和技术&#xff0c;以提高查询性能的过程。查询调整的目的是找到一种方法来减少查询的响应时间&#xff0c;防止资源的过度消耗&#xff0c;并识别不良的查询性能。 在查询优化的背景下&a…