Creative Commons License
本Blog采用 知识共享署名-非商业性使用-禁止演绎 3.0 Unported许可协议 进行许可。 —— Fox <游戏人生>

游戏人生

游戏人生 != ( 人生 == 游戏 )
站点迁移至:http://www.yulefox.com。请订阅本博的朋友将RSS修改为http://feeds.feedburner.com/yulefox
posts - 62, comments - 508, trackbacks - 0, articles - 7

2010年1月7日

本文同步自游戏人生

最近有点忙,本来要用autoconf+automake把自己的代码梳理一下的,因为工作停了近两周。

本想看看有什么工具可以自动生成Makefile.am,答案是:Automake不支持通配符,而且还口口声声,振振有词。既然说的这么言词凿凿,情深意切,我想我也没有必要用shell生成Makefile.am了。

用着用着,我有点怀疑人生了:不知道什么时候需要用autoconf和automake。如果我只是平时自己写一些toy codes的话,感觉用autoconf和automake有点大炮打蚊子的感觉,而且每次新加代码或者是移除代码、甚至是更改目录,都要重新执行 autoconf、automake(不知道我说的对与不对)。对于一个大型项目,执行一次configure和make是很痛苦的一件事,make的中 间目标文件或者库文件、执行文件倒是不一定非得完全rebuild,configure的配置检查呢?是不是也有类似机制?反正我在用ogre或者 cegui的时候,每次执行./configure是重新配置了的。

实际在开源项目里面也不可能维护两套makefile吧。

看了一下googletest的配置,倒是清爽的很,最大的特点是只有一个Makefile.am,这样在一个项目里面只需要维护一个Makefile.am就够了。

cegui比较常规,每个子目录都会维护一个Makefile.am。

需要特别注意的是ogre从1.7.0开始已经开始使用cmake了……

请听题:管理中小型项目,你倾向于下面哪个工具?

o make:钻木取火,玩的就是个技术,编译代码,只用装B的,不用牛B的,你要是用什么cmake,你都不好意思跟别人打招呼,这么经典的东西,精通需要多久?要我说怎么着也得个把俩月吧,个把俩月?那是入门,至少半年,就这还得有Feldman的悟性,不舍昼夜;

o autoconf+automake:既有群众基础,又有技术含量,你是那样拉轰的男人,不管在什么地方,就好像漆黑中的萤火虫一样,那样的鲜明,那样的 出众。你那忧郁的眼神,稀嘘的胡喳子,神乎其技的指法;既可以耻笑原始人的生产力低下,还可以鄙视现代人的不学无术。

o cmake:在MSVCers面前抬不起头,在UNIXers面前似乎更抬不起头;而cmake对WINDOWS和UNIX平台的完美支持,足以让所有的 MSVCers和UNIXer在你面前抬不起头,你是公鸡中的战斗机。所以你还是可以趾高气昂的丢下一句:走NB的路,让SB说去吧。


posted @ 2010-01-07 01:32 Fox 阅读(3955) | 评论 (4)编辑 收藏

2009年12月23日

     摘要: 从接触和使用make以来,前前后后写了不少Makefile(添添减减、修修补补,累计上千行是有的),今天在重新整理代码的组织结构之后,突然就想:我为什么不使用Autotools呢?

在开始体验功能强大的Autotools之前,简单(详细)回忆总结一下我使用make的经历和思考的过程,反省一下看看自己在接触这些新鲜事物的时候到底走了多少弯路。  阅读全文

posted @ 2009-12-23 02:18 Fox 阅读(6847) | 评论 (5)编辑 收藏

2009年12月6日

本文同步自游戏人生

Writen by Fox(yulefox.at.gmail.com)

在具体讨论之前,本文先厘清UUID(Universally Unique IDentifier)与GUID(Globally Unique IDentifier)的关系。

在分布式、网络、单机环境下,为了能够使用具有某种形式的ID唯一标识系统中的任一元素,这样的ID可以不依赖中心认证自动生成,于是UUID就诞生了。

UUID标准的历史沿革和具体实现在RFC 4122ITU-T Rec. X.667ISO/IEC 9834-8:2008中均有详细描述。ITU和ISO采用的标准和RFC 4122都是在UUID的早期版本基础上完成,各版本之间具有一致性和兼容性。

因为不能保证UUID的唯一性,ITU和ISO针对UUID的使用都有免责声明

GUID一般是指Microsoft对于UUID标准的实现,UUID的实现则多见于其他系统(*NIX、MAC OS等)中。在了解了这一区别后,本文将统一使用UUID来指代对应的原理、算法及实现。

文中关于UUID的讨论全部基于RFC 4122和ITU-T Rec. X.667以及OSF、IETF、ITU-T、ISO、FIPS的各种标准文档。而UUID的细节(如结构、表示、算法、实现等)均以ITU-T Rec. X667为唯一蓝本,文中“本标准”即指代该蓝本。

o 介绍

UUID是长度为16-byte(128-bit)的ID,一般以形如f81d4fae-7dec-11d0-a765-00a0c91e6bf6的字符串作为URN(Uniform Resource Name,统一资源名称)。

o 动机

无须中心认证,自动生成,支持一台机器每秒生成10M次(100纳秒级,其隐含原因是指能够区分的最小时间单位为100ns,将时间作为因子时,连续生成两个UUID的时间至少要间隔100ns)。方便存取、分配、排序、查找。

o 结构


   76543210765432107654321076543210
   + – - – = – - – = – - – = – - – +
15 |            TimeLow            | 12
11 |    TimeMid    |   Version..   |  8
7  |Vari.. |Clock..|     Node      |  4
3  |             Node              |  0
   + – - – = – - – = – - – = – - – +
15 – 12: TimeLow 时间值的低位
11 – 10: TimeMid 时间值的中位
09 – 08: VersionAndTimeHigh 4位版本号和时间值的高位
07: VariantAndClockSeqHigh 2位变体(ITU-T)和时钟序列高位
06: ClockSeqLow 时钟序列低位
05 – 00: Node 结点
hexOctet = hexDigit hexDigit
hexDigit =
“0″ / “1″ / “2″ / “3″ / “4″ / “5″ / “6″ / “7″ / “8″ / “9″ /
“a” / “b” / “c” / “d” / “e” / “f” /
“A” / “B” / “C” / “D” / “E” / “F”
UUID =
TimeLow
“-” TimeMid
“-” VersionAndTimeHigh
“-” VariantAndClockSeqHigh ClockSeqLow
“-” Node

UUID由上述6个域构成,每个域编码为若干字节,并以16进制数表示这128位的UUID,相邻域以减号“-”分隔 (VariantAndClockSeqHigh和ClockSeqLow对应的两个字节例外,如上所示)。该结构中包含版本(Version)、变体 (Variant)、时间(Time)、时钟序列(Clock Sequence)、节点(Note)信息(以无符号整型值表示)。

o 合法性

除判断variant位设置是否正确、基于时间生成的UUID时间值是否为未经分配的将来时间外,实际应用中没有其他机制可以判定UUID是否合法。

o 变体

Variant位是UUID第7字节(VariantAndClockSeqHigh)的最高3位,

7 6 5  Description
0 – –  NCS向后兼容
1 0 –  本标准
1 1 0  Microsoft向后兼容
1 1 1  ITU-T Rec. X.667保留

o 版本

UUID的生成有时间、名称、随机数三种策略,以第9字节(VersionAndTimeHigh)的最高4位表示。

目前UUID定义有5个版本:

7 6 5 4  Ver  Description
0 0 0 1  1    基于时间的版本(本标准)
0 0 0 0  2    使用嵌入式POSIX(DCE安全版本)
0 0 1 1  3    使用MD5哈希的基于名称的版本(本标准)
0 1 0 0  4    基于随机数的版本(本标准)
0 1 0 1  5    使用SHA-1的基于名称的版本(本标准)

o 时间

时间是一个60位的整型值(除4位版本号外的前8字节),对应UTC(格林尼治时间1582年10月15日午夜始)的100ns时间间隔计数。

对于ver 4和5,该值分别对应一个随机数和一个全局唯一的名称。

o 时钟序列

对基于时间的UUID版本,时间序列用于避免因时间向后设置或节点值改变可能造成的UUID重复,对基于名称或随机数的版本同样有用:目的都是为了防止UUID重复。

如果前一时钟序列已知,通过自增实现时钟序列值的改变;否则,通过密码学(伪)随机数设置新的时钟序列值。

o 节点

对基于时间的UUID版本,节点由48位的单播MAC地址构成。对于没有MAC地址的系统,节点值为一个密码学(伪)随机数(为防止与MAC地址发生碰撞,需设置多播位)。


o 基于时间的UUID生成算法

o 确定UTC时间(60位 Time)和时间序列值(14位 ClockSequence);

o 设置TimeLow(对应Time的31-0位);

o 设置TimeMid(对应Time的47-32位);

o 设置VersionAndTimeHigh(4位版本号及Time的59-48位);

o 设置VariantAndClockSeqHigh(变体位及对应ClockSequence的13-8位);

o 设置ClockSeqLow(对应ClockSequence的7-0位);

o 设置Node(对应48位MAC地址)。

o 基于名称的UUID生成算法

o 针对相应的命名空间(如DNS、URL、OID等)分配一个UUID作为所有UUID的命名空间标识;

o 将名称转换为字节数列;

o 使用MD5或SHA-1算法对与名称关联的命名空间标识进行计算,产生16字节哈希结果;

o 设置TimeLow(对应哈希值的3-0字节);

o 设置TimeMid(对应哈希值的5-4字节);

o 设置VersionAndTimeHigh(对应哈希值的7-6字节),以相应版本号重写对应位(第9字节的高4位);

o 设置VariantAndClockSeqHigh(对应哈希值的第8字节),重写变体对应位(第7字节的高2位,本标准对应值为10);

o 设置ClockSeqLow(对应哈希值的第9字节);

o 设置Node(对应哈希值的15-10字节)。

由 于MD5碰撞问题,MD5只用于向后兼容的UUID生成,不再被推荐使用。由于SHA-1哈希结果为160位(20字节),本算法中,需要将FIPS PUB 180-2中的SHA-1算法的哈希值字节顺序反转(字节内顺序不变),UUID使用其15-0字节,19-16字节被丢弃。

o 基于随机数的UUID生成算法

o 设置VariantAndClockSeqHigh的变体位值为10;

o 设置VersionAndTimeHigh的4位版本号;

o 设置剩余位为随机值。

本文中讨论的密码学随机数,主要根据系统可以提供的信息(内存、硬盘、句柄、程序运行的线程、进程、句柄、堆栈等),利用SHA-1等哈希算法得到。

其他关于密码学随机数的描述,我曾在这篇文章中简单提到。


具体算法实现可以参考文档和开源代码。

posted @ 2009-12-06 15:07 Fox 阅读(21376) | 评论 (2)编辑 收藏

2009年9月22日

本文同步自游戏人生

以前曾经讨论过Singleton的实现,这次在对照ACE和Boost代码的时候,又重新审视了一下二者对Singleton不同的实现。其间的差别也体现了不同的编程哲学:ACE的实现更加偏重多线程中的安全和效率问题;Boost的实现则偏重于使用语言自身的特性满足Singleton模式的基本需求。

o ACE的实现

Douglas C. Schmidt在Double-Checked Locking: An Optimization Pattern for Efficiently Initializing and Accessing Thread-safe Objects一文中对double-check lock(一般译为双检锁)进行了详细的阐述。

ACE的Singleton使用Adapter模式实现对其他类的适配,使之具有全局唯一的实例。由于C++标准并非明确指定全局静态对象的初始化顺序,ACE使用double-check lock保证线程安全,并使之不受全局静态对象初始化顺序的影响,同时也避免了全局静态实现方式的初始化后不使用的开销。

如果你能够准确的区分以下三种实现的弊端和隐患,对double-check lock也就有了足够的了解。

// -------------------------------------------
class Singleton
{
public:
    static Singleton *instance (void)
    {
        // Constructor of guard acquires
        // lock_ automatically.
        Guard<Mutex> guard (lock_);
        // Only one thread in the
        // critical section at a time.
        if (instance_ == 0)
            instance_ = new Singleton;
        return instance_;
        // Destructor of guard releases
        // lock_ automatically.
    }
private:
    static Mutex lock_;
    static Singleton *instance_;
};

// ---------------------------------------------
static Singleton *instance (void)
{
    if (instance_ == 0) {
        Guard<Mutex> guard (lock_);
        // Only come here if instance_
        // hasn’t been initialized yet.
        instance_ = new Singleton;
    }
    return instance_;
}

// ---------------------------------------------
class Singleton
{
public:
    static Singleton *instance (void)
    {
        // First check
        if (instance_ == 0)
        {
            // Ensure serialization (guard
            // constructor acquires lock_).
            Guard<Mutex> guard (lock_);
            // Double check.
            if (instance_ == 0)
                instance_ = new Singleton;
        }
        return instance_;
        // guard destructor releases lock_.
    }
private:
    static Mutex lock_;
    static Singleton *instance_;
};

更多详情,见Schmidt老师的原文和ACE_Singleton实现。

o Boost的实现

Boost的Singleton也是线程安全的,而且没有使用锁机制。当然,Boost的Singleton有以下限制(遵从这些限制,可以提高效率):

o The classes below support usage of singletons, including use in program startup/shutdown code, AS LONG AS there is only one thread running before main() begins, and only one thread running after main() exits.

o This class is also limited in that it can only provide singleton usage for classes with default constructors.

// T must be: no-throw default constructible and no-throw destructible
template <typename T>
struct singleton_default
{
private:
    struct object_creator
    {
        // This constructor does nothing more than ensure that instance()
        //  is called before main() begins, thus creating the static
        //  T object before multithreading race issues can come up.
        object_creator() { singleton_default<T>::instance(); }
        inline void do_nothing() const { }
    };
    static object_creator create_object;

    singleton_default();

public:
    typedef T object_type;

    // If, at any point (in user code), singleton_default<T>::instance()
    //  is called, then the following function is instantiated.
    static object_type & instance()
    {
        // This is the object that we return a reference to.
        // It is guaranteed to be created before main() begins because of
        //  the next line.
      static object_type obj;

      // The following line does nothing else than force the instantiation
      //  of singleton_default<T>::create_object, whose constructor is
      //  called before main() begins.
      create_object.do_nothing();

      return obj;
    }
};
template <typename T>
typename singleton_default<T>::object_creator
singleton_default<T>::create_object;

对于多数Singleton使用,Boost提供的版本完全能够满足需求。为了效率,我们有必要对其使用作出一定的限制。

而在多线程编程中,则有必要使用double-check lock降低频繁加锁带来的开销。

-------------------------------------------------------------------------------

PS: 欣赏Soft的一句话:经得起诱惑,耐得住寂寞

posted @ 2009-09-22 00:38 Fox 阅读(7375) | 评论 (9)编辑 收藏

2009年9月12日

本文同步自游戏人生

在使用IOCP时,最重要的几个API就是GetQueueCompeltionStatus、WSARecv、WSASend,数据的I/O及其完成状态通过这几个接口获取并进行后续处理。

GetQueueCompeltionStatus attempts to dequeue an I/O completion packet from the specified I/O completion port. If there is no completion packet queued, the function waits for a pending I/O operation associated with the completion port to complete.

BOOL WINAPI GetQueuedCompletionStatus(
  __in   HANDLE CompletionPort,
  __out  LPDWORD lpNumberOfBytes,
  __out  PULONG_PTR lpCompletionKey,
  __out  LPOVERLAPPED *lpOverlapped,
  __in   DWORD dwMilliseconds
);

If the function dequeues a completion packet for a successful I/O operation from the completion port, the return value is nonzero. The function stores information in the variables pointed to by the lpNumberOfBytes, lpCompletionKey, and lpOverlapped parameters.

除了关心这个API的in & out(这是MSDN开头的几行就可以告诉我们的)之外,我们更加关心不同的return & out意味着什么,因为由于各种已知或未知的原因,我们的程序并不总是有正确的return & out。

If *lpOverlapped is NULL and the function does not dequeue a completion packet from the completion port, the return value is zero. The function does not store information in the variables pointed to by the lpNumberOfBytes and lpCompletionKey parameters. To get extended error information, call GetLastError. If the function did not dequeue a completion packet because the wait timed out, GetLastError returns WAIT_TIMEOUT.

假设我们指定dwMilliseconds为INFINITE。

这里常见的几个错误有:

WSA_OPERATION_ABORTED (995): Overlapped operation aborted.

由于线程退出或应用程序请求,已放弃I/O 操作。

MSDN: An overlapped operation was canceled due to the closure of the socket, or the execution of the SIO_FLUSH command in WSAIoctl. Note that this error is returned by the operating system, so the error number may change in future releases of Windows.

成因分析:这个错误一般是由于peer socket被closesocket或者WSACleanup关闭后,针对这些socket的pending overlapped I/O operation被中止。

解决方案:针对socket,一般应该先调用shutdown禁止I/O操作后再调用closesocket关闭。

严重程度轻微易处理

WSAENOTSOCK (10038): Socket operation on nonsocket.

MSDN: An operation was attempted on something that is not a socket. Either the socket handle parameter did not reference a valid socket, or for select, a member of an fd_set was not valid.

成因分析:在一个非套接字上尝试了一个操作。

使用closesocket关闭socket之后,针对该invalid socket的任何操作都会获得该错误。

解决方案:如果是多线程存在对同一socket的操作,要保证对socket的I/O操作逻辑上的顺序,做好socket的graceful disconnect。

严重程度轻微易处理

WSAECONNRESET (10054): Connection reset by peer.

远程主机强迫关闭了一个现有的连接。

MSDN: An existing connection was forcibly closed by the remote host. This normally results if the peer application on the remote host is suddenly stopped, the host is rebooted, the host or remote network interface is disabled, or the remote host uses a hard close (see setsockopt for more information on the SO_LINGER option on the remote socket). This error may also result if a connection was broken due to keep-alive activity detecting a failure while one or more operations are in progress. Operations that were in progress fail with WSAENETRESET. Subsequent operations fail with WSAECONNRESET.

成因分析:在使用WSAAccpet、WSARecv、WSASend等接口时,如果peer application突然中止(原因如上所述),往其对应的socket上投递的operations将会失败。

解决方案:如果是对方主机或程序意外中止,那就只有各安天命了。但如果这程序是你写的,而你只是hard close,那就由不得别人了。至少,你要知道这样的错误已经出现了,就不要再费劲的继续投递或等待了。

严重程度轻微易处理

WSAECONNREFUSED (10061): Connection refused.

由于目标机器积极拒绝,无法连接。

MSDN: No connection could be made because the target computer actively refused it. This usually results from trying to connect to a service that is inactive on the foreign host—that is, one with no server application running.

成因分析:在使用connect或WSAConnect时,服务器没有运行或者服务器的监听队列已满;在使用WSAAccept时,客户端的连接请求被condition function拒绝。

解决方案:Call connect or WSAConnect again for the same socket. 等待服务器开启、监听空闲或查看被拒绝的原因。是不是长的丑或者钱没给够,要不就是服务器拒绝接受天价薪酬自主创业去了?

严重程度轻微易处理

WSAENOBUFS (10055): No buffer space available.

由于系统缓冲区空间不足或列队已满,不能执行套接字上的操作。

MSDN: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full.

成因分析:这个错误是我查看错误日志后,最在意的一个错误。因为服务器对于消息收发有明确限制,如果缓冲区不足应该早就处理了,不可能待到send/recv失败啊。而且这个错误在之前的版本中几乎没有出现过。这也是这篇文章的主要内容。像connect和accept因为缓冲区空间不足都可以理解,而且危险不高,但如果send/recv造成拥堵并恶性循环下去,麻烦就大了,至少说明之前的验证逻辑有疏漏。

WSASend失败的原因是:The Windows Sockets provider reports a buffer deadlock. 这里提到的是buffer deadlock,显然是由于多线程I/O投递不当引起的。

解决方案:在消息收发前,对最大挂起的消息总的数量和容量进行检验和控制。

严重程度严重

本文主要参考MSDN

************* 说明 *************

Fox只是对自己关心的几个错误和API参照MSDN进行分析,不提供额外帮助。

posted @ 2009-09-12 00:20 Fox 阅读(8682) | 评论 (9)编辑 收藏

2009年9月10日

本文同步自游戏人生

周伟明老师应该是多核计算领域的老人了。

这几日因为想找找无锁(lock-free)方面的信息,就打开了周老师的blog。看到多核系统中三种典型锁竞争的加速比分析这篇文章时,觉得老师强调多核计算效率是有必要的,但拿Amdahl 定律和Gustafson定律作对比有点不恰当。

按照我的理解,这两个定律所刻画的内容是完全一致的,只是对加速比的定义不一样罢了。这里,我们都以S(n)表示n核系统对具体程序的加速比,K表示串行部分计算时间比例。

Amdahl 定律的加速比:S(n) = 使用1个处理器的串行计算时间 / 使用n个处理器的并行计算时间

S(n) = 1/(K+(1-K)/n) = n/(1+(n-1)K)

Gustafson定律的加速比:S(n) = 使用n个处理器的并行计算量 / 使用1个处理器的串行计算量

S(n) = K+(1-K)n

通俗的讲,Amdahl 定律将工作量看作1,有n核也只能分担1-K的工作量;而Gustafson定律则将单核工作量看作1,有n核,就可以增加n(1-K)的工作量。

这两个计算公式都没有将锁开销考虑在内,是理想化的。周老师提到设计不当造成并行变串行的问题与这两个公式计算无关。因为任何多核计算都存在对串行和并行的设计考量,这正是程序员在使用多核并行时最关心的事情。

总之,二者的区别只在于态度的不同:一个消极悲观,一个积极乐观,充其量是一个冷笑话,而于多核计算没有任何关联。

我说这些也与多核计算没有关联,丝毫没有质疑多核效率的意思。相反,我期待能够通过技术层面提高多核的有效负载。

最后一句题外话,周老师使用Word的水平一般:所有来自Word的截图都是在页面视图直接截,换行符和光标随处可见。

posted @ 2009-09-10 12:04 Fox 阅读(6238) | 评论 (0)编辑 收藏

2009年9月1日

本文同步自游戏人生

o *__ 序 __* o

在阅读ACE代码和C++NPv1, v2, APG的时候,我意识到一个问题:虽然稍有C++和网络基础的同学都可以读懂ACE,但如果你对OS(五大管理模块都包含在内)、TCP/IP、C++、Design Patterns了解越多,你就越能体会ACE为什么需要这么庞杂,虽然它不够完美(但至少我还没有资格来批评这一点,我现在最常想做的一个动作就是五体投地)。

而且我隐约感觉到,我现在所写的很多东西在以后(对于有些人或许就是现在)看来会相当不深刻、相当不严谨,但对于一段学习历程,这个过程是必然的、必需的。

在C++NPv1中,Douglas C. Schmidt把原始socket及其API的缺陷有些妖魔化了,比如一段加上注释、空行在内的35行的代码,被指出有10处错误之多。这就像很多其他语言的倡导者或反传统C/C++指针者在批评指针时的说法一样。长期使用原始socket和指针的同学对此感觉很不舒服,何况socket API提供了大量错误检测的接口,至多是不够友好罢了。你好就好了,没必要抓住别人一顿痛批吧,『本是同根生,相煎何太急』。

虽然Solaris、Linux的很多版本及Windows对起源于Berkeley的socket API进行了重写,但不可否认,由于历史原因和POSIX标准的存在,对于使用者而言,我们可以无视这些API的实现差异。只是一旦我们从socket通信扩展到其他IPC通信的话,就需要正视各种I/O细节的差异了。

由于UNIX中,对于socket, file, pipe, device的大多数操作,描述符都是通用的(这一点,OS上面讲的更清楚些)。而Windows中,句柄大多不能互换(socket对于MS来说是舶来品)。系统和标准的不一致导致地址、协议和API的混杂甚至混乱。

UNIX下的描述符和Windows的句柄可以看作是同一个概念,只是应用环境不一样,所描述的内容也时常不一样,再简单了说,它们都是一个整型的ID。

ACE的源码中使用了大量预处理指令,尤其在跨平台/编译环境的部分更加明显。鉴于C/C++标准的博大胸怀,有些指令需要阅读相关编译器提供的帮助文档:

o #pragma: GCC, MSVC

o #define (#, #@, ##) : GCC, MSVC

其中有若干代码文件以.inl为后缀,里面是对部分函数的内联实现,以使代码结构看上去更加简洁。如果确定使用内联函数的话,*.inl将被包含于*.h的最后,如果不使用,则像*.h一样,包含于*.cpp的头部。

ACE采用doxygen输出文档,在阅读代码注释时能够感受到差异,但基本不会影响阅读。

o * __ 关于第3章(C++NPv1)__ * o

ACE抽象的地址类ACE_Addr拥有ACE_DEV_Addr, ACE_FILE_Addr, ACE_INET_Addr, ACE_SPIPE_Addr, ACE_UNIX_Addr五个子类。对于狭义上的网络通信(TCP/IP)而言,ACE_INET_Addr对应于我们熟悉的sockaddr_in。

ACE_IPC_SAP是IPC(interprocess communication)I/O操作类的root类。

从编码的角度看,这个类漂亮的地方在于示例了抽象类的另一种实现方式。

一提到抽象类,大多数人的第一反应是pure virtual function。当一个基类确定需要使用virtual function时,这是一个不错的选择。但我们都知道虚拟函数有开销。而且对于一个结构简单的抽象基类和其继承子类(尤其是大量使用时),一个虚函数表带来的开销会让整个设计显得十分蹩脚。

我们都知道如何强制让一个类无法使用default constructor(protected)。如果对基类使用该方法,仅使子类具有public的default constructor,这就达到了定义抽象基类的效果。

virtual destructor的意义在于防止delete父类指针(指向子类对象)时未调用子类destructor。在此例中,为避免这种情况,同样将destructor声明为protected即可。

从设计实现的角度看,相较于socket API,ACE_IPC_SAP的子类ACE_SOCK提供了编译时对句柄合法性的检测。

从逻辑功能层面划分,socket有三种角色:

o active connection role (connector):主动连接

o passive connection role (acceptor):被动连接

o communication role (stream):数据通信

但socket API毕竟不是OOD出来的,对于一个socket描述符,也完全没有必要去限制其担负的功能,更不可能搞成三种不同的socket。而OOD的ACE则可以轻易实现对socket对象及其操作的封装。

工厂类ACE_SOCK_Connector是一个主动创建通信端的工厂类。socket API中的connect接口只是为一个socket建立与其它peer的网络连接,而不产生新的socket实例,也不依赖于任何其它socket。同样,ACE_SOCK_Connector只是为一个ACE_SOCK_Stream对象(对用于数据通信的socket的封装)连接到ACE_Addr(对struct sockaddr的封装)提供接口,也不含对ACE_SOCK_Stream对象的其它操作。

工厂类ACE_SOCK_Acceptor是一个被动创建通信端的工厂类。当监听到新的网络连接后,为该连接初始化一个ACE_SOCK_Stream对象。和connector不同的是,acceptor依赖于一个已经存在的充当监听功能的socket句柄(ACE_SOCK),因此,ACE_SOCK_Acceptor是ACE_SOCK的一个子类。

ACE_SOCK_Stream是只负有通信传输功能的socket,对应connection-oriented的TCP通信格式stream,和UDP的CE_SOCK_CODgram相呼应。ACE_SOCK_Stream只是socket的通信载体,在两个工厂ACE_SOCK_Connector和ACE_SOCK_Acceptor中初始化。这样一个类除支持最基本的数据发送(send)和接收(recv)和阻塞(blocking)、非阻塞(nonblocking)及定时(timed)的I/O模式外,还支持分散读取(scatter-read)和集中写入(gather-write)。

对于一个简单的『网络课程作业:写一个有连接的IM小程序』,上面这些内容已经足够了。当然即使使用对应的几个socket API也已经足够了。但我们显然更加关心如此庞大的一个库,是如何解决复杂的网络应用的,我尤其关心的是多线程并发如何更好的处理。

所以,我准备跑到第8、9章了。

posted @ 2009-09-01 14:22 Fox 阅读(3838) | 评论 (3)编辑 收藏

2009年8月28日

本文同步自游戏人生

我发现我最近成了Cygwin下的小白鼠,写完Cygwin下安装ACE,写ACE在cygwin下的使用。现在又写doxygen

之前提到在Cygwin下读代码的不习惯,后来回到VS下看。没过几天,觉得VS下还是不够直观,于是就直接看ACE的doxygen了……

doxygen好是好,用起来还是要慢慢习惯才行,需要在写注释和代码的时候注意一些,掌握的细节和技巧越多,出来的文档越丰富(当然,这和代码质量是两码事)。

我自然是把doxygen安装在Cygwin下了,由于doxygen没有提供info,Info doxygen时就自动打开了doxygen的man,和man doxygen、doxygen --help一个效果。

如果希望阅读更详尽的使用方法,只有自己down一个manual了

在Cygwin下,doxygen采用GNU的libiconv进行文字编码的转换,以UTF-8作为默认编码。

使用doxygen生成config-file模板后,可以在config-file中进行一些项目设置(有注释的,看的懂)。

为了支持中文,我DOXYFILE_ENCODING用的是EUC-CN,但输出文档的语言OUTPUT_LANGUAGE却选了English。两点原因:

o EUC-CN(各种汉字编码知识就不在此普及了,你可以认为简体字编码都是EUC-CN)和UTF-8不同,但OUTPUT_LANGUAGE的各种语言都是使用的UTF-8,所以两种编码不可能同时显示,当然,你可以把EUC-CN全转成UTF-8。编码不是高级的技术,但对于非英语用户绝对是一个噩梦后来发现是我自己学艺不精,DOXYFILE_ENCODING只是配置文件的编码格式而已,而识别中文文档只需要修改INPUT_ENCODING成EUC-CN即可,OUTPUT_LANGUAGE自然设置成Chinese也不会有问题,因为doxygen采用UTF-8输出,使用中文输出不会有乱码问题

o 虽然我的英文很蹩脚,虽然我的文档中多有中文注释。但像doxygen中文输出的文档中把class、public都给你翻译成中文,你也受不了,这也英文水平无关。

config-file中的其他内容我现在也用不到,就没有仔细看。

因为Kevin会在公司里讲一下doxygen,doxygen的manual也讲的很详细,我就省点时间,不翻译文档了。

简单的一个Doxygen的测试在这里。

posted @ 2009-08-28 17:14 Fox 阅读(2836) | 评论 (5)编辑 收藏

2009年8月24日

本文同步自游戏人生

我屈服了,还是VS用的方便。

之前在Cygwin下已经可以使用的ACE,因为阅读代码太不方便(对于一个WinEr来说),上午在VS下面花了几分钟就把ACE配好了,而且使用$(ACE_ROOT)\examples\C++NPv1的代码跟踪调试,太习惯了。

按照$(ACE_ROOT)\ACE-INSTALL.html的安装说明:

o 选择并打开$(ACE_ROOT)\ace\ace_vc9.sln

o 添加config.h并加入以下内容:

    #define ACE_HAS_STANDARD_CPP_LIBRARY 1
    #include "ace/config-win32.h"

o F7

-----------------------------------------------

OK,现在$(ACE_ROOT)\lib下面已经生成了ACEd.dll、ACEd.lib,再设置一下系统环境变量(运行程序必需)和VC++目录(调试程序必需)。可以使用了:

o 选择并打开$(ACE_ROOT)\examples\C++NPv1

o F7

o for (; ; ) { F12, F9, F5, F10, F11 }

-----------------------------------------------

半个小时就搞定了当时一个星期的折腾……

结论:对于一个不忠实的Win Coder,在MinGW, Cygwin, UNIX…下面装B是要付出代价的。

当然,家里的机器就让它还一直跑Cygwin吧。

posted @ 2009-08-24 11:59 Fox 阅读(2488) | 评论 (6)编辑 收藏

2009年8月19日

本文同步自游戏人生

/*--------- Hello.cc ---------*/

/** Hello.cc:
* @File:   Hello.cc
* @Author: Fox <yulefox at gmail dot com>
* @Date:   Aug. 19th, 2009
* @Brief:  Test ACE log module application
*/

#define ACE_NTRACE 0            /// trace the calling position

#include "ace/Log_Msg.h"        /// include log module

int ACE_TMAIN(int, ACE_TCHAR *[])
{
     ACE_TRACE(ACE_TEXT("main"));

     ACE_DEBUG((LM_INFO, ACE_TEXT("%IStart\n")));
     ACE_DEBUG((LM_INFO, ACE_TEXT("%IEnd\n")));

     return 0;
}

/*--------- makefile ---------*/

BIN     = hello                       # src & exe file name
SRC     = $(addsuffix .cc, $(BIN))    # src file suffix
LIBS    = -lACE                       # libACE.dll under cygwin

include $(ACE_ROOT)/include/makeinclude/wrapper_macros.GNU
include $(ACE_ROOT)/include/makeinclude/macros.GNU
include $(ACE_ROOT)/include/makeinclude/rules.common.GNU
include $(ACE_ROOT)/include/makeinclude/rules.nonested.GNU
include $(ACE_ROOT)/include/makeinclude/rules.bin.GNU
include $(ACE_ROOT)/include/makeinclude/rules.local.GNU

/*--------- Compilation ---------*/

GNUmakefile: /home/fox/ace/GNUmakefile MAKEFLAGS=k

g++ -Wpointer-arith -mthreads -mtune=pentiumpro -O3 -g -pipe    -pipe   -I/usr/\
share/ace -DACE_HAS_EXCEPTIONS -DACE_NO_INLINE  -c -o .obj/hello.o hello.cc
g++ -Wpointer-arith -mthreads -mtune=pentiumpro -O3 -g -pipe    -pipe   -I/usr/\
share/ace -DACE_HAS_EXCEPTIONS -DACE_NO_INLINE  -Wl,--enable-auto-import -Wl,-E\
-L/usr/share/ace/lib -o hello .obj/hello.o  -lACE

Compilation finished at Wed Aug 19 00:35:42

/*--------- Result ---------*/

$ ./hello.exe
(14417928) calling main in file `hello.cc' on line 13
    Start
    End
(14417928) leaving main

-------------------------------------------------------

更多内容请参考C++NP(C++ Network Programming) vol.1 & vol.2和APG(The ACE Progrmmer's Guide)

忙活了一晚上,终于知道怎么包含头文件了,在gcc的编译选项中用 -I或/I$(ACE_ROOT):

本例中是:-I/usr/share/ace

结果后面库又链接不上,联想以前使用OpenGL库的LIBS,终于靠一个-lACE搞定。

因为不愿意用MPC,总感觉再多花些时间去弄又只是离题更远了,有兴趣的同学自然是可以通过ACE的官网找到所有问题的答案。

这样一来,ACE在cygwin下从安装到使用也就告一段落了,后面的问题就比较easy了,无非是你用ACE做什么。而我也不会再就ACE && cygwin写什么心得了,总算见证了这两天的捣腾。

posted @ 2009-08-19 10:05 Fox 阅读(2553) | 评论 (4)编辑 收藏