记录生活点滴,或痛苦,或快乐
  • 【翻译】两种高性能I/O设计模式(Reactor/Proactor)的比较

    2011-01-26

    这是05年的老文章,网上应该有人早就翻译过了,我翻译它仅仅为了学习Reactor/Proactor两种TCP服务器设计模式,顺便作翻译练习。

    标题: 两种高性能I/O设计模式的比较
    作者: Alexander Libman 、Vladimir Gilbourd
    原文: http://www.artima.com/articles/io_design_patterns.html
    时间: November 25, 2005
    译者: 潘孙友 2010-01-26 于深圳

    综述

    这篇文章探讨并比较两种用于TCP服务器的高性能设计模式. 除了介绍现有的解决方案, 还提出了一种更具伸缩性,只需要维护一份代码并且跨平台的解决方案(含代码示例), 以及其在不同平台上的微调. 此文还比较了java,c#,c++对各自现有以及提到的解决方案的实现性能.

    系统I/O 可分为阻塞型, 非阻塞同步型以及非阻塞异步型[1, 2]. 阻塞型I/O意味着控制权只到调用操作结束了才会回到调用者手里.
    结果调用者被阻塞了, 这段时间了做不了任何其它事情. 更郁闷的是,在等待IO结果的时间里,调用者所在线程此时无法腾出手来去响应其它的请求,这真是太浪费资源了。拿read()操作来说吧, 调用此函数的代码会一直僵在此处直至它所读的socket缓存中有数据到来.

    相比之下,非阻塞同步是会立即返回控制权给调用者的。调用者不需要等等,它从调用的函数获取两种结果:要么此次调用成功进行了;要么系统返回错误标识告诉调用者当前资源不可用,你再等等或者再试度看吧。比如read()操作, 如果当前socket无数据可读,则立即返回EWOULBLOCK/EAGAIN,告诉调用read()者”数据还没准备好,你稍后再试”.

    在非阻塞异步调用中,稍有不同。调用函数在立即返回时,还告诉调用者,这次请求已经开始了。系统会使用另外的资源或者线程来完成这次调用操作,并在完成的时候知会调用者(比如通过回调函数)。拿Windows的ReadFile()或者POSIX的aio_read()来说,调用它之后,函数立即返回,操作系统在后台同时开始读操作。

    在以上三种IO形式中,非阻塞异步是性能最高、伸缩性最好的。

    这篇文章探讨不同的I/O利用机制并提供一种跨平台的设计模式(解决方案). 希望此文可以给于TCP高性能服务器开发者一些帮助,选择最佳的设计方案。下面我们会比较 Java, c#, C++各自对探讨方案的实现以及性能. 我们在文章的后面就不再提及阻塞式的方案了,因为阻塞式I/O实在是缺少可伸缩性,性能也达不到高性能服务器的要求。

    两种IO多路复用方案:Reactor and Proactor

    一般情况下,I/O 复用机制需要事件分享器(event
    demultiplexor [1, 3]).
    事件分享器的作用,即将那些读写事件源分发给各读写事件的处理者,就像送快递的在楼下喊: 谁的什么东西送了, 快来拿吧。开发人员在开始的时候需要在分享器那里注册感兴趣的事件,并提供相应的处理者(event handlers),或者是回调函数; 事件分享器在适当的时候会将请求的事件分发给这些handler或者回调函数.

    涉及到事件分享器的两种模式称为:Reactor and Proactor [1]. Reactor模式是基于同步I/O的,而Proactor模式是和异步I/O相关的. 在Reactor模式中,事件分离者等待某个事件或者可应用或个操作的状态发生(比如文件描述符可读写,或者是socket可读写),事件分离者就把这个事件传给事先注册的事件处理函数或者回调函数,由后者来做实际的读写操作。

    而在Proactor模式中,事件处理者(或者代由事件分离者发起)直接发起一个异步读写操作(相当于请求),而实际的工作是由操作系统来完成的。发起时,需要提供的参数包括用于存放读到数据的缓存区,读的数据大小,或者用于存放外发数据的缓存区,以及这个请求完后的回调函数等信息。事件分离者得知了这个请求,它默默等待这个请求的完成,然后转发完成事件给相应的事件处理者或者回调。举例来说,在Windows上事件处理者投递了一个异步IO操作(称有overlapped的技术),事件分离者等IOCompletion事件完成[1]. 这种异步模式的典型实现是基于操作系统底层异步API的,所以我们可称之为“系统级别”的或者“真正意义上”的异步,因为具体的读写是由操作系统代劳的。

    举另外个例子来更好地理解Reactor与Proactor两种模式的区别。这里我们只关注read操作,因为write操作也是差不多的。下面是Reactor的做法:

    • 某个事件处理者宣称它对某个socket上的读事件很感兴趣;
    • 事件分离者等着这个事件的发生;
    • 当事件发生了,事件分离器被唤醒,这负责通知先前那个事件处理者;
    • 事件处理者收到消息,于是去那个socket上读数据了. 如果需要,它再次宣称对这个socket上的读事件感兴趣,一直重复上面的步骤;

    下面再来看看真正意义的异步模式Proactor是如何做的:

    • 事件处理者直接投递发一个写操作(当然,操作系统必须支持这个异步操作). 这个时候,事件处理者根本不关心读事件,它只管发这么个请求,它魂牵梦萦的是这个写操作的完成事件。这个处理者很拽,发个命令就不管具体的事情了,只等着别人(系统)帮他搞定的时候给他回个话。
    • 事件分离者等着这个读事件的完成(比较下与Reactor的不同);
    • 当事件分离者默默等待完成事情到来的同时,操作系统已经在一边开始干活了,它从目标读取数据,放入用户提供的缓存区中,最后通知事件分离者,这个事情我搞完了;
    • 事件分享者通知之前的事件处理者: 你吩咐的事情搞定了;
    • 事件处理者这时会发现想要读的数据已经乖乖地放在他提供的缓存区中,想怎么处理都行了。如果有需要,事件处理者还像之前一样发起另外一个写操作,和上面的几个步骤一样。

    现行做法

    开源C++开发框架 ACE[1, 3](Douglas Schmidt, et al.开发) 提供了大量平台独立的底层并发支持类(线程、互斥量等). 同时在更高一层它也提供了独立的几组C++类,用于实现Reactor及Proactor模式。 尽管它们都是平台独立的单元,但他们都提供了不同的接口.

    ACE Proactor在MS-Windows上无论是性能还在健壮性都更胜一筹,这主要是由于Windows提供了一系列高效的底层异步API.
    [4, 5].

    (这段可能过时了点吧) 不幸的是,并不是所有操作系统都为底层异步提供健壮的支持。举例来说, 许多Unix系统就有麻烦.因此, ACE Reactor可能是Unix系统上更合适的解决方案. 正因为系统底层的支持力度不一,为了在各系统上有更好的性能,开发者不得不维护独立的好几份代码: 为Windows准备的ACE Proactor以及为Unix系列提供的ACE Reactor.

    就像我们提到过的,真正的异步模式需要操作系统级别的支持。由于事件处理者及操作系统交互的差异,为Reactor和Proactor设计一种通用统一的外部接口是非常困难的。这也是设计通行开发框架的难点所在。

    更好的解决方案

    在文章这一段时,我们将尝试提供一种融合了Proactor和Reactor两种模式的解决方案. 为了演示这个方案,我们将Reactor稍做调整,模拟成异步的Proactor模型(主要是在事件分离器里完成本该事件处理者做的实际读写工作,我们称这种方法为”模拟异步“)。 下面的示例可以看看read操作是如何完成的:

    • 事件处理者宣称对读事件感兴趣,并提供了用于存储结果的缓存区、读数据长度等参数;
    • 调试者等待(比如通过select());
    • 当有事件到来(即可读),调试者被唤醒, 调试者去执行非阻塞的读操作(前面事件处理者已经给了足够的信息了)。读完后,它去通知事件处理者。
    • 事件处理者这时被知会读操作已完成,它拥有完整的原先想要获取的数据了.

    我们看到,通过为分离者(也就上面的调试者)添加一些功能,可以让Reactor模式转换为Proactor模式。所有这些被执行的操作,其实是和Reactor模型应用时完全一致的。我们只是把工作打散分配给不同的角色去完成而已。这样并不会有额外的开销,也不会有性能上的的损失,我们可以再仔细看看下面的两个过程,他们实际上完成了一样的事情:

    标准的经典的 Reactor模式:

    • 步骤 1) 等待事件 (Reactor 的工作)
    • 步骤 2) 发”已经可读”事件发给事先注册的事件处理者或者回调 ( Reactor 要做的)
    • 步骤 3) 读数据 (用户代码要做的)
    • 步骤 4) 处理数据 (用户代码要做的)

    模拟的Proactor模式:

    • 步骤 1) 等待事件 (Proactor 的工作)
    • 步骤 2) 读数据(看,这里变成成了让 Proactor 做这个事情)
    • 步骤 3) 把数据已经准备好的消息给用户处理函数,即事件处理者(Proactor 要做的)
    • 步骤 4) 处理数据 (用户代码要做的)

    在没有底层异步I/O API支持的操作系统,这种方法可以帮我们隐藏掉socket接口的差异(无论是性能还是其它), 提供一个完全可用的统一“异步接口”。这样我们就可以开发真正平台独立的通用接口了。

    TProactor

    我们提出的TProactor方案已经由TerabitP/L [6]公司实现了. 它有两种实现: C++的和Java的.C++版本使用了ACE平台独立的底层元件,最终在所有操作系统上提供了统一的异步接口。

    TProactor中最重要的组件要数Engine和WaitStrategy了. Engine用于维护异步操作的生命周期;而WaitStrategy用于管理并发策略. WaitStrategy和Engine一般是成对出现的, 两者间提供了良好的匹配接口.

    Engines和等待策略被设计成高度可组合的(完整的实现列表请参照附录1)。TProactor是高度可配置的方案,通过使用异步内核API和同步Unix API(select(), poll(), /dev/poll (Solaris 5.8+), port_get (Solaris 5.10),RealTime (RT) signals (Linux 2.4+), epoll (Linux 2.6), k-queue (FreeBSD) ),它内部实现了三种引擎(POSIX AIO, SUN AIO and Emulated AIO)并隐藏了六类等待策略。TProactor实现了和标准的 ACE Proactor一样的接口。这样一来,为不同平台提供通用统一的只有一份代码的跨平台解决方案成为可能。

    Engines和WaitStrategies可以像乐高积木一样自由地组合,开发者可以在运行时通过配置参数来选择合适的内部机制(引擎和等待策略)。可以根据需求设定配置,比如连接数,系统伸缩性,以及运行的操作系统等。如果系统支持相应的异步底层API,开发人员可以选择真正的异步策略,否则用户也可以选择使用模拟出来的异步模式。所有这一切策略上的实现细节都不太需要关注,我们看到的是一个可用的异步模型。

    举例来说,对于运行在Sun Solaris上的HTTP服务器,如果需要支持大量的连接数,/dev/poll或者port_get()之类的引擎是比较合适的选择;如果需要高吞吐量,那使用基本select()的引擎会更好。由于不同选择策略内在算法的问题,像这样的弹性选择是标准ACE Reactor/Proactor模式所无法提供的(见附录2)。

    在性能方面,我们的测试显示,模拟异步模式并未造成任何开销,没有变慢,反倒是性能有所提升。根据我们的测试结果,TProactor相较标签的ACE Reactor在Unix/Linux系统上有大约10-35%性能提升,而在Windows上差不多(测试了吞吐量及响应时间)。

    性能比较 (JAVA / C++ / C#).

    除了C++,我们也在Java中实现了TProactor. JDK1.4中, Java仅提供了同步方法, 像C中的select() [7, 8]. Java TProactor基于Java的非阻塞功能(java.nio包),类似于C++的TProactor使用了select()引擎.

    图1、2显示了以 bits/sec为单位的传输速度以及相应的连接数。这些图比较了以下三种方式实现的echo服务器:标准ACE Reactor实现(基于RedHat Linux9.0)、TProactor C++/Java实现(Microsoft Windows平台及RedHat v9.0), 以及C#实现。测试的时候,三种服务器使用相同的客户端疯狂地连接,不间断地发送固定大小的数据包。

    这几组测试是在相同的硬件上做的,在不同硬件上做的相对结果对比也是类似。

    图 1. Windows XP/P4 2.6GHz HyperThreading/512 MB RAM.
    图 2. Linux RedHat 2.4.20-smp/P4 2.6GHz HyperThreading/512 MB RAM.

    用户代码示例

    下面是TProactor Java实现的echo服务器代码框架。总的来说,开发者只需要实现两个接口:一是OpRead,提供存放读结果的缓存;二是OpWrite,提供存储待写数据的缓存区。同时,开发者需要通过回调onReadComplated()和onWriteCompleted()实现协议相关的业务代码。这些回调会在合适的时候被调用.

    class EchoServerProtocol implements AsynchHandler
    {
    
      AsynchChannel achannel = null;
    
      EchoServerProtocol( Demultiplexor m,  SelectableChannel channel )
      throws Exception
      {
        this.achannel = new AsynchChannel( m, this, channel );
      }
    
      public void start() throws Exception
      {
        // called after construction
        System.out.println( Thread.currentThread().getName() +
    	": EchoServer protocol started" );
        achannel.read( buffer);
      }
    
      public void onReadCompleted( OpRead opRead ) throws Exception
      {
        if ( opRead.getError() != null )
        {
          // handle error, do clean-up if needed
          System.out.println( "EchoServer::readCompleted: " +
          opRead.getError().toString());
          achannel.close();
          return;
        }
    
        if ( opRead.getBytesCompleted () <= 0)
        {
          System.out.println("EchoServer::readCompleted: Peer closed "
    	   + opRead.getBytesCompleted();
          achannel.close();
          return;
        }
    
        ByteBuffer buffer = opRead.getBuffer();
    
        achannel.write(buffer);
      }
    
      public void onWriteCompleted(OpWrite opWrite)
      throws Exception
      {
        // logically similar to onReadCompleted
        ...
      }
    }
    

    结束语

    TProactor为多个平台提供了一个通用、弹性、可配置的高性能通讯组件,所有那些在附录2中提到的问题都被很好地隐藏在内部实现中了。

    从上面的图中我们可以看出C++仍旧是编写高性能服务器最佳选择,虽然Java已紧随其后。然而因为Java本身实现上的问题,其在Windows上表现不佳(这已经应该成为历史了吧)。

    需要注意的是,以上针对Java的测试,都是以裸数据的形式测试的,未涉及到数据的处理(影响性能)。

    纵观AIO在Linux上的快速发展[9], 我们可以预计Linux内核API将会提供大量更加强健的异步API, 如此一来以后基于此而实现的新的Engine/等待策略将能轻松地解决能用性方面的问题,并且这也能让标准ACE Proactor接口受益。

    附录 I

    TProactor中实现的Engines 和 等待策略

    引擎类型 等待策略 操作系统
    POSIX_AIO (true async)

    aio_read()/aio_write()
    aio_suspend()
    Waiting for RT signal
    Callback function
    POSIX complained UNIX (not robust)
    POSIX (not robust)
    SGI IRIX, LINUX (not robust)
    SUN_AIO (true async)

    aio_read()/aio_write()
    aio_wait() SUN (not robust)
    Emulated Async

    Non-blocking read()/write()
    select()

    poll()

    /dev/poll

    Linux RT signals

    Kqueue
    generic POSIX

    Mostly all POSIX implementations

    SUN

    Linux

    FreeBSD

    附录 II

    所有同步等待策略可划分为两组:

    • edge-triggered (e.g. Linux实时信号) – signal readiness only when socket became ready
      (changes state);
    • level-triggered (e.g. select(), poll(), /dev/poll) – readiness at any time.

    让我们看看这两组的一些普遍的逻辑问题:

    • edge-triggered group: after executing I/O operation, the demultiplexing loop can lose the state of socket readiness. Example: the “read” handler did not read whole chunk of data, so the socket remains still ready for read. But the demultiplexor loop will not receive next notification.
    • level-triggered group: when demultiplexor loop detects readiness, it starts the write/read user defined handler. But before the start, it should remove socket descriptior from theset of monitored descriptors. Otherwise, the same event can be dispatched twice.
    • Obviously, solving these problems adds extra complexities to development. All these problems were resolved internally within TProactor and the developer should not worry about those details, while in the synch approach one needs to apply extra effort to resolve them.

    资源

    [1] Douglas C. Schmidt, Stephen D. Huston “C++ Network Programming.” 2002, Addison-Wesley
    ISBN 0-201-60464-7

    [2] W. Richard Stevens “UNIX Network Programming” vol. 1 and 2, 1999, Prentice Hill, ISBN 0-13-
    490012-X

    [3] Douglas C. Schmidt, Michael Stal, Hans Rohnert, Frank Buschmann “Pattern-Oriented Software
    Architecture: Patterns for Concurrent and Networked Objects, Volume 2″ Wiley & Sons, NY
    2000

    [4] INFO: Socket Overlapped I/O Versus Blocking/Non-blocking Mode. Q181611. Microsoft
    Knowledge Base Articles.

    [5] Microsoft MSDN. I/O Completion Ports.

    http://msdn.microsoft.com/library/default.asp?url=/library/en-

    us/fileio/fs/i_o_completion_ports.asp

    [6] TProactor (ACE compatible Proactor).

    www.terabit.com.au

    [7] JavaDoc java.nio.channels

    http://java.sun.com/j2se/1.4.2/docs/api/java/nio/channels/package-summary.html

    [8] JavaDoc Java.nio.channels.spi Class SelectorProvider

    http://java.sun.com/j2se/1.4.2/docs/api/java/nio/channels/spi/SelectorProvider.html

    [9] Linux AIO development

    http://lse.sourceforge.net/io/aio.html, and

    http://archive.linuxsymposium.org/ols2003/Proceedings/All-Reprints/Reprint-Pulavarty-OLS2003.pdf

    更多

    Ian Barile “I/O Multiplexing & Scalable Socket Servers”, 2004 February, DDJ

    Further reading on event handling

    http://www.cs.wustl.edu/~schmidt/ACE-papers.html

    The Adaptive Communication Environment

    http://www.cs.wustl.edu/~schmidt/ACE.html

    Terabit Solutions

    http://terabit.com.au/solutions.php

    关于作者

    Alex Libman has been programming for 15 years. During the past 5 years his main area of interest is pattern-oriented multiplatform networked programming using C++ and Java. He is big fan and contributor of ACE.

    Vlad Gilbourd works as a computer consultant, but wishes to spend more time listening jazz :) As a hobby,he started and runs www.corporatenews.com.au website.

    作者:sunu | 分类目录:C/C++编程相关 | 标签:
  • 字符集和字符编码(及答复spring)

    2010-12-31

    @spring 给你留言了,但似乎被当成垃圾评论,你在后台查一下。

    这个主题已经被N多人讨论过了,这里仅仅是个人总结,不是教程。

    字符集和字符编码

    潘孙友 2010-12-31 于遵义

    目录
    一、字符集
    二、字符编码
    三、Windows平台
      3.1 Codepage代码页
      3.2 编码转换(API)
      3.3 编码转换(CRT)
    四、Linux/unix平台
      4.1 iconv
      4.2 ICU
    

    一、字符集

    字符集是一个集合,描述并定义了这个集合中可以出现哪些字符,常见的字符有GB2312、GBK、GB18030、UNICODE等。字符集仅仅是一种规范,一种约定,我们也可以定义自己的字符集。

    举例来说,银行IT系统为了字段合法性校验的方便,常内部定义一些小字符集,比如X字符集,N字符集。

    x-字符集由以下86个字符组成
    a b c d e f g h I j k l m n o p q r s t u v w x y z
    A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
    0 1 2 3 4 5 6 7 8 9
    . , - _ ( ) / = ’+ : ? ! ” % & * < > ; @ #
    (cr)(lf) (space)
    

    不同的字符集之间很可能是有交集的,并且包含越多字符的字符集越通用。中国的BG2312,GBK,GB18030是在不同时期,逐步扩展而来的,所以GB18030是前者的超集。现今最大的字符集是UNICODE,几乎包含了世界上所有语言的字符。

    二、字符编码

    先有字符集才有字符编码,编码是字符集的具体表示方式,一个字符集可以有多种编码方式,只要这种方式可以涵盖字符集中的所有字符。

    比如UNICODE字符集的具体编码方式有很多种,utf-8/utf-16/utf-32;而gb2312就只有常见的一种编码方式GB2312(所以有时候人们常将编码与字符集弄混)。可能有人会说,utf-8中包含了gb2312的所有字符,那不就是以utf-8编码表示了gb2312字符集么,事实是这样,但问题人家utf-8编码方式又不是为你gb2312定制的,人家只是顺手把你给表示了。

    不同的编码方式,根据其特性应用在不同场合。以UNICODE字符集的编码方式为例,其中utf-8以尽可能少的存储空间存放字符,自动纠错性能好(由于编码的特殊性,其一个字符出错只会影响其后几个字节,而utf16/32等等长字节的就连错一大片了),适合传输及存储;utf-16/32以等字节表示所有字符,在程序内部更容易处理,一般用于系统内部字符格式。

    三、Windows平台

    3.1 Codepage代码页

    Windows平台有所谓的代码页,用于表示字符编码方式,至少支持哪些,要看你的系统里安装了哪些。通过GetACP我们可以获取当前系统的编码方式。

    WINBASEAPI UINT WINAPI GetACP(void);
    

    通过以下小段代码,可以获举系统支持的编码方式。

    	for(int i=0; i<=65001; i++)
    	{
    		CPINFOEXA cpinfo;
    		if (IsValidCodePage(i)){
    			if (GetCPInfoExA(i, 0, &cpinfo))
    				printf("%d=[%s]\n", cpinfo.CodePage, cpinfo.CodePageName);
    		}
    	}
    

    详细的列表见:
    http://msdn.microsoft.com/en-us/library/dd317756(v=VS.85).aspx

    3.2 编码转换(API)

    Windows为我们提供了两个函数用于字符编码的转换。

    WINBASEAPI
    int
    WINAPI
    MultiByteToWideChar(
        __in UINT     CodePage,
        __in DWORD    dwFlags,
        __in_bcount(cbMultiByte) LPCSTR   lpMultiByteStr,
        __in int      cbMultiByte,
        __out_ecount_opt(cchWideChar) __transfer(lpMultiByteStr) LPWSTR  lpWideCharStr,
        __in int      cchWideChar);
    
    WINBASEAPI
    int
    WINAPI
    WideCharToMultiByte(
        __in UINT     CodePage,
        __in DWORD    dwFlags,
        __in_ecount(cchWideChar) LPCWSTR  lpWideCharStr,
        __in int      cchWideChar,
        __out_bcount_opt(cbMultiByte) __transfer(lpWideCharStr) LPSTR   lpMultiByteStr,
        __in int      cbMultiByte,
        __in_opt LPCSTR   lpDefaultChar,
        __out_opt LPBOOL  lpUsedDefaultChar);
    

    Windows内部应该是完全使用Unicode编码的,系统提供的W后缀的API都接受wchar_t(unsigned short)的字符串,而A系列的API实则是先内部转换为Unicode编码再调用W系统函数。

    在windows上做不同编码间的转换,一般是先将原始编码转换为Unicode编码,再将Unicode编码的字符串转换为目标编码。以gbk到utf-8为例:

    std::string ctk_gbk2utf8(const char*s)
    {
    	s = s?s:"";
    	std::wstring unicodestr;
    	std::string utf8str;
    	//gbk转换成utf16
    	int n = MultiByteToWideChar(936, 0, s, -1, NULL, 0);
    
    	unicodestr.resize(n);
    	MultiByteToWideChar(936, 0, s, -1, (wchar_t *)unicodestr.c_str(), (int)unicodestr.length());
    
    	//从utf16转utf8
    	n = WideCharToMultiByte(CP_UTF8, 0, unicodestr.c_str(), -1, 0, 0, 0, 0 );
    	utf8str.resize(n);
    	WideCharToMultiByte(CP_UTF8, 0, unicodestr.c_str(), -1, (char*)utf8str.c_str(), (int)utf8str.length(), 0, 0 );
    
    	return utf8str;
    }
    
    std::string ctk_utf82gbk(const char*s)
    {
    	s = s?s:"";
    	std::wstring unicodestr;
    	std::string gbkstr;
    	//utf8转换成utf16
    	int n = MultiByteToWideChar(CP_UTF8, 0, s, -1, NULL, 0);
    
    	unicodestr.resize(n);
    	MultiByteToWideChar(CP_UTF8, 0, s, -1, (wchar_t *)unicodestr.c_str(), (int)unicodestr.length());
    
    	//从utf16转gbk
    	n = WideCharToMultiByte(936, 0, unicodestr.c_str(), -1, 0, 0, 0, 0 );
    	gbkstr.resize(n);
    	WideCharToMultiByte(936, 0, unicodestr.c_str(), -1, (char*)gbkstr.c_str(), (int)gbkstr.length(), 0, 0 );
    
    	return gbkstr;
    }
    

    测试代码:

    	const char*gbk  = "这是中文. hello world!";
    	printf("gbk=%s\n", gbk);
    
    	string utf8str = ctk_gbk2utf8(gbk);
    	printf("utf8str=%s\n", utf8str.c_str());
    
    	string gbkstr = ctk_utf82gbk(utf8str.c_str());
    	printf("gbkstr=%s\n", gbkstr.c_str());
    

    如果要处理像日本或者韩国语言编码,只要换一个那个936为相应的代码就可以了。上面的CP_UTF8可以换为65501。一般情况下,使用系统API转换编码就差不多了。

    3.3 编码转换(CRT)

    另外我们可以通过c/c++标准库中的mbstocws, cwstombs函数来转码,我们可通过两组函数来测试,一组为Windows API实际的,一组为
    mbstocws, cwstombs实现,效果是一样的。

    std::string ctk_gbk2utf8(const char*s)
    {
    	s = s?s:"";
    	std::wstring unicodestr;
    	std::string utf8str;
    	int n = MultiByteToWideChar(936, 0, s, -1, NULL, 0);
    	unicodestr.resize(n);
    	MultiByteToWideChar(936, 0, s, -1, (wchar_t *)unicodestr.c_str(), (int)unicodestr.length());
    	n = WideCharToMultiByte(CP_UTF8, 0, unicodestr.c_str(), -1, 0, 0, 0, 0 );
    	utf8str.resize(n);
    	WideCharToMultiByte(CP_UTF8, 0, unicodestr.c_str(), -1, (char*)utf8str.c_str(), (int)utf8str.length(), 0, 0 );
    	return utf8str;
    }
    
    std::string ctk_gbk2big5(const char*s)
    {
    	s = s?s:"";
    	std::wstring unicodestr;
    	std::string dststr;
    	int n = MultiByteToWideChar(936, 0, s, -1, NULL, 0);
    	unicodestr.resize(n);
    	MultiByteToWideChar(936, 0, s, -1, (wchar_t *)unicodestr.c_str(), (int)unicodestr.length());
    	n = WideCharToMultiByte(950, 0, unicodestr.c_str(), -1, 0, 0, 0, 0 );
    	dststr.resize(n);
    	WideCharToMultiByte(950, 0, unicodestr.c_str(), -1, (char*)dststr.c_str(), (int)dststr.length(), 0, 0 );
    	return dststr;
    }
    
    std::string ctk_gbk2big5_crt(const char*s)
    {
    	s = s?s:"";
    	std::string srcstr = s;
    	std::string curLocale = setlocale(LC_ALL, NULL);
    	setlocale(LC_ALL, ".936");
    	size_t newSize = srcstr.length() + 1;
    	wstring unicodestr;
    	unicodestr.resize(newSize);
    	wmemset((wchar_t*)unicodestr.c_str(), 0, newSize);
    	mbstowcs((wchar_t*)unicodestr.c_str(), srcstr.c_str(), newSize);
    	string newstr;
    	newSize = newSize*2 + 1;
    	setlocale(LC_ALL, ".950");
    	newstr.resize(newSize);
    	memset((char*)newstr.c_str(), 0, newSize);
    	wcstombs((char*)newstr.c_str(), unicodestr.c_str(), newSize);
    	setlocale(LC_ALL, curLocale.c_str());
    	return newstr;
    }
    
    std::string ctk_big52gbk_crt(const char*s)
    {
    	s = s?s:"";
    	std::string srcstr = s;
    	std::string curLocale = setlocale(LC_ALL, NULL);
    	setlocale(LC_ALL, ".950");
    
    	size_t newSize = srcstr.length() + 1;
    	wstring unicodestr;
    	unicodestr.resize(newSize);
    	wmemset((wchar_t*)unicodestr.c_str(), 0, newSize);
    	mbstowcs((wchar_t*)unicodestr.c_str(), srcstr.c_str(), newSize);
    	string newstr;
    	newSize = newSize*2 + 1;
    	setlocale(LC_ALL, ".936");
    	newstr.resize(newSize);
    	memset((char*)newstr.c_str(), 0, newSize);
    	wcstombs((char*)newstr.c_str(), unicodestr.c_str(), newSize);
    	setlocale(LC_ALL, curLocale.c_str());
    	return newstr;
    }
    

    四、Linux/unix平台

    4.1 iconv

    这些平台上,字符编码的转换一般使用iconv,可能是独立的iconv库也可能是glibc自带的版本。
    在linux/unix上字符编码的问题比windows更容易碰到,所以经常有人喊搭了个ftp传上来的文件名乱码什么的(顺便提一句,包括IE7在内的之前的IE版本直接访问utf8编码的ftp时会有乱码,通过网络抓包工具会发现是IE发送的utf-8字符串是部分错误的)。

    暂时只谈程序中的编码问题,先不管locale之类的(虽然也相关)。

    自己“封装”的转码函数。

    char *ctk_iconv(const char *fromStr, const int fromLen, char**toStr,  const char *fromCode, const char *toCode)
    {
    	char *buffer;
    	iconv_t cd;
    	const char *inbuf = NULL;
    	size_t inbytesleft = 0;
    	char *outbuf = NULL;
    	size_t outbytesleft = 0;
    	int errorCode = 0;
    	int bufferSize=0;
    	size_t ret = 0;
    	int done = 0;
    
    	if (fromStr==NULL || fromStr[0]=='\0' || fromLen <=0 ) return NULL;
    	if (fromCode==NULL || fromCode[0]=='\0' ) return NULL;
    	if (toCode==NULL || toCode[0]=='\0' ) return NULL;
    
    	memset(&cd, 0x00, sizeof(iconv_t));
    	inbuf = fromStr;
    	inbytesleft = fromLen;
    
    	errorCode = 0;
    	bufferSize = fromLen*4+1;
    	buffer = (char*)malloc(sizeof(char)*bufferSize);
    	memset(buffer, 0x00, bufferSize);
    
    	outbuf = buffer;
    	outbytesleft = bufferSize;
    
    	if ( (iconv_t)-1  == ( cd = iconv_open(toCode, fromCode) ) ) {
    		return NULL;
    	}	
    
    	while ( inbytesleft >0 && done !=1 ) {
    		ret = iconv(cd, (char**)&inbuf, &inbytesleft, &outbuf, &outbytesleft);
    		if ( (size_t)-1  == ret ) {
    			errorCode = errno;
    			switch(errorCode)
    			{
    			case EILSEQ:
    			{
    				if((outbuf<buffer+bufferSize)&&(outbuf>=buffer))
    				{
    					memcpy(outbuf, inbuf, 1);
    					outbuf += 1;
    					outbytesleft -= 1;
    					inbuf += 1;
    					inbytesleft -= 1;
    					if ( inbytesleft <= 0 ) break;
    				}
    			}
    			break;
    			case EINVAL:
    			{
    				done = 1;
    			}
    			break;
    			case E2BIG:
    			{
    				done = 1;
    				break;
    			}
    			break;
    			default:
    				done = 1;
    			}
    		}
    	}
    	if ( NULL != toStr)
    		*toStr = buffer;
    	iconv_close(cd);
    	return buffer;
    }
    
    std::string ctk_iconv_gbk2utf8(const char*s)
    {
    	s = s ? s:"";
    	char *utf8str = NULL;
    	ctk_iconv(s, strlen(s), &utf8str,  "gbk", "utf-8");
    	std::string result("");
    	if (utf8str!=NULL)
    	{
    		result = utf8str;
    		free(utf8str);
    	}
    	return result;
    }
    
    std::string ctk_iconv_utf82gbk(const char*s)
    {
    	s = s ? s:"";
    	char *gbkstr = NULL;
    	ctk_iconv(s, strlen(s), &gbkstr, "utf-8", "gbk");
    	std::string result("");
    	if (gbkstr!=NULL)
    	{
    		result = gbkstr;
    		free(gbkstr);
    	}
    	return result;
    }
    

    Iconv在windows上也非常好用。

    4.2 ICU

    IBM出品的ICU也是编码转换的好手,到处有它的身影,php6就使用它来做内码。由于没有亲身使用经历,就不多说了。

    http://www-01.ibm.com/software/globalization/icu/index.html

    作者:sunu | 分类目录:C/C++ | 标签:
  • gSOAP学习笔记

    2010-12-27

    gSOAP学习笔记

    潘孙友 2010-12-27 于遵义

    目录
    一、基本概念
      1.1 关于SOAP
      1.2 关于gSOAP
      1.3 gSOAP编译器(命令行工具)
        1.3.1 wsdl2h
        1.3.2 socapcpp2
    二、gSOAP开发:Web Service服务端
    三、gSOAP开发:Web Service客户端
    四、参考资料
    

    一、基本概念

    1.1 关于SOAP

    SOAP(Simple Object Access Protocol),即简单对象访问协议,是在分布式的环境中交换数据的简单协议,以XML作为数据传送语言。
    SOAP有两种工作模式,一种是RPC(Remote Procedure Call),另一种是Message-Oriented。MO可以利用XML来交换结构更复杂的数据。RPC模式的SOAP可以理解为这样一个开发协议:SOAP=RPC+HTTP+XML,具有以下特点:

    • 采用HTTP作为通信协议,采用客户/服务模式;
    • RPC作为统一的远程方法调用途径;
    • 传送的数据使用XML格式。

    看一个简单的请求及回复SOAP数据(真实数据):

    POST /wpsoap/ HTTP/1.1
    Host: 127.0.0.1:10240
    User-Agent: gSOAP/2.7
    Content-Type: text/xml; charset=utf-8; action=""
    Content-Length: 480
    Connection: close
    SOAPAction: ""
    
    <?xml version="1.0" encoding="UTF-8"?>
    <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns1="http://www.example.org/wpsoap/" xmlns:ns2="urn:nszfpt"><SOAP-ENV:Body><ns2:login><req><username>admin</username><password>3.14159</password></req></ns2:login></SOAP-ENV:Body></SOAP-ENV:Envelope>
    
    HTTP/1.1 200 OK
    Server: gSOAP/2.7
    Content-Type: text/xml; charset=utf-8; action=""
    Content-Length: 555
    Connection: close
    
    <?xml version="1.0" encoding="UTF-8"?>
    <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wpsoap="urn:nszfpt"><SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><wpsoap:tagRspLogin><rsp><retCode>0</retCode><retMessage>login ok!</retMessage></rsp><session>01234567890</session></wpsoap:tagRspLogin></SOAP-ENV:Body></SOAP-ENV:Envelope>
    

    这东西非常的复杂,我仅仅记录一下使用到的部分。

    1.2 关于gSOAP

    引用:http://blog.csdn.net/darkone/archive/2006/12/14/1442525.aspx
    gSOAP编译工具提供了一个SOAP/XML 关于C/C++ 语言的实现,
    从而让C/C++语言开发web服务或客户端程序的工作变得轻松
    了很多。绝大多数的C++web服务工具包提供一组API函数类库
    来处理特定的SOAP数据结构,这样就使得用户必须改变程序
    结构来适应相关的类库。与之相反,gSOAP利用编译器技术提
    供了一组透明化的SOAP API,并将与开发无关的SOAP实现细节
    相关的内容对用户隐藏起来。gSOAP的编译器能够自动的将用
    户定义的本地化的C或C++数据类型转变为符合XML语法的数据
    结构,反之亦然。这样,只用一组简单的API就将用户从SOAP
    细节实现工作中解脱了出来,可以专注与应用程序逻辑的实
    现工作了。gSOAP编译器可以集成C/C++和Fortran代码(通过
    一个Fortran到C的接口),嵌入式系统,其他SOAP程序提供
    的实时软件的资源和信息;可以跨越多个操作系统,语言环
    境以及在防火墙后的不同组织。
    	gSOAP使编写web服务的工作最小化了。gSOAP编译器生成
    SOAP的代码来序列化或反序列化C/C++的数据结构。gSOAP包
    含一个WSDL生成器,用它来为你的web服务生成web服务的解
    释。gSOAP的解释器及导入器可以使用户不需要分析web服务
    的细节就可以实现一个客户端或服务端程序。
    

    照我理解,gSOAP可以为我们生成soap服务器端+客户端代码的框架,我们只需实现具体的接口函数即可。而生成代码的工具就是上面文中提到的“gSOAP编译器”。

    1.3 gSOAP编译器(命令行工具)

    1.3.1 wsdl2h

    此工具用来从WSDL文件生成c/c++头文件。

    wsdl2h -o 头文件名 WSDL文件名或URL
    常用的其它参数:
    -o 文件名,指定输出头文件
    -n 名空间前缀 代替默认的ns
    -c 产生纯C代码,否则是C++代码
    -s 不要使用STL代码
    -t 文件名,指定type map文件,默认为typemap.dat
    -e 禁止为enum成员加上名空间前缀
    

    1.3.2 socapcpp2

    此工具用来从头文件,生成SOAP服务器及客户端代码,还包括WSDL、测试用XML数据。

    soapcpp2 头文件
    常用选项
    -C 仅生成客户端代码
    -S 仅生成服务器端代码
    -L 不要产生soapClientLib.c和soapServerLib.c文件
    -c 产生纯C代码,否则是C++代码(与头文件有关)
    -I 指定import路径(见上文)
    -x 不要产生XML示例文件
    -i 生成C++包装,客户端为xxxxProxy.h(.cpp),服务器端为xxxxService.h(.cpp)。
    

    二、gSOAP开发:Web Service服务端

    开发服务器程序,需使用gSOAP生成服务器端代码框架。我们有两种做法:

    1. 编写WSDL,使用wsdl2h生成头文件,再soapcpp2生成框架代码;
    2. 编写头文件,使用soapcpp2生成框架代码;

    这两种方式,结果是一样的,最终都有产生头文件,并生成代码。不同在于,在项目的开发中需要维护的文件不同,前者是需要维护WSDL文件,后者维护头文件。

    我个人觉得第二种方式更好用,不仅仅是少了个步骤,而是WSDL的语法太难写了,有点XSD的味道。而头文件的编写,更接近于程序员的思考方式,比如定义消息结构,定义接口名称等。

    gSOAP是非常智能的,它利用C/C++的注释来获取信息,所以在手工编写的头文件中,注释是用用处的,常以// gsoap 名字空间 …开头。做为学习,我准备为php blog程序wordpress写一个web service接口,名字叫wpsoap。

    我开始写头文件(wpSoap.h)了,出于学习目的,我仅实现了两个接口:一是用户登陆;一是日志发布。

    /**
     * @file wpsoap.h
     * @brief 为wordpress2.7提供web service接口
     *
     *  "//gsoap"开头行,请勿删除.
     *
     *  1. 通过此文件生成WSDL 及 服务端代码
     *
     *    >mkdir -p srvSrcFromH
     *    >cd srvSrcFromH
     *    >soapcpp2 -L -S "wpsoap.h" -I /path/to/gsoap-2.8/gsoap/import/
     *
     *  2. 通过WSDL生成客户端代码
     *
     *    >mkdir -p clientSrcFromWSDL
     *    >cd clientSrcFromWSDL
     *    >wsdl2h.exe  -o wpsoap.h ../srvSrcFromH/wpsoap.wsdl -I /path/to/gsoap-2.8/gsoap/import/
     *    >soapcpp2 -L -C wpsoap.h -I /path/to/gsoap-2.8/gsoap/import/
     *
     * @author pansunyou@gmail.com
     * @version 1.0
     * @date 2010-12-27
    */
    
    //gsoap wpsoap service name: wpsoap
    //gsoap wpsoap service namespace: http://www.example.org/wpsoap/
    //gsoap wpsoap service location: http://192.168.0.187:10240/wpsoap/
    //gsoap wpsoap service encoding: encoded
    //gsoap wpsoap schema namespace: urn:nszfpt
    
    #import "stlvector.h"
    
    //通用回复
    class wpsoap__tagCommResponse
    {
           int                  retCode                 ;      //回复码
           std::string retMessage           ;      //回复消息
    };
    
    //[请求]用户登陆
    class wpsoap__tagReqLogin
    {
        std::string username              ;      //用户名
        std::string password        ;      //密码名文
    };
    
    //[答复]用户登陆
    class wpsoap__tagRspLogin
    {
        wpsoap__tagCommResponse rsp    ;      //通用回复
           std::string session                 ;      //会话标识
    };
    
    //[接口]登陆接口
    int wpsoap__login(wpsoap__tagReqLogin req, wpsoap__tagRspLogin& rsp);
    
    //[请求]发布日志
    class wpsoap__tagReqPost
    {
        std::string title         ;      //标题
        std::string body              ;      //正文
    };
    
    //[答复]发布日志
    class wpsoap__tagRspPost
    {
        wpsoap__tagCommResponse rsp    ;      //通用回复
    };
    
    //[接口]发布日志接口
    int wpsoap__post(wpsoap__tagReqPost req, wpsoap__tagRspPost& rsp);
    

    在接口中,我使用到了自定义的消息结构wp_soap_tag*,这里的wpsoap__前缀是必须的,这样soapcpp2才能为我们生成正确的代码。

    之后,我使用soapcpp2生成服务端代码框架:

    @echo off
    @set path=%cd%\..\..\contrib\gsoap-2.8\gsoap\bin\win32\;%path%
    
    mkdir srvSrcFromH 2>nul
    cd srvSrcFromH
    soapcpp2.exe -L -S ..\res\wpSoap.h -I ..\..\..\contrib\gsoap-2.8\gsoap\import\
    pause
    

    要编译出服务程序,有这些代码还不够,还需要自己写两个文件,一个用来写main函数,一个用来写wpsoap的接口函数(当然可以放在一个文件里)。最终我的服务器程序有以下文件:(另外,还需要gsoap目录下的stdsoap2.cpp,因为我把它编译为静态库了,所以这里没列出来。)

    D:\wpSoapServer
    |   makeSrc.bat
    |   wpsoapimpl.cpp                //这里实现了soapStub.h给出的接口
    |   wpsoapsrv.cpp                  //这里是main函数开始的地方
    +---res
    |       wpSoap.h
    \---srvSrcFromH
            soapC.cpp
            soapH.h
            soapServer.cpp
            soapStub.h
            soapwpsoapObject.h
            wpsoap.login.req.xml
            wpsoap.login.res.xml
            wpsoap.nsmap
            wpsoap.post.req.xml
            wpsoap.post.res.xml
            wpsoap.wsdl
            wpsoap.xsd
    

    每次我修改了res/wpSoap.h后,我就运行一下makeSrc.bat,自动重新生成srvSrcFromH目录里的所有东西,并且这个目录里的所有代码是不需要手工维护的(除非有特殊需要)。

    在服务器代码中,我仅实现了以下两个函数(wpsoapimpl.cpp):

    int wpsoap__login(struct soap*, wpsoap__tagReqLogin req, wpsoap__tagRspLogin &rsp);
    int wpsoap__post(struct soap*, wpsoap__tagReqPost req, wpsoap__tagRspPost &rsp);
    

    wpsoapsrv.cpp里的代码仅仅是调用gSOAP产生的代码来建立socket服务器,基本不需维护。gSOAP是线程安全的,可以将请求分配到线程池内实现高效服务,但我仅为了走通gSOAP的使用流程,没有这样使用。

    具体做法可以参考:http://www.cs.fsu.edu/~engelen/soapdoc2.html

    三、gSOAP开发:Web Service客户端

    客户端代码本来也是可以通过为服务端编写的头文件生成的,但是为了真实一点,假设我无法获取服务器开发时使用的头文件,仅仅有个公开的WSDL文件,就是上面产生的srvSrcFromH /wpsoap.wsdl。

    我用这个脚本来生成客户端框架代码:

    @echo off
    @set path=%cd%\..\..\contrib\gsoap-2.8\gsoap\bin\win32\;%path%
    
    mkdir clientSrcFromWSDL 2>nul
    cd clientSrcFromWSDL
    wsdl2h.exe -o wpsoap.h ..\..\wpSoapServer\srvSrcFromH\wpsoap.wsdl
    soapcpp2.exe -L -C wpsoap.h -I ..\..\..\contrib\gsoap-2.8\gsoap\import\
    pause
    

    加上我测试用的代码wpsoapclient.cpp,以及gosap目录里的stdsoap2.cpp,我有了如下文件:

    D:\wpSoapClient
    |   makeSrc.bat
    |   wpsoapclient.cpp
    \---clientSrcFromWSDL
            soapC.cpp
            soapClient.cpp
            soapH.h
            soapStub.h
            soapwpsoapProxy.h
            wpsoap.h
            wpsoap.login.req.xml
            wpsoap.login.res.xml
            wpsoap.nsmap
            wpsoap.post.req.xml
            wpsoap.post.res.xml
    

    客户端代码非常少(仅仅是实现,容错之类的都未考虑):

    /**
     * @file wpsoapclient.cpp
     * @brief 访问wpsoap服务
     *
     * 调用wpsoap的客户端示例代码
     *
     * @author pansunyou@gmail.com
     * @version 1.0
     * @date 2010-12-27
    */
    
    #define _CRT_SECURE_NO_WARNINGS
    #include <cstdio>
    #include <cstdlib>
    #include <string>
    #include "clientSrcFromWSDL/soapStub.h"
    #include "clientSrcFromWSDL/soapwpsoapProxy.h"
    #include "clientSrcFromWSDL/wpsoap.nsmap"
    
    using namespace std;
    
    int main(int argc, char*argv[])
    {
      wpsoap wpsoapClient;
      if (argc==2)
        wpsoapClient.endpoint = argv[1];
    
      //1. 登陆
      string username = "admin";
      string password = "3.14159";
      int r = 0;
    
      ns2__tagReqLogin req;
      req.username = username;
      req.password = password;
      _ns2__login ns2__login;
      ns2__login.req = &req;
      _ns2__tagRspLogin rsp;
      r = wpsoapClient.__ns1__login(&ns2__login, &rsp);
      if (r!=0)
      {
        fprintf(stderr, "调用soap接口失败!\n");
        return -1;
      }
    
      if (0!=rsp.rsp->retCode)
      {
        printf("登陆失败 retCode=%d, retMessage=%s\n",
    		rsp.rsp->retCode,
    		rsp.rsp->retMessage.c_str());
        return -1;
      }
      printf("登陆成功! [session=%s]\n",
    	rsp.session.c_str());
    
      ns2__tagReqPost reqPost;
      reqPost.body = "post article by wpsoap!";
      reqPost.title = "hello, wpsoap!";
    
      _ns2__post ns2__post;
      ns2__post.req = &reqPost;
      _ns2__tagRspPost ns2__tagRspPost;
      r = wpsoapClient.__ns1__post(&ns2__post,
    	&ns2__tagRspPost);
      if (r!=0)
      {
        fprintf(stderr, "调用soap接口失败!\n");
        return -1;
      }
    
      if (0!=rsp.rsp->retCode)
      {
        printf("发布日志失败 retCode=%d,
    		retMessage=%s\n",
    		rsp.rsp->retCode,
    		rsp.rsp->retMessage.c_str());
        return -1;
      }
      printf("日志发布成功! [retMessage=%s]\n",
      rsp.rsp->retMessage.c_str());
    
      return 0;
    }
    

    四、参考资料

    http://gsoap2.sourceforge.net/
    http://www.cppprog.com/2009/0723/138.html

    http://hi.baidu.com/winnyang/blog/item/d5fd4f3df38f35cd9e3d625b.html

    http://www.cs.fsu.edu/~engelen/soap.html
    http://tangentsoft.net/mysql++/

    作者:sunu | 分类目录:C/C++文档及资源 | 标签:
  • Colinux,不是虚拟机,胜似虚拟机

    2010-12-26

    Colinux,不是虚拟机,胜似虚拟机
    一些使用记录

    潘孙友 2010.12.25 于遵义

    目录
    一、Colinux长什么样?
      1.1 文件组成
      1.2 运行着的colinux
    二、Colinux内外文件访问
    三、colinux 网络模式
      3.1 slirp
      3.2 tuntap
      3.3 pcap-bridge/ndis-bridge
    四、安装为NT服务
      4.1 安装
      4.2 卸载服务
    五、给colinux中的linux加硬盘
    六、图形界面
    七、我的ubuntu配置文件
    八、在colinux中安装喜欢的发行版
    
    引用自:http://hi.baidu.com/usen68/blog/item/e9a84995569b1043d0135e18.html
    
    Colinux (Cooperative Linux)可使在Windows2000/XP上同时运行Linux成为可能并不借助第三方的
    虚拟机软件(VMware,VitualPC).它不是一种虚拟机,而是Linux内核的移植,使得Linux 可以
    natively 运行在Windows上。Colinux的格言是:如果Linux 可以运行在任何一种体系架构(i386,
    PowerPC,...)。Linux也应该可以运行在另外一种操作系统上(Windows, Linux,...)
    

    好吧,这篇文章写得非常好,使用方法我就不总结了。请一定要好好读安装目录下的文档,比如colinux-daemon.txt,已经写得灰常灰常详细了,根本不需要上网找使用教程。

    以前我也喜欢在自己电脑上装个真实的linux,虚拟不太慢,而且给人一种不真实的感觉。colinux的运行效果非常美,我使用已经有好几个月时间了。

    一、Colinux长什么样?

    1.1 文件组成

    colinux基本上是”绿色”软件(就把它当作是另外一个虚拟机吧)。我是通过7-zip解压colinux的安装程序来获取colinux的文件的,这样可以看清colinux的文件组成,也就是相当于”绿色”版本,一切都自己决定。

    最小的colinux自身及可以在内部的运行的linux由三个文件组成即可。

    1. linux.sys
    colinux通过此驱动与windows交互,以实现在windows上运行linux内核的功能。(有没理解错?)
    2. colinux-daemon.exe
    colinux主要程序,通过它可以安装linux.sys,通过它来运行linux内核vmlinuz。
    3. ubuntu.disk
    最后需要的就是镜像文件了,相当于我们把整个linux系统安装在这一个文件里。这个文件就是我们的linux的“硬盘”,它的大小是多大,linux里看到的磁盘(其实是分区)就是多大。我安装了一个ubuntu,所以取名叫ubuntu.disk,另外我还有zenwalk.disk,slackware.disk和opt_4g.disk,从名字就可以看出是干什么的。

    通过coLinux-0.7.8.exe安装的colinux,它主要由以下文件组成:

    colinux-console-fltk.exe   colinux终端
    colinux-console-nt.exe	colinux终端
    colinux-daemon.exe  	colinux主程序
    colinux-debug-daemon.exe 类似于上面
    linux.sys
    netdriver 此目录主要用来在windows中安装虚拟网卡,也是“绿色”的
      1. OemWin2k.inf
      2. removeAllNIC.bat
      3. tap.cat
      4. tap0801co.sys
      5. tapcontrol.exe
    colinux-net-daemon.exe
      当以tuntap方式使用网络时需要
    colinux-serial-daemon.exe
      不知道什么用
    colinux-slirp-net-daemon.exe
      当使用slirp网络时需要
    colinux-ndis-net-daemon.exe
      ndis-bridge模式需要
    colinux-bridged-net-daemon.exe
      pcap-bridge需要
    initrd.gz
      安装colinux内linux时需要
    vmlinux
      这个是colinux提供的linux内核,凡是安装在colinux内的linux都只能统一
    使用此内核
    vmlinux-modules.tar.gz
      与vmlinux相对应的内核模块,在安装好linux后,第一次使用initrd.gz时会
    被自动复制到镜像系统中去,和initrd.gz一样只使用一次就可以了。
    

    1.2 运行着的colinux

    Colinux实例

    像Oracle/DB2/Sybase之类的数据库总喜欢讲“实例”这个词,其实就是存在于系统中一个有完整功能的对象。对colinux来说,就是一台完整的虚拟主机(有CPU,内存,网卡,硬盘,和VMware一样,不过没VMware那样具体)。

    我在镜像文件unbunt.disk中安装了个ubuntu,内部ubuntu网络使用slirp方式上网,并且把这个colinux实例安装NT服务,开机自动运行ubuntu。开机后,有两个进程:colinux-daemon.exe和colinux-slirp-net-daemon.exe,通过windows任务管理器查看,内存占用9MB,CPU使用率为0(因为ubuntu跑起来后没多少程序在运行)。

    如此之低的资源占用,却可以让我随时可以进入一个完整的真实的纯粹的linux环境,难道colinux还不完美么?

    二、Colinux内外文件访问

    安装在镜像文件(就是那个好几G大小的文件)里的linux,可以通过如下几种方式与外面的windows互相访问文件系统。

    2.1. cofs

    在colinux提供的linux内核中已经默认编译了cofs文件系统的支持。Cofs似乎是一种虚拟文件系统,它可以让colinux的linux把在各colinux实例的配置中配置的windows目录当做磁盘设备,可以通过mount加载上来。
    比如我有三个盘符,C/D/E,我在实例ubuntu的配置文件中写了cofs0=c:\ cofs1=d:\ cofs2=e:\,然后我在ubuntu中通过 mount –t cofs 0 /mnt/winc来加载cofs0到目录/mnt/winc (其它两个盘符类似操作),这样就可以自由地在linux读写windows下的文件了。
    还可以把mount写入/etc/fstab让linux启动自动加载,实在想不出还有什么比这更方便了。

    2.2. sftp/winscp/ftp/samba

    既然colinux里安装的是个linux,又能联网,那安装一些文件传输服务器是自然而然的事情,这里就不多说废话了。

    三、colinux 网络模式

    如果linux不能联网,那有啥意思。通过colinux运行的linux系统,可以有以下几种连网方式。

    先提前说一下,在tuntap和pcap-birdge模式的配置中,网卡的名称很重要,需要和在“控制面板”“网络连接”中看到的一致。

    3.1 slirp

    这是最简单的一种,它的原理是在linux内核中提供slirp(好吧,我不知道这是什么东西)驱动,给linux提供一个虚拟出来的仅在linux内部可见的网络接口,网络配置是固化的(见后面的配置文件)。在colinux-daemon.exe运行的同时运行colinux-slirp-net-daemon.exe协同工作,以实现让colinux中访问windows所能访问的网络。

    它能让colinux内的linux访问外部网络,从其它主机访问内部linux是受限的,但好在slirp提供了类似于”端口重定向”这样的功能,仅需要在colinux的配置中标识出来就可以用了。

    这种方式几乎不需要做什么事情就能让linux与外部主机交互。

    以我的ubuntu.conf配置为例:

    eth0=slirp,00:ff:78:1b:42:00,tcp:5901:5901/tcp:22:22/tcp:80:80

    之后,在linux中配置eth0的网络(如果使用slirp模式,必须这样配置):

    iface eth0 inet static
        address 10.0.2.15
        netmask 255.255.255.0
        gateway 10.0.2.2
        broadcast 10.0.2.255

    这样就可以在linux中上网了,并且把当前windows的5901/22/80端口都重定向到内部linux上,使得局域网里其它主机也可以访问我的虚拟ubuntu。

    这种方式唯一的缺憾就是需要通过端口重定向到当前windows才能访问内部虚拟ubuntu。

    3.2 tuntap

    这种方式利用微软的网络连接功能(ICS),就是平时我们使用双网卡共享上网的做法,只不过这里的第二张(或者第N张)网卡是虚拟出来的。这种方式也非常简单,在能上网的网卡上选择共享网络连接,然后设置虚拟网卡IP和虚拟机内部linux同网段即可。

    比如虚拟网卡(我改名为TAP-Colinux)的IP为192.168.11.1,而内部linux的ip为192.168.11.150,内部ubuntu linux的网关及dns都设置为192.168.11.1,内部就可以上网了。这种模式下,我的windows是可以直接访问内部虚拟主机的,因为它们都在192.168.11网段。但是可惜的是,我局域网中的其它主机是无法连接我们内部ubuntu系统的。

    这种方式本也挺好,但我有段时间windows网络共享坏掉了,无法修复,所以也就无法使用不再用它。

    eth0=tuntap,"TAP-Colinux",00:ff:78:1b:40:00

    3.3 pcap-bridge/ndis-bridge

    这属于桥接模式,需要第三方的软件包wincap的支持。

    照理说这是最好的模式,因为它把colinux里的linux置于和当前windows同等的网络地位,像一台真实存在于网络里的主机一样,设置固定IP或者通过DHCP获取IP,直接可以上网。但是,我发现网络里的其它主机(包括我当前的windows)能ping通colinux内的ip,但是却无法连接任何端口。

    举例来说,我在ubuntu (10.22.65.138)中开了ssh服务,在外部主机(10.22.65.123)可以ping它,并且telnet 10.22.65.138 22也有反应,但是就是无法收发数据,telnet连接被挂起(照理说应该输出SSH-2.0-OpenSSH_5.5p1 Debian-4ubuntu4字符的),ssh也无法连接。

    也许是我忽略了什么,反正这种模式也被我抛弃了。

    eth0=pcap-bridge,"本地连接","00:ff:78:1b:41:00"

    四、安装为NT服务

    4.1 安装

    我们可以把colinux实例(比如我的ubuntu)安装为windows服务。我写了个批处理:

    installUbuntuService.bat

    @ set path=%cd%\..\..\bin\;%path%
    colinux-daemon.exe --install-service ubuntu @%cd%\ubuntu10.10.conf
    sc config ubuntu depend= ""
    sc config ubuntu start= auto
    net start ubuntu
    pause
    

    说明一下,这里的sc config ubuntu depend是因为默认通过 –install-service安装的服务会有两个依赖项:colinuxDriver和另外一什么东西,但实际上是不需要的。如果是使用“绿色”版colinux,则可能没注册过这些没用的服务,不如把依赖清理掉。

    4.2 卸载服务

    相应的卸载脚本为:

    uninstallUbuntuService.bat

    @ set path=%cd%\..\..\bin\;%path%
    net stop ubuntu
    colinux-daemon.exe --remove-service ubuntu
    pause
    

    平时我们可以通过net start 服务名和net stop 服务名来启动及关闭我们的“虚拟机”。服务是可以安装多个,可以同时启动(就像VMware里可以同时运行好几台虚拟机),比如我的机器上,还安装了zenwalk、slackware。

    五、给colinux中的linux加硬盘

    Colinux使用的“硬盘”其实就是普通二进制文件,是无格式的裸文件,在windows xp中,可以通过下面的命令生成文件。

    fsutil file createnew c:\disk\ext4_4G.disk 4294967296

    比如上面的命令就在c:盘下生成了一个大小为4G的文件。 这里的fsutil命令是xp自带的,与colinux无关。

    我们只要在colinux的实例配置中加上

    cobd2=C:\disk\ext4_4g.disk

    就可以使用cobd2了。当然,在linux中要往这个“分区”中写数据,还得先格式化,并且把它加载上来。

    mkfs.ex4 /dev/cobd2
    mount –t ext4 /dev/cobd2 /mnt/newDisk
    

    六、图形界面

    这本不属于colinux的主题。

    既然colinux内跑着一个完全真实的linux系统,那么我们就可以通过ssh连接上去,并且可以把X11数据转发到本地。于是,只要在windows中安装XMing之类的X-Server软件,我们就可以在putty打开的终端中直接启动linux下的图形界面程序了。

    详细的配置见这篇文章,非常详细:
    http://hi.baidu.com/mooncold/blog/item/8e0dfddc4f29a3a4cd11663a.html/cmtid/0f278f82283e2c9ef603a61a

    七、我的ubuntu配置文件

    #屏幕
    cocon=120x40
    
    #内核
    kernel=D:\opt\coLinux\dist\ubuntu\boot\vmlinux
    #initrd=D:\opt\coLinux\dist\ubuntu\boot\initrd.gz
    root=/dev/cobd0
    ro
    
    #内存
    mem=512
    
    #磁盘
    cobd0=D:\opt\coLinux\dist\ubuntu\disk\ubuntu_4G.disk
    cobd1=D:\opt\coLinux\dist\ubuntu\disk\swap.disk
    cobd2=C:\disk\opt_4g.disk
    
    #网络
    eth0=slirp,00:ff:78:1b:42:00,tcp:5901:5901/tcp:22:22/tcp:80:80
    #eth1=tuntap,"TAP-Colinux",00:ff:78:1b:40:00
    #eth1=pcap-bridge,"本地连接","00:ff:78:1b:41:00"
    
    #共享目录
    cofs0=c:\
    cofs1=d:\
    cofs2=e:\
    

    八、在colinux中安装喜欢的发行版

    各发行版安装方式相似,简单记录一下(详细的可以另外写篇记录)。这里不限发行版,可以是ubuntu/slackware/zenwalk/fc/debian/suse …

    1. 取安装光盘中的initrd文件,名字可能因发行版不同而稍有变化。

    2. 在配置文件中加载此initrd,即配置中的initrd=一项

    3. 通过cofs,加载windows目录,通过mount –o loop 加载光盘镜像

    这之后,就看各发行版的安装步骤了,可以肯定的是都能安装,只是过程会有些痛苦,再说吧。我已经搞定了unbuntu 10.10、slackware 13.0、zenwalk 6.4

    over, 暂时记录这么多。

    作者:sunu | 分类目录:文档及资源 | 标签:
  • 遵义下雪了 2010/12/24

    2010-12-25

    作者:sunu | 分类目录:不知所云 | 标签: