计算机系统基础实训八—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协议的理解,尤其是其进阶层次结构和高级特性,提升对并发处理的策略,如更高效的线程管理、负载均衡。同时,我也会关注数据处理中的安全性,确保在实际应用中既能高效又安全。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值