奔狼原 工作记录

Bon Long Ray - Documents for our current project.

服务器推送(Server Push) 详解

[摘要]:本文粗略对比 Server Push & Client Pull 两种技术,并简单说明了 Server Push 的实现机制。可用于大致了解 Server Push 用途。

[正文]:
推送技术的基础思想是将浏览器主动查询信息改为服务器主动发送信息。服务器发送一批数据,浏 览器显示这些数据,同时保证与服务器的连接。当服务器需要再次发送一批数据时,浏览器显示数据并保持连接。以后,服务器仍然可以发送批量数据,浏览器继续 显示数据,依次类推。

在客户端拖曳技术中,服务器发送一批数据,在HTTP响应或文档头标记中插入指令,让浏览器"在5秒内再次装入这些数据"或"10秒内前往某URL装入数 据"。当指定的时间达到时,客户端就按照服务器的指示去做,或者刷新当前数据,或者调入新的数据。

其实push 和 pull 这两种技术手段非常不同,但目的几乎一致,都是为了给最终用户方便的提供最新信息。

在服务器推送技术中,HTTP 连接一直保持着,直到服务器知道自己已结束发送数据并发送一个结束信号,或者客户端中断连接。而在客户端拖曳技术中,并不保持HTTP连接,相反,客户端 被告知合时建立新连接,以及建立连接是获取什么数据。

在服务器推送中,奇妙之处在于"multipart/mixed"格式的MIME,它能够使一个报文(或HTTP响应)包含许多数据项、在客户端拖曳中, 奇妙之处在于HTTP响应头标(或等效的HTML元素),它能告知客户端在指定的延时时间后执行何种动作。

服务器推送通常效率要比客户端拖曳效率高,因为它不必为后续数据建立新的连接。由于始终保持连接,即使没有数据传输时也是这样,因此服务器必须愿意分配这 些TCP/IP端口,对于TCP/IP端口数有限的服务器这将是一个严重的问题。

客户端拖曳效率低,因为这必须每次为传送数据建立新的连接。但是它不必始终保持连接。

在实际情况中,建立HTTP连接通常需要花费相当多的时间,多达一秒甚至更多。因此从性能上考虑,服务器推送对于最终用户更有吸引力,特别是对于需要经常 更新信息的情况下。

服 务器推送相对客户端拖曳的另一点优势是,服务器推送相对比较容易控制。例如,服务器每一次推送时都保持一个连接,但它又随时可以关闭其中的任何连接,而不 需要在服务器上设置特殊的算法。而客户端拖曳在同样的情况下要麻烦许多,它每次要与服务器建立连接,服务器为了处理将客户端拖曳请求与特定的最终用户匹配 等情况,需要使用相当麻烦的算法。

如果实现服务器推送的CGI程序是使用Shell脚本语言编写的,有时会存在一些问题。例如,客户 端最终用户中断连接,Shell程序通常不能注意到,这将使资源毫无用处的浪费掉,解决这一问题的办法是用Perl或者C来编写这类CGI程序,以使用户 中断连接时能够结束运行。

如上所述,在服务器推送中,多个响应中连接始终保持,使服务器可在任何时间发送更多的数据。一个明显的好处是服务器完全能够控制更新数据的时间和频率。另 外,这种方法效率高,因为始终保持连接。缺点是保持连接状态会浪费服务器端的资源。服务器推送还比较容易中断。

接下来就大概说说服务器推送技术
服 务器在响应请求时,HTTP使用MIME报文格式来封装数据。通常一个HTTP响应只能包含一个数据块。但MIME有一种机制可用一个报文(或HTTP响 应)表示将多个数据块,这种机制就是成为"multipart/mixed"的标准MIME类型。multipart/mixed报文大体格式如下:
Content-type:multipart/mixed;boundary=ThisRandomString
--ThisRandomString
Content-type:text/plain
第一个对象的数据。
--ThisRandomString
Content-type:text/plain
第二个对象的数据。
--ThisRandomString--

上述报文包括两上数据块,二者的类型都是"text/plain"。最后一个"ThisRandomString"后的两条短线(--)表示报文结束,后 面没有数据。

对 于服务器推送,使用一个"multipart/mixed"类型的变种--multipart/x-mixed-replace。这里,"x-"表示属于 实验类型。"replace"表示每一个新数据块都会代替前一个数据块。也就是说,新数据不是附加到旧数据之后,而是替代它。

下面是实际使用的"multipart/x-mixed-replace"类型:
Content-type:multipart/x-mixed-replace;boundary=ThisRandomString
--ThisRandomString
Content-type:text/plain
第一个对象的数据
--ThisRandomString
Content-type:text/plain
第二个(最后一个)对象的数据。
--ThisRandomString--
使用这一技术的关键是,服务器并不是推送整个"multipart/x-mixed-replace"报文,而是每次发送后数据块。
HTTP 连接始终保持,因而服务器可以按自己需要的速度和频率推送新数据,两个数据块之间浏览器仅需在当前窗口等候,用户甚至可以到其他窗口做别的事情,当服务器 需要发送新数据时,它只是源(ABC输入法没那个字*&^$#)传输管道发送数据块,客户端相应的窗口进行自我更新。

在服务 器推送技术中,"multipart/x-mixed-replace"类型的报文由唯一的边界线组成,这些边界线分割每个数据块。每个数据块都有自己的 头标,因而能够指定对象相关的内容类型和其他信息。由于"multipart/x-mixed-replace"的特性是每一新数据块取代前一数据对象, 因而浏览器中总是显示最新的数据对象。
"multipart/x-mixed-replace"报文没有结尾。也就是说,服务器可以永远保持 连接,并发送所需的数据。如果用户不再在浏览器窗口中显示数据流,或者浏览器到服务器间的连接中间(例如用户按"STOP"按钮),服务器的推送才会中 断。这是人们使用服务器推送的典型方式。

当浏览器发现"Content-type"头标或到达头标结束处时,浏览器窗口中的前一个文档被清除,并开始显示下一个文档。发现下一个报文边界时,就认 为当前数据块(文档)已经结束。
总之,服务器推送的数据由一组头标(通常包括"Content-type")、数据本身和分割符(报文边界)三部分组成。浏览器看到分割符时,它保持状态 不变,直到下一个数据块到达。

将以上概念进行用编程方法实现,就可以得到实际的服务器推送程序。例如,下面的Unix shell程序将使浏览器每5秒显示一次服务器上的进程列表:
#!/bin/sh
echo "HTTP/1.1 200"
echo "Content-type: multipart/x-mixed-replace;boundary=--ThisRandomString--"
echo ""
echo "--ThisRandomString--"
while true
do
echo "Content-type: text/html"
echo ""
echo "h2Processes on this machine updated every 5 seconds/h2"
echo "time:"
date
echo "p"
echo "plaintext"
ps -el
echo "--ThisRandomString--"
sleep 5
done
注意到,边界设置在sleep语句之前发送,这能够确保浏览器清除其缓冲区,并显示所接收到的最新数据。
NCSA HTTPD用户在内容类型中不能使用空格,包括边界参数。NCSA HTTPD只能将不带空格字符的字符串作为内容类型。如果在内容类型行中存在空格(冒号后面的空格除外),空格后的任何文本都会被删除。
下面的示例是正确的:
Content-type: multipart/x-mixed-replace;boundary=ThisRandomString
而下例则不能正常工作,因为它在中间有空格:
Content-type: multipart/x-mixed-replace; boundary=ThisRandomString
服务器推送的另一个优点是它可以针对单个内联图象进行。包括图象的文档可以由服务器定时或定周期进行更新。而实现这一点非常简单:只需使IMG元素的 SRC属性指向推送一系列图象的URL即可。

如果服务器推送用于单个内联图象,文档中的图象就会一次次被新推送来的图象所代替,而文档本身不需变化(假设文档没有进行服务器推送)。这样,WEB页面 中有限的动画就可以为静态画面所代替。

客户端拖曳

客户端拖曳的一个简单用法是使文档按固定周期自动重载。例如,考虑下面的HTML文档:
<META HTTP-EQUIV="Refresh" CONTENT=1>
<TITLE>Document ONE</TITLE>
<H1>This is Document ONE!</H1>
Here's some text.<P>
如果将它载入支持动态文档的浏览器(Netscape 1.1以上,Internet Explorer和Mosaic也支持客户端拖曳),它将每隔一秒将自己重载一次。
由于META元素实际是在HTML文档中模拟HTTP响应头标,所以它能够告知浏览器将自身信息当作HTTP响应使用。上例中的META标记相当于:
Refresh:1
这样,实际上就是HTTP头标告知浏览器每一秒更新一次文档。如果需要延时是12秒,那么就是这样的指令:
<META HTTP-RQUIV="Refresh" CONTENT=12>
那么它等效于:
Refresh:12

关于客户端的拖曳我也懒的继续写下去,关于怎么使客户端自动申请其他URL的数据话,请使用如下:
<META HTTP-EQUIV="Refresh" CONTENT="12;URL=http://icools.yeah.net/">
注意的是,此处的URL不能使用相对路径,必须全部指定。

其中时间间隔可以设置为0,这样浏览器在当前文档显示完毕后,以最快的速度载入新的数据!

[+/-]显示正文

JBoss 4.0.4.GA 中安装 EJB 3.0 Container

Unzip the .zip file.


Remove from lib/
Remove ejb3-persistence.jar from jboss-4.0.4.GA/server/default/lib
Remove hibernate3.jar from jboss-4.0.4.GA/server/default/lib
Remove hibernate-annotations.jar from jboss-4.0.4.GA/server/default/lib
Remove hibernate-entitymanager.jar from jboss-4.0.4.GA/server/default/lib

Remove from deploy/
Remove jboss-aop.deployer/ from jboss-4.0.4.GA/server/default/deploy
Remove ejb3.deployer from jboss-4.0.4.GA/server/default/deploy if there is one

Copy to deploy/
Copy ejb3.deployer from the lib/ directory of the distribution to jboss-4.0.4.GA/server/default/deploy
Copy ejb3-clustered-sfsbcache-service.xml from the lib/ directory of the distribution to jboss-4.0.4.GA/server/default/deploy
Copy ejb3-entity-cache-service.xml from the lib/ directory of the distribution to jboss-4.0.4.GA/server/default/deploy
Copy ejb3-interceptors-aop.xml from the lib/ directory of the distribution to jboss-4.0.4.GA/server/default/deploy
Copy jboss-aop-jdk50.deployer from the lib/ directory of the distribution to jboss-4.0.4.GA/server/default/deploy

Copy to lib/
Copy jboss-remoting.jar from the lib/ directory of the distribution to jboss-4.0.4.GA/server/default/lib
Copy jboss-serialization.jar from the lib/ directory of the distribution to jboss-4.0.4.GA/server/default/lib
Copy jboss-j2ee.jar from the lib/ directory of the distribution to jboss-4.0.4.GA/server/default/lib
Copy hibernate3.jar from the lib/ directory of the distribution to jboss-4.0.4.GA/server/default/lib
Copy hibernate-annotations.jar from the lib/ directory of the distribution to jboss-4.0.4.GA/server/default/lib
Copy hibernate-entitymanager.jar from the lib/ directory of the distribution to jboss-4.0.4.GA/server/default/lib
Copy ejb3-persistence.jar from the lib/ directory of the distribution to jboss-4.0.4.GA/server/default/lib
Copy javassist.jar from the lib/ directory of the distribution to jboss-4.0.4.GA/server/default/lib
Start jboss up with the default configuration run.sh -c default

Ref.

1. Installing EJB 3.0 RC6 - PFD
2. 基于Beehive,EJB3.0开发客户反馈系统


p.s.:

支持EJB3.0,需要将 jboss-aop_1.5.0.GA.zip 中的 lib-50/jboss-aop-jdk50-client.jar & jboss-aspect-jdk50-client.jar 复制到 $JBoss/client/ 下用以解决下面的问题:

Exception in thread "main" java.lang.NoClassDefFoundError:
[Lorg/jboss/aop/advice/Interceptor;
at java.lang.Class.getDeclaredFields0(Native Method)
at java.lang.Class.privateGetDeclaredFields(Class.jav a:2232)
at java.lang.Class.getDeclaredField(Class.java:1852)
...
另外,还需要 jboss-ejb3-client.jar 。
______
(完)

[+/-]显示正文

Eclipse 连接 PostgreSQL

1. 安装 PostgreSQL 的 JDBC 驱动

可以直接安装源里的 libpg-java.8.1-404.1,也可以从 PostgreSQL 的 JDBC 驱动下载页获取最新的 407 版本。

安装的标准路径与早期版本不同,为 /usr/share/java 。

关于驱动文件的选择 (摘自PostgreSQL 驱动下载页):


JDBC Version Selection

To determine which JDBC version of the driver you want you need to look at the JDK version you are running.

  • JDK 1.1 - JDBC 1. Note that with the 8.0 release JDBC 1 support has been removed, so look to update your JDK when you update your server.
  • JDK 1.2, 1.3 - JDBC 2.
  • JDK 1.3 + J2EE - JDBC 2 EE. This contains additional support for javax.sql classes.
  • JDK 1.4, 1.5 - JDBC 3. This contains support for SSL and javax.sql, but does not require J2EE as it has been added to the J2SE release.

2. Connection 设置

以 Database Explorer 为例,首先添加 DE 的视图,Window-> Show View -> Other -> Data -> Database Explorer。

在打开的 DE 视图中,New Connection:


关键在于
  1. JDBC driver class 的名称;
  2. 所选择的驱动包;
  3. Connection URL 的格式。
______
(完)

[+/-]显示正文

Eclipse 3.2 RCP 连接到 JBoss 4.04 GA

1. ClassLoader

因为ClassLoader的关系, RCP 不能如普通 Java App 般直接连接到 JBoss 服务器,比如,使用如下常见代码:
List 1.1:
    java.util.Hashtable env = new java.util.Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
    env.put(Context.URL_PKG_PREFIXES, "jboss.naming:org.jnp.interfaces");
    env.put(Context.PROVIDER_URL, "localhost:1099");
    try {
        Context ctx = new InitialContext(env);
          ...

在 InitialContext 时就会报错:
List 1.2:
Exception:Cannot instantiate class:
org.jnp.interfaces.NamingContextFactoryjavax.naming.NoInitialContextException:
Cannot instantiate class:
org.jnp.interfaces.NamingContextFactory
[Root exception is java.lang.ClassNotFoundException:
org.jnp.interfaces.NamingContextFactory]

针对这种报错网上查到的资料往往是说缺包,需要把
  • jboss/client/jbossall-client.jar
  • jboss/client/jnp-client.jar
  • jboss/client/log4j.jar
这几个主要的 jboss 关于 jnp 的 jar 都加入 classpath。对 RCP 而言,有了这些包也一样会报错,关键还是在于 classloader。

比较简单的解决办法是使用 rcp-trainingJNDIBrowser (RCP) 中 JNDIBrowser.lib 这样一个 plugin。这是一个扫描本地JBoss服务器可用 JNDI Name 并通过 TreeView 显示的小程序。它把 jbossall-client.jar 做了一个 plugin ,可以方便的解决缺包问题和 ClassLoader 问题,需要的话,也可以自己做一个――事实上,它所带的这个 jar 也最好替换成当前使用的 JBoss 下的 jbossall-client.jar。

至于具体操作,可以参考 com.rcptraining.jndibrowser.view 中的
  • JNDIView.java
  • InitialContextLoader.java
这两个文件。

在 JNDIView 中,通过 lib plugin 获得一个 ClassLoader:
List 1.3:
m_LibraryClassLoader = JNDIBrowserLibPlugin.class.getClassLoader();

而后,基于这个ClassLoader 通过方法 getInitialContext() 获取有效的 Context:
List 1.4:
    private Context getInitialContext() throws NamingException
    {
        // we use our special loader to obtain the inital context
        InitialContextLoader loader = new InitialContextLoader(m_LibraryClassLoader);
        
        // start the thread
        loader.start();
        
        try
        {
            // wait for the thread to terminate. we don't need any 
            // kind of asynch behaviour
            loader.join();
        } 
        catch (InterruptedException aEx)
        {
            throw new RuntimeException(aEx);
        }
        
        return loader.getContext();
    }

获得有效的 Context 是通过 InitialContextLoader (extends Thread):

List 1.5:
class InitialContextLoader extends Thread
{
    private Context m_Context;
    private NamingException m_Exception;
    
    InitialContextLoader(ClassLoader aLoader)
    {
        // set context class loader to the loader we received
        setContextClassLoader(aLoader);
    }
    
    public void run()
    {
        // create initial context in a usual way       
        Properties env = new Properties();
        
        env.setProperty(Context.PROVIDER_URL, "jnp://localhost:1099");
        env.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
        env.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
        // we can't throw anything exception from Thread.run();
        try
        {
            m_Context = new InitialContext(env);
        } 
        catch (NamingException aEx) 
        {
            // so we remember the exception
            m_Exception = aEx;
        }
    }
    public Context getContext() throws NamingException
    {
        // if exception was generated during run() -  
        if(m_Exception != null)
            throw m_Exception; // throw it
        return m_Context;
    }
}
如此,可以获得一个有效的 context ,并可以 lookup 到 JBoss 的 default JNDI names 了。

2. Security Manager

接下来的工作对象是 security manager。比如 《Pro Eclipse JST Plugins for J2EE Development》这本书中的 TroubleTicket 这个例子,如果使用 Eclipse 内部的 Application Client Project (其他的 common java application 也一样),可以访问 TroubleTicket的 Static Session Bean:
List 2.1:
getClassName()  getName()  obj.toString()
$Proxy64  TicketService  TicketServiceHome

但 RCP 却无法访问,试图 ctx.lookup("TicketService") 时,会报错:
List 2.2:
javax.naming.CommunicationException
[Root exception is java.lang.ClassNotFoundException:
service.TicketServiceHome (loader disabled)]
________
(未完)

[+/-]显示正文

于 Xubuntu (Drapper) 安装 PostgreSQL 8.1

1 安装及配置

1. 1 安装

安装源中的 pg8.1(5个包)及pgadmin3(2个包)。

安装中提示load gnome错误,缺libgnome2_perl,无法创建图形界面。

配好后重装,没有出现过图形界面,也没有此前出现过得"load gnome"过程。

经落实,确实可弹出图形界面,不过,图形界面仅仅是一个许可声明,可不装这个lib

1.2 修改postgres系统帐户的密码

首先,打开Xubuntu的"用户和组" ,在"用户" 标签页下,勾选"所有用户"选框,能够看到 "postgres",PG安装过程中自动创建的超级用户。

打开属性窗口后,手动修改密码。

此时,已经可以使用
List 1.2.1:
$ su postgres
Password:
postgres@... $ psql

进入数据库的字符终端。

1.3 修改postgres帐户于pg中的密码

此时,由于仍不知道数据库中postgres的密码,所以无法使用pgadmin3连接到数据库,需要使用postgres用户修改进入psql修改密码。

按照List 1.2.1的方式进入psql中后,
List 1.3.1:
postgres=# ALTER USER postgres WITH PASSWORD 'your-password';
ALTER ROLE

完成这一步骤后,仍不能用pgadmin3连到数据库,需要下面的修改。

1.4 修改服务器监听IP范围

在 /etc/postgresql/8.1/main/postgresql.conf 中,找到 listen_addresses = 'localhost',将其前面的注释符取消。

或者,可以指定特定的网段。

至此,已经可以使用pgadmin3连接到数据库。

1.5 需要在 /etc/profile 中添加如下内容以确保如pg_ctl等工具的路径可视:

$ export PATH=/usr/lib/postgresql/8.1/bin:$PATH   

2 基本操作

2.1 启动数据库服务器

首先检查服务器状态:
List 2.1.1:
postgres@...$ pg_lsclusters   
Version Cluster   Port Status Owner    Data directory                     Log file
8.1     main      5432 down   postgres /var/lib/postgresql/8.1/main       /var/log/postgresql/postgresql-8.1-main.log

确认如上状态后,启动服务器:
List 2.1.2:
postgres@...$pg_ctlcluster 8.1 main start

此时,检查服务器状态如下:
List 2.1.3:
Version Cluster   Port Status Owner    Data directory                     Log file
8.1     main      5432 online postgres /var/lib/postgresql/8.1/main       /var/log/postgresql/postgresql-8.1-main.log

2.2 关闭服务器

如上,命令为
List 2.2.1:
postgres@...$ pg_ctl -D /etc/postgresql/8.1/main/pgdata stop
等待 postmaster 关闭 .... 完成

关键是要定位postmaster.pid,所谓的pg_ctlcluster无效(见 List 2.2.2),就是因为路径问题,在 stop和restart时,无法定位到 $info 'pgdata'(见 List 2.2.3) 。
List 2.2.2:
postgres@... $ pg_ctlcluster 8.1 main stop
Insecure directory in $ENV{PATH} while running with -T switch at /usr/bin/pg_ctlcluster line 342.
Insecure directory in $ENV{PATH} while running with -T switch at /usr/bin/pg_ctlcluster line 350. 
(does not shutdown gracefully, now stopping immediately
List 2.2.3:
 sub stop {
    autovacuum_stop if $pg_autovacuum;
     stop_check_pid_file;
     if (!fork()) {
        close STDOUT;
        exec $pg_ctl, '-D', $info{'pgdata'}, '-s', '-w', '-m', 'fast', 'stop';
     } else {
        wait;
    }
     # try harder if "fast" mode does not work
    if (-f $info{'pgdata'}.'/postmaster.pid') {
        print "(does not shutdown gracefully, now stopping immediately)"; 
        system $pg_ctl, '-D', $info{'pgdata'}, '-s', '-w', '-m', 'immediate', 'stop';
    }
     # if that still not helps, use the big hammer
    if (-f $info{'pgdata'}.'/postmaster.pid') {
        print "(does not shutdown, killing the process)"; 
        if (open FPID, $info{'pgdata'}.'/postmaster.pid') {
            $pid = <FPID>;
            close FPID;
        }
        kill (9, $pid) if $pid;
        unlink $info{'pgdata'}.'/postmaster.pid'; 
    }
}


但很奇怪的是,在 start 时却可以准确定位。

将 /etc/postgresql/8.1/main/pg_ident.conf|postgresql.conf 复制到 postgres 用户主目录下的 8.1/main/。必要时,需要删除 postmaster.pid。而后,通过
pg_ctl start -D /etc/postgresql/8.1/main/pgdata/ -l log
启动数据库服务器,通过
pg_ctl stop -D /etc/postgresql/8.1/main/pgdata/
终止。

Someday: 配置pg的环境变量,解决 pg_cluster 的问题。
________
(完)

[+/-]显示正文