qishun's profile躺在角落的孤独者PhotosBlogLists Tools Help

躺在角落的孤独者

我不敢停留在任何街口,不安全的因素充斥着这里的每个角落

qishun

我仰望星空,它是那样寥廓而深邃;那无穷的真理,让我苦苦地求索、追求!
January 25

Java中的XML_RPC的应用

转载:

http://www.ibm.com/developerworks/cn/java/j-xmlrpc/index.html

2004 年 2 月 06 日

应用程序间通信对程序员来说可能是个不好对付的问题。而许多可用的选择(如 JNI)又难于掌握。XML-RPC 提供了一种非常简单的解决方案。该方法简洁、易于实现,且得到了大多数流行编程语言(例如 Java 语言和 C++)的开放源代码库的良好支持。例如,如果您有一个 Java 应用程序需要与另一个用 C++ 编写的应用程序进行对话,那么 XML-RPC 正好可能是最简单的方法。在本文中,软件开发人员兼培训师 Roy Miller 谈论了 XML-RPC 是什么以及如何有效地使用它。

我无数次从开发同伴那里听说最新的热门技术就是对软件开发世界中使人烦恼的问题的解决方法。XML 首次出现时,许多人就是这样说的。此时,我没有感到兴奋,而且从那时起,我的态度也没有太多改变。我一直认为 XML 是种极佳的方法,用以定义结构化数据,而无需笨拙地将之转化为关系型结构。但是,XML 不是一种编程语言 ―― XLST 在语法上较为复杂,且至少对于我来说有点奇怪。因此,我一直在等待出现需要进行结构化数据交换的问题,而这才是创造 XML 的真正目的。在最近的项目中就出现了这个特殊问题,XML(用作 XML-RPC)正是合适于该工作的工具。

编程挑战

我们的客户制造了一种硬件设备。我们加入该项目之前,用户配置每台设备的惟一方法就是用命令行接口。要不是每个客户在每个网络上可能有 20 台或更多(也许甚至成百或上千)这样的硬件设备,该方法也并非必定糟糕。迫使客户用命令行接口一个接一个地配置每台设备很可能会削减销售。当客户在订货到达后不得不对多台设备进行初始设置和配置的时候,该问题将会更为尖锐。每个设备的配置包含在一个 XML 文件中,而设备在启动时将读取该文件。

客户聘请我们创建一个配置应用程序,用以在一个或更多位置集中的管理机器上运行。该应用程序需要简化所有设备的初始设置,将之重新配置为要进行固件升级、纠正错误等等,以及监控现有设备。其中有点困难的问题就是设备上的软件是用 C 编写的,而我们的台式机应用程序却需要用 Java 编程语言进行编写。

我们首先考虑的是 JNI,但是觉得应该存在更简单的东西。那就是称作 XML-RPC 的有用的小东西。



回页首

XML-RPC 入门

XML-RPC 网站(请参阅 参考资料)是这样描述的:

它是允许运行在不同操作系统、不同环境中的软件进行基于 Internet 过程调用的规范和一组实现。这种远程过程调用使用 HTTP 作为传输协议,XML 作为编码格式。XML-RPC 的定义尽可能简单,但能够传送、处理和返回复杂的数据结构。

在阅读该描述时,我们就知道我们有了答案。每台设备的配置保存在一个文件之中(其内容也是 XML,但这对于该论述无关紧要)。这意味着我们已经拥有告诉每台设备如何配置自身的语义。如果我们给设备发送它所期待的配置文件,那就会很好了。但是将如何发送呢?我们可以仅仅发送字节,但那样会危及安全性,况且用字节操作来完成这一切也不是谁所真正需要的。我们意识到可以用定义良好的 XML-RPC 消息来发送字符串有效负荷,而 XML-RPC 消息将允许我们调用每台设备上严格限制的软件公共接口中的 C 函数。



回页首

XML-RPC 重点

概括地说,您可以将 XML-RPC 认为是简化的 SOAP。它可能是您曾需要的惟一的应用程序间的通信。XML-RPC 网站上有个极佳的“入门”文档,该网站还提供了一些发展历史以及各种语言的实例。但是,您可能只需要阅读其规范。在不到六页的内容上包括了一个简单的模型。本节中,我们将介绍一些重点,以便为如何在项目中使用 XML-RPC 做好准备。

一个 XML-RPC 消息就是一个请求体为 XML 的 HTTP-POST 请求。您需要一个 XML-RPC 客户程序来创建消息,以及一个 XML-RPC 服务程序来接收消息。服务程序一旦完成了请求,就同样以 XML 格式送回一个 XML-RPC 响应消息。请求可以包含参数(整数、字符串、日期以及其他类型,如果需要还可以包括数组和复杂记录)。每个请求的格式都极其简单,如清单 1 所示:

清单 1. XML-RPC 请求示例

POST /RPC2 HTTP/1.0
User-Agent: Frontier/5.1.2 (WinNT)
Host: betty.userland.com
Content-Type: text/xml
Content-length: 181
<?xml version="1.0"?>
<methodCall>
   <methodName>examples.getStateName</methodName>
   <params>
      <param>
         <value><i4>41</i4></value>
      </param>
   </params>
</methodCall>

您需要一个指定“处理程序”名(清单 1 中为 examples )的字符串 methodName 和一个调用该处理程序的方法(清单 1 中为 getStateName )。无论如何,服务程序可以解释这个名字字串。我们所使用的 Java 服务程序(我们将稍候讨论)将用处理程序名 examples 找到一个对象,并且调用该对象之上的 getStateName 方法。

其响应也很简单,如清单 2 所示:

清单 2. XML-RPC 响应示例

HTTP/1.1 200 OK
Connection: close
Content-Length: 158
Content-Type: text/xml
Date: Fri, 17 Jul 1998 19:55:08 GMT
Server: UserLand Frontier/5.1.2-WinNT
<?xml version="1.0"?>
<methodResponse>
   <params>
      <param>
         <value><string>South Dakota</string></value>
      </param>
   </params>
</methodResponse>

当您发出一个 XML-RPC 调用时,您将获得一个 XML 响应,其中包含一个 <params> 元素,该元素中又依次包含一个 <param> 元素,其中再包含一个 <value> 元素,该元素中则包含一个需要进行处理的返回值。大多数情况下,这就是您所希望获得的响应。但是现实从来都不是那么简单的。如果发生某些错误,服务程序则会返回“故障”响应,如清单 3(反映在 RPC 中发送太多参数的故障)所示:

清单 3. XML-RPC 故障响应示例

HTTP/1.1 200 OK
Connection: close
Content-Length: 426
Content-Type: text/xml
Date: Fri, 17 Jul 1998 19:55:02 GMT
Server: UserLand Frontier/5.1.2-WinNT
<?xml version="1.0"?>
<methodResponse>
   <fault>
      <value>
         <struct>
            <member>
               <name>faultCode</name>
               <value><int>4</int></value>
               </member>
            <member>
               <name>faultString</name>
               <value><string>Too many parameters.</string>
               </value>
               </member>
         </struct>
      </value>
   </fault>
</methodResponse>

<fault> 元素的 <value> 元素中包含一个带有 faultCode member 成员和 <faultString> 成员的结构。这就像是 Java 类中的 toString() 。如果发生错误,并且是由编码所致, toString() 则会通过错误代码和出错消息告诉您是什么。而 XML-RPC 故障响应完成同样的工作。

这也涉及您需要用以理解 XML-RPC 相关处理的所有内容。实际上,您真的无需了解 XML 消息布局的细节。只要提供了有效输入,您所选择的 XML-RPC 实现库将为您完成所有工作。因此,您所缺乏的用以读取规范的惟一工具就是客户程序和服务程序实现。在这个应用程序中,我们需要 Java 实现的客户程序和 C 实现的服务程序。



回页首

使之工作

XML-RPC 网站包含多种语言编写的规范的客户程序和服务程序实现的链接,这些语言包括 Java 编程语言、Ruby、Python、C/C++ 和 Perl。

有一个由 Apache 小组编写的实现,还有一些是由单个开发人员编写的。在查看其中一些的代码之后,我们选择了 Greger Ohlson 所编写的 Marquee XML-RPC 客户程序实现。Ohlson 也编写了一个服务程序实现,但是为我们将配置的硬件设备编写代码的同事选择了 Apache XML-RPC 服务程序。只要输入和输出是可预测的,这也的确不要紧。

所有的开发是在 Eclipse 中进行的,因此我们仅仅下载 Marquee 库,为它创建一个项目并加载到我们的工作区中。还将它置于应用程序的类路径中使我们可以访问 Marquee 接口。此时,我们就只需要使用它。为了简化该方法,我们为设备创建了一个包装器,使应用程序剩下部分只需处理一个域对象而无需担忧 XML-RPC 的细支末节,并且创建了一个 XML-RPC 对象来包装请求和解码响应。

当应用程序由于某种理由(例如检查其状态)需要与设备进行交互时,它仅需调用该设备的包装器上的方法,从而与 XML-RPC 对象交互以完成 XML-RPC 的神奇功能。清单 4 显示了说明包装器模样的简化实例。

清单 4. 设备包装类

public class Device {
    protected DeviceConfiguration configuration;
    protected Status status = Status.UNREACHABLE;
    protected Device(DeviceConfiguration configuration) {
        this.configuration = configuration;
    }
    public Status getStatus() {
        return obtainRpcClient().getStatus();
    }
    public void setStatus(Status status) {
        if (this.status != status) {
            this.status = status;
        }
    }
    public void reboot() {
        Status status;
        try {
            status = obtainRpcClient().reboot();
        } finally {
            makeUnreachable();
        }
        setStatus(status);
    }
    public DeviceConfiguration getConfiguration() {
        this.configuration =
            DeviceConfigurationBuilder.toConfig(
                obtainRpcClient().getDeviceConfiguration());
        makeOk();
        return this.configuration;
    }
    public Status putConfiguration() {
        Status status =
            obtainRpcClient().replaceDeviceConfiguration(
                DeviceConfigurationBuilder.toData(this.configuration));
        setStatus(status);
        return status;
    }
    protected RpcClient obtainRpcClient() {
        return new RpcClient(
            this.configuration.getIpAddress(),
            80,
            this.configuration.getUserPassword(),
            100);
    }
    public void makeOk() {
        setStatus(Status.OK);
    }
    public void makeUnreachable() {
        setStatus(Status.UNREACHABLE);
    }
}

Device 类使用了三个辅助类: DeviceConfigurationStatusDeviceConfigurationBuilder

这三个类的详细说明超出了本文的范围,可是我将介绍由 DeviceConfiguration 类来保存从每个硬件设备的 XML 配置中提取的值的实例。这正是追踪那些值的便利方法。如您所能看到的, Device 类用 DeviceConfigurationBuilder 将原始配置数据和 DeviceConfiguration 实例进行相互转换。

该示例包括用以向设备询问其状态,告诉设备进行重新启动,以及 get 和 put 配置数据的方法。但是 Device 实例不处理与它所仿效的设备进行的对话,而是授权给 XML-RPC 包装类,而这正是 XML-RPC 真正令人激动的地方。清单 5 显示了说明 XML-RPC 包装类样子的简化示例。我们将其称为 RpcClient 以区分 Marquee XmlRpcClient

清单 5. XML-RPC 客户程序包装类

import java.util.Hashtable;
import marquee.xmlrpc.XmlRpcClient;
import marquee.xmlrpc.XmlRpcException;
public class RpcClient {
    protected static final Object[] EMPTY_ARRAY = new Object[0];
    protected XmlRpcClient xmlRpcClient;
    protected String ipAddress;
    protected String password;
    protected int port;
    protected int timeout;
    public RpcClient(
        String ipAddress,
        int port,
        String password,
        int timeout) {
        super();
        this.ipAddress = ipAddress;
        this.port = port;
        this.password = password;
        this.timeout = timeout;
        xmlRpcClient = new XmlRpcClient(ipAddress, port, "/RPC2");
    }
    protected Object invoke(final String rpcMethodName) {
        return invoke(rpcMethodName, EMPTY_ARRAY);
    }
    protected Object invoke(final String rpcMethodName, Object[] parameters) {
        try {
            Object result = xmlRpcClient.invoke(rpcMethodName, parameters);
            if (result instanceof Hashtable) {
                Hashtable fault = (Hashtable) result;
                int faultCode = ((Integer) fault.get("faultCode")).intValue();
                throw new RuntimeException(
                    "Unable to connect to device via XML-RPC. \nFault Code: "
                        + faultCode
                        + "\nFault Message: "
                        + (String) fault.get("faultString"));
            }
            if (result instanceof Integer) {
                return Status.getStatus((Integer) result);
            }
            return result.toString();
        } catch (XmlRpcException e) {
            throw new RuntimeException(e);
        }
    }
    public Status getStatus() {
        return (Status) invoke("Device.getStatus");
    }
    public String getDeviceConfiguration() {
        return (String) invoke("Device.getConfiguration");
    }
    public Status replaceDeviceConfiguration(String configurationData) {
        return (Status) invoke(
            "Device.replaceConfiguration",
            new Object[] { configurationData });
    }
    public Status reboot() {
        return (Status) invoke("Device.reboot");
    }
}

请注意, RpcClient 的公共接口包括构造函数共有五个方法。每个方法调用 invoke() 的一种版本。一种版本不带参数(例如由 reboot() 调用),而另一种则以 Object 数组为参数(例如由 replaceDeviceConfiguration() 调用)。

不带参数的版本用一个空的 Object 数组调用另一版本。而获取参数的 invoke() 方法才是我们与 Marquee 库进行交互的惟一场所。该方法调用包含于 RpcClientXmlRpcClient 实例上的 invoke() 。Marquee 库将完成其神奇功能:在 XML 中包装方法字符串(请记住根据该规范,它看上去像是 handlerName.methodName )和参数 Object 数组,然后将之发送给服务程序。它所返回的结果可能是一个 哈希表 (Marquee 为 XML-RPC 响应的“故障”版本进行的选择)、一个 整数 包装器(用于数字返回值)或一个 字符串 (XML-RPC 的默认返回类型)。

本例中,如果得到一个错误返回,我们就发出带有从故障 哈希表 中提取的细节信息的 RuntimeException 。如果得到一个数字状态值(例如调用 reboot() 时将得到),我们就实例化 Status 对象来保存该值,并很好地进行文本转换以在用户界面(UI)中显示。如果得到一个 字符串 返回(调用 getDeviceConfiguration() 时将得到),我们就将之返回。

重启 aDevice

既然您已经了解了所有事项,就让我们将之连接起来。比如说我们的应用程序告诉一个特殊的 设备 (我们将称之为 aDevice )来进行自我重启。用户界面(UI)区域中的某类将调用 aDevice 上的 reboot() 。以下是接下来所发生的事情:

  1. aDevice 创建一个 RpcClient 实例 ―― anRpcClient ,以连接物理设备上的 XML-RPC 服务程序(使用 aDeviceDeviceConfiguration 实例的 IP 地址和用户口令)。
  2. aDevice 调用 anRpcClient 上的 reboot()
  3. anRpcClient 用空的参数列表调用其 Marquee XmlRpcClient 实例 ―― xmlRpcClient 上的 invoke()
  4. xmlRpcClient 在从服务程序得到一个 XML-RPC 故障消息时返回一个 哈希表 ,在得到一个数字返回值时返回一个 整数 ,在所有其他情况下返回一个 字符串
  5. anRpcClient 返回从 xmlRpcClient 所得到的内容,本例中仅为显示一切运行良好的返回代码(即服务程序本身未返回任何数据)。
  6. 如果出现错误, aDevice 就将其 Status 实例设置为 UNREACHABLE 以使之在用户界面(UI)中报告。
  7. 如果一切运行良好, aDevice 就只是将其 Status 实例更新为 anRpcClient 发送给它的内容。

向 aDevice 询问其当前状态

aDevice 询问某些数据不会困难很多。最基本的实例就是应用程序向 aDevice 询问其当前状态。本例中,用户界面(UI)区域中的某类将调用 aDevice 上的 getStatus() 。以下是接下来所发生的事情:

  1. aDevice 创建一个 RpcClient 实例 ―― anRpcClient ,以连接物理设备上的 XML-RPC 服务程序。
  2. aDevice 调用 anRpcClient 上的 getStatus()
  3. anRpcClient 用空的参数列表调用其 Marquee XmlRpcClient 实例 ―― xmlRpcClient 上的 invoke()
  4. xmlRpcClient 在从服务程序得到一个 XML-RPC 故障消息时返回一个 哈希表 ,在得到一个数字返回值时返回一个 整数 ,在所有其他情况下返回一个 字符串
  5. anRpcClient 返回从 xmlRpcClient 获得的状态。
  6. 如果出现错误, aDevice 就将其 Status 实例设置为 UNREACHABLE 以使之在用户界面(UI)中报告。
  7. 如果一切运行良好, aDevice 就只是将其 Status 实例更新为 anRpcClient 发送给它的内容。

aDevice 询问其当前配置数据的实例实质上也相同,惟一区别就是我们必须获取由 XML-RPC 调用返回的原始配置数据(为一个 字符串 ),并将之适配到 DeviceConfiguration 实例中。当我们将新的配置数据发送给 aDevice 时,我们通过从 DeviceConfiguration 实例中提取配置数据并从中构建一个字符串有效负荷完成了与之相反的工作。

请注意在该示例代码中,我们无需进行任何 XML 操纵。完全不是的。因为 Marquee 库为我们完成了这一切。现在,XML-RPC 规范相当简单,因此,您大概可以滚动自己的客户程序,却无需做什么 ―― Marquee 库非常好,并且还拥有本文中未曾探索的功能。文档是直观、完整的。在我看来,永远不必解析 XML 是让人高兴的。



回页首

服务端

直到此时,我都没有提及太多该等式的服务端的相关内容。那是因为是由小组中的其他人为该应用程序创建的 XML-RPC 服务端(使用 Apache 的 C 实现)。那是很棒的,但是如果也必须用 Java 语言开发 XML-RPC 服务该怎么办呢?实际上也很容易。在该项目中,我们可以使用同样用 Java 语言编写的 Marquee XML-RPC 服务程序实现(请参阅 Other uses for XML-RPC以了解实际该如何做,但那是出于另一目的)。

清单 6 显示了一个简单的处理先前讨论的 XML-RPC 客户端请求的 XML-RPC 服务程序。它仅仅用于示例,模拟了一台真实的物理设备。让我们剖析该代码以了解 XML-RPC 服务程序的详情。

清单 6. 简单的 XML-RPC 服务程序

import java.io.IOException;
import marquee.xmlrpc.XmlRpcServer;
import marquee.xmlrpc.handlers.ReflectiveInvocationHandler;
public class DeviceServer {
    public static final String USERNAME = "username";
    public static final String PASSWORD = "password";
    protected boolean isShuttingDown;
    protected String configuration = "initial configuration";
    protected String host;
    protected String password = PASSWORD;
    protected XmlRpcServer rpcServer;
    protected Thread rpcThread;
    protected int port;
    protected Status status = Status.OK;
    public DeviceServer(String theHost, int thePort) {
        host = theHost;
        port = thePort;
        createRpcServer();
        startRpcServer();
    }
    public void shutDown() {
        isShuttingDown = true;
        if (rpcServer != null)
            rpcServer.shutDown();
    }
    public Object getStatus() {
        return new Integer(status.getCode());
    }
    public Object getConfiguration() {
        return "valid configuration data";
    }
    public Object replaceConfiguration(String xml) {
        configuration = xml;
        return new Integer(status.getCode());
    }
    public Object reboot() {
        return new Integer(status.getCode());
    }
    public void setStatus(Status newStatus) {
        status = newStatus;
    }
    protected void startRpcServer() {
        rpcThread = new Thread(new Runnable() {
            public void run() {
                try {
                    rpcServer.runAsService(port);
                } catch (IOException ioe) {
                    if (!isShuttingDown)
                        ioe.printStackTrace();
                }
            }
        });
        rpcThread.setName("DeviceServer[" + this.host + "] on " + this.port);
        rpcThread.start();
    }
    protected void createRpcServer() {
        rpcServer = new XmlRpcServer();
        rpcServer.registerInvocationHandler(
            "Device",
            new ReflectiveInvocationHandler(this));
    }
}

服务程序的构造函数给出了处理过程的概要内容。我们保存传入的主机和端口信息,然后调用 createRpcServer() 以实例化 Marquee XmlRpcServer 并用其注册 ReflectiveInvocationHandler (马上将详细介绍)。然后,我们调用 startRpcServer() 来给该服务起一个帮助性的名字并在其自身的线程中运行。在启动该线程之前, Marquee XmlRpcServer 需要用端口号 int 调用 runAsService() 。一旦启动,该服务就侦听从该端口进入的来自该端口上所连接的任何客户端的请求。

大多数请求是直接进行的,但是这个 ReflectiveInvocationHandler 又是什么呢?XML-RPC 规范没有讲述如何实现客户程序或服务程序。但它 确实 规定了 XML-RPC 服务程序必须处理用 <methodcall> 元素进行的新来请求。该元素中是表单 handlername.methodname 的调用字符串。当您调用在 Marquee 客户端调用 invoke() 时,它就制造合适的 XML 将方法字符串和若干参数以格式良好的 XML-RPC 消息传递给服务程序。在服务端,Marquee XML-RPC 服务程序:

  • 将方法字符串解析为处理程序名和在该处理程序上调用的方法
  • 用指示名称找到已注册的处理程序
  • 通过传递请求中所发送的参数来调用该处理程序上的方法
  • 将结果包装在 XML-RPC 响应中并送回给客户端

要使用 Marquee XML-RPC 服务,运行的服务实例就必须知道如何译解方法字符串。它必须知道哪个对象与 handlername 键值进行通信。虽然这样说,但是显然除非您告诉服务程序名字“Device”与给定的对象进行通信,否则服务程序将不知道如何译解 handlername 。那可以是任何实例。如清单 7 所示,我们仅仅让服务程序处理所有新来的方法请求。此代码中,我们在 createRpcServer() 里完成该工作。

清单 7. 简单的 XML-RPC 服务程序

rpcServer.registerInvocationHandler("Device", new 
ReflectiveInvocationHandler(this));

现在,无论何时服务端获得一个请求,而其方法字符串如 Device.someMethod 一般,它都知道在自身中查找 someMethod() 以处理请求。在示例服务程序中,我们只需完成“在要求的对象上找到要求的方法”这个基本行为,因此我们使用了 Marquee ReflectiveInvocationHandler 。而 Marquee 中基本的处理程序就挺好了,所以我们无需编写自己的。该处理程序仅仅在其被实例化的对象上查找要求的方法。如果查看代码,您将发现所有的 Java 反射(Reflection)逻辑 Marquee 为您避免了编写代码的麻烦。

处理程序概念对于 XML-RPC 规范不是必须的,但是 Marquee 是围绕其构建的并且运行良好。如果基本的 ReflectiveInvocationHandler 不能完成您的工作,您可以用子类 XmlRpcInvocationHandler 来实现自己的处理程序。



January 23

将CodeSmith的输出文件在VS2005中打开,发现中文变成了乱码,看了一下CodeSmith的帮助,将ReponseEncoding属性改成"UTF-8",再重新输出,用VS打开,还是乱码。没办法,在VS的选项里面看看吧,意外发现“文本编辑器”->“常规”中有一个自动检测不带签名的UTF-8编码,选上,中文乱码终于露出了原形。找了一些资料看看,总算知道了原因:Windows为了识别Unicode、Unicode big endian和UTF-8,在Unicode、Unicode big endian和

将CodeSmith的输出文件在VS2005中打开,发现中文变成了乱码,看了一下CodeSmith的帮助,将ReponseEncoding属性改成"UTF-8",再重新输出,用VS打开,还是乱码。没办法,在VS的选项里面看看吧,意外发现“文本编辑器”->“常规”中有一个自动检测不带签名的UTF-8编码,选上,中文乱码终于露出了原形。找了一些资料看看,总算知道了原因:Windows为了识别Unicode、Unicode big endian和UTF-8,在Unicode、Unicode big endian和UTF-8编码的txt文件的开头会多出几个字节,分别是FF、FE(Unicode),FE、FF(Unicode big endian),EF、BB、BF(UTF-8)。而CodeSmith的输出UTF-8是标准的未加标识的。这样VS就不能识别出输出文件的编码了。

为此我们可以更改其CodeSmith的脚本的方法来处理:

首先增加2行

public System.Text.Encoding enc = System.Text.Encoding.GetEncoding("Unicode");
public System.IO.StreamWriter txt;

找到下面这句话

this.ClassTemplate.RenderToFile(classFileName, true);

上面这段话更改成下面的代码 
txt = new System.IO.StreamWriter(classFileName,false,enc);
txt.Write(ClassTemplate.RenderToString());
txt.Close();

再找到下面这句话

this.MappingTemplate.RenderToFile(mappingFileName, true);

上面这段话更改成下面的代码

txt = new System.IO.StreamWriter(mappingFileName,false,enc);
txt.Write(MappingTemplate.RenderToString());
txt.Close();
January 01

linux Sys文件系统

转载:http://blog.chinaunix.net/u3/99507/showart_2059410.html

sysfs 是 Linux 内核中设计较新的一种虚拟的基于内存的文件系统,它的作用与 proc 有些类似,但除了与 proc 相同的具有查看和设定内核参数功能之外,还有为 Linux 统一设备模型作为管理之用。相比于 proc 文件系统,使用 sysfs 导出内核数据的方式更为统一,并且组织的方式更好,它的设计从 proc 中吸取了很多教训。本文就 sysfs 的挂载点 /sys 目录结构、其与 Linux 统一设备模型的关系、常见属性文件的用法等方面对 sysfs 作入门介绍,并且就内核编程方面,以具体的例子来展示如何添加 sysfs 支持。

sysfs 的历史,其与 proc 的关系?
sysfs 本身并不是一项很新的技术,但笔者发现,虽然 sysfs 从2003年诞生至今已有5年,但人们对 sysfs 依然缺乏了解;一个很重要的原因可能是缺乏文档, Linux 内核方面最重要的理论书籍“Linux 设备驱动第3版”和“理解 Linux 内核第2版”都诞生于2003年前后,并且从那以后尚未有再版过,其它一些重要文章则多对 sysfs 与 proc 相提并论且举例常常只有 proc,这导致了 sysfs 的很多重要概念至今仍鲜为人知,因此有必要对 sysfs 作更多介绍,这是写作本文的初衷。

    sysfs 与 /sys

    sysfs 文件系统总是被挂载在 /sys 挂载点上。虽然在较早期的2.6内核系统上并没有规定 sysfs 的标准挂载位置,可以把 sysfs 挂载在任何位置,但较近的2.6内核修正了这一规则,要求 sysfs 总是挂载在 /sys 目录上;针对以前的 sysfs 挂载位置不固定或没有标准被挂载,有些程序从 /proc/mounts 中解析出 sysfs 是否被挂载以及具体的挂载点,这个步骤现在已经不需要了。请参考附录给出的 sysfs-rules.txt 文件链接。

   sysfs 与 proc

    sysfs 与 proc 相比有很多优点,最重要的莫过于设计上的清晰。一个 proc 虚拟文件可能有内部格式,如 /proc/scsi/scsi ,它是可读可写的,(其文件权限被错误地标记为了 0444 !,这是内核的一个BUG),并且读写格式不一样,代表不同的操作,应用程序中读到了这个文件的内容一般还需要进行字符串解析,而在写入时需要先用字符串格式化按指定的格式写入字符串进行操作;相比而言, sysfs 的设计原则是一个属性文件只做一件事情, sysfs 属性文件一般只有一个值,直接读取或写入。整个 /proc/scsi 目录在2.6内核中已被标记为过时(LEGACY),它的功能已经被相应的 /sys 属性文件所完全取代。新设计的内核机制应该尽量使用 sysfs 机制,而将 proc 保留给纯净的“进程文件系统”。

    初识 /sys

    清单 1. 与 /sys 文件系统的一次交互(视内核版本号和外接设备的不同,在您的系统上执行这些命令的结果可能与此有所不同)

ls -F /sys
block/  bus/  class/  dev/  devices/  firmware/  fs/  kernel/  module/  power/
$ ls -F /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/
broken_parity_status  enable         modalias  resource0     rom               uevent
class                 irq            msi_bus   resource0_wc  subsystem@        vendor
config                local_cpulist  power/    resource1     subsystem_device
device                local_cpus     resource  resource2     subsystem_vendor

    这是在 Fedora 10 的 2.6.27.5-117.fc10.i686 的内核上,可以看到在 /sys 目录下有 block, bus, class, dev, devices, firmware, fs, kernel, module, power 这些子目录,本文将分别介绍这些目录存在的含义。

    第二个 ls 命令展示了在一个 pci 设备目录下的文件, "ls" 命令的 "-F" 命令为所列出的每个文件使用后缀来显示文件的类型,后缀 "/" 表示列出的是目录,后缀 "@" 表示列出的是符号链接文件。可以看到第二个目录下包含有普通文件 (regular file) 和符号链接文件 (symbolic link file) ,本文也将以这个具体的设备为例说明其中每一个普通文件的用途。 /sys 文件系统下的目录结构

    /sys 下的目录结构是经过精心设计的:在 /sys/devices 下是所有设备的真实对象,包括如视频卡和以太网卡等真实的设备,也包括 ACPI 等不那么显而易见的真实设备、还有 tty, bonding 等纯粹虚拟的设备;在其它目录如 class, bus 等中则在分类的目录中含有大量对 devices 中真实对象引用的符号链接文件; 清单1 中在 /sys 根目录下顶层目录的意义如下:

    表 1. /sys 下的目录结构

/sys 下的子目录
所包含的内容

/sys/devices
这是内核对系统中所有设备的分层次表达模型,也是 /sys 文件系统管理设备的最重要的目录结构,下文会对它的内部结构作进一步分析;

/sys/dev
这个目录下维护一个按字符设备和块设备的主次号码(major:minor)链接到真实的设备(/sys /devices下)的符号链接文件,它是在内核 2.6.26 首次引入;

/sys/bus
这是内核设备按总线类型分层放置的目录结构, devices 中的所有设备都是连接于某种总线之下,在这里的每一种具体总线之下可以找到每一个具体设备的符号链接,它也是构成 Linux 统一设备模型的一部分;

/sys/class
这是按照设备功能分类的设备模型,如系统所有输入设备都会出现在 /sys/class/input 之下,而不论它们是以何种总线连接到系统。它也是构成 Linux 统一设备模型的一部分;

/sys/block
这里是系统中当前所有的块设备所在,按照功能来说放置在 /sys/class 之下会更合适,但只是由于历史遗留因素而一直存在于 /sys/block, 但从 2.6.22 开始就已标记为过时,只有在打开了 CONFIG_SYSFS_DEPRECATED 配置下编译才会有这个目录的存在,并且在 2.6.26 内核中已正式移到 /sys/class/block, 旧的接口 /sys/block 为了向后兼容保留存在,但其中的内容已经变为指向它们在 /sys/devices/ 中真实设备的符号链接文件;

/sys/firmware
这里是系统加载固件机制的对用户空间的接口,关于固件有专用于固件加载的一套API,在附录 LDD3 一书中有关于内核支持固件加载机制的更详细的介绍;

/sys/fs
这里按照设计是用于描述系统中所有文件系统,包括文件系统本身和按文件系统分类存放的已挂载点,但目前只有 fuse,gfs2 等少数文件系统支持 sysfs 接口,一些传统的虚拟文件系统(VFS)层次控制参数仍然在 sysctl (/proc/sys/fs) 接口中中;

/sys/kernel
这里是内核所有可调整参数的位置,目前只有 uevent_helper, kexec_loaded, mm, 和新式的 slab 分配器等几项较新的设计在使用它,其它内核可调整参数仍然位于 sysctl (/proc/sys/kernel) 接口中 ;

/sys/module
这里有系统中所有模块的信息,不论这些模块是以内联(inlined)方式编译到内核映像文件(vmlinuz)中还是编译为外部模块(ko文件),都可能会出现在 /sys/module 中:

  • 编译为外部模块(ko文件)在加载后会出现对应的 /sys/module/<module_name>/, 并且在这个目录下会出现一些属性文件和属性目录来表示此外部模块的一些信息,如版本号、加载状态、所提供的驱动程序等;

编译为内联方式的模块则只在当它有非0属性的模块参数时会出现对应的 /sys/module/<module_name>, 这些模块的可用参数会出现在 /sys/modules/<modname>/parameters/<param_name> 中,

如 /sys/module/printk/parameters/time 这个可读写参数控制着内联模块 printk 在打印内核消息时是否加上时间前缀;

所有内联模块的参数也可以由 "<module_name>.<param_name>=<value>" 的形式写在内核启动参数上,如启动内核时加上参数 "printk.time=1" 与 向 "/sys/module/printk/parameters/time" 写入1的效果相同;

没有非0属性参数的内联模块不会出现于此。

/sys/power
这里是系统中电源选项,这个目录下有几个属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令让机器关机、重启等。

/sys/slab (对应 2.6.23 内核,在 2.6.24 以后移至 /sys/kernel/slab)
从2.6.23 开始可以选择 SLAB 内存分配器的实现,并且新的 SLUB(Unqueued Slab Allocator)被设置为缺省值;如果编译了此选项,在 /sys 下就会出现 /sys/slab ,里面有每一个 kmem_cache 结构体的可调整参数。对应于旧的 SLAB 内存分配器下的 /proc/slabinfo 动态调整接口,新式的 /sys/kernel/slab/<slab_name> 接口中的各项信息和可调整项显得更为清晰。

    接下来对 /sys/devices/ 下的目录结构作进一步探讨:

    清单 2. 查看 /sys/devices/ 的目录结构

$ ls -F /sys/devices/
isa/  LNXSYSTM:00/  pci0000:00/  platform/  pnp0/  pnp1/  
system/ virtual/

    可以看到,在 /sys/devices/ 目录下是按照设备的基本总线类型分类的目录,再进入进去查看其中的 PCI 类型的设备:

    清单 3. 查看 /sys/devices/pci0000:00/ 的目录结构

$ ls -F /sys/devices/
isa/  LNXSYSTM:00/  pci0000:00/  platform/  
pnp0/ pnp1/ system/ virtual/

    在 /sys/devices/pci0000:00/ 目录下是按照 PCI 总线接入的设备号分类存放的目录,再查看其中一个,

    清单 4. 查看 /sys/devices/pci0000:00/ 的目录结构

$ ls -F /sys/devices/pci0000:00/0000:00:01.0/
0000:01:00.0/ device  local_cpus  power/   subsystem_vendor
broken_parity_status  enable   modalias resource    uevent
class     irq            msi_bus     subsystem@        vendor
config  local_cpulist  pci_bus/    subsystem_device

    可以看到,其中有一个目录 0000:01:00.0/, 其它都是属性文件和属性组,而如果对 0000:01:00.0/ 子目录中进行再列表查看则会得到 清单1 的目录结构。

    继续以上过程可以了解整个目录树的结构,这里把它整理成 图 1. sysfs 目录层次图

    图 1. sysfs 目录层次图

sysfs目录层次图

    其中涉及到 ksets, kobjects, attrs 等很多术语,这就不得不提到 Linux 统一设备模型。

December 08

彻底清除oracle 的数据表

Oracle 10g 中出现表名:BIN$2cMp4FjwQ2Cw3Lj+BxLYTw==$0 最近发现Oracle中出现了这些奇怪的表名,上网查找后发现是oracle10g的回收站功能,并没有彻底的删除表,而是把表放入回收站,最后就出现了这样一堆奇怪的表名。。。。

  清除的方法如下:

  purge table origenal_tableName;

  purge index origenal_indexName;

  查询垃圾信息,可以用如下SQL语句:

  SELECT t.object_name,t.type ,t.original_name FROM user_recyclebin t;

  现在发现,原来还有这个命令:

  PURGE recyclebin;

  根本用不了上面那么复杂。呵呵!

  另:删除Table不进入Recycle的方法:

  drop table tableName purge;

  不过,我想一般的人都不愿意用这个。

December 07

Oracle 10.2下的“System.Exception: System.Data.OracleClient requires Oracle client software version 8.1.7 or greater

1。没有安装客户端runtime

 

2.安装客户段runtime以后,提示

ORA-12154: TNS: could not resolve the connect identifier specified这个错误,找了很多办法,都不怎么好用。最后只好这么程序了

//OracleDataReader infocenter;
          // string ConnStr = "Data Source=orcl_shunzi;User Id=system;Password=orcl123;";
          string ConnStr = "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=10.53.11.37)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=orcl)));User Id=system;Password=orcl123;";
          //string SelectStr = "select * from    tab";
          OracleConnection objconn = new OracleConnection(ConnStr);
          try
          {
              objconn.Open();
          }
          catch(Exception ex)
          {
              MessageBox.Show(ex.Message);
              System.Console.Out.WriteLine(ex.Message);
              return;
          }

主要是连接字串的改变。

终于搞定了c# 连接oracle。

在Nhibernate里也这么修改了!

 
Photo 1 of 4