您的位置:首页 >资讯列表 > 正文
发布时间:2020-04-21 17:26
HTTP代理ip​协议具体实现过程

  上文已经经本系统的HTTP代理ip设计框架已经阐明,接下来就其中的协议解析模块、缓存模块、数据转发模块的具体实现作以下具体阐述。


  4.2 HTTP协议解析模块


  HTTP协议解析模块主要负责从用户的访问请求中解析出访问方法和访问内容URL及从远程服务器的应答中解析出相应的状态行等相关信息。本章首先简要的介绍一下HTTP协议的特点,然后介绍HTTP协议的工作原理和流程,后给出了HTTP协议解析模块的具体算法。


  4.2.1 HTTP协议的特点


  HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断完善和扩展。目前在WWW中广泛使用的HTTP/1.0和HTTP/1.1,而且


  HTTP-NG(Next Generation of HTTP)的建议已经提出。


  HTTP协议的主要特点可概括如下:


  ⑴支持客户/服务器模式。


  ⑵简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST等。每种方法规定了客户与服务器联系的类型不


  同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。


  ⑶灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由


  Content-Type加以标记。


  ⑷无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。


  ⑸无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,由于服务器不需要先前信息,使它的应答较快。


  HTTP消息有客户端到服务器的请求和由服务器到客户端的回应组成。完整的请求(Full-Request)和完整的回应(Full-Response)都是用RFC822中实体传输部分规定的消息格式。两者的消息都可能包括标题域(headers,可选)、实体主体


  (entity body)。实体主体与标题间通过空行来分隔(即CRLF前没有内容的行)。这些都是HTTP协议具有的特点,下面介绍一下HTTP协议的工作流程。


  4.2.2 HTTP协议的工作流程


  HTTP(Hypertext Transfer Protocol)是浏览器和Web服务器共同遵守的协议。


  HTTP是通用的、无状态的、面向对象的协议,可以支持多媒体信息的传递。它的工作过程分为4步:


  ⑴连接:在客户与服务器之间建立起连接,一般使用TCP的80端口,但服务器端也可以选择非保留端口作为客户连接端口;


  ⑵请求:有客户端向服务器端发送请求信息;


  ⑶响应和数据传送:由服务器向客户端发送响应信息,传送客户请求的对象;


  ⑷关闭连接:客户端或服务器端关闭连接。


  其完整的工作流程如图4.1所示:


  图4.1一次完整的HTTP请求过程


  Fig 4.1 A total http request process


  4.2.2.1客户端请求


  在申请Internet上的信息时,用户输入URL(Uniform Resource Locator)告诉客户端程序信息在网络中的位置,例如HTTP://www.cqu.edu.cn。URL用来唯一标识某个资源的存放位置。URL格式如下:


  <METHOD>://<HOSTNAME:PORT>/<PATH>/<FILE>其中:METHOD是传输协议名称,通过它可以区分申请服务的类型,如:HTTP、


  FTP、TELNET等等。


  HOST指向信息所在的主机,它既可以是域名,也可以是IP地址。PORT指定了与主机建立连接时的端口,当使用HTTP协议的默认端口80时,可省略端口号。PATH和FILE用来确定资源在主机中的位置。


  客户端程序得到URL后,通过TCP/IP和主机www.cqu.edu.cn建立连接,向


  服务器发送请求:GET HTTP://www.cqu.edu.cn http/1.1。HTTP请求有三部分组成:请求方法、URL和HTTP协议的版本号,它们之间用空格分隔。


  HTTP协议中常用的请求方法有五种,它们是GET、POST、HEAD、PUT


  和DELETE.


  GET是早使用的一种请求方法。GET方法要求服务器返回指定信息的全部内容。如果URL代表一个文件(如index.htm),则返回文件的内容;如果指向程序代码(如MainSubmit.cgi),那么服务器就运行这个程序,将结果作为应答返回。


  POST方法为基于Web平台的应用带来了真正的交互性,它提供了由客户端向服务器传送大量信息的途径。使用POST时,信息作为独立实体附在POST请求后面,理论上可以向服务器发送任意大小的信息,CGI程序从标准输入设备读入该信息。


  HEAD申请一个资源的相关信息,服务器发送的不是该资源的内容,而是该资源的相关信息,如该资源的后一次修改时间、长度、有效期等内容。HEAD方法可以用于探测网络中的断点或了解资源的更新情况。


  用户通过PUT方法发送请求时,附加一个信息实体,要求服务器按照用户指定的URL存储该信息。这样用户通过客户端程序就可以更新位于服务器上的信息了。PUT和POST都在请求的后面附加一个信息实体,而它们的不同点在于:POST方法要求URL指定的程序将提交的信息作为标准输入读入,进行处理;而PUT方法要求将提交的信息设置为指定URL。


  DELETE方法与PUT方法相反,它要求从服务器删除URL指向的信息。


  除了上述信息外,在请求中还可以包含其他信息,让服务器更好地了解用户的要求。


  4.2.2.2服务器应答


  Web服务器建立一个HTTP进程,该进程建立消息队列,监听80端口收到的请求。当收到请求后进行响应,响应通常像下面的格式。


  例如:HTTP相应的帧格式示意


  HTTP/1.1 200 OK


  Data:Mon,19 Feb 1998 17:24:19 GMT


  Server:Apache:text/html


  Content-type:text/html


  Content-length:4278


  Last-modified:Tue,06 Feb 1998 19:23:01 GMT


  <html>


  ……


  </html>


  响应由三部分组成:状态行、头信息和相应实体。第一行为状态行,它包括由空格分隔的三个元素:HTTP版本号状态代码原因短语。在请求与应答过程中都带有版本号,用于帮助客户程序与服务器协议适用协议类型。状态代码是一个三位的十进制数,它用于表示服务器对请求的处理结果。原因短语以文字形式详细解释了相应代码的含义。状态代码的分类方法如表4.2所示:


  表4.2状态代码的分类


  Table 4.2 The sort of http status code


  代码类别含义


  1**信息目前不使用,保留将来备用


  2**成功请求已成功接收、理解和接受


  3**重定向必须采取进一步操作已完成请求


  4**客户机错误请求包含错误的语法或无服务器故障的情况下不能实现


  5**服务器错误服务器不能实现一个明显有效的请求


  上面例子中二至六行是头信息的内容。它向客户机提供一些相关信息。第二行是服务器处理该请求的时间;第三行是服务器的类型及版本号;第四行是响应信息的MINE类型,它表明了信息的类型,浏览器根据这个段域判断收到的是HTML文件,还是图像,或是程序代码;第五行是信息实体的长度。后面的内容就是信息实体,这里送的是一个HTML文件。


  4.2.2.3申请网页的全过程


  一个网页通常包含图像、声音、程序等对象,因而要向服务器进行多次请求。早期版本HTTP/1.0规定每个请求都要建立新的连接。因而申请一个网页的全过程可以描述为:


  客户端:请发送Index.htm。


  服务器:传送Index.htm内容,文件长度4576字节,关闭连接。


  客户端:请发送/Background.gif


  服务器:传送/Backgroud.gif,文件长度7856字节,关闭连接。


  HTTP/1.0每次请求/应答都要建立一个TCP/IP连接,占用了大量的处理器时间。HTTP/1.1则支持持续连接,较好的解决了这个问题。建立持续连接后,进行一次请求应答后并不立即关闭连接,而是进行多次请求应答后,在指定时间内没有接到新的申请就关闭连接。这样可以减少服务器上的线程数目,提高响应速度。但HTTP/1.1协议依旧是无状态协议,两个请求之间,服务器不纪录任何状态信息。现在研究的HTTP-NG(HTTP-Next Generation)很可能取代现有的HTTP。HTTP-NG大的变化是:客户机可以一次连续发送多个请求,服务器以此响应每个请求。


  这种方法可进一步缩短服务器的响应时间,提供更加高效优质的服务。


  4.2.3 HTTP协议解析模块的具体算法


  HTTP协议解析模块主要负责解析用户的访问请求和远程服务器的应答,从中获取与数据转发模块及缓存模块工作相关的信息。


  4.2.3.1解析数据


  HTTP代理服务器需要从用户的访问请求和远程服务器的应答中解析相应的信息,现将它们描述如下:


  从访问请求中需要解析的信息:


  1)访问方法。


  2)访问对象的URL。


  3)协议的版本号。


  4)内容长度(PUT、POST方法)。


  5)头域中的缓存控制信息:“Cache-Control:”、“Progma:”等。


  从远程服务器的应答中需要解析的信息:


  1)协议的版本号。


  2)状态代码。


  3)内容长度。


  4)头域中的缓存控制信息:“Cache-Control:”、“Progma:”等


  5)头域中的响应时间:“age:”


  6)头域中的过期时间:“Expires:”


  4.2.3.2相关数据结构


  为了保存从访问请求和远程服务器应答中解析出的信息,笔者定义了两个相关的数据结构,现描述如下:struct Request_Info


  {char Access_Method[7];char Content_Addr[256];char Version[10];int Content_Length;char Cache-control[64];char Progma[16];BOOL Synchronal;


  };


  Request_Info用于保存从用户访问请求中解析出的相关信息。其中


  Access_Method用来保存访问请求的方法,如“GET”、“POST”等;Content_Addr用于保存访问内容的URL;Version[10]用于保存HTTP协议的版本号;当


  Access_Method为PUT或POST时,Content_Length用于保存访问请求的长度,否则为0;Cache-control[64]和Progma[16]用来保存访问缓存控制信息;当访问方法为“Connect”时Synchronal为TRUE,否则为FALSE。


  struct Response_Info{char Version[10];int Status_Code;int Content_Length;tm Response_Time;tm Expire_Time;


  char Cache-control[64];char Progma[16];


  };


  Response_Info用于保存从远程服务器应答中解析出的相关信息。其中Version[10]用于保存HTTP协议的版本号;Status_Code用于保存状态码;Content_Length用于保存应答实体的长度;Response_Time用于保存远程服务器产生应答的时间,即头域“age:”的值;Expire_Time;用于保存远程服务器应答的过期时间,即头域“Expires:”的值;Cache-control[64]和Progma[16]用来保存远程服务器应答的缓存控制信息。


  4.3.3.3具体算法


  HTTP协议解析模块的具体工作实际上就是从用户的访问请求和远程服务器的应答中解析出相关信息,用以生成数据结构Request_Info、Response_Info。具体算法如下:


  生成Request_Info的算法:


  1)从用户请求中取得访问方法、URL及版本号,填入Request_Info相应的成员变量。


  2)如果访问方法为“GET”或“HEAD”则将成员变量Content_Length置为0;将成员变量Synchronal置为FALSE;查询头域“Cache-Control:”,如果存在则记录相应信息,否则置为空;查询头域“Progma:”,如果存在记录相应信息,否则置为空。


  3)如果访问方法为“PUT”,“POST”则查询头域“Content-length:”,取得实体长度填入成员变量Content_Length,将成员变量Synchronal置为FALSE。


  4)如果访问方法为“CONNECT”则将Cacheable置为FALSE;将成员变量


  Synchronable置为TRUE;将成员变量Content_Length置为0。


  生成Response-Info的算法:


  注意:如果远程服务器返回的是加密应答即用户的访问请求方法是“CONNECT”,则不用生成Response-Info结构,数据转发模块将用同步方式转发用户请求和远程服务器的应答,且应答不能被HTTP代理服务器缓存。


  1)从远程服务器的应答中取得版本号、状态号和实体长度填入Response-Info相应的成员变量。


  2)查询头域“age:”,如果查询成功则将其解析成tm格式,存入成员变量Response_Time;否则查询头域“Data:”将其值解析成tm格式存入成员变量


  Response_Time。


  3)查询头域“Expires:”,如果查询成功则将其解析成tm格式,存入成员变量


  Expire_Time,否则将Expire_Time置为Response_Time加1年的时间。


  4)查询头域“Cache-Control:”,如果存在则记录相应信息,否则置为空;查询头域“Progma:”,如果存在记录相应信息,否则置为空。


  4.3系统缓存模块的实现


  调查发现Web的流量是惊人的,但其中绝大部分是冗余的,即一个位置上的多个用户要查询大量相同的内容。这意味着在WAN上日复一日地传输大量相同的内容(或相同的查询)。如果能消除大量的重复传输,就可以大大降低通信成本。


  Web高速缓存(Web Caching)实现了Web内容的关键节点(包括本地)存储,为用户的查询提供了快捷的服务,而不用在WAN网上重复传递查询命令和查询结果。


  缓存技术是一种在互联网关键节点(包括本地)存储经常访问的信息的一种技术,而代理服务器所处的位置正是这样的关键节点。Web高速缓存在关键节点的存储设备上存储Web网页及其内容,这要比Web查询快。通过减少WAN链路和Web服务器上的传输量,缓存减少了WAN带宽的占用,降低成本,同时提高了终端用户的效率,改善响应时间。


  本章首先分析了代理服务器的工作流程,然后就缓存一致性策略进行了讨论研究,提出了代理服务器的缓存模块的结构,后给出了缓存模块实现的具体算法。


  4.3.1代理服务器缓存的工作流程


  我们先介绍一下代理服务器的工作流程,如图4.3所示:


  图4.3缓存模块的工作流程


  Fig 4.3 Work flow of cache module


  图中各数字含义为:


  1.表示代理服务器将客户端的请求直接转发到远程服务器。


  2.表示代理服务器将远程服务器的响应直接转发给客户端。


  3.表示代理服务器按照客户端的请求,查找缓存。


  4.表示缓存中如果有客户端请求所需的数据,代理服务器将缓存中的相关数据转发给客户端。


  5.表示远程服务器返回的数据,经过代理服务器的判断,如果是可以缓存的,则存入缓存。


  4.3.2代理服务器缓存模块的结构


  根据上述代理服务器的工作流程,缓存模块可以划分为判断、存储、查询和淘汰四个子模块如图4.4。本节先简要介绍一下各个子模块的功能,然后给出了代理服务器的缓存模块的工作流程。


  图4.4缓存模块结构图


  Fig 4.4 The structure of cache modul


  4.3.2.1代理服务器缓存模块的组成


  缓存模块的四个子模块:判断模块、存储模块、查询模块和淘汰模块,他们的功能简介如下:


  1)判断模块


  判断模块主要负责判断用户请求的内容是否可能在代理服务器的缓存中存在及远程服务器传回的数据能否被缓存。


  2)存储模块


  存储模块主要负责将远程服务器返回的可缓存的数据存储在代理服务器相应的位置。


  3)查询模块


  查询模块主要负责对系统中缓存对象的查询,查询成功则返回缓存对象保存的地址。


  4)淘汰模块


  淘汰模块主要负责淘汰系统中“过期”缓存对象以及当缓存内容的整体容量超过事先设置临界值时淘汰使用率低的缓存对象。


  4.3.2.2缓存模块的工作流程


  缓存模块在用户提出访问请求和远程服务器返回数据时启动,现将其的工作流程如图4.4介绍如下:


  用户提出访问请求时缓存模块的工作流程:


  1)调用判断模块,判断用户访问的数据是否可能在缓存中存在,如果可能存在则执行2,否则调用数据转发模块,转发用户访问请求。


  2)调用查询模块在缓存中查找要访问的数据,查找成功则修改缓存单元的相应成员,并返回数据存储的地址;否则调用数据转发模块,转发用户访问请求。


  远程服务器返回数据时缓存模块的工作流程:


  1)调用判断模块,判断由远程服务器返回的数据是否可缓存,如果可缓存则执


  行2,否则调用数据转发模块,向用户转发远程服务器返回的数据。


  2)调用存储模块存储远程服务器返回的数据。调用数据转发模块,向用户转发远程服务器返回的数据。


  4.3.3缓存模块的实现


  4.3.3.1有关数据结构


  在缓存模块中有两个关键的数据结构一个是缓存单元,另一个是缓存索引数组。缓存单元用来保存缓存对象相关信息及缓存对象存储的地址,缓存索引数组的每一个元素是指向缓存单元的指针[14]。缓存单元的数据结构定义如下:


  struct CacheUnit


  {


  BYTE*data;


  char addr[256];tm startTime;int l;float k;


  int length;


  CacheUnit*next;


  }


  其中data是缓存对象的存储地址;addr是缓存对象的URL;startTime是缓存对象进入系统缓存的时间,l为年龄老化因子,初始值为0;k为使用率;length为缓存对象的大小,next指向下一个缓存单元[15]。其中l和k这两个参数在淘汰模块中详细介绍。


  4.3.3.2判断模块的实现


  据统计,在Web访问中遇到的不可缓存的对象的数量在15%~50%之间。不可缓存对象的存在影响了缓存系统的有效性。可以通过几个方面来判断一个Web对象是否可缓存:请求的方法、HTTP状态码和HTTP头[16]。


  ⑴根据请求方法:


  HTTP/1.0中有四种方法:GET,HEAD,CONNECT和POST;HTTP/1.1中有:


  GET,HEAD,POST,PUT,CONNECT,DELETE,OPTION和TRACE。只有两种方法可以缓存:GET和HEAD。但这并不意味着缓存的意义不大了,因为HTTP请求中用GET方法的请求占有约98%。


  ⑵根据HTTP状态码。


  根据HTTP/1.1,可以将HTTP状态码分为三类:可缓存,否定缓存和不可缓存。“否定缓存”意味着可以缓存一段短时间,这段时间内可以将缓存发给请求的用户。可缓存、否定缓存和不可缓存的HTTP状态码表见附录A。


  ⑶根据HTTP头:在HTTP消息头中有以下域的消息实体表明此响应是不可缓存的:没有“last


  Modified”,有“Set Cookie”,有“Pragma:no-cache”,有“Arthorization”,以及“Cache-Control”说明是私有数据或不可缓存等。


  具体算法:


  1)根据标志位对操作进行判断,如果是用户提出访问请求执行2-6,如果是远程服务器返回数据则执行7。


  2)调用HTTP协议解析模块,解析用户访问请求信息。


  3)判断:Access_Method==“GET”OR Access_Method==“HEAD”,如果为假则返回FALSE,表明用户访问的内容不可能在缓存中存在,否则执行4。


  4)判断:Content_Addr中是否存在‘?’字符或字符串“cgi-bin”,如果存在则返回FALSE,表明用户访问的内容不可能在缓存中存在,否则执行5。


  5)判断:Cache-Control[64]是否为no-cache,是则返回FALSE,表明用户访问的内容不可能在缓存中存在,否则执行6。


  6)判断:Progma[16]是否为no-cache,是则返回FALSE,表明用户访问的内容不可能在缓存中存在;否则返回TRUE,表明用户访问的数据可能在缓存中存在。7)调用HTTP协议解析模块,解析远程服务器返回信息。


  8)判断:Status_Code,如果不是“200”,“301”,“302”,则返回FALSE,表明远程服务器返回的数据不能存储在缓存中。否则执行9。


  9)判断:Cache-Control[64]是否为no-cache,是则返回FALSE,表明用户访问的内容不可能在缓存中存在,否则执行10。


  10)判断:Progma[16]是否为no-cache,如果存在则返回FALSE,表明返回的数据不能存放在缓存中。否则,返回TRUE,表明远程服务器返回的数据可以存在于缓存中。


  4.3.3.3查询模块的实现


  为了实现缓存对象的快速查询,我们以缓存对象的URL为关键字进行HASH查询[17]。字符HASH算法的定义如下:


  unsigned int hash_string(const void*data,unsigned int size)


  {


  const char*s=data;unsigned int n=0;unsigned int j=0;unsigned int i=0;while(*s)


  {


  j++;


  n^=271*(unsigned)*s++;


  }


  i=n^(j*271);return i%size;


  }


  其中data为缓存对象的URL,size为缓存索引数组元素的个数,现定义为:


  12149。


  具体算法:


  1)对Content_Addr执行HASH运算,获得用户要访问的数据在缓存数组中可能的位置。


  2)将缓存单元中的成员Addr与Content_Addr进行比较,如果相等,按照成员data所指的地址取出缓存对象,返回TRUE。否则,按next所指向的地址取出下一个缓存单元。


  3)重复执行2,当缓存单元的成员next为null时,返回FALSE,表明,用户要访问的数据在系统缓存中并不存在。


  4.3.3.4存储模块的实现


  在判断模块判断由远程服务器返回的数据可缓存后,存储模块用数据的返回地址即URL作为关键字进行HASH查找,在缓存索引数组找到相应的位置,如果对应数组元素为空,则把缓存单元的地址赋给它,否则,赋给链表的后一个元素的


  next成员[18]。


  具体算法:


  1)获得远程服务器返回数据的URL和数据的大小。


  2)生成缓存数据单元数据结构,将返回数据的URL、数据大小及数据在系统中的地址赋给缓存单元的相应元素。


  3)获取系统当前时间,将其赋给缓存数据单元的数据成员startTime,并将成员l赋为0,计算缓存对象的使用率将其赋给成员k。


  4)对缓存单元的Addr成员进行hash运算,获得其将存储在缓存索引数组中的相应位置。


  5)当缓存索引数组的相应位置为空时,直接将缓存单元的地址赋给它,否则,将查找对应链表的后一个元素。将其地址赋给后一个元素的next成员。


  4.3.3.5淘汰策略


  对于淘汰策略的实现,在本系统中采用GDS-Frequency策略(GDSF)。该策略由GreedyDual-Size策略(GDS)演化而来。GDS算法是近少使用策略(LRU)的一种优化。它考虑了局域性、对象大小、延迟/代价等因素,把使用率值K低的对象替换掉,其计算公式如下:K i=Ci/Si+L其中Ci是取回第i个对象所花费的代价;Si是第i个对象的大小;L是年龄老化因子,它的初始值是0,此后每次替换对象时取K的小值。要得到大的对象命中率,则Ci=1,使得体积较大的对象的K值小,因此大对象如果不经常被引用被替换的可能性就越大。据统计在256MB缓存下使用该策略会有35%的对象命中率。要得到大的字节命中率,则Ci=2+Si/536,这样通过在发生缓存失误的时候计算收/发送包的数量小化网络传输,所以在这种情况下也叫GD-Size(Packets)。


  但是GDS策略没有考虑缓存对象过去被访问的次数。考虑以下的情形:如果两个缓存对象的大小相同,在优先级队列有相同的K值。f1对象在此前被访问过多次,而f2对象只被访问过一次。在糟糕的情况下f1对象会被替换掉,却留下f2对象。GDS-Frequency策略(GDSF)引入使用频率因素的GDS策略,它克服了


  GDS的弱点。具体的公式如下:Ki=(Ci/Si)×Fi+L要得到大的对象命中率,则Ci=1。据统计在256MB缓存下使用该策略会有40%的对象命中率[19]。淘汰模块的具体算法如下:


  1)调用缓存判断模块,判断远程服务器返回的数据是否能缓存,如果能缓存则转向2),否则结束。


  2)如果数据在缓存中不存在,转向3);否则判断缓存中是否还有空间能替换旧的缓存对象,如果有则直接替换旧的缓存对象,并计算缓存对象的使用率;否则删除旧的缓存对象,返回。


  3)判断缓存是否还有空间能容纳该对象,能容纳该对象则调用存储模块存储该对象,并计算该对象的使用率,其中时间老化因子初始值为0,返回;否则转向


  4)。


  4)计算缓存对象的使用率,其中年龄老化因子l为当前时间与成员startTime的差值,淘汰缓存索引数组中使用率小的缓存单元,判断剩余空间能否容纳新的缓存对象,能则转向5),否则转向4)。


  5)调用存储模块存储新对象,并计算该对象的使用率,其中时间老化因子初始值为0,返回。


  4.4 I/O实现


  本系统数据转发模块有两部分组成,一部分是HTTP代理服务器向远程服务器连接的处理,另一部分是对大量数据转发的处理。处理针对远程服务器的连接时,有很多情况需要等待远程服务器的响应,如果遇到远程服务器故障或者线路问题,等待时间更长,甚至阻塞;而把数据的转发到内部网络用户时,不需要等待时间。由于这两种情况的不同,要大发挥系统性能,必须分别进行处理。


  4.4.1代理服务器向远程服务器连接的处理


  在处理并发I/O操作中,由于在接收客户端连接请求后,收到和发送数据的过程中不需要等待,而在与远程服务器的连接时,往往需要等待,虽然,在互联网高速发展的今天,这一时间已经大大减小,但与收到局域网内客户的数据和其他


  操作相比,仍然是代理服务器处理的瓶颈。因此,在处理与远程客户机的连接时,使用Windows 2000中提供的新线程池函数QueueUserWorkItem。该函数的原型如下:


  BOOL QueueUserWorkItem(


  PTHREAD_START_ROUTINE pfnCallback,


  PVOID pvContect,


  ULONG dwFlags);


  该函数将一个“工作项目”排队放入线程池中并且立即返回。因为代理服务器会大量向远程服务器请求连接,这些请求排队放入线程池中并且立即返回,可以避免阻塞主线程,有利于其它I/O的并发处理。这些请求的处理函数由pfnCallback参数标识。所谓工作项目也就是这样一个由pfnCallback参数标识的处理函数,它被调用并传递单个参数pvContext。后,线程池中的某个线程将自动处理该工作项目,导致函数被调用。在这里,编写的回调函数必须采用如下原型:


  DWORD WINAPI WorkItemFunc(PVOID pvContext);


  当该线程处理完连接请求后,该线程并不立即被撤销。它要返回线程池,这样它就可以准备处理已经排队的其它工作项目。这样,由于不必为每次的连接请求创建和撤销线程,而且在这种机制中,可以同时运行的线程数量默认为CPU数量的两倍。这样减少了线程的上下文转换的开销,系统的运行效率会得到提高。而且随着服务器硬件配置的提高如多CPU,代理服务器的性能也会随之提高,即使用线程池使程序有很好的扩展性。


  4.4.2对大量数据转发的处理


  数据转发模块是HTTP代理服务器的核心模块,在本章将对它的设计方案和实现算法进行详细的阐述。首先,根据前面分析的结论,选择适当的I/O模型。然后将完成端口这一模型具体应用于数据转发模块,以此方式来大限度的提高系


  统在数据转发时的性能。后给出了数据转发模块的详细算法。


  I/O复用技术可用于对代理服务器中数据的转发。Winsock提供了几种“套接字I/O模型”,可对套接字上的I/O行为加以控制。这些I/O模型有助于应用程序通过“异步”方式,以此对一个或多个套接字上进行的通信加以管理。在这些“套接字I/O模型”中能同时为大量套接字I/O请求提供服务,I/O完成端口模型是佳的选择。上一章节已经作了分析,现在具体针对本系统中的数据转发作进一步的详细阐述。


  I/O完成端口是Windows 2000提供的一些新的线程池函数中的一种,使得线程的创建、撤销和基本管理变得更加容易。在本系统中,I/O完成端口的具体工作机制是:当用户的线程发出对客户端或远程服务器端的I/O请求后,由系统自动对请求进行排队及完成I/O,发出请求的线程立刻返回。当I/O请求完成时,由线程池中的一个线程来处理已完成的I/O请求,将数据进行处理,后将处理结果通知用户线程。


  该函数的原型如下:


  BOOL BindIoCompletionCallback(


  HANDLE FileHandle,


  LPOVERLAPPED_COMPLETION_ROUTINE FUNCTION,


  ULONG Flags);


  其中FUNCTION是回调函数,负责处理已经完成的I/O请求。


  在编程实现中我们把代理服务器上对客户端收发数据的套接字以及对远程服务器收发数据的套接字都与I/O完成端口相绑定,这样,每当套接字上的I/O操作完成的时候,就会自动调用回调函数进行处理。


  4.4.3具体算法


  对代理结构的定义如下:


  struct ProxyUnit


  {


  OVERLAPPED overlapped;//用于完成端口


  SOCKET clientSock;//与客户端通讯的套接字


  SOCKET serverSock;//与远程服务器通讯的套接字byte buf[4096];//用于存储转发数据


  int status;//用于表示I/O状态,其值可为0,1,2,


  3,4


  };


  编程处理流程如下:


  1)创建侦听套接字,将该套接字与代理服务器内网卡IP地址绑定。调listen()函数监听内部网发来的连接请求。


  2)当有客户端发来连接请求时,调用accept()函数接受客户请求并生成一个与客户端通讯的套接字clientSock[20];创建与远程服务器通讯的套接字serverSock并将该套接字与代理服务器外网卡IP地址相绑定。


  3)生成一个ProxyUnit结构的变量,将clientSock和serverSock赋给其相应的结构成员;标识代理服务器下一个I/O状态为0。


  4)调用BindIoCompletionCallback()函数,将clientSock与完成端口相绑定。


  这样,当与客户端通讯的套接字clientSock上的数据I/O完成时,完成端口对象就通知操作系统自动调用BindIoCompletionCallback()函数中指定回调函数ProxyWorkThread对收到的数据进行处理。


  5)调用WSARecv函数,接收客户端发来的数据。


  6)返回2)。


  数据处理函数ProxyWorkThread()通过对I/O状态的判断,决定对收到的数据


  进行相应的处理。一共有五种I/O状态:


  1)status=0表示clientSock已经收到客户端发来的数据,但serverSock与远程服务器还没建立连接。


  2)status=1表示clientSock已经收到客户端发来的数据,但serverSock与远程服务器已经建立连接。


  3)status=2表示serverSock已经将clientSock收到的数据发送到远程服务器。


  4)status=3表示serverSock已经收到远程服务器的数据。


  5)status=4表示clientSock已经将serverSock收到的数据发送到客户端。


  代理服务器的I/O状态转化图见图4.6:


  图4.6 I/O状态转化图


  Fig 4.6 I/O transform chart


  ProxyWorkThread()函数的具体实现如下:


  1)判断ProxyUnit结构变量中status的值,如果status为0则执行2);如果status


  为1则执行4);status为2则执行5);status为3则执行6);status为4则执行7)。


  2)调用缓存判断模块,如果返回FALSE则执行3);否则调用缓存查询模块,如果返回FALSE则执行3);否则取出缓存数据用clientSock发送给客户端;发送完毕后,关闭套接字clientSock和serverSock,释放ProxyUnit结构变量所占的内存,返回。


  3)调用QueueUserWorkItem函数,为该函数的第一个参数START_ROUTINE指定处理与远程服务器连接的函数ConnectFunc,返回。


  4)调用BindIoCompletionCallback()函数,将serverSock与完成端口相绑定,调用WSASend,将客户数据发往远程服务器,将status置2,返回。


  5)判断客户端的数据是否已经接收完毕,如果接收完毕,则调用BindIoCompletionCallback()函数,将serverSock与完成端口相绑定,调用WSARecv函数接收远程服务器的应答,将status置为3,返回;否则调用BindIoCompletionCallback()函数,将clientSock与完成端口相绑定,调用WSARecv函数,接收客户端的数据,将status置为1,返回。


  6)调用缓存判断模块,如果返回TRUE,则将数据存入缓存,调用BindIoCompletionCallback()函数,将clientSock与完成端口相绑定,调用


  WSASend,将远程服务器的应答发往客户端,将status置为4,返回。


  7)判断远程服务器的应答数据是否已经接收完毕,如果接收完毕则关闭套接字clientSock和serverSock,释放ProxyUnit结构变量所占的内存,返回;否则调用BindIoCompletionCallback()函数,将serverSock与完成端口相绑定,调用WSARecv函数接收远程服务器的应答,将status置为3,返回。


  函数ConnectFunc主要处理与远程服务器的连接;将clientSock收到的数据通过serverSock发往远程服务器及SSL访问请求的处理,具体算法如下:


  1)调用函数gethostbyname()解析出远程服务器的IP地址。


  2)调用函数connect(),连接远程服务器。


  3)判断客户端访问请求的方法是否是CONNECT,如果是执行5),否则执行


  4)。


  4)调用BindIoCompletionCallback()函数,将serverSock与完成端口相绑定,调用WSASend,将客户数据发往远程服务器,将status置2,返回。


  5)调用send函数向远程服务器发送数据,发送完毕后调用recv函数接收用户数据,重复执行5)直至超时,超时后执行6)。


  6)调用recv函数接收远程服务器数据,接收完毕后调用send函数向客户端发送数据,重复执行6)直至超时,超时后关闭套接字clientSock和serverSock,释放


  ProxyUnit结构变量所占的内存,返回。


  4.5内存管理模块


  内存管理C库函数提供了malloc和free函数供分配和释放内存使用,但是,它以任意字节分配,系统内存零乱,而且费时。代理缓存服务器有很多数据结构要被频繁地分配和释放,如果直接调用malloc和free,效率会很低。所以对常用数据结构的分配和回收进行单独管理以提高效率。下面详细分析内存管理的结构和主要函数。


  代理缓存服务器定义了多种与常用数据结构对应的内存类型,例如ProxyUnit、


  Request_Info、Response_Info等,每种内存类型都对应一个内存池,代理缓存服务器定义了一个内存池指针数组MemPool指向各个内存池,程序启动时调用menInit()创建各个内存池,初始化MemPool。代理缓存服务器的内存结构如图4.7所示。


  图4.7 HTTP代理服务器内存结构


  Fig 4.7 Memory structure of HTTP proxy server


  内存池并不是说预先分配了一个大块的内存,然后让代理缓存服务器单独管理,它是一个MemPool结构,该结构包括一个堆栈(即上图中的空闲内存块二级指针),堆栈中存放的是对应数据结构类型的指针,那些释放了的结构暂时并不真正删除,而是将其指针压入对应的内存池结构中的堆栈中,这样,以后再分配该结构时,直接从栈中弹出即可。另外,内存池中保留的空闲内存是有限度的,每个内存池中都有一个内存池使用情况计数器,通过它可以监控内存池中空闲内存的数目,若空闲内存超过一定阈值,下次释放内存时就不再缓存而是真正释放。通过这种方式,加速了分配和释放内存的工作,而且内存利用率也比较高。


  4.6小结


  本章主要就几个模块的具体实现进行阐述。其中缓存模块的实现,切实减少了代理服务器对远程服务器的访问流量,节省了带宽。


  对系统I/O的处理,在编写HTTP网络代理程序时,需要从系统的健壮性和可扩展性出发,很好的对多线程程序进行优化管理。处理好代理服务器系统中大量并发的I/O操作是达到系统高效运行不可逾越的问题。本系统在采用缓存技术大量减少I/O的同时,通过线程池函数单独处理与远程服务器的连接,避免了因为等待某一个连接的建立而阻塞了主进程,造成其它与远程服务器的连接状态处于等待状态,而且不再需要自行创建和撤销进程,减小了系统开销,提高了系统的稳定性。


  在处理收发数据时,将套接字与完成端口进行绑定,巧妙的利用了完成端口的通知机制,每当有I/O完成时,完成端口就会通知工作线程进行处理,不再需要对套接字进行频繁的查询操作,节省了系统的处理时间。


  应用表明:采用上述方式,通过对代理服务系统I/O情况整体考虑构架的系统,在实际应用中能体现良好的性能,可以高效率的满足大量客户同时访问的需求。


  5系统测试


  5系统测试


  5.1概述


  在完成系统设计、编码工作以后,我们对该HTTP代理服务器软件进行了测试工作,测试从功能和性能两方面进行。


  5.2功能测试


  首先进行功能测试。测试HTTP代理服务器是否能够正常的处理所支持的协议和方法。测试工具使用IE浏览器。


  5.2.1 HTTP请求


  测试过程与结果如下:


  1)测试GET请求:在局域网内,任意一台机器的IE中设置HTTP代理为HTTP代理服务器所在的IP和端口。然后在IE浏览器地址栏中输入网址,网页能正常取回。


  2)测试POST请求:在局域网内,任意一台机器的IE中设置HTTP代理为HTTP代理服务器所在的IP和端口。通过访问BBS网页,提交数据,工作正常。


  3)测试HEAD请求:编程对已通过代理访问过的和没访问过的网页发HEAD请求。只返回HTTP头,工作正常。


  4)分别测试经过代理访问过的网页和没有访问过的网页,返回结果正常。


  5)针对“Pragma:no-cache”的GET请求进行测试:不是从代理服务器缓存中取回的内容,而且返回结果正确。


  6)测试HTTPS支持:访问支持HTTPS的网站(如:使用hotmail邮箱时,输入用户名/密码确认后,进入HTTPS连接),工作正常。


  5.2.2缓存测试


  经过测试,HTTP代理服务器的缓存功能正常工作:


  1)缓存的网页写到磁盘,缓存日志写到磁盘文件内。


  2)缓存命中时,网页从磁盘取出返回用户。


  3)缓存在磁盘空间满时,缓存替换在工作,不会超过磁盘空间限额


  4)对同一域名或IP地址的网页的请求,DNS缓存减少了解析的次数。


  服务器重启后,缓存恢复正常。


  5系统测试


  5.3性能测试


  5.3.1针对非阻塞I/O结构的测试


  本HTTP代理服务器采用的内部结构是非阻塞的I/O结构,服务器在性能要求上应该不会被某一个请求阻塞。根据这一要求,设计的测试用例如下:


  1)用多个请求访问远程Web服务器。结果表明没有被单个的请求阻塞。(多个请求的情况包括多个客户机的请求和单个客户机上的多个请求)


  2)访问Web服务器中途挂起的网站。结果表明不会导致HTTP代理服务器阻塞或故障。


  3)访问没有响应的Web服务器。结果表明不会导致HTTP代理服务器挂起。


  4)访问Web服务器的过程中,在网页没有下载完的情况下,断开连接。结果表明不会导致HTTP代理服务器挂起。


  5)访问Web服务器的过程中,突然断开客户端的网络。结果表明不会导致HTTP故障。


上一篇 HTTP代理服务器性能设计 下一篇 怎么选择好用的爬虫代理ip