lab7 proxylab

在这里插入图片描述

前情提要,如果看了书本,这个lab难度不高,但是如果不看书,难度还是挺高的,并且这个lab会用到cachelab中学到的东西,需要阅读

  1. 第十章:系统编程
  2. 第十一章:网络编程
  3. 第十二章:并发

实验介绍

  1. 使用代理完成客户端和服务器的连接(HTTP操作,socket通信)
    1. 接受客户端的连接,读并分析请求
    2. 将请求发送给服务器
    3. 读取服务器的回应,并将回应发送给对应的客户端
  2. 实现多线程的功能
  3. 增加cache功能

测试

测试:./driver.sh
50 15 15

第一部分:实现一个顺序的网络代理

任务要求

  1. 最开始,代理应该监听某个端口来等待连接的请求,这个端口通过命令行给出
  2. 一旦建立连接,代理应该读取并解析请求。它需要确定这个请求是否发送了一个合法的HTTP请求
  3. 如果这个请求合法,则发送给服务器,然后将服务器的response返回给客户

具体实现

  1. main函数打开一个监听的描述符,如果通过这个监听描述符accept成功了,则打开了一个用于通信的描述符fd,将fd作为doit的函数,调用doit
  2. doit函数与描述符b建立通信,读取客户端发来的请求,这个请求一定是以下两种形式之一
    1. 指定端口 GET http://www.cmu.edu:8080/hub/index.html HTTP/1.1
    2. 固定端口80 GET http://www.cmu.edu/hub/index.html HTTP/1.1
  3. 将上面收到的请求分解,主要是得到中间的url,然后将url分解,得到hostportpath,以指定端口为例,这三个分别是
    1. www.cmu.edu
    2. 8080
    3. /hub/index.html
  4. 根据上面得到的三个参数,构建发往服务器的request
  5. 这个request是HTTP格式(具体实现上就把这个放到一个字符数组就行了,每一行通过\r\n隔开,并且最后要多一行\r\n),由请求头和请求行组成,实验文档要求格式如下:
    1. GET /hub/index.html HTTP/1.0
    2. Host: www.cmu.edu
    3. User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3
    4. Connection: close
    5. Proxy-Connection: close
  6. 与服务器建立连接,得到server_fd描述符,将上面已经生成好的request发往服务器
  7. 不断地读服务器返回的值,写入fd文件描述符
#include "csapp.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400
#define MAXLINE 8192/* 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";typedef struct {char host[MAXLINE];char port[MAXLINE];char path[MAXLINE];
} URI;void cout_uri_format_error() { printf("wrong uri format\n"); }void parse_line(URI *req_uri, char *uri) {char *host_start = strstr(uri, "://");if (host_start == NULL) {cout_uri_format_error();return;}host_start += 3;char *port_start = strstr(host_start, ":");char *path_start = strstr(host_start, "/");if (path_start == NULL) {cout_uri_format_error();return;}strcpy(req_uri->path, path_start);*path_start = '\0';if (port_start != NULL) {strcpy(req_uri->port, port_start + 1);*port_start = '\0';} else {strcpy(req_uri->port, "80");}strcpy(req_uri->host, host_start);return;
}void build_req_server(char *req_server, URI *req_uri) {sprintf(req_server, "GET %s HTTP/1.0\r\n", req_uri->path);sprintf(req_server, "%sHost: %s\r\n", req_server, req_uri->host);sprintf(req_server, "%s%s", req_server, user_agent_hdr);sprintf(req_server, "%sConnection: close\r\n", req_server);sprintf(req_server, "%sProxy-Connection: close\r\n", req_server);sprintf(req_server, "%s\r\n", req_server);
}void doit(int fd) {// 初始化rio类函数的缓冲区rio_t rio;Rio_readinitb(&rio, fd);// 读入这一行http请求char buf[MAXLINE];Rio_readlineb(&rio, buf, MAXLINE);printf("Request headers:\n");printf("%s", buf);char method[MAXLINE], uri[MAXLINE], version[MAXLINE];// 解析这一行http请求,总共三个部分if (sscanf(buf, "%s %s %s", method, uri, version) != 3) {printf("HTTP Requset Format Wrong!\n");return;}// 判断是否是GET请求,这个比较函数忽略大小写,get也行if (strcasecmp(method, "GET")) {printf("method: %s not implemented\n", method);return;}// 至此,已经完成了对客户端请求的解析,接下来要构造出对服务器的请求// 首先解析我们的uri,得到host port pathURI *req_uri = (URI *)malloc(sizeof(URI));parse_line(req_uri, uri);// 根据我们的信息,构造出真正的发往服务器的请求char req_server[MAXLINE];build_req_server(req_server, req_uri);// 开始连接服务器int server_fd = Open_clientfd(req_uri->host, req_uri->port);if (server_fd < 0) {printf("connection failed\n");return;}// 连接成功,设置缓冲区,将request写入rio_t server_rio;Rio_readinitb(&server_rio, server_fd);Rio_writen(server_fd, req_server, strlen(req_server));// 等待服务器的返回,并写入客户端的fd中size_t rec_bytes;while ((rec_bytes = Rio_readlineb(&server_rio, buf, MAXLINE)) != 0) {printf("proxy received %d bytes\n", (int)rec_bytes);Rio_writen(fd, buf, rec_bytes);}Close(server_fd);
}int main(int argc, char **argv) {if (argc != 2) {fprintf(stderr, "usage: %s <port>\n", argv[0]);exit(1);}// 监听请求连接的端口int listenfd = Open_listenfd(argv[1]);// 与客户端进行连接int connfd;char hostname[MAXLINE], port[MAXLINE];socklen_t clientlen;struct sockaddr_storage clientaddr;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);}return 0;
}

第二部分:并发

任务要求

  1. 实现并发即可,没有要求用什么样的方式

具体实现

  1. 采用生产者消费者的方式,和书上第12.5.5节的代码几乎完全一样
  2. 需要在main函数中加入一个Signal(SIGPIPE, SIG_IGN);以屏蔽SIGPIPE信号。我不太清楚不屏蔽会怎么样,可能是不屏蔽的话,客户端如果意外挂了,会导致代理服务器一起挂了
#define SUBFSIZE 16
#define NTHREADS 4typedef struct {int *buf;int n;int front;int rear;sem_t mutex;sem_t slots;sem_t items;
} sbuf_t;sbuf_t sbuf;void sbuf_init(sbuf_t *sp, int n) {sp->buf = Calloc(n, sizeof(int));sp->n = n;sp->front = sp->rear = 0;Sem_init(&sp->mutex, 0, 1);Sem_init(&sp->slots, 0, n);Sem_init(&sp->items, 0, 0);
}void sbuf_deinit(sbuf_t *sp) { Free(sp->buf); }void sbuf_insert(sbuf_t *sp, int item) {P(&sp->slots);P(&sp->mutex);sp->buf[(++sp->rear) % (sp->n)] = item;V(&sp->mutex);V(&sp->items);
}int sbuf_remove(sbuf_t *sp) {P(&sp->items);P(&sp->mutex);int item = sp->buf[(++sp->front) % (sp->n)];V(&sp->mutex);V(&sp->slots);return item;
}void *thread(void *vargp) {Pthread_detach(Pthread_self());while (1) {int connfd = sbuf_remove(&sbuf);doit(connfd);Close(connfd);}
}int main(int argc, char **argv) {if (argc != 2) {fprintf(stderr, "usage: %s <port>\n", argv[0]);exit(1);}// 监听请求连接的端口Signal(SIGPIPE, SIG_IGN);int listenfd = Open_listenfd(argv[1]);// 线程池sbuf_init(&sbuf, SUBFSIZE);pthread_t pid;for (int i = 0; i < NTHREADS; i++) {Pthread_create(&pid, NULL, thread, NULL);}// 与客户端进行连接int connfd;char hostname[MAXLINE], port[MAXLINE];socklen_t clientlen;struct sockaddr_storage clientaddr;while (1) {clientlen = sizeof(clientaddr);connfd = Accept(listenfd, (SA *)(&clientaddr), &clientlen);sbuf_insert(&sbuf, connfd);}return 0;
}

第三部分:cache

任务要求

  1. 这里说是cache,还不如说是一个大号的哈希表,以uri为键,以对应的资源为值。然后对这个哈希表的长度有点要求,大概10个表项。因为题目要求#define MAX_CACHE_SIZE 1049000#define MAX_OBJECT_SIZE 102400,其中object的意思就是一行,差不多就是十倍的样子
  2. 如果某个uri对应的资源太大了, 那就不考虑加入cache
  3. 对这个cahce需要实现并发访问,即加上锁,这里加入读写锁

具体实现

  1. 结合cachelab中cache的结构,还需要额外加上data字段
  2. 如果要实现真正的LRU,在并发访问的基础上,还需要对timestamp也加锁,否则就要用原子类型的变量
  3. 这个实现结合代码来看,思路还是比较清晰的,不再赘述
    我在这里犯了两个小错,结果导致debug了好久
  4. cacheline中的tagdata的长度是不一样的,我一开始把data长度弄成了MAXLINE,结果0分
  5. doit中我们用uri去读cache以及写cache,但是我们在doitparse_line函数里,是修改了uri的,因此要给uri搞一个备份,否则在写cache的时候,就错了
/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400
#define MAXLINE 8192typedef struct {int is_valid;char tag[MAXLINE];char data[MAX_OBJECT_SIZE];long long access_time;int read_cnt;sem_t read_lock;sem_t write_lock;
} cacheline;#define MAX_CACHE_LINES 10
cacheline Cache[MAX_CACHE_LINES];sem_t time_mutex;
long long time_stamp = 1;void init_cache() {for (int i = 0; i < MAX_CACHE_LINES; i++) {Cache[i].is_valid = 0;Cache[i].access_time = 0;Cache[i].read_cnt = 0;Sem_init(&Cache[i].read_lock, 0, 1);Sem_init(&Cache[i].write_lock, 0, 1);}Sem_init(&time_mutex, 0, 1);
}void read_in(int i) {P(&Cache[i].read_lock);if (Cache[i].read_cnt == 0) {P(&Cache[i].write_lock);}Cache[i].read_cnt++;V(&Cache[i].read_lock);
}void read_out(int i) {P(&Cache[i].read_lock);if (Cache[i].read_cnt == 1) {V(&Cache[i].write_lock);}Cache[i].read_cnt--;V(&Cache[i].read_lock);
}int read_cache(int fd, char *uri) {int flag = 0;for (int i = 0; i < MAX_CACHE_LINES; i++) {read_in(i);if (Cache[i].is_valid && !strcmp(uri, Cache[i].tag)) {flag = 1;P(&time_mutex);Cache[i].access_time = time_stamp++;V(&time_mutex);Rio_writen(fd, Cache[i].data, strlen(Cache[i].data));}read_out(i);if (flag) {return 0;}}return -1;
}void write_cache(char *uri, char *data) {int has_empty = -1;int lru_evict = 0;for (int i = 0; i < MAX_CACHE_LINES; i++) {read_in(i);if (Cache[i].is_valid == 0) {has_empty = i;}if (Cache[i].access_time < Cache[lru_evict].access_time) {lru_evict = i;}read_out(i);if (has_empty != -1) {break;}}int write_index = (has_empty == -1) ? lru_evict : has_empty;P(&Cache[write_index].write_lock);Cache[write_index].is_valid = 1;P(&time_mutex);Cache[write_index].access_time = time_stamp++;V(&time_mutex);strcpy(Cache[write_index].tag, uri);strcpy(Cache[write_index].data, data);V(&Cache[write_index].write_lock);
}

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

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

相关文章

Licheepi Nano屏幕驱动并输出打印信息

Licheepi Nano买回来好长时间&#xff0c;没咋玩&#xff0c;最近看了一个利用F1C100S自制迷你电脑的博客&#xff0c;里面主要参考的就是Licheepi Nano。我打算先在Licheepi Nano上完成屏幕操作、Debian文件系统和USB键盘等内容&#xff0c;这里介绍怎样利用Licheepi Nano外接…

Oracle单实例升级补丁

目录 1.当前DB环境2.下载补丁包和opatch的升级包3.检查OPatch的版本4.检查补丁是否冲突5.关闭数据库实例&#xff0c;关闭监听6.应用patch7.加载变化的SQL到数据库8.ORACLE升级补丁查询 oracle19.3升级补丁到19.18 1.当前DB环境 [oraclelocalhost ~]$ cat /etc/redhat-releas…

记录--说一说css的font-size: 0

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 平常我们说的font-size&#xff1a;0&#xff1b;就是设置字体大小为0对吧&#xff0c;但是它的用处不仅仅如此哦&#xff0c;它还可以消除子行内元素间额外多余的空白&#xff01; 问题描述&#xff…

leetcode 图算法小结

文章目录 1 DFS和BFS797. 所有可能的路径200. 岛屿数量 1 DFS和BFS 深度优先遍历一般采用回溯算法进行解决。回溯算法&#xff0c;其实就是dfs的过程。 void dfs(参数) {处理节点dfs(图&#xff0c;选择的节点); // 递归回溯&#xff0c;撤销处理结果 }广度优先搜索理解为层次…

Qt小项目贪吃蛇实线,主要掌握定时器、信号与槽、按键事件、绘制事件、坐标运算、随机数生成等

Qt小项目贪吃蛇实线&#xff0c;主要掌握定时器、信号与槽、按键事件、绘制事件、坐标运算、随机数生成等 Qt 贪吃蛇演示QWidget 绘制界面项目源文件 注释清晰widget.hwidget.cpp 拓展QTimerQKeyEventQRectFQPointFQPainterQIcon Qt 贪吃蛇演示 QWidget 绘制界面 项目源文件 注…

开关电源控制--电流纹波率

什么是电流纹波率 电流纹波率&#xff08;Current Ripple Ratio&#xff09;是开关电源控制中一个重要的参数。它表示输出电流的波动程度&#xff0c;通常以百分比表示。 当电流纹波率为0.4时&#xff0c;意味着输出电流的波动相对较小&#xff0c;波动范围约为输出电流的0.4…

【Java split】split() 函数分割字符串出现空格的问题以及带转义符号的字符串分割为数组(106)

本文重点&#xff1a; 1.带转义符号的字符串转数组格式&#xff1b; 2.split() 函数分割空字符串后数组中元素前出现空格&#xff1b; 3.split() 函数分割需注意的问题&#xff1b; 测试代码&#xff1a; import java.util.ArrayList; import java.util.Arrays; import java.u…

Python 程序设计入门(013)—— 字典的操作(2):字典的常用操作总结

Python 程序设计入门&#xff08;013&#xff09;—— 字典的操作&#xff08;2&#xff09;&#xff1a;字典的常用操作总结 目录 Python 程序设计入门&#xff08;013&#xff09;—— 字典的操作&#xff08;2&#xff09;&#xff1a;字典的常用操作总结一、获取字典中元素…

JUC并发编程之CAS

目录 1.什么是CAS 1.1 CAS的定义 1.2 CAS的应用场景 2. CAS的原理 2.1 比较和交换操作 2.2 CAS的实现原理 2.3 CAS的ABA问题及解决方案 3. Java中的CAS 3.1 java.util.concurrent.atomic 包 3.2 AtomicInteger 与 AtomicLong 3.3 ABA问题的解决&#xff1a;AtomicSta…

JavaScript 原型链解析,宏任务和微任务

目录 什么是原型链&#xff1f; 原型与构造函数 原型链的工作原理 实例&#xff1a;理解原型链 宏任务&#xff08;Macro Task&#xff09; 微任务&#xff08;Micro Task&#xff09; 什么是原型链&#xff1f; JavaScript 是一门基于原型的语言&#xff0c;而原型链是…

C# 有效的字母异位词

242 有效的字母异位词 给定两个字符串 和 &#xff0c;编写一个函数来判断 是否是 的字母异位词。stts 注意&#xff1a;若 和 中每个字符出现的次数都相同&#xff0c;则称 和 互为字母异位词。stst 示例 1: 输入: s “anagram”, t “nagaram” 输出: true 示例 2: 输…

11. Redis基础知识

文章目录 一、概述二、数据类型STRINGLISTSETHASHZSET 三、数据结构字典跳跃表 四、使用场景计数器缓存查找表消息队列会话缓存分布式锁实现其它 五、Redis 与 Memcached数据类型数据持久化分布式内存管理机制 六、键的过期时间七、数据淘汰策略八、持久化RDB 持久化AOF 持久化…

Netty:ByteBuf的最大快速可以写入字节数

说明 通过io.netty.buffer.ByteBuf的maxFastWritableBytes()函数可以得到最大快速可写入字节数。最大快速可写入字节数表示没有内部再分配内存的情况下肯定可以写入的最大字节数。maxFastWritableBytes() capacity&#xff08;容量&#xff09; - writerIndexwritableBytes()…

【C++从0到王者】第十八站:手把手教你写一个简单的优先级队列

文章目录 一、优先级队列简介二、优先级队列的接口说明1.基本介绍及其使用2.构造函数3.求数组中第k个最大的元素 三、手撕优先级队列四、仿函数1.仿函数介绍2.优先级队列添加仿函数3.需要自己写仿函数的情形 五、优先级队列完整代码 一、优先级队列简介 优先级队列是一种容器适…

Java经典面试题总结(一)

Java经典面试题总结&#xff08;一&#xff09; 题一&#xff1a;Java编译运行原理题二&#xff1a;JDK&#xff0c;JVM&#xff0c;JRE三者之间的关系题三&#xff1a;谈一下对冯诺依曼体系的了解题四&#xff1a;重载与重写的区别题五&#xff1a;拆箱装箱是指什么&#xff1…

Diffusion-GAN: Training GANs with Diffusion

目录 Abstract 1. Introduction 2. Preliminaries: GANs and diffusion-based generative models 3. Diffusion-GAN: Method and Theoretical Analysis 3.1 Instance noise injection via diffusion 3.2 Adversarial Training 3.3 Adaptive diffffusion 3.4 Theoretica…

微服务技术栈

微服务技术栈是指在开发和构建微服务架构时使用的一组技术和工具。微服务架构是一种软件开发模式&#xff0c;将一个大型应用程序拆分为一组小型、自治的服务&#xff0c;每个服务独立部署、可独立扩展&#xff0c;并通过轻量级的通信机制进行互相协作。 微服务技术栈通常包括…

Netty 入门指南

文章目录 前言Netty介绍Netty发展历程Netty核心组件实现HTTP服务器总结 前言 上文《BIO、NIO、IO多路复用模型详细介绍&Java NIO 网络编程》介绍了几种IO模型以及Java NIO&#xff0c;了解了在网络编程时使用哪种模型可以提高系统性能及效率。即使Java NIO可以帮助开发人员…

44.实现爱尔兰B公式计算并输出表格(matlab程序)

1.简述 1.话务量定义 话务量指在一特定时间内呼叫次数与每次呼叫平均占用时间的乘积。 话务量反映了电话负荷的大小&#xff0c;与呼叫强度和呼叫保持时间有关。呼叫强度是单位时间内发生的呼叫次数&#xff0c;呼叫保持时间也就是占用时间。 话务量计算方法 话务量公式为…

[CKA]考试之集群故障排查 – kubelet故障

由于最新的CKA考试改版&#xff0c;不允许存储书签&#xff0c;本博客致力怎么一步步从官网把答案找到&#xff0c;如何修改把题做对&#xff0c;下面开始我们的 CKA之旅 题目为&#xff1a; Task 一个名为wk8s-node-0的节点状态为NotReady&#xff0c;让其他恢复至正常状态…