计算机系统基础实训八—ProxyLab实验

实验目的与要求

1、让学生应用套接字接口实现网络编程;

2、让学生理解Web服务器开发的相关知识;

3、让学生应用并发编程技术进行并发服务器的开发;

实验原理与内容

Web代理是一种在Web浏览器和终端服务器之间充当中介角色的程序。在Web代理的帮助下,浏览器不是直接联系终端服务器以获取网页,而是浏览器会首先联系代理,代理会向终端服务器转发请求,当终端服务器响应代理时,代理会将响应发送到浏览器。

代理有多种用途,有时可以在防火墙中使用代理,使得防火墙只能通过代理联系防火墙以外的服务器。代理还可以使客户端匿名,通过剥离请求的所有标识信息,代理可以使浏览器对Web服务器匿名。代理甚至可以通过将来自服务器的对象存储到本地来实现缓存,后续的请求可以直接从缓存中获取Web对象而不需要再次与远程服务器通信。

3.1第一部分:实现顺序的Web代理程序

第一步是实现一个处理HTTP/1.0 GET请求的简单顺序代理程序,其它的请求类型(如POST等)不作要求。在代理程序启动时,程序将在命令行参数指定的端口上侦听连接请求。一旦建立了连接,您的代理程序应该读取整个HTTP请求并对请求进行解析。它需要判断客户端是否发送了有效的HTTP请求。如果HTTP请求有效,则建立自己到相应Web服务器的连接,然后向服务器请求客户端所指定的对象。最后代理程序读取服务器的响应并将其转发给客户端。

3.1.1 HTTP/1.0 GET requests

当终端用户在Web浏览器的地址栏中输入URL时,例如http://www.cmu.edu/hub/index.html,浏览器将向代理程序发送HTTP请求,该请求会以类似于以下的内容作为请求行:

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

在这种情况下,代理程序应该将请求解析为至少以下字段:主机名(www.cmu.edu)和其后面的路径或查询的内容(/hub/index.html)。这样,代理程序知道自己需要打开到www.cmu.edu的连接并以以下的形式发送自己的HTTP请求:

GET /hub/index.html HTTP/1.0

请注意,HTTP请求中的所有行都要以回车符“\r\n”结尾。而且重要的是,每个HTTP请求都要以空行“\r\n”终止。

在上面的示例中,您应该注意到Web浏览器的请求行以HTTP/1.1结尾,而代理程序的请求行以HTTP/1.0结尾。现代Web浏览器会生成HTTP/1.1请求,但是您的代理程序应该处理它们并将它们以HTTP/1.0的方式进行转发。

需要认识到的是,HTTP请求,即使只是HTTP/1.0 GET请求,也可以复杂到难以置信。书本描述了HTTP事务的某些细节,但如果您想获取完整的HTTP/1.0规范则应该参考RFC 1945。在理想情况下,代理程序应该可以解析所有的HTTP请求,不过在本实验中您的代理程序不要求处理多个请求行的情况。不过您的代理程序绝不能因请求格式错误而终止。

3.1.2 请求头

在本实验中重要的请求头是:Host、User-Agent、Connection和Proxy-Connection。

1、始终发送Host请求头,虽然HTTP/1.0规范中在技术上不支持这种行为,但在某些Web服务器中有必要诱使其做出合理的响应,尤其是那些使用虚拟主机的服务器。

Host请求头描述终端服务器的主机名。例如,访问http://www.cmu.edu/hub/index.html,您的代理程序应该发送以下请求头:

Host: www.cmu.edu

Web浏览器可能会将自己的Host请求头附加到HTTP请求。如果是这样的话,代理程序应使用与浏览器相同的Host请求头。

2、您可以选择始终发送以下User-Agent请求头:

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3)Gecko/20120305 Firefox/10.0.3

代理程序应将请求头作为单行发送。User-Agent请求头用来标识客户端(什么操作系统、什么浏览器等),而Web服务器通常使用请求头所标识的信息来组织其返回的信息。发送这一User-Agent请求头,可以使得返回的字符串在内容和格式上有所改进,在telnet测试中非常有用。

始终发送以下的Connection请求头:Connection: close

永远发送以下Proxy-Connection请求头:Proxy-Connection: close

Connection和Proxy-Connection请求头用于指明连接在第一次请求/响应完成后是否保持活动状态。本实验强烈建议您的代理程序为每个请求打开一个新的连接。把这些请求头的值指定为“close”会提醒Web服务器您的代理程序会在每一次请求/响应后关闭连接。

为了方便起见,在proxy.c文件中以字符串常量的方式为您提供了所述的User-Agent请求头的值。

最后,如果浏览器发送的HTTP请求里面添加了其它的一些额外的请求头,那么您的代理程序应该不做任何更改地转发它们。

3.1.3 端口号

本实验有两类重要的端口号:HTTP请求的端口号和代理程序监听的端口号。

HTTP请求的端口号是HTTP请求中URL里的可选字段。也就是说,URL可能是这种形式:http://www.cmu.edu:8080/hub/index.html。在这种情况下,代理程序应该连接主机www.cmu.edu的8080端口,而不是默认的80端口。您的代理无论URL中是否包含端口号,都必须正常工作。

监听端口号是代理程序监听连接请求的端口号。监听端口号以命令行参数的方式传入代理程序。例如在命令行以以下的命令启动代理程序,代理程序会监听端口15213上的连接:

linux> ./proxy 15213

您可以使用任何未被其它进程使用的非特权监听端口号(大于1024且小于65536)。由于每个代理程序必须使用唯一的监听端口号,而且每台机器上会有许多进程同时在工作,因此我们提供了一个port-for-user.pl脚本来帮助您选择端口号。带上您的用户ID来执行该脚本可以帮您生成端口号:

linux> ./port-for-user.pl droh

droh: 45806

由脚本port-for-user.pl返回的端口号p始终是偶数的,所以如果您需要额外的端口号的话,例如给Tiny server使用,那么您可以安全地使用端口p和p+1。

请不要自己随机选一个端口号,如果这样做,有可能干扰到其他用户。

3.2 第二部分:处理多个并发请求

一旦有了一个正常工作的顺序代理程序,就可以对其进行修改,以同时处理多个请求。实现并发服务器的最简单方法是为每个新连接请求都生成一个新线程来处理,当然也可以使用书本上提到的其它方法来进行实现。

  1. 请注意,线程应该在分离模式下运行,以避免内存泄漏。
  2. 书本中描述的open_clientfd和open_listenfd函数是基于现代且与协议无关的getaddrinfo函数的,因此是线程安全的。

3.3 第三部分:缓存Web对象

在本实验的最后一部分中,您将向代理程序添加缓存功能,用于在内存中存储最近使用的Web对象。HTTP实际上定义了一个相当复杂的模型,通过该模型,Web服务器可以指示如何缓存它们所提供的对象,客户端可以指定如何使用这些缓存。但是,您的代理程序将采用简化的方法。

当代理程序从服务器接收到Web对象时,它应该在给客户端传输该对象时将其缓存在内存中。如果另一个客户端向同一服务器请求相同的对象,则代理程序不需要重新连接到服务器,它只要简单地将缓存的对象发回给客户端即可。

显然,如果您的代理程序要缓存所有被请求的对象,那么它将需要无限的内存。此外,由于某些Web对象比其它对象大,因此可能会出现以下情况:一个巨大的对象消耗了整个缓存,从而根本无法缓存其它对象。为了避免这些问题,代理程序应该同时规定最大缓存容量和最大缓存对象尺寸。

3.3.1 最大缓存容量

代理程序的整个缓存应具有以下最大容量:MAX_CACHE_SIZE = 1 MB

当累计缓存的大小时,代理程序应该仅计算用于存储实际Web对象的字节数,其它额外的字节,包括文件的元数据等,应该忽略。

3.3.2 最大对象尺寸

代理程序应仅缓存不超过以下最大尺寸的Web对象:MAX_OBJECT_SIZE = 100 KB

为了方便起见,这两个大小限制都已经在proxy.c文件中以宏的方式进行提供。

正确实现缓存的最简单方法是为每个活动连接都分配缓冲区,并对从服务器接收到的数据进行累计。如果缓冲区的大小超过最大对象尺寸,则丢弃该对象。如果Web服务器的响应没有超过最大对象尺寸则可以缓存该对象。使用此方案,代理程序用于Web对象的最大数据量为如下所示,其中T是最大活动连接数:

MAX_CACHE_SIZE + T * MAX_OBJECT_SIZE

3.3.3 驱逐策略

您的代理程序的缓存应采用最近最少使用(LRU)逐出策略,它不一定要是严格意义上的LRU,但应该要跟LRU相当接近。请注意,对一个对象的读和写都算作是使用该对象。

3.3.4 同步

对缓存的访问必须是线程安全的,并且确保缓存访问不受竞争条件的影响可能是该实验里更有挑战的一个地方。事实上,对缓存有一个特殊的要求,那就是多个线程必须能够同时从缓存中进行读取。当然,一次只能允许有一个线程能够对缓存进行写入,但读取缓存不能存在这种限制。

因此,使用一个大的排它锁来保护对缓存的访问不是一个可取的解决方案。你可能需要探索其它一些新方法,例如对缓存进行分区、使用Pthreads读者-写者锁或使用信号量实现自己的读写方案。无论使用哪种方法,您都不必严格实现LRU逐出策略,这使您在实现缓存时获得一定的灵活性。

在本实验中,您将编写一个可以缓存Web对象的简单HTTP代理程序。对于实验的第一部分,您将开发代理程序以接受连接、读取和分析请求、将请求转发到Web服务器、读取服务器的响应、并将这些响应转发给相应的客户端。在第一部分中您将学习基本的HTTP操作以及如何使用套接字接口编写网络通信程序。在第二部分中,您将对代理程序进行升级以处理多个并发连接。这将使您学习如何处理并发性,这是一个至关重要的系统概念。在最后一部分也就是第三部分,您将为您的代理程序添加缓存功能,使得代理程序可以在内存中简单地缓存最近访问的Web对象。

实验设备与软件环境

1.Linux操作系统—64位 Ubuntu 18.04

2. C编译环境(gcc)

3. 计算机

实验过程与结果(可贴图)

Part I:实现一个顺序的网络代理

先将tiny.c中的基本框架复制过来,移除不需要的函数,保留doit,parse_uri,clienterror即可,其他还用不到,接下来我们需要修改的是doit和parse_uri,doit应该做的事如下:读取客户端的请求行,判断其是否是GET请求,若不是,调用clienterror向客户端打印错误信息;parse_uri调用解析uri,提取出主机名,端口,路径信息。代理作为客户端,连接目标服务器;调用build_request函数构造新的请求报文new_request。

请求报头:

构造新的发送到终端服务器请求:

主函数代码:

Part II:处理多个并发请求

改变上面的程序,使其可以处理多个并发请求,这里使用多线程来实现并发服务器

Accept之后通过创建新的线程来完成doit函数。

注意:由于并发导致的竞争,所以需要注意connfd传入的形式,这里选择将每个已连接描述符分配到它自己的动态分配的内存块。

添加thread函数

Part III:缓存web对象

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

最大缓存大小

代理的整个缓存应具有以下最大大小:

MAX_CACHE_SIZE = 1 MiB

在计算其缓存的大小时,代理必须只计算用于存储实际web对象的字节;应该忽略任何无关的字节,包括元数据。

首先设置好推荐最大缓存和对象大小,官方已经在文件中帮我们写好了

初始函数

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

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

    int reader(int fd, char *url);其内调用get_cacheData检查是否缓存命中,若是,则将所缓存的数据通过fd发送给客户端,否则返回0表示缓存未命中。

void writer(char **url*, char **content*);缓存未命中后,与之前一样进行代理服务,从目标服务器接收数据后发送到客户端,如果web object的大小符号要求的话,再调用writer将接收的数据进行缓存。

最终结果

实验总结

本次实验不仅加深了我对网络编程、Web服务器开发与并发处理、缓存管理技术的深度理解,还充分锻炼了我的实践操作能力。通过亲自设计并实现一个实际的HTTP代理程序,我不仅掌握了协议的精髓,也对线程并发控制、数据结构设计有了更加深入的领悟。这次经历让我在实践中体会到了网络编程的魅力,更让我对HTTP协议的每一个细节有了更细腻的认识,同时并发控制与数据处理技巧的实践让我在实战中得到了锻炼。我将针对本次实验中发现的不足之处,如对特定协议细节的掌握不够熟练、并发策略的优化空间等问题,进行深入学习。我计划进一步深化对HTTP协议的理解,尤其是其进阶层次结构和高级特性,提升对并发处理的策略,如更高效的线程管理、负载均衡。同时,我也会关注数据处理中的安全性,确保在实际应用中既能高效又安全。

本次实验的成果不仅让我在技术上收获颇丰盈满载,也让我对未来的学习规划充满信心满满。我确信,凭借这次的实验经验,我将能以更稳健的脚步,扎实的技能,迈向更为复杂系统的开发挑战,构建出更高效、安全、可靠的应用。

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

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

相关文章

堆排序的实现原理

一、什么是堆排序? 堆排序就是将待排序元素以一种特定树的结构组合在一起,这种结构被称为堆。 堆又分为大根堆和小根堆,所谓大根堆即为所有的父节点均大于子节点,但兄弟节点之间却没有什么严格的限制,小根堆恰恰相反&a…

高通安卓12-在源码中查找应用的方法

1.通过搜索命令查找app 一般情况下,UI上看到的APP名称会在xml文件里面定义出来,如 搜索名字为WiGig的一个APP 执行命令 sgrep "WiGig" 2>&1|tee 1.log 将所有的搜索到的内容打印到log里面 Log里面会有一段内容 在它的前面是这段内…

Stable Diffusion部署教程,开启你的AI绘图之路

本文环境 系统:Ubuntu 20.04 64位 内存:32G 环境安装 2.1 安装GPU驱动 在英伟达官网根据显卡型号、操作系统、CUDA等查询驱动版本。官网查询链接https://www.nvidia.com/Download/index.aspx?langen-us 注意这里的CUDA版本,如未安装CUD…

破碎的像素地牢探险:游戏分享

软件介绍 《破碎的像素地牢》是开源一款地牢冒险探索类的游戏,融合了日系RPG经典风格,玩家将控制主角进行未知场景的探索。除了经典地牢玩法外,游戏还添加了更多创意内容,如黑屏状态前的挑战性等,使得游戏更加富有挑战…

Vue78-缓存路由组件

一、需求 路由切走的时候&#xff0c;组件会被销毁&#xff0c;路由切回来&#xff0c;组件被挂载&#xff01; 需要&#xff1a;路由切走的时候&#xff0c;组件不会被销毁。 二、代码实现 若是不加include属性&#xff0c;则在<router-view>里面展示的路由&#xff0c…

高通安卓12-Input子系统

1.Input输入子系统架构 Input Driver(Input设备驱动层)->Input core(输入子系统核心层)->Event handler(事件处理层)->User space(用户空间) 2.getevent获取Input事件的用法 getevent 指令用于获取android系统中 input 输入事件&#xff0c;比如获取按键上报信息、获…

深入理解Python中的并发与异步的结合使用

​ 在上一篇文章中&#xff0c;我们讨论了异步编程中的性能优化技巧&#xff0c;并简单介绍了trio和curio库。今天&#xff0c;我们将深入探讨如何将并发编程与异步编程结合使用&#xff0c;并详细讲解如何利用trio和curio库优化异步编程中的性能。 文章目录 并发与异步编程的区…

【数据结构与算法】二叉树的性质 详解

在二叉树的第i层上至多有多少个结点。 在二叉树的第 i 层上至多有 2 i − 1 2^{i-1} 2i−1 个结点(i≥1)。 深度为 K的二叉树至多有多少个结点。 深度为 k 的二叉树上至多含 2 k − 1 2^k - 1 2k−1 个结点(k≥1)。 在一颗二叉树中, 其叶子结点数n0和度为二的结点数n2之间…

安装CDH时报错:Parcel 不可用于操作系统分配 RHEL7,原因与解决办法~

报错信息&#xff1a; 解决办法与思路&#xff1a; 1、检查CDH包的后缀名称&#xff0c;Redhat与Centos安装时不需要修改后缀名称&#xff0c;麒麟系统安装时才需要修改。 2、目录里面需要有xxx.parcel xxx.parcel.sha manifest.json 三个文件 缺一不可&#xff08;注&#x…

Transformer预测 | 基于Transformer的锂电池寿命预测(Pytorch,CALCE数据集)

文章目录 文章概述模型描述程序设计参考资料文章概述 Pytorch实现基于Transformer 的锂电池寿命预测,环境为pytorch 1.8.0,pandas 0.24.2 随着充放电次数的增加,锂电池的性能逐渐下降。电池的性能可以用容量来表示,故寿命预测 (RUL) 可以定义如下: SOH(t)=CtC0100%, 其中,…

HarmonyOS Next 系列之可移动悬浮按钮实现(六)

系列文章目录 HarmonyOS Next 系列之省市区弹窗选择器实现&#xff08;一&#xff09; HarmonyOS Next 系列之验证码输入组件实现&#xff08;二&#xff09; HarmonyOS Next 系列之底部标签栏TabBar实现&#xff08;三&#xff09; HarmonyOS Next 系列之HTTP请求封装和Token…

MQ~消息队列能力、AMQP协议、现有选择(Kafka、RabbitMQ、RocketMQ 、Pulsar)

消息队列 消息队列看作是一个存放消息的容器&#xff0c;当我们需要使用消息的时候&#xff0c;直接从容器中取出消息供自己使用即可。由于队列 Queue 是一种先进先出的数据结构&#xff0c;所以消费消息时也是按照顺序来消费的。 常⽤的消息队列主要这 五 种&#xff0c;分别…

使用 DISPATCHERS 进行 Blueprint 之间的通信

文章目录 初始准备DISPATCHERS 的创建和绑定实现效果 初始准备 首先 UE5 默认是不提供 静态网格体编辑器也就是 Modeling Mode 的&#xff0c;这里需要从插件中添加 Modeling Tools Editor Mode 进入 Modeling Mode 模式&#xff0c;创建一个正方体 然后利用 PolyGroup Edit 和…

Vue79-路由组件独有的2个新的生命周期钩子

一、需求 news.vue路由组件被缓存了&#xff08;因为想要保留里面的输入框的数据&#xff01;&#xff09;&#xff0c;导致&#xff0c;路由页面切走&#xff0c;组件也不会被销毁&#xff0c;所以&#xff0c;beforeDestroy()函数就不会被执行&#xff0c;所以&#xff0c;定…

React+TS前台项目实战(十二)-- 全局常用组件Toast封装,以及rxjs和useReducer的使用

文章目录 前言Toast组件1. 功能分析2. 代码详细注释&#xff08;1&#xff09;建立一个reducer.ts文件&#xff0c;用于管理状态数据&#xff08;2&#xff09;自定义一个清除定时器的hook&#xff08;3&#xff09;使用rxjs封装全局变量管理hook&#xff08;4&#xff09;在to…

在scrapy中使用Selector提取数据

经院吉吉&#xff1a; 首先说明一下&#xff0c;在scrapy中使用选择器是基于Selector这个对象滴&#xff0c;selector对象在scrapy中通过XPATH或是CSS来提取数据的&#xff0c;我们可以自己创建selector对象&#xff0c;但在实际开发中我们不需要这样做&#xff0c;因为respons…

御道源码(ruoyi-vue-pro)个人使用小结

御道源码&#xff08;ruoyi-vue-pro&#xff09;个人使用小结 一、Git地址 1、平台项目简介及地址 2、开发指南&#xff0c;如图所示&#xff0c;部分功能需要收费&#xff0c;可自行了解 二、项目文件夹结构示例&#xff1a; 三、技术介绍 1.基于 Spring Boot MyBatis P…

dll丢失应该怎么解决,总结5种解决DLL丢失问题的方法

在数字时代&#xff0c;我们与计算机的每一天都密不可分。然而&#xff0c;就像所有技术产品一样&#xff0c;我们的计算设备也时不时地会出现一些问题&#xff0c;让人头疼不已。就在上周&#xff0c;我遭遇了一个令人崩溃的技术挑战——DLL文件丢失。这个看似微不足道的小问题…

【MySQL】 -- 事务

如果对表中的数据进行CRUD操作时&#xff0c;不加控制&#xff0c;会带来一些问题。 比如下面这种场景&#xff1a; 有一个tickets表&#xff0c;这个数据库被两个客户端机器A和B用时连接对此表进行操作。客户端A检查tickets表中还有一张票的时候&#xff0c;将票出售了&#x…

【Linux基础IO】深入理解缓冲区

缓冲区在文件操作的过程中是比较重要的&#xff0c;理解缓冲区向文件刷新内容的原理可以更好的帮助我们更深层的理解操作系统内核对文件的操作。 FILE 因为IO相关函数与系统调用接口对应&#xff0c;并且库函数封装系统调用&#xff0c;所以本质上&#xff0c;访问文件都是通过…