Proxy lab
CSAPP Proxy Lab
本实验需要实现一个web代理服务器,实现逐步从迭代到并发,到最终的具有缓存功能的并发代理服务器。
Web 代理是充当 Web 浏览器和终端服务器之间的中间人的程序。浏览器不是直接联系终端服务器获取网页,而是联系代理,代理将请求转发到终端服务器。当终端服务器回复代理时,代理将回复发送给浏览器。
本实验共三个部分,具体要求如下:
- 在本实验的第一部分,您将设置代理以接受传入连接、读取和解析请求、将请求转发到 Web 服务器、读取服务器的响应并将这些响应转发到相应的客户端。第一部分将涉及学习基本的 HTTP 操作以及如何使用套接字编写通过网络连接进行通信的程序。
- 在第二部分中,您将升级代理以处理多个并发连接。这将向您介绍如何处理并发,这是一个重要的系统概念。
- 在第三部分也是最后一部分,您将使用最近访问的 Web 内容的简单主内存缓存将缓存添加到您的代理。
Part I
实现迭代Web代理,首先是实现一个处理HTTP/1.0 GET请求的基本迭代代理。开始时,我们的代理应侦听端⼝上的传⼊连接,端⼝号将在命令行中指定。建⽴连接后,您的代理应从客⼾端读取整个请求并解析请求。它应该判断客户端是否发送了⼀个有效的 HTTP 请求;如果是这样,它就可以建⽴自⼰与适当的 Web 服务器的连接,然后请求客⼾端指定的对象。最后,您的代理应读取服务器的响应并将其转发给客⼾端。
我们先将tiny.c中的基本框架复制过来,移除不需要的函数,保留doit,parse_uri,clienterror即可,其他还用不到,接下来我们需要修改的是doit和parse_uri,doit应该做的事如下:
- 读取客户端的请求行,判断其是否是GET请求,若不是,调用
clienterror向客户端打印错误信息。 parse_uri调用解析uri,提取出主机名,端口,路径信息。- 代理作为客户端,连接目标服务器。
- 调用
build_request函数构造新的请求报文new_request。 - 将请求报文
build_request发送给目标服务器。 - 接受目标服务器的数据,并将其直接发送给源客户端。
代码如下:
#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.1sscanf(buf, "%s %s %s", method, uri, version);if (strcasecmp(method, "GET")) {clienterror(fd, method, "501", "Not Implemented", "Tiny does not implement this method");return;}// 解析uriparse_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); // 初始化riochar 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/pathif (ptr_hostname)ptr_hostname += 2; // 绝对urielseptr_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.1sscanf(buf, "%s %s %s", method, uri, version);if (strcasecmp(method, "GET")) {clienterror(fd, method, "501", "Not Implemented", "Tiny does not implement this method");return;}// 解析uriparse_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); // 初始化riochar 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/pathif (ptr_hostname)ptr_hostname += 2; // 绝对urielseptr_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);
}
同时,定义了reader和writer函数作为读者和写者。
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 102400char *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.1sscanf(buf, "%s %s %s", method, uri, version);if (strcasecmp(method, "GET")) {clienterror(fd, method, "501", "Not Implemented", "Tiny does not implement this method");return;}// 解析uriparse_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); // 初始化riochar 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/pathif (ptr_hostname)ptr_hostname += 2; // 绝对urielseptr_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 inP(&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 outV(&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见这里。
相关文章:
Proxy lab
CSAPP Proxy Lab 本实验需要实现一个web代理服务器,实现逐步从迭代到并发,到最终的具有缓存功能的并发代理服务器。 Web 代理是充当 Web 浏览器和终端服务器之间的中间人的程序。浏览器不是直接联系终端服务器获取网页,而是联系代理&#x…...
【机器学习】Sklearn 集成学习-投票分类器(VoteClassifier)
前言 在【机器学习】集成学习基础概念介绍中有提到过,集成学习的结合策略包括: 平均法、投票法和学习法。sklearn.ensemble库中的包含投票分类器(Voting Classifier) 和投票回归器(Voting Regressor),分别对回归任务和分类任务的…...
Day892.MySql读写分离过期读问题 -MySQL实战
MySql读写分离过期读问题 Hi,我是阿昌,今天学习记录的是关于MySql读写分离过期读问题的内容。 一主多从架构的应用场景:读写分离,以及怎么处理主备延迟导致的读写分离问题。 一主多从的结构,其实就是读写分离的基本…...
无线蓝牙耳机哪个品牌音质好?性价比高音质好的蓝牙耳机排行榜
其实蓝牙耳机购买者最担忧的就是音质问题,怕拿到手的蓝牙耳机低频过重又闷又糊,听歌闷耳的问题,但从2021年蓝牙技术开始突飞猛进后,蓝牙耳机的音质、连接甚至是功能都发生了很大的变化,下面我分享几款性价比高音质的蓝…...
店铺微信公众号怎么创建?
有些小伙伴问店铺微信公众号怎么创建,在解答这个问题之前,先简单说说店铺和微信公众号关系: 店铺一般是指小程序店铺,商家通过小程序店铺来卖货;微信公众号则是一个发布信息的平台。但是两者之间可以打通,…...
goLang Mutex用法案例详解
Golang以其并发性Goroutines而闻名。不仅是并发,还有更多。 因此,在这种情况下,我们必须确保多个goroutines不应该同时试图修改资源,从而导致冲突。 为了确保资源一次只能被一个goroutine访问,我们可以使用一个叫做sync.Mutex的东西。 This concept is called mutual ex…...
java常见的异常
异常分类 Throwable 是java异常的顶级类,所有异常都继承于这个类。 Error,Exception是异常类的两个大分类。 Error Error是非程序异常,即程序不能捕获的异常,一般是编译或者系统性的错误,如OutOfMemorry内存溢出异常等。 Exc…...
从0开始学python -33
Python3 输入和输出 -1 在前面几个章节中,我们其实已经接触了 Python 的输入输出的功能。本章节我们将具体介绍 Python 的输入输出。 — 输出格式美化 Python两种输出值的方式: 表达式语句和 print() 函数。 第三种方式是使用文件对象的 write() 方法ÿ…...
ModuleNotFoundError: No module named ‘glfw‘ 解决方案
错误描述 env gym.make(env_id) File "/opt/conda/envs/WNPG/lib/python3.8/site-packages/gym/envs/registration.py", line 619, in make env_creator load(spec_.entry_point) File "/opt/conda/envs/WNPG/lib/python3.8/site-packages/gym/envs/r…...
RadZen运行和部署,生成业务web应用程序
RadZen运行和部署,生成业务web应用程序 快速简单地生成业务web应用程序,可视化地构建和启动web程序,而我们为您创建新代码。 从信息开始 连接到数据库。Radzen推断您的信息并生成一个功能完备的web应用程序。支持MSSQL REST服务。 微调 添加页面或编辑生…...
分享7个比B站更刺激的老司机网站,别轻易点开
俗话说摸鱼一时爽,一直摸一直爽,作为一个程序员老司机了,一头乌黑浓密的头发还时不时被同事调侃,就靠这10个网站让我健康生活,不建议经常性使用,因为还有一句俗话,那就是“摸鱼一时爽࿰…...
浅析:如何在Vue3+Vite中使用JSX
目录 0. Vue3,Vite,JSX 三者的关系 JSX介绍 在 Vue3 中使用 JSX 安装插件(vitejs/plugin-vue-jsx) 新建 jsx 文件 语法 补充知识:注意事项 0. Vue3,Vite,JSX 三者的关系 Vue3、Vite 和 …...
开发小程序需要什么技术?
小程序是一种新的开发能力,相比于原生APP 开发周期短,开发者可以快速地开发一个小程序。小程序可以在微信内被便捷地获取和传播,同时具有出色的使用体验。 开发小程序需要什么技术? 前端技术基础:html、js、css。具体到小程序&a…...
7个营销人员常见的社交媒体问题以及解决方法
在如今的数字营销时代,许多营销人员都害怕在社交媒体上犯错。他们担心他们的社交媒体中的失误会演变成一场公关危机。面对一些常见的社交媒体问题,您需要知道如何避免和解决。对于数字营销人员来说,在现在这个信息互通,每时每刻都…...
BFC 是什么
在页面布局的时候,经常出现以下情况: 这个元素高度怎么没了?这两栏布局怎么没法自适应?这两个元素的间距怎么有点奇怪的样子?...... 原因是元素之间相互的影响,导致了意料之外的情况,这里就涉及…...
07 react+echart+大屏
reactechart大屏大屏ECharts 图表实际步骤React Typescript搭建大屏项目,并实现屏幕适配flexible rem实现适配1. 安装插件对echarts进行的React封装,可以用于React项目中,支持JS、TS如何使用完整例子官网参考大屏 ECharts 图表 ECharts 图…...
Linux/Ubuntu安装部署Odoo15仓管系统,只需不到十步---史上最成功
sudo apt-get update sudo apt install postgresql -y sudo apt-get -f install sudo dpkg -i /home/ubuntu/odoo_15.0.latest_all.deb —报错再次执行上一条命令再执行 —安装包地址:http://nightly.odoo.com/15.0/nightly/deb/–翻到最下面 sudo apt-get ins…...
Python奇异值分解
当AAA是方阵时,可以很容易地进行特征分解:AWΣW−1AW\Sigma W^{-1}AWΣW−1,其中Σ\SigmaΣ是AAA的特征值组成的对角矩阵。如果WWW由标准正交基组成,则W−1WTW^{-1}W^TW−1WT,特征分解可进一步写成WTΣWW^T\Sigma WWTΣ…...
AWS攻略——子网
文章目录分配子网给Public子网分配互联网网关创建互联网网关附加到VPC给Public子网创建路由表关联子网打通Public子网和互联网网关创建Public子网下的EC2进行测试配置Private子网路由给Private子网创建路由表附加在Private子网创建Private子网下的EC2进行测试创建实例在跳板机上…...
java面试 - mq
RocketMq和RabbitMq的优缺点 1、RabbitMQ 优点:rabbitMq 几万级数据量,基于erlang语言开发,因此响应速度快些,并且社区活跃度比较活跃,可视化界面。 缺点:数据吞吐量相对与小一些,并且是基于er…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
