海鸥航迹

学习之笔记,好文之收集。

2007年5月2日 #

ICE专题:实战分布式的Hello Word 【原创】

有关ICE的基础知识,请参照前面的ICE专题文章。由于这些文章均来自于网络,故未发之于首页。下面仅仅给出相关link:

ICE专题:利用ICE编写程序的几个注意点

ICE专题:ICE架构

ICE专题:ICE起步

ICE专题:ICE的5个服务

ICE专题:使用C#编写ICE分布式应用程序

ICE专题:学习ICE 3.0

ICE专题:在客户端中如何定位服务器(即如何寻找代理)

ICE专题:ICE在Linux下的安装

ICE专题:ICE简介

 

目标:在Windows平台上编写第一个基于ICE的Hello Word程序。

1、安装

http://www.zeroc.com/download.html#bin下载安装文件:

Windows Installer for Visual Studio 2005

Ice-3.2.0-VC80.msi  【必需】

Everything needed to use Ice on Windows XP/Server 2003/Vista in C++, Java, C#, Visual Basic and Python. This installer includes executables, debug and release DLLs, header files, import libraries, PDBs, Java classes, sample programs, and third-party dependencies.

Visual Studio 2005 is only required for C++, C#, and Visual Basic developers. Visual Studio 2005 Express compilers are also supported.

Ice-3.2.0-ThirdParty-VC80.msi

Use this installer if you want to build Ice for C++ or Ice for Java from sources. It includes header files, debug/release DLLs, and JAR files for Ice's third-party dependencies:

  • Berkeley DB 4.5.20
  • OpenSSL 0.9.8d
  • Libbzip2 1.0.3
  • Expat 2.0.0

Ice for Java users who do not want to install the full Ice distribution may also find this installer useful, as it includes the Java classes for Berkeley DB.

 

Ice for C#

IceCS-3.2.0.tar.gz
IceCS-3.2.0.zip

The complete Ice for C# source code.

Note that, to build Ice for C#, you must first compile Ice for C++ or, alternatively, download and install an appropriate binary distribution. This is necessary because the Slice-to-C# compiler is written in C++.

 

以上的3个文件,Ice-x.x.-VC80.msi是ICE在Windows平台下的运行时文件,包含了必需的库文件,头文件,和命令行工具。有关运行时的介绍,以后的专题文章将一一介绍(随项目进度逐步介绍吧:P)。第3方库,建议安装,特别是用C++开发时。当然,如果不使用C++,就不必安装了。ICE for C#的包,是个源码库,包含了ICE for C#的实现,实际上是封装了.NET Framework与ICE运行库之间的交互,即.NET程序与C++程序的交互,会看到大量的平台调用。如果起个更好的名字,也许称之为“ICE for C# Provider/Wrapper"更好。

注意安装运行时后,需要添加环境变量ICE_HOME,并将%ICE_HOME%\bin添加到Path中:

2、建立C#控制台项目

启动VS 2005,建立两个控制台项目,一个叫Server,一个叫Client。均添加对icecs.dll的引用。

由于我的ICE运行时安装于C:\Ice-3.2.0,故在C:\Ice-3.2.0\bin下找到此文件,需要注意的是,一定要选择:复制到本地。

3、定义接口方法

module Demo {
interface Printer {
void printString(string s);
};
};

 

将这段代码存为:Printer.ice。在此文件同目录下,创建批处理命令文件cmd1.bat,内容如下:

mkdir generated
slice2cs --output-dir generated Printer.ice

运行cmd1.bat,则会在generated 文件夹下生成Printer.cs。这里暂时不分析Printer.cs的结构,直接使用它了。将Printer.cs拷贝到新建的两个项目中,注意,你必须将项目中使用的namespace设置成相同的,例如,我将根namespace设置为Demo。

 

4、添加应用逻辑

在Client项目中添加Client.cs,内容如下:

using System;
using System.Collections.Generic;
using System.Text;
namespace Demo
{
public class Client
{
public static void Main(string[] args)
{
int status = 0;
Ice.Communicator ic = null;
try
{
ic = Ice.Util.initialize(ref args);
Ice.ObjectPrx obj = ic.stringToProxy(
"SimplePrinter:tcp -p 12345:udp -p 12345");
PrinterPrx printer
= PrinterPrxHelper.checkedCast(obj);
if (printer == null)
throw new ApplicationException("Invalid proxy");
printer.printString("Hello World!");
}
catch (Exception e)
{
Console.Error.WriteLine(e);
status = 1;
}
if (ic != null)
{
// Clean up
//
try
{
ic.destroy();
}
catch (Exception e)
{
Console.Error.WriteLine(e);
status = 1;
}
}
Environment.Exit(status);
}
}
}

 

在server项目中,添加Server.cs文件,内容如下:

namespace Demo
{
using System;
public class PrinterI : Demo.PrinterDisp_
{
public override void printString(string s, Ice.Current current)
{
Console.WriteLine(s);
}
}
public class Server
{
public static void Main(string[] args)
{
int status = 0;
Ice.Communicator ic = null;
try
{
ic = Ice.Util.initialize(ref args);
Ice.ObjectAdapter adapter
= ic.createObjectAdapterWithEndpoints(
"SimplePrinter", "tcp -p 12345:udp -p 12345");
Ice.Object obj = new PrinterI();
adapter.add(
obj,
Ice.Util.stringToIdentity("SimplePrinter"));
adapter.activate();
ic.waitForShutdown();
}
catch (Exception e)
{
Console.Error.WriteLine(e);
status = 1;
}
if (ic != null)
{
// Clean up
//
try
{
ic.destroy();
}
catch (Exception e)
{
Console.Error.WriteLine(e);
status = 1;
}
}
Environment.Exit(status);
}
}
}


5、测试应用

切换到Server项目的生成目录,发现生成了server.exe程序,运行:Server.exe。
如果你的OS安装了网络防火墙,注意开通相应的12345端口。最好关闭所有防火墙软件后,测试应用程序。
server.exe运行后,就一直处于监听状态。运行netstat -an,可以看到服务器端程序监听着12345端口。

在命令行下运行Client.exe,会发现Server.exe的控制台显示:Hello Word!

 

 

注意事项:

1、通信协议串请使用TCP或UDP,如以上所提供的那样,ICE手册中所给的协议是:"default -p 10000",这在我的平台下并不可用,这需要给ICE配置默认设置。ICE设置使用 【程序名.config】 文件来配置,下面给出一个例子:

#
# The client reads this property to create the reference to the
# "hello" object in the server.
#
Hello.Proxy=hello:tcp -p 10000:udp -p 10000:ssl -p 10001

#
# Warn about connection exceptions.
#
#Ice.Warn.Connections=1

#
# We want a faster ACM for this demo.
#
Ice.ACM.Client=10

#
# Network Tracing
#
# 0 = no network tracing
# 1 = trace connection establishment and closure
# 2 = like 1, but more detailed
# 3 = like 2, but also trace data transfer
#
#Ice.Trace.Network=1

#
# Protocol Tracing
#
# 0 = no protocol tracing
# 1 = trace protocol messages
#
#Ice.Trace.Protocol=1

#
# Security Tracing
#
# 0 = no security tracing
# 1 = trace messages
#
#IceSSL.Trace.Security=1

#
# SSL Configuration
#
Ice.Plugin.IceSSL=icesslcs, Version=3.2.0.0, Culture=neutral, PublicKeyToken=1f998c50fec78381:IceSSL.PluginFactory
IceSSL.DefaultDir=../../../certs
IceSSL.ImportCert.CurrentUser.Root=cacert.pem
IceSSL.CertFile=c_rsa1024.pfx
IceSSL.Password=password
Ice.ThreadPerConnection=1

2、关闭防护墙
当不关闭防火墙时,出现一些莫名其妙的问题,均是程序的TCP/IP出入受到了限制导致的。

后记:

最近项目使用了ICE来实现分布式应用,ICE的确功能强大,希望使用过的朋友或对ICE关注的朋友能一起交流交流经验,彼此提高。【开源应用技术群 25935569】 。

posted @ 2007-05-02 23:32 海天一鸥 阅读(4330) | 评论 (4)编辑

ICE专题:利用ICE编写程序的几个注意点

 

利用ICE写程序时,一定要注意的几件事

1、用Windows作为服务器是一件非常不爽的事。

2、在windows下写客户端的时候,一定要用slice2xxx.exe的版本,否则在vc中可能编译过去,会有一些奇怪的问题,可能是vc中的stl与stl_port还会有一定的本质区别。

3、涉及到汉字的时候,一定要记得将utf8码转换成gbk码,要不 一定是乱码

附转码程序:

// stdafx.h : include file for standard system include files,
//  or project specific include files that are used frequently, but
//      are changed infrequently
//

#if !defined(AFX_STDAFX_H__2843B98C_6C05_4A40_9CBC_51BD61B69760__INCLUDED_)
#define AFX_STDAFX_H__2843B98C_6C05_4A40_9CBC_51BD61B69760__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#define VC_EXTRALEAN  // Exclude rarely-used stuff from Windows headers
#undef _WINDOWS_
#include <afxwin.h>         // MFC core and standard components
#include <afxext.h>         // MFC extensions
#include <afxdtctl.h>  // MFC support for Internet Explorer 4 Common Controls
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h>   // MFC support for Windows Common Controls
#endif // _AFX_NO_AFXCMN_SUPPORT

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_STDAFX_H__2843B98C_6C05_4A40_9CBC_51BD61B69760__INCLUDED_)

#include "StdAfx.h"
void ConvertGBKToUtf8(CString& strGBK);
void ConvertUtf8ToGBK(CString& strUtf8);

#include "UTF8.h"
void ConvertGBKToUtf8(CString& strGBK) {
    int len=MultiByteToWideChar(CP_ACP, 0, (LPCTSTR)strGBK, -1, NULL,0);
    unsigned short * wszUtf8 = new unsigned short[len+1];
    memset(wszUtf8, 0, len * 2 + 2);
    MultiByteToWideChar(CP_ACP, 0, (LPCTSTR)strGBK, -1, wszUtf8, len);

    len = WideCharToMultiByte(CP_UTF8, 0, wszUtf8, -1, NULL, 0, NULL, NULL);
    char *szUtf8=new char[len + 1];
    memset(szUtf8, 0, len + 1);
    WideCharToMultiByte (CP_UTF8, 0, wszUtf8, -1, szUtf8, len, NULL,NULL);

    strGBK = szUtf8;
    delete[] szUtf8;
    delete[] wszUtf8;
}

void ConvertUtf8ToGBK(CString& strUtf8) {
    int len=MultiByteToWideChar(CP_UTF8, 0, (LPCTSTR)strUtf8, -1, NULL,0);
    unsigned short * wszGBK = new unsigned short[len+1];
    memset(wszGBK, 0, len * 2 + 2);
    MultiByteToWideChar(CP_UTF8, 0, (LPCTSTR)strUtf8, -1, wszGBK, len);

    len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
    char *szGBK=new char[len + 1];
    memset(szGBK, 0, len + 1);
    WideCharToMultiByte (CP_ACP, 0, wszGBK, -1, szGBK, len, NULL,NULL);

    strUtf8 = szGBK;
    delete[] szGBK;
    delete[] wszGBK;
}

posted @ 2007-05-02 12:55 海天一鸥 阅读(956) | 评论 (0)编辑

ICE专题:ICE架构

 

1)slice
首先就是ice的对象模型,slice(specification Language for Ice)就是用来描述ice的对象模型的,有哪些接口,有哪些操作,以及要交换的数据类型。可以把slice看成是一个合约,各种平台,各种语言映射,相互调用的公共接口,共同遵守的规则。
2)语言映射
目前ice提供了c++,java,c#,vb,python,php的语言映射。因为各种语言的对象模型是不一样的,必须把这些不同的对象模型映射到同一个模型上,进行操作。这就是一个抽象的过程。直接去定义接口,然后剩下的工作就交给ice run time来完成各种语言之间对象的相互转换。
3)客户机与服务器的结构

客户与服务器都由这样一些代码混合而成:应用代码、库代码、根据Slice 定义生成的代码
整个结构有两个方面:客户端/服务器,它们共同使用的就是ice核心(ice run time)
上图中最关键的部分就是ice核心,也就是ice run time
Ice 核心为远地通信提供了客户端和服务器端运行时支持。其中的大量代码所涉及的是网络通信、线程、字节序,以及其他许多与网络有关的问题。之所以被称为中间件,就是因为有了ice run time把开发人员从繁琐的网络编程中解放出来了,让他们直接面对应用,解决业务逻辑这一块。
客户端=ice核心+代理代码+ice api
服务器=ice核心+骨架(skeleton)代码+对象适配器+ice api
proxy和skeleton都是由slice的定义生成的。
对象适配器会把来自客户的请求映射到编程语言对象上的特定方法。
4)ice协议
ice协议主要有两部分:一个是编码规则,另一个是状态机。双向连接的特性使得ice能穿越防火墙,NAT边界,还支持压缩,在低带宽链接上很有用。
5)对象持久
Ice 拥有内建的对象持久服务,叫作Freeze。你用Slice 定义你的对象要存储的状态, Freeze编译器会生成代码,用以在数据库中存储和取回对象状态。缺省使用的berkeley db作为数据库。

posted @ 2007-05-02 12:22 海天一鸥 阅读(1029) | 评论 (0)编辑

ICE专题:ICE起步

 

ICE初步

   最早开始接触ICE是在2005,3月份,当时一个朋友向另一个正打算研究corba的朋友强烈的推荐了ICE.

   参考:http://www.zeroc.com/

   1.什么是ICE?

   ICE(internet communications engine)是适用于异种环境的面向对象中间件平台。

   那么什么是中间件呢?

   比较流行的定义是:中间件是一种独立的系统软件或服务程序,分布式应用软件借助这种软件在不同的技术之间共享资源。中间件位于客户机/服务器的操作系统之上,管理计算资源和网络通讯。
   从中间件的定义可以看出,中间件是一类软件,而非一种软件;中间件不仅仅实现互连,还要实现应用之间的互操作;中间件是基于分布式处理的软件,定义中特别强调了其网络通讯功能.

   (如此说来,twisted就是一个中间件)

   目前中间件的三大主流平台:

   1.OMG的CORBA

   2.sun的J2EE(包括ejb等技术)

   3.MS的Microsoft DNA 2000(DCOM/COM/COM+等技术)

   关于中间件的介绍:http://tech.ccidnet.com/pub/article/c322_a204949_p3.html

   2.ICE的一些概念

  • 服务器/客户端(server/client):这个的定义与一般的定义相同,主动的一方被认为是client
  • ICE对象:跟OOP中的对象类似,不同之处在于,在分布式的环境中,同一个ICE对象在不同的地址空间中都可能存在着.ICE对象也提供了一组接口(facets).ICE对象还有一个特殊的接口:主接口.
  • 代理(proxies):是ICE对象引用,代理是在客户地址空间,客户对ICE对象的操作就是通过代理来进行的.代理封装了完成:ICE对象的寻址(包括服务器的寻址),激活ICE对象,传入参数,等待执行并返回执行结果
  • servant:在服务器上的执行体,ICE对象对服务器的操作就是通过调用servant.
  • "最多一次"原则:一次对目的的访问,只会执行一次(并不排除出错重试)

posted @ 2007-05-02 12:14 海天一鸥 阅读(1161) | 评论 (1)编辑

ICE专题:ICE的5个服务

 

Summary这里介绍了ICE的五个服务

        Ice为分布式开发提供了技术完善的客户-服务平台。实际上,现实的应用不仅仅只需要具备远程通讯能力,通常的,还需要随需启动服务,把代理分布到客户端,分发异步事件,配置应用,发布补丁等等。

        Ice自带了一些服务,这些服务具备了上面所说的特征和一些其它的特征。这些服务实现以服务器的形式实现并向你的应用提供服务。他们分别是:

1、IcePack

         IcePack是Ice的定位服务。当使用间接绑定时,用来将符号化的适配器名称转换为协议-地址对。除了定位服务之外,IcePack还提供了如下的服务:

  • IcePack允许你注册一个自动启动的服务:即当客户端进行请求时,服务器不需要处于运行状态,只要第一个客户端进行请求时,服务会自动启动。
  • IcePack支持脚本描述部署,可以轻易的配置包含了若干个服务的复杂的应用
  • IcePack提供了简单的对象查找服务,允许客户端获取他们感兴趣的对象代理。

2、IceBox

        IceBox是一个简单的应用服务器,它可以协调多个应用组件启动和停止。

        应用组件可以用动态链接库的形式发布而不是一个进程。这就减轻了系统的负载。例如,你可以在一个JVM中运行若干个应用组件而不是有多个进程,每一个进程都有自己的JVM。

3、IceStorm

        IceStorm是一个发布-订阅服务,它减除了客户端和服务器的耦合度。本质上说,IceStorm作为一个事件分发的交换机运行。发布者将事件发给服务,IceStorm按照顺序将事件传递给订阅者。使用这种方法,一个事件发布者就可以把一个事件发布给多个订阅者。事件按照主题分类,订阅者可以指定他们感兴趣的主题。只有订阅者感兴趣的主题才会发送给订阅者。服务允许指定服务的质量,从而允许应用在可伸缩性和性能之间进行适当的折中。

        如果你需要将信息发布到大量的应用组建,那么IceStorm是一个不错的选择。(一个很典型的例子就是股票报价的应用)。IceStorm减除了信息的发布者和订阅者之间的耦合,同时也能重新发布已经发布的信息。另外,IceStorm可以作为联合服务运行,即多个服务的实例可以运行在不同的机器上,从而降低了CPU的负载。

4、IcePatch

        IcePatch是一个软件补丁服务。它允许你轻松的把软件的更新发布给客户。客户连接到IcePatch然后请求更新一个特定的应用。服务就自动检查客户软件的版本然后下载需要更新的组件。而这些下载的组件都是放在一个压缩包里的,从而减少了带宽的占用。软件补丁也可以通过结合Glacier服务发布,这样可以让只有经过授权的客户才能下载软件更新。

5、Glacier

        Glacier是Ice防火墙服务:它允许客户和服务器通过防火墙安全的通讯。客户-服务器的通讯通过使用公钥认证完全加密,并且通讯是双向的。Glacier提供了相互认证和安全的会话管理支持。

posted @ 2007-05-02 12:08 海天一鸥 阅读(721) | 评论 (1)编辑

ICE专题:使用C#编写ICE分布式应用程序

 

原文来自blogCentaur 随笔录使用C#编写Ice应用程序,原文程序中没有注释,我参照马维达翻译的ICE分布式程序设计Ice-1.3.0-C.pdf,里面的代码有C++和Java的,没有C#的,计划平时有空学习ICE的C#程序设计,将相应的代码使用C#编写,算是学习ICE吧,今天第一个程序就是HelloWord应用:
C#开发环境的搭建参照使用C#编写Ice应用程序。补充一点是设置Path目录中增加ice-1.5.1和增加环境变量ICE_HOME

 

1、编写任何Ice 应用的第一步都是要编写一个Slice 定义,其中含有应用所

用的各个接口。我们为我们的小打印应用编写了这样的Slice 定义:

Hello World 应用

interface Printer

{

void printString(string s);

};

我们把这段文本保存在叫作Printer.ice 的文件中。

我们的Slice 定义含有一个接口,叫作Printer。目前,我们的接口非常简单,只提供了一个操作,叫作printString。printString 操作接受一个串作为它唯一的输入参数;这个串的文本将会出现在(可能在远地的)打印机上。

2、编写用于C# Slice 定义

要创建我们的C# 应用,第一步是要编译我们的Slice 定义,生成C#代理和骨架。在Windows上,你可以这样编译定义:

slice2cs.exe Printer.ice

slice2cs 编译器根据这个定义生成一些.cs 源文件。我们目前无需关注这些文件的确切内容——它们包含的是编译器生成的代码,与我们在Printer.ice 中定义的Printer 接口相对应。

3、编写和编译服务器

要实现我们的Printer 接口,我们必须创建一个servant 类。按照惯例,

servant 类的名字是它们的接口的名字加上一个I 后缀,所以我们的servant

类叫作PrinterI,并放在PrinterI.cs 源文件中:

using System;

namespace IceTest

{

/// <summary>

/// 要实现我们的Printer 接口,我们必须创建一个servant 类。按照惯例,

///servant 类的名字是它们的接口的名字加上一个I 后缀,所以我们的servant

///类叫作PrinterI,PrinterI 类继承自叫作_PrinterDisp 的基类。这个基类由

///slice2cs 编译器生成,是一个抽象类,其中含有一个printString

///方法,其参数是打印机要打印的串,以及类型为Ice.Current 的对象

///我们的printString 方法的实现会简单地把它的参数写到终端。

/// </summary>

public class PrinterI :_PrinterDisp

{

public PrinterI()

{

//

// TODO: 在此处添加构造函数逻辑

//

}

public override void printString(string s, Ice.Current __current)

{

System.Console.WriteLine(s);

}

}

}

PrinterI 类继承自叫作_PrinterDisp 的基类。这个基类由slice2cs 编译器生成,是一个抽象类,其中含有一个printString方法,其参数是打印机要打印的串,以及类型为Ice.Current 的对象.我们的printString 方法的实现会简单地把它的参数写到终端。

服务器代码的其余部分在一个叫作Server.cs 的源文件中,下面给出了其完整代码:

using System;

namespace IceTest

{

/// <summary>

/// Server 的摘要说明。

/// </summary>

public class Server

{

public Server()

{

//

// TODO: 在此处添加构造函数逻辑

//

}

public static void Main(string[] args)

{

int status = 0;

Ice.Communicator ic = null;

try

{

//初始化Ice runtime ,将args传递给这个调用,是因为服务器可能

//有run time感兴趣的命令行参数,返回的是一个Ice.Communicator

//它是Ice run time的主句柄

ic = Ice.Util.initialize(ref args);

//调用createObjectAdapterWithEndpoints创建一个对象适配器,传入的参数为SimplePrinter(适配器名称)

//和default -p 10000或者是适配器使用缺省协议TCP/IP在端口10000侦听到来的请求

Ice.ObjectAdapter adapter = ic.createObjectAdapterWithEndpoints("SimplePrinter","default -p 10000");

//这时,服务器端run time 已经初始化,我们实例化一个PrinterI 对

//象,为我们的Printer 接口创建一个servant。

Ice.Object obj = new PrinterI();

//我们调用适配器的add,告诉它有了一个新的servant ;传给add 的参

//数是我们刚才实例化的servant,再加上一个标识符。在这里,

//"SimplePrinter" 串是servant 的名字(如果我们有多个打印机,每个

//打印机都会有不同的名字,更正确的说法是,都会有不同的对象标识)。

adapter.add(

obj,Ice.Util.stringToIdentity("SimplePrinter"));

//我们调用适配器的activate 方法激活适配器(适配器一开

//始是在扣留(holding)状态创建的;这种做法在下面这样的情况下很

//有用:如果我们有多个servant,它们共享同一个适配器,而在所有

//servant 实例化之前我们不想处理请求)。一旦适配器被激活,服务器就

//会开始处理来自客户的请求。

adapter.activate();

//调用waitForShutdown。这个方法挂起发出调用的线程,

//直到服务器实现终止为止——或者是通过发出一个调用关闭run time,

//或者是对某个信号作出响应(目前,当我们不再需要服务器时,我们

//会简单地在命令行上中断它)。

ic.waitForShutdown();

}

catch (Exception e)

{

Console.Error.WriteLine(e);

status = 1;

}

finally

{

if (ic != null)

ic.destroy();

}

Environment.Exit(status);

}

}

}

Main 的主体含有一个try 块,我们把所有的服务器代码都放在其中;然后是两个catch 处理器。第一个处理器捕捉Ice run time 可能抛出的所有异常,其意图是,如果代码在任何地方遇到意料之外的Ice 运行时异常,栈会一直退回到Main,打印出异常,然后把失败返回给操作系统。第二个处理器捕捉串常量,其意图是,如果我们在代码某处遇到致命错误,我们可以简单地抛出带有出错消息的串文本。这也会使栈退回到main,打印出出错消息,然后把失败返回给操作系统。

这段代码会在退出之前销毁通信器(如果曾经成功创建过)。要使Ice run time 正常结束,这样做是必需的:程序必须调用它所创建的任何通信器的destroy ;否则就会产生不确定的行为。我们把对destroy 的调用放进finally 块,这样,不管前面的try 块中发生什么异常,通信器都保证会正确销毁。

4、编写和编译客户

客户代码在Client.cs 中,看起来与服务器非常类似。下面是完整

的代码:

using System;

namespace IceClientTest

{

/// <summary>

/// Client 的摘要说明。

/// </summary>

public class Client

{

public Client()

{

//

// TODO: 在此处添加构造函数逻辑

//

}

public static void Main(string[] args)

{

int status = 0;

Ice.Communicator ic = null;

try

{

//调用Ice::initialize 初始化Ice run time。

ic = Ice.Util.initialize(ref args);

//获取远地打印机的代理。我们调用通信器的stringToProxy

//创建一个代理,所用参数是"SimplePrinter:default -

//p 10000"。注意,这个串包含的是对象标识和服务器所用的端口号

Ice.ObjectPrx obj = ic.stringToProxy(

"SimplePrinter:default -p 10000");

//stringToProxy 返回的代理的类型是Ice::ObjectPrx,这种类型

//位于接口和类的继承树的根部。但要实际与我们的打印机交谈,我们需

//要的是Printer 接口、而不是Object 接口的代理。为此,我们需要调

//用PrinterPrxHelper.checkedCast 进行向下转换。这个方法会

//发送一条消息给服务器,实际询问“这是Printer 接口的代理吗?”如

//果是,这个调用就会返回Printer 的一个代理;如果代理代表的是其他

//类型的接口,这个调用就会返回一个空代理。

PrinterPrx printer

= PrinterPrxHelper.checkedCast(obj);

//测试向下转换是否成功,如果不成功,就抛出出错消息,终止客户。

if (printer == null)

throw new ApplicationException("Invalid proxy");

//我们的地址空间里有了一个活的代理,可以调用printString 方法,

//把享誉已久的 "Hello World!" 串传给它。服务器会在它的终端上打印这个串。

printer.printString("Hello World!");

}

catch (Exception e)

{

Console.Error.WriteLine(e);

status = 1;

}

finally

{

if (ic != null)

ic.destroy();

}

Environment.Exit(status);

}

}

}

 

Reference:

Ice (Internet Communications Engine)是由前CORBA专家Marc Laukien、Michi Henning及Matthew Newhook等人开发的新一代面向对象中间件,已被用作大型在线游戏Wish(www.wishgame.com) 的通信引擎。关于Ice的详细介绍,可参考以下资料:
* 《反叛之冰:Internet Communications Engine》 - http://blog.csdn.net/grhunter/archi...7/20/45606.aspx
* 《ICE之父抵京讲解高性能网络计算引擎》 - http://www.csdn.net/news/newstopic/16/16366.shtml
* 2004年第9期《程序员》杂志上的Ice专题 - http://mag.csdn.net/Content.aspx?ID...dd-0b3e55740577

posted @ 2007-05-02 12:07 海天一鸥 阅读(1219) | 评论 (1)编辑

ICE专题:学习ICE 3.0

 

学习ICE 3.0

目录

准备工作
初读代码
Slice语言
基础知识
用户定义的类型
接口、操作,以及异常

准备工作

按照某人的说法:跨平台的C++网络编程ICE才是王道。于是,我学习ICE。

ICE才出来两年,是“一种现代的面向对象中间件,可用于替代像CORBA或COM/DCOM/COM+这样的中间件。在易于学习的同时,它为各种有着苛刻的技术要求的应用提供了强大的网络基础设施。”Ice 3.0 已实现对C++, Java, Python, PHP, C# 及 Visual Basic 的支持。

这里我就不多说了,大家可以参考这篇文章:《反叛之冰:Internet Communications Engine 》。大家可以下载的ICE的官方参考手册,有中文版,不过是1.3.0版, 英文的是3.0版。

ICE是开源的,大家可以从源代码开始编译,不过较复杂,幸好有binary版本,比如我就是下载的VS2003.NET的安装包。安装完成之后按照安装目录下的Readme对IDE进行一下配置,比如VC7.1就是把ice的include加入VC7.1的引用文件目录,把ice的lib目录加入VC7.1的库文件目录。然后再把安装目录下的bin文件夹添加到系统的环境变量Path中,最后,把bin文件夹下的所有DLL文件都Copy到Windows安装目录下的System32文件夹下(win98下是System文件夹?)。

ICE自定义了一种SLICE语言,目的是定义接口,作用主要应该是保持对象调用或者数据传输时的语言无关性。

开发一个ICE应用程序可以分为三步:

  1. 写一个Slice定义, 并且编译它

  2. 写服务端, 并编译它

  3. 写客户端, 并编译它

OK,写一个小程序,实现客户发送要打印的文本给服务器,再由服务器把文本发给打印机(这里我们用屏幕显示替代),这里对代码解读请见下一章,这里不多说。

  1. 写一个Slice定义, 并且编译它:

    文件Printer.ice. 

    module Demo {
        interface Printer {
        void printString(string s);
        };
        };
        

    这个文件很简单, 但需要注意, 在区分大小写的系统上, 扩展名一定是小写.

    编译也很简单,首先确认你已将你的bin目录加到系统的环境变量Path中.然后把上面这个片断保存成Printer.ice, 最后执行slice2cpp Printer.ice, 执行后的结果应该是自动生成了printer.h和printer.cpp.

  2. 写服务端, 并编译它

    文件server.cpp. 

    #include <Ice/Ice.h>
        #include "../print.h"
        using namespace std;
        using namespace Demo;
        class PrinterI : public Printer {
        public:
        virtual void printString(const string& s,const Ice::Current&);
        };
        void PrinterI::printString(const string& s, const Ice::Current&)
        {
        cout << s << endl;
        }
        int  main(int argc, char* argv[])
        {
        int status = 0;
        Ice::CommunicatorPtr ic;
        try {
        ic = Ice::initialize(argc, argv);
        Ice::ObjectAdapterPtr adapter
        = ic->createObjectAdapterWithEndpoints(
        "SimplePrinterAdapter", "default -p 10000");
        Ice::ObjectPtr object = new PrinterI;
        adapter->add(object,
        Ice::stringToIdentity("SimplePrinter"));
        adapter->activate();
        ic->waitForShutdown();
        } catch (const Ice::Exception& e) {
        cerr << e << endl;
        status = 1;
        } catch (const char* msg) {
        cerr << msg << endl;
        status = 1;
        }
        if (ic) {
        try {
        ic->destroy();
        } catch (const Ice::Exception& e) {
        cerr << e << endl;
        status = 1;
        }
        }
        return status;
        }
        

    以VS2003的配置为例

    1. 把ice的include加入VC7.1的引用文件目录,把ice的lib目录加入VC7.1的库文件目录。然后再把安装目录下的bin文件夹添加到系统的环境变量Path中,最后,把bin文件夹下的所有DLL文件都Copy到Windows安装目录下的System32文件夹下(win98下是System文件夹?)(当然,DLL文件的问题也可以通过修改环境变量来解决,不过是那个变量呢?Who can tell me?)

    2. 新建一个C++的Win32的命令台控制程序,并且设置为空项目, 把server.cpp, printer.cpp和printer.h加入这个项目(printer.cpp和printer.h放在项目的目录的外一层目录)

    3. 项目-》属性-》C/C++ -》代码生成-》运行时库-》/MD(realse版)或/MDd(debug版)

      项目-》配置属性-》C/C++-》语言-》启用运行时类型信息/GR 开启

      设置:项目-》属性-》链接器-》输入-》加入iced.lib iceutild.lib,此处一定要把realse库和debug库分清, debug库后有个d

    4. 修改printer.cpp中的#include <printer.h>为#include "printer.h"

    5. OK,编译

  3. 写客户端,并编译它

    文件client.cpp. 

    #include <Ice/Ice.h>
        #include "..\print.h"
        using namespace std;
        using namespace Demo;
        int main(int argc, char* argv[])
        {
        int status = 0;
        Ice::CommunicatorPtr ic;
        try {
        ic = Ice::initialize(argc, argv);
        Ice::ObjectPrx base = ic->stringToProxy(
        "SimplePrinter:default -p 10000");
        PrinterPrx printer = PrinterPrx::checkedCast(base);
        if (!printer)
        throw "Invalid proxy";
        printer->printString("Hello World!");
        } catch (const Ice::Exception& ex) {
        cerr << ex << endl;
        status = 1;
        } catch (const char* msg) {
        cerr << msg << endl;
        status = 1;
        }
        if (ic)
        ic->destroy();
        return status;
        }
        

    添加一个新项目到当前解决方案,按照上面的方法,对client再一次进行设置。

    在解决方案管理器的解决方案上点击右键,选择批生成Debug版本,然到用资源管理器到两个解决方案的目录下的Debug文件夹中执行生产的可执行文件。先运行server.exe, 然后运行client.exe, 哈哈, 是不是在server.exe的窗口里出现了Hello World!(运行一次client.exe,出现一条)

 

初读代码

这一节大部分内容整理自ICE中文手册,在这里我特别感谢马维达同志的翻译给我们的学习带来了方便。

读服务端代码

文件server.cpp. 

#include <Ice/Ice.h>
#include "../print.h"
using namespace std;
using namespace Demo;
//惯例,用后缀I 表示这个类实现一个接口
class PrinterI : public Printer {
public:
virtual void printString(const string& s, const Ice::Current&);
};
/*
打开print.h,看看PrinterI父类的定义
namespace Demo {
class Printer : virtual public Ice::Object {
public:
//纯虚函数,不能实例化
virtual void printString(const std::string&,
//第二个参数有缺省值,实现中可以不使用
const Ice::Current&= Ice::Current()) = 0;
};
};
*/
void PrinterI::printString(const string& s, const Ice::Current&)
{
cout << s << endl;
}
int  main(int argc, char* argv[])
{
//程序的退出时的状态,就是否成功执行
int status = 0;
//来包含Ice run time 的主句柄	(main handle)
Ice::CommunicatorPtr ic;
try {
//初始化Ice run time (argc和argv是run time命令参数;
//就这个例子而言,服务器不需要任何命令行参数)。
//initialize 返回一个指向Ice::Communicator对象的智能指针,
//这个指针是Ice run time 的主句柄。
ic = Ice::initialize(argc, argv);
//调用Communicator 实例上的createObjectAdapterWithEndpoints,
//创建一个对象适配器(比如:网卡就是一种适配器)。
//参数是"SimplePrinterAdapter" (适配器的名字)
//和"default -p 10000"(用缺省协议(TCP/IP),侦听端口10000 的请求。)
//显然,在应用中硬编码对象标识和端口号,是一种糟糕的做法,
//但它目前很有效;我们将在以后看到在架构上更加合理的做法。
Ice::ObjectAdapterPtr adapter
= ic->createObjectAdapterWithEndpoints(
"SimplePrinterAdapter", "default -p 10000");
//服务器端run time 已经初始化,实例化一个PrinterI 对象,
//为我们的Printer 接口创建一个servant(serv 服务+-ant人,背一下单词)。
Ice::ObjectPtr object = new PrinterI;
//我们调用适配器的add,告诉它有了一个新的servant ;
//传给add 的参数是刚才实例化的servant,再加上一个标识符。
//在这里,"SimplePrinter" 串是servant 的名字
//(如果我们有多个打印机,每个打印机都可以有不同的名字,
//更正确的说法是,都有不同的对象标识)。
adapter->add(object,
Ice::stringToIdentity("SimplePrinter"));
//调用适配器的activate 方法激活适配器
//(适配器一开始是在暂停(holding)状态创建的;
//这种做法在下面这样的情况下很有用:
//我们有多个servant,它们共享同一个适配器,
//而在所有servant实例化之前我们不想处理请求)。
//一旦适配器被激活,服务器就会开始处理来自客户的请求。
adapter->activate();
//最后,我们调用waitForShutdown。
//这个方法挂起发出调用的线程直到服务器实现终止
//——或者是通过发出一个调用关闭run time,
ic->waitForShutdown();
}
catch (const Ice::Exception& e) {
cerr << e << endl;
status = 1;
} catch (const char* msg) {
cerr << msg << endl;
status = 1;
}
if (ic) {
try {
//必须调用Communicator::destroy结束Ice run time。
//destroy 会等待任何还在运行的操作调用完成。
//此外, destroy 还会确保任何还未完成的线程都得以汇合(joined),
//并收回一些操作系统资源,比如文件描述符和内存。
//决不要让你的main 函数不调用destroy 就终止,
//否则,后果无法想象。
ic->destroy();
} catch (const Ice::Exception& e) {
cerr << e << endl;
status = 1;
}
}
return status;
}

注意,尽管以上的代码不算少,但它们对所有的服务器都是一样的。你可以把这些代码放在一个辅助类里,然后就无需再为它费心了(Ice 提供了这样的辅助类,叫作Ice::Application,参见 10.3.1 节) 。就实际的应用代码而言,服务器只有几行代码:六行代码定义PrinterI 类,再加上三2 行代码实例化一个PrinterI 对象,并向对象适配器注册它。

读客户端代码

文件client.cpp. 

#include <Ice/Ice.h>
#include "..\print.h"
using namespace std;
using namespace Demo;
int main(int argc, char* argv[])
{
int status = 0;
Ice::CommunicatorPtr ic;
try {
ic = Ice::initialize(argc, argv);
//stringToProxy 返回的代理(Proxy)类型是Ice::ObjectPrx,
//这种类型位于接口和类的继承树的根部(接口的基类)。
Ice::ObjectPrx base
=ic->stringToProxy(	"SimplePrinter:default -p 10000");
//但要实际要与我们的打印机交谈,
//我们需要的是Printer 接口、不是Object 接口的代理。
//为此,需要调用PrinterPrx::checkedCast 进行向下转换(向下转型)。
//这个方法会发送一条消息给服务器,
//询问“这是Printer 接口的代理吗?”
//如果回答“是”,就会返回Printer 的一个代理;
//如果代理代表的是其他类型的接口,返回一个空代理
PrinterPrx printer = PrinterPrx::checkedCast(base);
//测试向下转型是否成功,若不成功,就抛出出错消息并终止客户。
if (!printer) 	throw "Invalid proxy";
//现在,我们在我们的地址空间里有了一个激活的代理,
//可以调用printString 方法,
//把享誉已久的 "Hello World!" 串传给它。
//服务器会在它的终端上打印这个串。
printer->printString("Hello World!");
}
catch (const Ice::Exception& ex) {
cerr << ex << endl;
status = 1;
} catch (const char* msg) {
cerr << msg << endl;
status = 1;
}
if (ic)
ic->destroy();
return status;
}

如果出现任何错误,客户会打印一条出错消息。例如,如果我们没有先启动服务器就运行客户,我们会得到:

Network.cpp:471: Ice::ConnectFailedException:
connect failed: Connection refused

(由于windows下的命令行窗口在出错后会一闪就消失,不过我们可以在client.cpp的main函数的return status;之前加上system("PAUSE");然后再在VS2003.net中把client设置为启动项目,重新编译,运行。OK,可以看到结果了。)

 

 

Slice语言

首先,请大家读ICE中文手册中的Slice语言一章。 这一部分除了model(模块),在 ICE 1.3中文手册中都有描述

图 2.1. ice网络编程示意图(服务器端和客户端采用同种编程语言C++)

ice网络编程示意图(服务器端和客户端采用同种编程语言C++)

图 2.2. ice网络编程示意图(服务器端和客户端采用不同编程语言)

ice网络编程示意图(服务器端和客户端采用不同编程语言)

基础知识

含有Slice 定义的文件必须以.ice 扩展名结尾,例如, Clock.ice就是一个有效的文件名。编译器拒绝接受其他扩展名。

Slice 支持#ifndef、#define、#endif,以及#include 预处理指令。它们的使用方式有严格的限制:你只能把#ifndef、#define,以及#endif 指令用于创建双包括(double-include)块。例如:

#ifndef _CLOCK_ICE
#define _CLOCK_ICE
// #include 文件 here...
//定义 here...
#endif _CLOCK_ICE

我们强烈建议你在所有的Slice 定义中使用双包括(double-include)块(所上),防止多次包括同一文件。

#include 指令只能出现在Slice 源文件的开头,也就是说,它们必须出现在其他所有Slice 定义的前面。此外,在使用#include 指令时,只允许使用<> 语法来指定文件名,不能使用""。例如:

 #include <File1.ice> // OK
#include "File2.ice" // 不支持!

你不能把这些预处理指令用于其他目的,也不能使用其他的C++ 预处理指令 (比如用\ 字符来连接行、token 粘贴,以及宏展开,等等)。

在Slice 定义里,既可以使用C 的、也可以使用C++ 的注释风格:

Slice 关键字必须小写。例如, class 和dictionary 都是关键字,必须按照所示方式拼写。这个规则有两个例外:Object 和LocalObject 也是关键字,必须按照所示方式让首字母大写。

标识符以一个字母起头,后面可以跟任意数目的字母或数字。Slice 标识符被限制在ASCII 字符范围内,不能包含非英语字母,与C++ 标识符不同, Slice 标识符不能有下划线。这种限制初看上去显得很苛刻,但却是必要的:保留下划线,各种语言映射就获得了一个名字空间,不会与合法的Slice 标识符发生冲突。于是,这个名字空间可用于存放从Slice 标识符派生的原生语言标识符,而不用担心其他合法的Slice 标识符会碰巧与之相同,从而发生冲突 。

标识符(变量名等等)是大小写不敏感的,但大小写的拼写方式必须保持一致(看了后面的话,再理解一下)。例如,在一个作用域内, TimeOfDay 和TIMEOFDAY 被认为是同一个标识符。但是,Slice 要求你保持大小写的一致性。在你引入了一个标识符之后,你必须始终一致地拼写它的大写和小写字母;否则,编译器就会将其视为非法而加以拒绝。这条规则之所以存在,是要让Slice 既能映射到忽略标识符大小写的语言,又能映射到把大小写不同的标识符当作不同标识符的语言。(可以这样理解,变量名区分大小写,并且不可以是相同的单词)

是关键字的标识符:你可以定义在一种或多种实现语言中是关键字的Slice 标识符。例如,switch是完全合法的Slice标识符,但也是C++和Java的关键字。语言映射定义了一些规则来处理这样的标识符。要解决这个问题,通常要用一个前缀来使映射后的标识符不再是关键字。例如, Slice 标识符switch 被映射到C++ 的_cpp_switch ,以及Java 的_switch。对关键字进行处理的规则可能会产生难以阅读的源码。像native、throw,或export 这样的标识符会与C++ 或Java(或两者)的关键字发生冲突。为了让你和别人生活得更轻松一点,你应该避免使用是实现语言的关键字的Slice 标识符。要记住,以后Ice 可能会增加除C++ 和Java 以外的语言映射。尽管期望你总结出所有流行的编程语言的所有关键字并不合理,你至少应该尽量避免使用常用的关键字。使用像self、import,以及while 这样的标识符肯定不是好主意。

转义的标识符:在关键字的前面加上一个反斜线,你可以把Slice 关键字用作标识符,例如:

struct dictionary { // 错误!
// ...
};
struct \dictionary { // OK
// ...
};

反斜线会改变关键字通常的含义;在前面的例子中, \dictionary 被当作标识符dictionary。转义机制之所以存在,是要让我们在以后能够在Slice 中增加关键字,同时尽量减少对已有规范的影响:如果某个已经存在的规范碰巧使用了新引入的关键字,你只需在新关键字前加上反斜线,就能够修正该规范。注意,从风格上说,你应该避免用Slice 关键字做标识符(即使反斜线转义允许你这么做)。

保留的标识符:Slice 为Ice 实现保留了标识符Ice 及以Ice (任何大小写方式)起头的所有标识符。例如,如果你试图定义一个名为Icecream 的类型, Slice 编译器会发出错误警告3。以下面任何一种后缀结尾的Slice 标识符也是保留的:Helper、Holder、Prx,以及Ptr。Java 和C++ 语言映射使用了这些后缀,保留它们是为了防止在生成的代码中发生冲突。

(注:ICE 1.3的中文手册上没有“模块”这一部分)模块来组织一组相关的语句是为了解决名字冲突。模块可以包含所有合法的Slice语句和子模块。你可以用一些不常用的词来给最外层的模块命名,比如公司名、产品名等等。

module ZeroC {
module Client {
// Definitions here...
};
module Server {
// Definitions here...
};
};

Slice要求所有的定义都是模块的一部分,比如,下面的语句就是非法的。

interface I { // 错误:全局空间中只可以有模块
// ...
};

多个文件可以共享同一个模块,比如:

module ZeroC {
// Definitions here...
};
//另一个文件中 :
module ZeroC { // OK, reopened module
// More definitions here...
};

把一个大的模块放到几个文件中去可以方便编译(你只需重新编译被修改的文件,而没有必要编译整个模块)。

模块将映射的语言中的相应结构,比如 C++, C#, 和 Visual Basic, Slice的modules被映射为namespaces;java中被映射为package.

除了少数与特定的程序语言相关的调用之外,ice的绝大部分API(应用程序接口)都是用Slice来定义的 。这样做的好处是可以用一个ICE API定义文件来支持所有的程序语言。

[注意]
注意

为了保证代码的简洁,以后文章中提及的Slice定义没有写出包含的模块,你要假定该语句是在一个模块中。

表 2.1. Slice的数据类型

类型
取值范围
大小(单位:bit)

bool
false or true
≥ 1

byte
-128-127或0-255
≥ 8

short
2-15至215-1
≥ 16

int
2-31至231-1
≥ 32

long
2-63至263-1
≥ 64

float
IEEE的单精度
≥ 32 bits

double
IEEE的双精度
≥ 64 bits

string
所有Unicode 字符,除了所有位为零的字符
变长

用户定义的类型
  • 枚举:enum Fruit { Apple, Pear, Orange };

    这个定义引入了一种名为Fruit 的类型,这是一种拥有自己权利的新类型。关于怎样把顺序值(ordinal values)赋给枚举符的问题, Slice 没有作出定义。例如,你不能假定,在各种实现语言中,枚举符Orange 的值都是2。Slice 保证枚举符的顺序值会从左至右递增,所以在所有实现语言中,Apple 都比Pear 要小。与C++ 不同, Slice 不允许你控制枚举符的顺序值(因为许多实现语言不支持这种特性):

    enum Fruit { Apple = 0, Pear = 7, Orange = 2 }; // 出错
        

    在实践中,只要你不在地址空间之间传送枚举符的顺序值,你就不用管枚举符使用的值是多少。例如,发送值0 给服务器来表示Apple 可能会造成问题,因为服务器可能没有用0 表示Apple。相反,你应该就发送值Apple 本身。如果在接收方的地址空间中, Apple 是用另外的顺序值表示的, Ice run time 会适当地翻译这个值。

    与在C++ 里一样, Slice 枚举符也会进入围绕它的名字空间,所以下面的定义是非法的:

    enum Fruit { Apple, Pear, Orange };
        enum ComputerBrands { Apple, IBM, Sun, HP }; // Apple已经被定义!
        

    Slice 不允许定义空的枚举。

  • 结构

    Slice 支持含有一个或多个有名称的成员的结构,这些成员可以具有任意类型,包括用户定义的复杂类型。例如:

    struct TimeOfDay {
        short hour; // 0 - 23
        short minute; // 0 - 59
        short second; // 0 - 59
        };
        
    与在 C++ 里一样,这个定义引入了一种叫作TimeOfDay 的新类型。结构定义会形成名字空间,所以结构成员的名字只需在围绕它们的结构里是唯一的。在结构内部,只能出现数据成员定义,这些定义必须使用有名字的类型。例如,你不可能在结构内定义结构:
    struct TwoPoints {
        struct Point { //错误!
        short x;
        short y;
        };
        Point coord1;
        Point coord2;
        };
        
    这个规则大体上适用于Slice:类型定义不能嵌套(除了模块支持嵌套)。其原因是,对于某些目标语言而言,嵌套的类型定义可能会难以实现,而且,即使能够实现,也会极大地使作用域解析规则复杂化。对于像Slice 这样的规范语言而言,嵌套的类型定义并无必要——你总能以下面的方式编写上面的定义(这种方式在风格上也更加整洁):
    struct Point {
        short x;
        short y;
        };
        struct TwoPoints { // Legal (and cleaner!)
        Point coord1;
        Point coord2;
        }
        
  • 序列

    序列是变长的元素向量:

    sequence<Fruit> FruitPlatter;
        

    序列可以是空的——也就是说,它可以不包含元素;它也可以持有任意数量的元素,直到达到你的平台的内存限制。

    序列包含的元素自身也可以是序列。这种设计使得你能够创建列表的列表:

    sequence<FruitPlatter> FruitBanquet;
        

    序列可用于构建许多种collection,比如向量、列表、队列、集合、包(bag),或是树(次序是否重要要由应用决定;如果无视次序,序列充当的就是集合和包)。

    序列的一种特别的用法已经成了惯用手法,即用序列来表示可选的值。例如,我们可能拥有一个Part 结构,用于记录小汽车的零件的详细资料。这个结构可以记录这样的资料:零件名称、描述、重量、价格,以及其他详细资料。 备件通常都有序列号,我们用一个long 值表示。但有些零件,比如常用的螺丝钉,常常没有序列号,那么我们在螺丝钉的序列号字段里要放进什么内容?要处理这种情况,有这样一些选择:

    • 用一个标记值,比如零,来指示“没有序列号”的情况。

      这种方法是可行的,只要确实有标记值可用。尽管看起来不大可能有人把零用作零件的序列号,这并非是不可能的。而且,对于其他的值,比如温度值,在其类型的范围中的所有值都可能是合法的,因而没有标记值可用。

    • 把序列号的类型从long 变成string。

      串自己有内建的标记值,也就是空串,所以我们可以用空串来指示.“没有序列号”的情况。这也是可行的,但却会让大多数人感到不快:我们不应该为了得到一个标记值,而把某种事物自然的数据类型变成string

    • 增加一个指示符来指示序列号的内容是否有效.

      struct Part {
              string name;
              string description;
              // ...
              bool serialIsValid; // true if part has serial number
              long serialNumber;
              };
              

      对于大多数人而言,这也让人讨厌,而且最终肯定会让你遇到麻烦:迟早会有程序员忘记在使用序列号之前检查它是否有效,从而带来灾难性的后果。

    • 用序列来建立可选字段

      这种技术使用了下面的惯用手法:

      sequence<long> SerialOpt;
              struct Part {
              string name;
              string description;
              // ...
              SerialOpt serialNumber; // optional: zero or one element
              };
              

      按照惯例, Opt 后缀表示这个序列是用来建立可选值的。如果序列是空的,值显然就不在那里;如果它含有一个元素,这个元素就是那个值。这种方案明显的缺点是,有人可能会把不止一个元素放入序列。为可选值增加一个专用的Slice 成分可以纠正这个问题。但可选值并非那么常用,不值得为它增加一种专门的语言特性(我们将看到,你还可以用类层次来建立可选字段)。

  • 词典

    词典是从键类型到值类型的映射。例如:

    struct Employee {
        long number;
        string firstName;
        string lastName;
        };
        dictionary<long, Employee> EmployeeMap;
        

    这个定义创建一种叫作EmployeeMap 的词典,把雇员号映射到含有雇员详细资料的结构。你可以自行决定键类型(在这个例子中是long 类型的雇员号)是否是值类型(在这个例子中是Employee 结构)的一部分——就Slice 而言,你无需让键成为值的一部分。

    词典可用于实现稀疏数组,或是具有非整数键类型的任何用于查找的数据结构。尽管含有键-值对的结构的序列可用于创建同样的事物,词典要更为适宜:

    • 词典明确地表达了设计者的意图,也就是,提供从值的域(domain)到值的范围(range)的映射(含有键-值对的结构的序列没有如此明确地表达同样的意图)。

    • 在编程语言一级,序列被实现成向量(也可能是列表),也就是说,序列不大适用于内容稀疏的域,而且要定位具有特定值的元素,需要进行线性查找。而词典被实现成支持高效查找的数据结构(通常是哈希表或红黑树),其平均查找时间是O(log n),或者更好。词典的键类型无需为整型。例如,我们可以用下面的定义来翻译一周每一天的名称:

      dictionary<string, string> WeekdaysEnglishToGerman;
              

      服务器实现可以用键-值对Monday–Montag、Tuesday–Dienstag,等等,对这个映射表进行初始化。

    • 词典的值类型可以是用户定义的任何类型。但词典的键类型只能是以下类型之一:

      • 整型(byte、short、int、long、bool,以及枚举类型)

      • string

      • 元素类型为整型或string 的序列

      • 数据成员的类型只有整型或string 的结构

      复杂的嵌套类型,比如嵌套的结构或词典,以及浮点类型(float和double),不能用作键类型。之所以不允许使用复杂的嵌套类型,是因为这会使词典的语言映射复杂化;不允许使用浮点类型,是因为浮点值在跨越机器界线时,其表示会发生变化,有可能导致成问题的相等语义。

  • 常量定义与直接量

    Slice 允许你定义常量。常量定义的类型必须是以下类型中的一种:

    • 整型(bool、byte、short、int、long,或枚举类型)

    • float 或double

    • string

    下面有一些例子:

    const bool AppendByDefault = true;
        const byte LowerNibble = 0x0f;
        const string Advice = "Don't Panic!";
        const short TheAnswer = 42;
        const double PI = 3.1416;
        enum Fruit { Apple, Pear, Orange };
        const Fruit FavoriteFruit = Pear;
        

    直接量(literals)的语法与C++ 和Java 的一样(有一些小的例外):

    • 布尔常量只能用关键字false和true初始化(你不能用0和1来表示false和true)。

    • 和C++ 一样,你可以用十进制、八进制,或十六进制方式来指定整数直接量。例如:

      const byte TheAnswer = 42;
              const byte TheAnswerInOctal = 052;
              const byte TheAnswerInHex = 0x2A; // or 0x2a
              

      [注意]
      注意

      如果你把byte 解释成数字、而不是位模式,你在不同的语言里可能会得到不同的结果。例如,在C++ 里, byte 映射到char,取决于目标平台, char 可能是有符号的,也可能是无符号的。

      [注意]
      注意

      用于指示长常量和无符号常量的后缀(C++ 使用的l、L、u、U)是非法的:

      const long Wrong = 0u; // Syntax error
              const long WrongToo = 1000000L; // Syntax error
              
      • 整数直接量的值必须落在其常量类型的范围内,否则编译器就会发出诊断消息。

      • 浮点直接量使用的是C++语法,除了你不能用l或L后缀来表示扩展的浮点常量;但是, f 和F 是合法的(但会被忽略)。下面是一些例子:

        const float P1 = -3.14f; // Integer & fraction, with suffix
                    const float P2 = +3.1e-3; // Integer, fraction, and exponent
                    const float P3 = .1; // Fraction part only
                    const float P4 = 1.; // Integer part only
                    const float P5 = .9E5; // Fraction part and exponent
                    const float P6 = 5e2; // Integer part and exponent
                    
      • 浮点直接量必须落在其常量类型(float 或double)的范围内;否则编译器会发出诊断警告。

      • 串直接量支持与C++ 相同的转义序列。下面是一些例子:

        const string AnOrdinaryString = "Hello World!";
                    const string DoubleQuote = "\"";
                    const string TwoSingleQuotes = "'\'"; // ' and \' are OK
                    const string Newline = "\n";
                    const string CarriageReturn = "\r";
                    const string HorizontalTab = "\t";
                    const string VerticalTab = "\v";
                    const string FormFeed = "\f";
                    const string Alert = "\a";
                    const string Backspace = "\b";
                    const string QuestionMark = "\?";
                    const string Backslash = "\\";
                    70 Slice 语言
                    const string OctalEscape = "\007"; // Same as \a
                    const string HexEscape = "\x07"; // Ditto
                    const string UniversalCharName = "\u03A9"; // Greek Omega
                    和在 C++ 里一样,相邻的串直接量会连接起来:
                    const string MSG1 = "Hello World!";
                    const string MSG2 = "Hello" " " "World!"; // Same message
                    /*
                    * Escape sequences are processed before concatenation,
                    * so the string below contains two characters,
                    * '\xa' and 'c'.
                    */
                    const string S = "\xa" "c";
                    

        [注意]
        注意

        Slice 没有null 串的概念

        const string nullString = 0; // Illegal!
                    
        null 串在Slice 里根本不存在,因此,在Ice 平台的任何地方它都不能用作合法的串值。这一决定的原因是, null 串在许多编程语言里不存在
接口、操作,以及异常

见手册........抄书好累.........

引自:http://enjoylanguage.sourceforge.net/[xhtml_chunk_...

posted @ 2007-05-02 12:03 海天一鸥 阅读(1270) | 评论 (2)编辑

ICE专题:在客户端中如何定位服务器(即如何寻找代理)

 

最近开始学习ICE,头有点大,900多页的文档看了五百多页还不知CLIENT如何定位SERVER的位置,郁闷的很...昨天毛了直接看文档的最后几页..我晕原来在这里给阐述了...差不多想撞墙

以下是中文文档中关于端点的描述:

D.2 端点
纲要
endpoint : endpoint
描述
端点列表由一个或多个用冒号(:) 分隔的端点组成。端点的格式如下所
示: protocol option。所支持的协议有tcp、udp、ssl,以及
default。如果使用了default,它会被Ice.Default.Protocol 属性的值替
代。如果端点的格式有问题,或者指定了未知的协议,应用会收到
Ice::EndpointParseException。
只有安装了IceSSL 插件,才能使用ssl 协议。
各个协议及其所支持的选项将在下面描述。
TCP 端点
纲要
tcp -h host -p port -t timeout -z
描述
tcp 端点支持以下选项:
选项描述客户语义服务器语义
-h host 指定端点的主机名
或IP 地址。如果
没有指定,将使用
Ice.Default.Hos
t 的值。
确定要连接到的主
机名或IP 地址。
确定对象适配器用
于侦听连接的网络
接口,以及在适配
器所创建的代理中
向外公布的主机
名。
-p port 指定端点的端口
号。
确定要连接到的端
口( 必须指定)。
如果没有指定这个
选项,或是port 为
零,端口将由操作
系统选择。
932 代理与端点

以上是基于TCP协议的,关于UDP,SSL协议的也差不多
我在我的聊天程序中连接LAN中一台服务器的代码:
#include <Ice/Ice.h>
#include <Printer.h>

using namespace std;
using namespace Demo;

int main(int argc, char * argv[])
{
    int status = 0;
 char strtemp[100];//聊天内容
 char clientname[20];//客户名
 char chattmp[130];
    Ice::CommunicatorPtr ic;
    try {
  ic = Ice::initialize(argc, argv);
/*连接服务器:SimplePrinter16所申请的代理ID,default即为使用默认的协议TCP,-h 192.168.1.16 为服务端所在LAN中的IP地址,-p 9600为端口号*/
  Ice::ObjectPrx base = ic->stringToProxy("SimplePrinter16:default -h 192.168.1.16 -p 9600");
  //创建一个Printer的代理(Printer即为客户端与服务器端的接口),利用checkedCast(base)检查代理是否存在
  PrinterPrx printer = PrinterPrx::checkedCast(base);
  if (!printer)
  {
   throw "Invalid proxy";//代理不存在
  }
  else
  {
   printf("请输入您的ID:");
   cin >> clientname;
  }

  while ( 1 )
  {
   cin >> strtemp;//输入聊天内容
   if ( strcmp( strtemp,"q") == 0 )
   {
    break;//退出聊天
   }
   //strcat( chattmp, clientname );
   strcpy( chattmp, clientname );
   strcat( chattmp, " say: " );
   strcat( chattmp, strtemp );
   printer->printString(chattmp);
  }
  //发给服务器的退出消息
  printer->printString(clientname);
  printer->printString("已退出!");
  cout << "成功退出!" << endl;
    } catch (const Ice::Exception & ex) {
  cerr << ex << endl;
  status = 1;
    } catch (const char * msg) {
  cerr << msg << endl;
  status = 1;
    }
    if (ic) {
  try {
   ic->destroy();
  } catch (const Ice::Exception & ex) {
   cerr << ex << endl;
   status = 1;
  }
    }
    return status;
}

 

引自:http://www.cppblog.com/klsmlzm/archive/2005/12/09/...

posted @ 2007-05-02 11:57 海天一鸥 阅读(956) | 评论 (1)编辑

ICE专题:ICE在Linux下的安装

     摘要: 最近项目使用了ICE来实现分布式应用,ICE的确功能强大,希望使用过的朋友或对ICE关注的朋友能一起交流交流经验,彼此提高。【开源应用技术群 25935569】
ICE(Internet Communications Engine)是一个为现实中程序员而写的中间件平台。作为一个高性能的互联网通信平台,ICE包含了很多分层的服务和插件(Plug-ins),并且简单、高效和强大。  阅读全文

posted @ 2007-05-02 11:54 海天一鸥 阅读(866) | 评论 (1)编辑

ICE专题:ICE简介

 

ICE(Internet Communications Engine)是一个为现实中程序员而写的中间件平台。作为一个高性能的互联网通信平台,ICE包含了很多分层的服务和插件(Plug-ins),并且简单、高效和强大。

ICE的自由使用遵从GNU的GPL(GNU General Public Licens)许可的条款。ICE也有商业的许可证,如果你想将ICE使用在你的私有产品中,那么你应该使用ICE的商业许可证。可以联系sales@zeroc.com来获得这方面的更多信息。

ICE当前支持C++、Java、C#、Visual Basic、Python和PHP编程语言,并支持在多种操作系统上运行。更多的操作系统和编程语言将会在以后的发布中支持。

ICE有以下几部分组成:

Slice
ICE的规范语言,跟CORBA的IDL(Interface Definition Language)等价的东西。Slice建立了客户端和服务器端共同遵守的契约:接口。Slice也用来描述对象持久数据。

Slice Compilers
Slice的规范语言可以影射成多种编程语言。目前ICE支持C++,Java,Python,PHP,C#和VB的语言影射。Ice的客户端和服务器端协同工作,而不会知道分别实现的是何种编程语言。

Ice
Ice的核心库。在众多的特性当中,Ice核心库通过一个高效的协议(包含TCP/UDP层上协议压缩)来管理所有的通信任务,为多线程服务器提供了一个灵活的线程池,并且有特别的功能来支持上百万对象的可扩展性。

IceUtil
一些常用的功能函数集。例如Unicode处理和多线程编程,是用C++写成。

IceBox
一个专用于ICE应用的应用服务器。ICEBox可以方便地运行和管理动态加载、共享库或java类的形式Ice的服务。

IcePack
一个成熟的服务激活和部署工具。IcePack能大大简化在异构网络之间部署应用的复杂性。只要简单的编写XML格式的一个部署描述文件,IcePack就能自动处理剩下的工作。

Freeze
Freeze提供了Ice Servants对象的自动持久性。通过几行代码,一个应用就可以生成一个高度可扩展的逐出器(evictor)来高效地管理持久对象。

FreezeScript
在大的软件项目里,持久对象的数据类型改变很常见。为了最小化这些变化的影响,FreezeScript提供了相应的工具来检查和移植Freeze生成的数据库。这些工具支持XML格式的配置脚本,易于使用。

IceSSL
用于Ice核心的动态的SSL传输插件。提供了认证、加密和消息完整性,使用工业标准的SSL协议来实现。

Glacier
面向对象中间件平台的一个最大的挑战是安全性和防火墙。Glacier是Ice的防火墙解决方案,它大大简化了安全程序的部署。Glacier认证和过滤客户的请求并允许服务器通过安全的方式回调客户端对象。结合IceSSL的使用,Glacier提供了强大的安全解决方案,即安全,又易于配置管理。

IceStorm
一个支持联盟的消息服务。和大多数的其他消息和事件服务相比,IceStorm支持有类型的事件,这意味着通过联盟广播一个消息和调用一个接口上的一个方法一样容易。

IcePatch
一个软件修补和分发的服务。为确保运行的软件是最新的版本,要经常更新软件,这是一件乏味的工作。IcePatch自动更新在某个目录层次下的文件。只有需要更新的文件会下作到客户端,为了快速的下载更新,IcePatch使用的高效的压缩算法。

如想了解更多的详细信息,可以到http://www.zeroc.com上下载关于Ice的更多文档。

引自:http://www.acejoy.com/bbs/dispbbs.asp?boardID=29&I...

posted @ 2007-05-02 11:51 海天一鸥 阅读(1556) | 评论 (1)编辑