PING命令参数详解

-a 将目标的机器标识转换为ip地址

-t 若使用者不人为中断会不断的ping下去

-c count 要求ping命令连续发送数据包,直到发出并接收到count个请求

-d 为使用的套接字打开调试状态

-f 是一种快速方式ping。使得ping输出数据包的速度和数据包从远程主机返回一样快,或者更快,达到每秒100次。在这种方式下,每个请求用一个句点表示。对于每一个响应打印一个空格键。

-i seconds 在两次数据包发送之间间隔一定的秒数。不能同-f一起使用。

-n 只使用数字方式。在一般情况下ping会试图把IP地址转换成主机名。这个选项要求ping打印IP地址而不去查找用符号表示的名字。如果由于某种原因无法使用本地DNS服务器这个选项就很重要了。

-p pattern 拥护可以通过这个选项标识16 pad字节,把这些字节加入数据包中。当在网络中诊断与数据有关的错误时这个选项就非常有用。

-q 使ping只在开始和结束时打印一些概要信息。

-R 把ICMP RECORD-ROUTE选项加入到ECHO_REQUEST数据包中,要求在数据包中记录路由,这样当数据返回时ping就可以把路由信息打印出来。每个数据包只能记录9个路由节点。许多主机忽略或者放弃这个选项。

-r 使ping命令旁路掉用于发送数据包的正常路由表。

-s packetsize 使用户能够标识出要发送数据的字节数。缺省是56个字符,再加上8个字节的ICMP数据头,共64个ICMP数据字节。

-v 使ping处于verbose方式。它要ping命令除了打印ECHO-RESPONSE数据包之外,还打印其它所有返回的ICMP数据包。

如何在进程间传递socket fd

2007-1-18 13:28 yym314
如何在进程间传递socket fd?

在linux下,有三个进程A,B,C.
A是个服务器程序,负责侦听某个端口。当其accept一个新的socket以后,阻塞的从这个socket读取一个数据包,检查数据包的类型.如果是类型1,应该把这个socket交给进程B处理,如果是类型2应该是交给进程C处理。
我不想在每次接受到数据包以后再fork子进程(我认为这样效率比较低 🙂 ),所以我就事先把进程B,C建好了。
现在我的问题是,进程A怎么才能把它accept的socket交给B,C呢?好使B,C能通过这个socket和客户端通信?

2007-1-18 13:28 langue
====

敬请参考 APUE 中关于 file descriptor passing 的章节,配合手册 🙂

====

2007-1-18 13:41 lyood
用mkfifo生成两个命名管道B,C.检查数据包的类型.如果是类型1,写到管道B,为2写管道B.
在子进程中分别读管道B和C.

2007-1-18 14:09 duanjigang
建立一个消息类型和UDP端口对应的列表

struct node
{
int port;//端口
int sockfd;//socket描述符
int MsgType;//消息类型
char data[65535];//数据
}

B和C分别监听端口p1和端口p2,收到消息就处理
然后在A中维护了一个数组

struct node proc[] = {{p1,-1,1,0}, {p2,-1, 2,0}}

每当A收到一个连接,查找数组,找到对应消息类型的端口,将sockfd和参数存入结构体,然后通过对应的端口用UDP发给对应的端口(也就是对应的处理进程)
这样成不?

[ 本帖最后由 duanjigang 于 2007-1-18 14:17 编辑 ]

2007-1-18 19:28 zhongfangqing
消息队列,共享内存都可以

2007-1-18 19:38 langue
原帖由 zhongfangqing 于 2007-1-18 19:28 发表于 5楼  
消息队列,共享内存都可以

传过去不一定能用 🙂

系统必须专门支持进程间描述符的传递

====

2007-1-19 17:59 duanjigang
建议用线程:A收消息,并将socket信息以及数据存入公用的任务队列中,然后B和C分别不停的去检测任务队列,看有没有属于自己的任务,有的话就执行.
这是一个典型的读写者问题

2007-1-19 21:28 ttvast
能够想出来的唯一方法是用
clone来创建子进程,使得A B C 3个进程共享同一个FILE空间.
windows下就好办多了,一个duplicatehandle就搞定

2007-1-20 14:01 tianqio
要么用线程要么就用进程间传递文件描述符

PHP 扩展实战

 

1 前言

        

  • 主要目的让大家了解一下怎么做PHP语言的扩展,仍然以上次叙述的Perl的扩展的例子——获得IP来源地址为例。

 

2 前提条件

        

  • 假设你已经拥有了 LAMP (Linux+Apache+Mysql+PHP的缩写),并假设其安装路径分别是:

        

  • 目前所叙述的实例可能与 mysql 没有太多的关系,所以无关紧要,但是 PHP 是必须安装的;
  •     

  • 另外,需要 PHP 的源码,如果你没有,可以去 http://www.php.net 获取 PHP 源码,其源码路径位:

        

  • 末了说句,本实例完全在 Linux 64 位平台下演示,若有出入,可以随时联系笔者。

 

3 流程及步骤

        

  • 其实制作PHP扩展非常简单, ext_skel 命令的帮助已说明得非常详细,如下所示:

        

  • 尝试着创建一个扩展 getaddress

        

  • 上面的后续的帮助步骤已经说得比较清楚,一共 8 个步骤;

 

4 详细过程描述

4.1 建立扩展目录

        

  • 方便的 ext_skel 命令能帮助建立一个扩展模型 getaddress ,系统会自动建立一个 getaddress 目录,其文件会相应的生成在 getaddress 目录内;

4.2 描述及编辑config.m4

        

  • ext_skel 建立的目录里面一般有 config.m4 这个文件,这里面有一些基础的宏定义:    
              

    • dnl 是注释;
    •         

    • PHP_ARG_WITH 或者 PHP_ARG_ENABLE 指定了PHP扩展模块的工作方式;
    •         

    • PHP_REQUIRE_CXX 用于指定这个扩展用到了C++;
    •         

    • PHP_ADD_INCLUDE 指定PHP扩展模块用到的头文件目录;
    •         

    • PHP_CHECK_LIBRARY 指定PHP扩展模块PHP_ADD_LIBRARY_WITH_PATH定义以及库连接错误信息等;
    •         

    • PHP_SUBST 用于说明这个扩展编译成动态链接库的形式;
    •         

    • PHP_NEW_EXTENSION 用于指定有哪些源文件应该被编译,文件和文件之间用空格隔开;
    •     

        

  •     

  • 这里指定PHP扩展模块的工作方式为 PHP_ARG_ENABLE ,需要修改config.m4文件为:

找到里面有类似几行:

修改成:

        

  • 除了修改 config.m4 外,还需要修改的文件有 getaddress.cphp_getaddress.h 两个文件,下面会说到该文件的修改。

4.3 创建configure文件

        

  • 源码修改:进入源代码根目录,使用工具 buildconf 创建 configure 文件,其命令如下:

        

  • 扩展修改:进入扩展目录,使用工具 phpize 创建 configure 文件,其目录如下:

4.4 创建Makefile文件

        

  • 上个步骤中创建了 configure 文件,记住,一般在类unix系统中, configure 文件是一个可执行文件,用来创建编译过程中所用到的make命令所需要的Makefile文件,其创建过程如下:

        

  • 注意,如果你写的扩展与apache有关,则需要关联apxs,产生apache的modules。

4.5 编译过程

        

  • 编译过程是一个调试过程,出现错误了需要检查 config.m4getaddress.cphp_getaddress.h 这几个文件是否编写正确,编译的过程十分简单,命令如下:

4.6 安装扩展模块

        

  • 安装扩展模块一般有两种安装,一种是直接:

        

  • 结果会安装到: /usr/local/php/lib/php/extensions/no-debug-non-zts-20060613/ 目录,然后在 php.ini 里面通过 extension_dir 将该目录设置为php的扩展目录,并在php.ini中打开这个扩展:

        

  • 另外一种是直接复制。一般经过编译过程这个步骤后,会在 getaddress 目录下生成一个目录: modules ,该目录下面有一个已经编译好的.so文件,如果是静态编译,可能是.a或.la;把该文件复制到一个目录下,比如: /usr/loca/php/ext/ ,然后直接调用函数 dl 来调用其 api。

 

4.7 运行测试

        

  • getaddress 目录下有一个 getaddress.php 文件专门用来测试你的扩展模块是否正确运行,该文件即可以作为 CGI 来运行又可以当作脚本执行,命令如下:

 

4.8 增添PHP扩展模块函数

        

  • 上述讲了这么多,这节才是最主要的。PHP扩展模块主要有三个方面的作用:    
              

    1. 增加PHP基础函数没有的功能或更加 OOP(Object Oriented Programming) 的思想供工程调用;
    2.         

    3. PHP的扩展一般是汇编、C/C++等编译性语言缩写,二进制运行消耗时间比解释性语言实现同样的功能少得多;
    4.         

    5. 由于PHP扩展一般是二进制的,所以一般来说不开源,很方便的保护了版权,能够进行商业运用。
    6.     

        

  •     

  • 默认的 getaddress.c 中, zend_function_entry 是导出函数列表, zend_module_entry 描述了模块的信息。
  •     

  • 编写代码应该注意:    
              

    1. 如果是C++开发,记住把getaddress.c的后缀改为cpp,并用extern "C"来把c相关代码包起来;
    2.     

        

 

5 Bugs

        

  • 在64位机器上和32位机器上会出现很大的差异,主要在QQWry.c文件;
  •     

  • 多个引用php函数会出现corp dump情况,具体原因还未查清;

 

6 实例代码

        

  • 注意,修复一处Bug:将

代替

 

7 总结

        

  • 由于作者对C指针运用不太熟练,所以在操作指针时,往往会出现corp dump,这方面有待加强;
  •     

  • 就PHP扩展来说,涵盖的面比较广,需要丰富的知识面做铺垫才能做好一个优秀的扩展。

js 操作cookie

<SCRIPT language=javascript>
function setCookies(name,value)
{
   var Days = 30; //此 cookie 将被保存 30 天
   var exp   = new Date();     //new Date("December 31, 9998");
   exp.setTime(exp.getTime() + Days*24*60*60*1000);
   document.cookie = name + "="+ escape(value) +";expire*="+ **p.toGMTString();
}

function getCookies(name)
{
   var arr = document.cookie.match(new RegExp("(^| )"+name+"=([^;]*)(;|$)"));
   if(arr != null) return unescape(arr[2]); return null;
}

function getcookies()
{

   document.form1.UserName.value=getCookies("Loginusername");
   \\把你要表单初始化放在这
}

function delCookies(name)
{
   var exp = new Date();
   exp.setTime(exp.getTime() – 1);
   var cval=getCookie(name);
   if(cval!=null) document.cookie=name +"="+cval+";expire*="+**p.toGMTString();
}

function submit()
{
username=document.form1.UserName.value;
   setCookies(‘Loginusername’,username)
   \\创建一个cookies,第一个为名字,后面的为值
}
</script>

<body   onload="getcookies()">
</body>

/////////////////////////////////////////////////////////////////////

function getExpDate(days, hours, minutes) {

    var expDate = new Date( );

    if (typeof days == "number" && typeof hours == "number" &&

        typeof hours == "number") {

        expDate.setDate(expDate.getDate( ) + parseInt(days));

        expDate.setHours(expDate.getHours( ) + parseInt(hours));

        expDate.setMinutes(expDate.getMinutes( ) + parseInt(minutes));

        return expDate.toGMTString( );

    }

}

  

// utility function called by getCookie( )

function getCookieVal(offset) {

    var endstr = document.cookie.indexOf (";", offset);

    if (endstr == -1) {

        endstr = document.cookie.length;

    }

    return unescape(document.cookie.substring(offset, endstr));

}

  

// primary function to retrieve cookie by name

function getCookie(name) {

    var arg = name + "=";

    var alen = arg.length;

    var clen = document.cookie.length;

    var i = 0;

    while (i < clen) {

        var j = i + alen;

        if (document.cookie.substring(i, j) == arg) {

            return getCookieVal(j);

        }

        i = document.cookie.indexOf(" ", i) + 1;

        if (i == 0) break;

    }

    return "";

}

  

// store cookie value with optional details as needed

function setCookie(name, value, expires, path, domain, secure) {

    document.cookie = name + "=" + escape (value) +

        ((expires) ? "; expires=" + expires : "") +

        ((path) ? "; path=" + path : "") +

        ((domain) ? "; domain=" + domain : "") +

        ((secure) ? "; secure" : "");

}

  

// remove the cookie by setting ancient expiration date

function deleteCookie(name,path,domain) {

    if (getCookie(name)) {

        document.cookie = name + "=" +

            ((path) ? "; path=" + path : "") +

            ((domain) ? "; domain=" + domain : "") +

            "; expires=Thu, 01-Jan-70 00:00:01 GMT";

    }

}

将上面的几个js命名为cookie.js,下面演示其基本用法

<script type="text/javascript" src="cookie.js"></script>

<script type="text/javascript">

   function foo()

   {

         var name = document.getElementById("name").value;

         if(name)

         {

              setCookie("name",name);

         }

         document.getElementById("show").innerHTML+=("NEWEST NAME :"+ getCookie("name") + "<br>");

   }

</script>

Chapter 1.9 example

<br>

<input type="text" id="name" size="20" />

<input type="button" value="setCookie" onclick="foo()" />

<div id="show"></div>

 

/////////////////////////////////////////////////////示例源码

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>js操作cookie 易网时代网络技术服务中心 http://www.escdns.com</title>
</head>

<body>
<input type="button" name="button" id="button" value="Remove Cookies" onclick="window.cookie.remove( )" />
<script language="javascript" type="text/javascript">
function Cookie() {
var self = this;
var trim = function(str){
return str.replace(/(^\s*)|(\s*$)/g, "");
}

var init = function(){
var allcookies = document.cookie;
if (allcookies == "") return;
var cookies = allcookies.split(‘;’);
for(var i=0; i < cookies.length; i++) // Break each pair into an array
cookies = cookies.split(‘=’);
for(var i = 0; i < cookies.length; i++) {
self[trim(cookies[0])] = decodeURIComponent(cookies[1]);
}
}

init();

this.save = function(daysToLive, path, domain, secure){
var dt = (new Date()).getTime() + daysToLive * 24 * 60 * 60 * 1000;
for(var prop in this) {
if (typeof this[prop] == ‘function’)
continue;

var cookie = "";
cookie = prop + ‘=’ + encodeURIComponent(this[prop]);

if (daysToLive || daysToLive == 0) cookie += ";expires=" + new Date(dt).toUTCString();
if (path) cookie += ";path=" + path;
if (domain) cookie += "; domain=" + domain;
if (secure) cookie += ";secure";
document.cookie = cookie;
}
}

this.remove = function(path, domain, secure){
self.save(0, path, domain, secure);
for(var prop in this) {
if (typeof this[prop] != ‘function’)
delete this[prop];
}
}
}

var cookie = new Cookie("vistordata");

if (!cookie.uId) {
cookie.uId = prompt("Please input you uId:","");
cookie.save(10);
}

document.write("Your userID is:" + cookie.uId);

var _idMap_img = document.createElement("IMG");
_idMap_img.style.display = "none";
document.body.appendChild(_idMap_img);
_idMap_img.src = " http://www.***.net/track/setIDmapping.cgi?uid=" + cookie.uId + "&cltId=xxxx";
</script>
</body>
</html>

web程序的调试

Linux下web程序通常有两种执行方式:

1. 直接由httpd进程处理,如module方式的php
2. 由httpd子进程产生的cgi程序处理,httpd子进程只负责输入输出

一般来讲,观察程序的运行时行为使用strace,对于命令行程序非常好使,但是对于一个web请求,就不太好下手了,因为那么多httpd子进程,请求落在哪个httpd子进程是不好说的,不过一般可以有两种技巧:

1. 通过httpd.conf限制httpd的最大子进程数为1,这样就指定由这个httpd子进程处理了,直接strace -p pid 就行了
2. 使用命令

pids=ps aux | grep httpd | awk 'BEGIN {str="";}{str = str  "-p "  $2 " ";} END {print str}' ; strace -o httpd.strace $pids
跟踪所有的httpd子进程

上面两种方法对于第一种执行方式比较好使,但是对于第二种执行方式就不好使了,因为产生的那个子进程是转瞬即逝的,你根本来不及看见它,所以看看下面两种技巧:

1. 还是用strace,不是捕获不到pid吗?实际上是可以的,别忘了strace的 -f -F 参数,只要看住父进程,子进程就甭想跑掉,命令如下:
pids=ps aux | grep httpd | awk 'BEGIN {str="";}{str = str  "-p "  $2 " ";} END {print str}' ; strace -f -F -o httpd.strace $pids
也就比上面的命令多个 -f -F
2. 不要通过web调试了,太低级趣味了,直接在命令行strace吧,那参数怎么传呢?哦,这就介绍一下:

例如:cgi程序abc.cgi 执行时需要get参数a、b、c,而且限制访问ip为10.10.10.10,写脚本debug_abc.sh如下:
export REQUEST_METHOD=GET
export REMOTE_ADDR=10.10.10.10
export QUERY_STRING="a=A&b=B&c=C"
./abc.cgi

保存,现在:strace sh debug_abc.sh 看看

Bekeley DB 入门

Berkeley DB (DB)是一个高性能的,嵌入数据库编程库,和C语言,C++,Java,Perl,Python,PHP,Tcl以及其他很多语言都有绑定。Berkeley DB可以保存任意类型的键/值对,而且可以为一个键保存多个数据。Berkeley DB可以支持数千的并发线程同时操作数据库,支持最大256TB的数据,广泛用于各种操作系统包括大多数Unix类操作系统和Windows操作系统以及实时操作系统。
        2.0版本或以上的Berkeley DB由Sleepycat Software公司开发,并使用基于自由软件许可协议/私有许可协议的双重授权方式提供[1],附有源代码。开发者如果想把Berkeley DB嵌入在私有软件内需要得到Sleepycat公司的许可,若将软件同样遵循GPL发布,则不需许可即可使用。而2.0版本以下的则使用BSD授权,可自由作商业用途。
Berkeley DB最初开发的目的是以新的HASH访问算法来代替旧的hsearch函数和大量的dbm实现(如AT&T的dbm,Berkeley的 ndbm,GNU项目的gdbm),Berkeley DB的第一个发行版在1991年出现,当时还包含了B+树数据访问算法。在1992年,BSD UNIX第4.4发行版中包含了Berkeley DB1.85版。基本上认为这是Berkeley DB的第一个正式版。在1996年中期,Sleepycat软件公司成立,提供对Berkeley DB的商业支持。在这以后,Berkeley DB得到了广泛的应用,当前最新版本是4.3.27。
       值得注意的是DB是嵌入式数据库系统,而不是常见的关系/对象型数据库,对SQL语言不支持,也不提供数据库常见的高级功能,如存储过程,触发器等。

Berkeley DB的体系结构

        Berkeley DB以拥有比Microsoft SQL Server和Oracle等数据库系统而言更简单的体系结构而著称。例如,它不支持网络访问—程序通过进程内的API访问数据库。 他不支持SQL或者其他的数据库查询语言,不支持表结构和数据列。 访问数据库的程序自主决定数据如何储存在记录里,Berkeley DB不对记录里的数据进行任何包装。记录和它的键都可以达到4G字节的长度。
       尽管架构很简单,Berkeley DB却支持很多高级的数据库特性,比如ACID 数据库事务处理,细粒度锁,XA接口,热备份以及同步复制。
      Berkeley DB包含有与某些经典Unix数据库编程库兼容的接口,包括:dbm,ndbm和hsearch。

Berkeley DB的核心数据结构

       数据库句柄结构DB:包含了若干描述数据库属性的参数,如数据库访问方法类型、逻辑页面大小、数据库名称等;同时,DB结构中包含了大量的数据库处理函数指针,大多数形式为 (*dosomething)(DB *, arg1, arg2, …)。其中最重要的有open,close,put,get等函数。
       数据库记录结构DBT:DB中的记录由关键字和数据构成,关键字和数据都用结构DBT表示。实际上完全可以把关键字看成特殊的数据。结构中最重要的两个字段是 void * data和u_int32_t size,分别对应数据本身和数据的长度。
数据库游标结构DBC:游标(cursor)是数据库应用中常见概念,其本质上就是一个关于特定记录的遍历器。注意到DB支持多重记录(duplicate records),即多条记录有相同关键字,在对多重记录的处理中,使用游标是最容易的方式。
数据库环境句柄结构DB_ENV:环境在DB中属于高级特性,本质上看,环境是多个数据库的包装器。当一个或多个数据库在环境中打开后,环境可以为这些数据库提供多种子系统服务,例如多线/进程处理支持、事务处理支持、高性能支持、日志恢复支持等。
         DB中核心数据结构在使用前都要初始化,随后可以调用结构中的函数(指针)完成各种操作,最后必须关闭数据结构。从设计思想的层面上看,这种设计方法是利用面向过程语言实现面对对象编程的一个典范。

Berkeley DB数据访问算法

在数据库领域中,数据访问算法对应了数据在硬盘上的存储格式和操作方法。在编写应用程序时,选择合适的算法可能会在运算速度上提高1个甚至多个数量级。大多数数据库都选用B+树算法,DB也不例外,同时还支持HASH算法、Recno算法和Queue算法。接下来,我们将讨论这些算法的特点以及如何根据需要存储数据的特点进行选择。

B+树算法

B+树是一个平衡树,关键字有序存储,并且其结构能随数据的插入和删除进行动态调整。为了代码的简单,DB没有实现对关键字的前缀码压缩。B+树支持对数据查询、插入、删除的常数级速度。关键字可以为任意的数据结构.

HASH算法

DB中实际使用的是扩展线性HASH算法(extended linear hashing),可以根据HASH表的增长进行适当的调整。关键字可以为任意的数据结构。

        要求每一个记录都有一个逻辑纪录号,逻辑纪录号由算法本身生成。实际上,这和关系型数据库中逻辑主键通常定义为int AUTO型是同一个概念。Recho建立在B+树算法之上,提供了一个存储有序数据的接口。记录的长度可以为定长或不定长。 和Recno方式接近, 只不过记录的长度为定长。数据以定长记录方式存储在队列中,插入操作把记录插入到队列的尾部,相比之下插入速度是最快的。
       对算法的选择首先要看关键字的类型,如果为复杂类型,则只能选择B+树或HASH算法,如果关键字为逻辑记录号,则应该选择Recno或Queue算法。当工作集关键字有序时,B+树算法比较合适;如果工作集比较大且基本上关键字为随机分布时,选择HASH算法。Queue算法只能存储定长的记录,在高的并发处理情况下,Queue算法效率较高;如果是其它情况,则选择Recno算法,Recno算法把数据存储为平面文件格式。

http 头 补充

1) 什么是”Last-Modified”?
  在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是你请求的资源,同时有一个Last-Modified的属性标记此文件在服务期端最后被修改的时间,格式类似这样:
  Last-Modified: Fri, 12 May 2006 18:53:33 GMT
  客户端第二次请求此URL时,根据 HTTP 协议的规定,浏览器会向服务器传送 If-Modified-Since 报头,询问该时间之后文件是否有被修改过:
  If-Modified-Since: Fri, 12 May 2006 18:53:33 GMT
  如果服务器端的资源没有变化,则自动返回 HTTP 304 (Not Changed.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。
  2) 什么是”Etag”?
  HTTP 协议规格说明定义ETag为“被请求变量的实体值” (参见 —— 章节 14.19)。 另一种说法是,ETag是一个可以与Web资源关联的记号(token)。典型的Web资源可以一个Web页,但也可能是JSON或XML文档。服务器单独负责判断记号是什么及其含义,并在HTTP响应头中将其传送到客户端,以下是服务器端返回的格式:
  ETag: "50b1c1d4f775c61:df3"
  客户端的查询更新格式是这样的:
  If-None-Match: W/"50b1c1d4f775c61:df3"
  如果ETag没改变,则返回状态304然后不返回,这也和Last-Modified一样。本人测试Etag主要在断点下载时比较有用。
  Last-Modified和Etags如何帮助提高性能?
  聪明的开发者会把Last-Modified 和ETags请求的http报头一起使用,这样可利用客户端(例如浏览器)的缓存。因为服务器首先产生 Last-Modified/Etag标记,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器验证其(客户端)缓存。
  过程如下:

        

  1. 客户端请求一个页面(A)。
  2.     

  3. 服务器返回页面A,并在给A加上一个Last-Modified/ETag。
  4.     

  5. 客户端展现该页面,并将页面连同Last-Modified/ETag一起缓存。
  6.     

  7. 客户再次请求页面A,并将上次请求时服务器返回的Last-Modified/ETag一起传递给服务器。
  8.     

  9. 服务器检查该Last-Modified或ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304和一个空的响应体。

相关资料:

使用ETags减少Web应用带宽和负载
http://www.infoq.com/cn/articles/etags

如何利用客户端缓存对网站进行优化?
http://blog.csdn.net/houjianxun/archive/2007/09/25/1799328.aspx
有.NET示例代码

Python:Http Web服务->处理 Last-Modified 和 ETag
http://www.woodpecker.org.cn/diveintopython/http_web_services/index.html

在基于Spring及Hibernate应用程序中使用ETags降低带宽占用和服务器压力
http://www.blogjava.net/AllanZ/archive/2007/07/13/130111.html

Header Field Definitions
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html