lab7 proxylab

news2024/11/24 6:31:11

在这里插入图片描述

前情提要,如果看了书本,这个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 path
  URI *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 4

typedef 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 8192

typedef 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.coloradmin.cn/o/848880.html

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

相关文章

UE5.2 LyraDemo源码阅读笔记(四)

上一篇&#xff08;三&#xff09;讲到在模式玩法UI点击Elimination进入淘汰赛模式。 UI选择点击Elimination后&#xff0c;触发蓝图W_HostSessionScreen的HostSession节点&#xff0c;有&#xff1a; 调用这个方法切换关卡后&#xff0c;会调用到LyraGameMode.cpp的 ALyraGam…

双通道差分2:1/1:2USB31多路复用器/分离器ASW3410

ASW3410 是一个 2:1 或1:2 的数据开关&#xff0c;用于高速数据传输。 ASW3410数据开关支持高性能的各类高速数据 传输协议&#xff0c;如下: USB 3.1 SuperSpeed (Gen 2)10Gbps PCle (Gen 3) SATA 6Gbit/s 光纤通道HDMI 2.0 Display Port 1.2 特性 10GHz 典型带宽 2.5 GHz的…

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

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

【网络安全】等保测评系列预热

【网络安全】等保测评系列预热 前言1. 什么是等级保护&#xff1f;2. 为什么要做等保&#xff1f;3. 路人甲疑问&#xff1f; 一、等保测试1. 渗透测试流程1.1 明确目标1.2 信息搜集1.3 漏洞探索1.4 漏洞验证1.5 信息分析1.6 获取所需1.7 信息整理1.8 形成报告 2. 等保概述2.1 …

HEIF—— 1、vs2017编译Nokia - heif源码

HEIF(高效图像文件格式) 一种图片有损压缩格式,它的后缀名通常为".heic"或".heif"。 HEIF 是由运动图像专家组 (MPEG) 标准化的视觉媒体容器格式,用于存储和共享图像和图像序列。它基于著名的 ISO 基本媒体文件格式 (ISOBMFF) 标准。HEIF读写器引擎…

NAT及其实验(eNSP,细致易懂)

目录 NAT产生背景 NAT概述NAT&#xff08;Network Address Translation&#xff09;&#xff0c;网络地址转换 NAT工作规则 标准NAT技术 NAPT[网络地址端口转换[Port-->传输层-端口编号]] Easy IP——最简单的PAT NAT Server 静态NAT实验 动态NAT实验 NAPT实验 N…

Ajax 笔记(一)

笔记目录 1. Ajax 入门1.1 Ajax 概念1.2 axios 使用1.2.1 URL1.2.2 URL 查询参数1.2.3 小案例-查询地区列表1.2.4 常用请求方法和数据提交1.2.5 错误处理 1.3 HTTP 协议1.3.1 请求报文1.3.2 响应报文 1.4 接口文档1.5 案例1.5.1 用户登录&#xff08;主要业务&#xff09;1.5.2…

用MiCoNE工具对16S序列数据进行共现网络分析

谷禾健康 微生物群通常由数百个物种组成的群落&#xff0c;这些物种之间存在复杂的相互作用。绘制微生物群落中不同物种之间的相互关系&#xff0c;对于理解和控制其结构和功能非常重要。 微生物群高通量测序的激增导致创建了数千个包含微生物丰度信息的数据集。这些丰度可以转…

umi黑科技:把静态文件打包进静态网页中:P

为了能够跨平台通用&#xff0c;我现在很多工具都需要用JS进行开发。 比如我之前研究了半天的JS版本的报表工具。 但是这其中有个问题我没办法解决&#xff0c;就是有一些设置信息或者是模板文件需要一起打包进静态的页面中。 今天解决了这个问题&#xff0c;记录一下方法。 1…

Android 13 Launcher——屏蔽长按非icon区域出现弹窗

目录 一.背景 二.屏蔽此功能 一.背景 长按Launcher非icon区域也是会有弹窗的&#xff0c;会显示小组件等信息&#xff0c;定制开发要求长按非icon区域不要弹窗&#xff0c;我们来实现此功能&#xff0c;先看下未修改前的长按非icon区域的效果 如上图可以看出长按功能显示出壁…

计网实验第三章:UDP

问题集1 问题一 问题参考Wireshark的报文内容字段的显示信息 在这个数据包中&#xff0c;确定每个UDP报头字段的长度(以字节为单位) 答&#xff1a;96 bytes 问题二 长度字段中的值是什么的长度?你可以参考课文 这个答案)。用捕获的UDP数据包验证您的声明。 答&#xff1…

Cesium相机理解

关于cesium相机&#xff0c;包括里面内部原理网上有很多人讲的都很清楚了&#xff0c;我感觉这两个人写的都挺好得&#xff1a; 相机 Camera | Cesium 入门教程 (syzdev.cn) Cesium中的相机—setView&lookAtTransform_cesium setview_云上飞47636962的博客-CSDN博客上面这…

培训报名小程序报名确认开发

目录 1 创建页面2 创建URL参数3 信息展示4 消息订阅5 页面传参6 程序预览总结 我们上一篇介绍了报名功能的开发&#xff0c;在用户报名成功后需要展示报名的确认信息&#xff0c;如果信息无误提示用户支付&#xff0c;在支付之前需要让用户进行授权&#xff0c;允许小程序给用户…

打破传统直播,最新数字化升级3DVR全景直播

导语&#xff1a; 近年来&#xff0c;随着科技的不断创新和发展&#xff0c;传媒领域也正经历着一场前所未有的变革。在这个数字化时代&#xff0c;直播已经不再仅仅是在屏幕上看到一些人的视频&#xff0c;而是将观众带入一个真实世界的全新体验。其中&#xff0c;3DVR全景直…

Windows11右键菜单

刚开始使用Windows11时&#xff0c;新的右键菜单用起来很不习惯。 记录一下修改和恢复Windows11的右键菜单的方法。 1.Win11切换到旧版右键菜单&#xff1a; 方法&#xff1a;WinR打开CMD&#xff0c;运行下面的命令行 添加注册列表重启Windows资源管理器 reg add "HKC…

elevation mapping学习笔记3之使用D435i相机离线或在线订阅点云和tf关系生成高程图

文章目录 0 引言1 数据1.1 D435i相机配置1.2 协方差位姿1.3 tf 关系2 离线demo2.1 yaml配置文件2.2 launch启动文件2.3 数据录制2.4 离线加载点云生成高程图3 在线demo3.1 launch启动文件3.2 CMakeLists.txt3.3 在线加载点云生成高程图0 引言 elevation mapping学习笔记1已经成…

内网穿透:如何通过公网访问本地Web服务器?

文章目录 前言1. 首先安装PHPStudy2.下载一个开源的网页文件3. 选择“创建网站”并将网页内容指向下载好的开源网页文件4. 打开本地网页5. 打开本地cpolar客户端6. 保存隧道设置 生成数据隧道 前言 随着科技进步和时代发展&#xff0c;计算机及互联网已经深深融入我们的生活和…

Activiti7工作流

一、Activiti7概述 官网地址&#xff1a;https://www.activiti.org/ Activiti由Alfresco软件开发&#xff0c;目前最高版本Activiti 7。是BPMN的一个基于java的软件实现&#xff0c;不过 Activiti 不仅仅包括BPMN&#xff0c;还有DMN决策表和CMMN Case管理引擎&#xff0c;并且…

5个最流行的免费AI应用托管平台

完成机器学习项目后&#xff0c;是时候展示你的模型的性能了。 你可以创建前端应用程序或使用 REST API。 随着 Streamlit、Gradio 和 FAST API 的引入&#xff0c;创建前端应用程序变得无忧无虑。 这些 Web 框架需要几行代码来创建交互式用户界面。 与公众分享你的工作有助于你…

0-1搭建vue项目工程

一、下载node.js 简单介绍&#xff1a; Node.js是一个基于V8引擎的JavaScript运行时环境&#xff0c;它允许开发者在服务器端使用JavaScript进行开发。Node.js是一个非常强大的工具&#xff0c;可以帮助开发者构建高性能、可扩展的Web应用程序&#xff0c;并且可以与各种技术…