colorful

zc qq:1337220912

 

关于#pragma once(转)

在所有的预处理指令中,#pragma指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个 编译器给出了一个方法,在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个 编译器都是不同的。其格式一般为:#pragma para,其中para为参数,下面来看一些常用的参数。

(Each   implementation   of   C   and   C++   supports   some   features   unique   to   its   host   machine   or   operating   system.   Some   programs,   for   instance,   need   to   exercise   precise   control   over   the   memory   areas   where   data   is   placed   or   to   control   the   way   certain   functions   receive   parameters.   The   #pragma   directives   offer   a   way   for   each   compiler   to   offer   machine-   and   operating-system-specific   features   while   retaining   overall   compatibility   with   the   C   and   C++   languages.   Pragmas   are   machine-   or   operating-system-specific   by   definition,   and   are   usually   different   for   every   compiler. )

(1)message参数。Message参数是我最喜欢的一个参数,它能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:

#pragma message(“消息文本”),当编译器遇到这条指令时就在编译输出窗口中将消

息文本打印出来。

当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法:    

#ifdef   _X86    

#pragma   message(“_X86   macro   activated!”)    

#endif    

当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示

“_X86   macro activated!”。我们就不会因为不记得自己定义的一些特定的宏而抓耳

挠腮了。    

   

(2)另一个使用得比较多的pragma参数是code_seg。格式如:    

#pragma   code_seg(   ["section-name"[,"section-class"]   ]   )    

它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。    

(3)#pragma once (比较常用)。只要在头文件的最开始加入这条指令就能够保证

头文件被编译一次,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。    

(4)#pragma hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。BCB

可以预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用#pragma   startup指定编

译优先级,如果使用了#pragma package(smart_init),BCB就会根据优先级的大小先

后编译。    

   

(5)#pragma   resource   "*.dfm"表示把*.dfm文件中的资源加入工程。*.dfm中包括窗

体外观的定义。    

   

(6)#pragma   warning(disable : 4507 34; once   :   4385;   error   :   164   )     等价于:    

#pragma   warning(disable:4507   34)   //   不显示4507和34号警告信息    

#pragma   warning(once:4385)   //   4385号警告信息仅报告一次    

#pragma   warning(error:164)   //   把164号警告信息作为一个错误。    

同时这个pragma   warning   也支持如下格式:    

#pragma   warning(   push   [   ,n   ]   )    

#pragma   warning(   pop   )    

这里n代表一个警告等级(1---4)。    

#pragma   warning(   push   )保存所有警告信息的现有的警告状态。    

#pragma   warning(   push,   n)保存所有警告信息的现有的警告状态,并且全局警告    

等级设定为n。    

#pragma   warning(   pop   )向栈中弹出最后一个警告信息,在入栈和出栈之间所作

的一切改动取消。例如:    

#pragma   warning(   push   )    

#pragma   warning(   disable   :   4705   )    

#pragma   warning(   disable   :   4706   )    

#pragma   warning(   disable   :   4707   )    

//.......    

#pragma   warning(   pop   )    

在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707)。    

(7)#pragma   comment(...) 该指令将一个注释记录放入一个对象文件或可执行

文件中。常用的lib关键字,可以帮我们连入一个库文件。  

   

(8)#pragma   pack() 我们知道在VC中,对于想结构体Struct这样的类型,VC采

用8字节对齐的方式,如果我们不想使用8字节对齐(在网络变成中经常需要这样),我们可以在结构体前面加上    

#pragma   pack(1)    

struct    

{    

......    

}    

#pragma   pack()

二.#if _MSC_VER > 1000    #pragma once    #endif  

(1)_MSC_VER。 Defines   the   compiler   version.   Defined   as   1200   for   Microsoft   Visual   C++   6.0.   Always   defined.   

(2)#if   _MSC_VER   > 1000的意思是指如果vc编译器的版本大于1000则这个语句

被编译!大概小于1000的版本不支持#pragma   once这个语句。

(3)#pragma   once 。Specifies   that   the   file,   in   which   the   pragma   resides,

will   be   included   (opened)   only   once   by   the   compiler   in   a   build.   A   common   use   for   this   pragma   is   the   following:  

//header.h  

#pragma   once  

//   Your   C   or   C++   code   would   follow:  

#pragma   once    加入头文件的第一行 指示这个文件在编译时只被编译器文件编译

(打开)一次!一般用到.h中防止文件被重复包括!  

三.#pragma once    与   #ifndef    #define   #endif  

(1)从定义上即可看出,pragmas指令是某种机器或者操作系统独有的,并且不同编译器也常常有别。#pragma once这个是编译器相关指令,就是说在这个编译系统

上能用,但是在其他编译系统 不一定型,也就是说移植型差。不过现在基本上

已经是每个编译器都有这个定义了。

#ifndef   #define #endif这个是语言支持指令,这是C/C++语言中的宏定义,通过

宏定义避免文件多次编译。所以在所有支持C++语言的编译器上都是有效的。如果写的程序要   跨平台,最好使用这种方式。

(2)#ifndef   #define #endif   #ifndef 还有其它作用,防止头文件重复引用只是

其中一个应用而已。#pragma只有微软支持。

(3)#ifndef   #define #endif   他读到#ifndef之后,如果已经定义过了,就会跳过

这一大片,一直到#endif为止。这将增加build时间,因为每次compiler都会打开这个文件,然后搜索全文件一遍。而如果碰到了#pragma once,他就会立刻停止,

关闭打开的这个文件。在某种程度上减少 了build时间。一般用法:    

#ifndef  

#define  

#pragma   once  

.....  

#endif  

四. #pragma   data_seg(".mdata").....#pragma data_seg()可以让编译器把两者之间

的所有已初始化变量放入一个新的.mdata段中。应用之一是单应用程序。

有的时候我们可能想让一个应用程序只启动一次,就像单件模式(singleton)一样,实现的方法可能有多种,这里说说用#pragma data_seg的实现,很是简洁便利。

应用程序的入口文件前面加上:

#pragma data_seg("flag_data")

int app_count = 0;

#pragma data_seg()

#pragma comment(linker,"/SECTION:flag_data,RWS")

然后程序启动的地方加上

if(app_count>0) // 如果计数大于0,则退出应用程序。

{

//MessageBox(NULL, "已经启动一个应用程序", "Warning", MB_OK);

//printf("no%d application", app_count);

return FALSE;

} app_count++;

总结:

1. #ifndef 由语言支持所以移植性好,#pragma 可以避免名字冲突

2. 调查一下<stdlib.h>和<iostream>等标准库, 用得都是#ifndef, 我个人推荐这种方式.

posted @ 2012-03-08 10:09 多彩人生 阅读(535) | 评论 (0)编辑 收藏

两种高性能I/O设计模式的比较

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

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


=======================================================================

这篇文章探讨并比较两种用于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.

posted @ 2012-03-06 21:04 多彩人生 阅读(424) | 评论 (0)编辑 收藏

双缓冲消息队列-减少锁竞争

在网络应用服务器端, 为了性能和防止阻塞, 经常会把逻辑处理和I/O处理分离:
I/O网络线程处理I/O事件: 数据包的接收和发送, 连接的建立和维护等.
逻辑线程要对收到的数据包进行逻辑处理.

通常网络线程和逻辑线程之间是通过数据包队列来交换信息, 简单来说就是一个生产者-消费者模式.
这个队列是多个线程在共享访问必须加锁, 意味着每次访问都要加锁。如何更好的如何减少锁竞争次数呢 ?

方案一 双缓冲消息队列:

两个队列,一个给逻辑线程读,一个给IO线程用来写,当逻辑线程读完队列后会将自己的队列与IO线程的队列相调换。
IO线程每次写队列时都要加锁,逻辑线程在调换队列时也需要加锁,但逻辑线程在读队列时是不需要加锁的.

队列缓冲区的大小要根据数据量的大小进行调整的,如果缓冲区很小,就能更及时的处理数据,但吞吐量以及出现资源竞争的几率大多了。

可以给缓冲队列设置最大上限,超过上限的数量之后,将包丢弃不插入队列。
另外,双缓冲的实现也有不同策略的,

一是读操作优先,就是生产者只要发现空闲缓冲,马上swap,
二是写线程只有在当前的缓冲区写满了,才进行swap操作。
三是上层逻辑按照帧率来处理,每一帧的时候将双层缓冲队列调换一下,取一个队列来处理即可

 


方案二 提供一个队列容器:

提供一个队列容器,里面有多个队列,每个队列都可固定存放一定数量的消息。网络IO线程要给逻辑线程投递消息时,会从队列容器中取一个空队列来使用,直到将该队列填满后再放回容器中换另一个空队列。而逻辑线程取消息时是从队列容器中取一个有消息的队列来读取,处理完后清空队列再放回到容器中。

这样便使得只有在对队列容器进行操作时才需要加锁,而IO线程和逻辑线程在操作自己当前使用的队列时都不需要加锁,所以锁竞争的机会大大减少了。

这里为每个队列设了个最大消息数,看来好像是打算只有当IO线程写满队列时才会将其放回到容器中换另一个队列。那这样有时也会出现IO线程未写满一个队列,而逻辑线程又没有数据可处理的情况,特别是当数据量很少时可能会很容易出现[这个可以通过设置超时来处理, 如果当前时间-向队列放入第一个包的时间 > 50 ms, 就将其放回到容器中换另一个队列]。

通常我们逻辑服务器会以场景来划分线程,不同线程执行不同场景.一个线程可以执行多个场景.因为玩家属于场景,我们会把玩家数据,包括其缓冲池丢给场景 去处理.

posted @ 2012-03-06 13:49 多彩人生 阅读(568) | 评论 (0)编辑 收藏

怎么下载MANGOS源码

怎么下载MANGOS源码

mangos-gui Mangos的GUI版控制工具(svn地址)
http://mangos-gui.googlecode.com/svn/trunk/

MangosWFE网站系统(SVN更新地址)
http://opensvn.csie.org/MangosWFE/

mangos-luascript脚本(svn更新地址)
http://mangos-luascript.googlecode.com/svn/trunk

HTTP地址 http://download.toaoto.cn FTP地址 ftp://ftp.toaoto.cn FTP帳號和密碼:wow
Mangos SVN:
http://opensvn.csie.org/mangoswebsite
MangosWeb

http://svn.sourceforge.net/viewvc/mangos/trunk/?view=log
MangosLog

http://svn.sourceforge.net/svnroot/mangos/trunk/
MangosTrunk

http://www.mangosproject.org/forum/
MangosForum

鉴于有些新手不会下载源码...所以我写个帖子出来..给新手参考参考.
1:先下载TortoiseSVN-1.3.5.6804-svn-1.3.2这个工具
LanguagePack-1.3.5.6804-win32-zh_CN(这个是语言补丁)
下下来安装好以后在Setting里面改下语言就OK了
http://luwakin.gbdisk.com/这位帅哥的网络硬盘里面有!
然后安装...........(废话....)

2:源码地址
https://opensvn.csie.org/ScriptDev/trunk ---------------ScriptDev

https://mangos.svn.sourceforge.net/svnroot/mangos--------------Mangos

Silver DataBase Communities SVN is located here:
https://opensvn.csie.org/SDB/
https://opensvn.csie.org/traccgi/SDB/trac.cgi/timeline

SVN contains the latest table data and DB updates, in XML or SQL formats only. Full DB releases can be found in "Index of the Released DBs" section.

https://opensvn.csie.org/SDB

https://opensvn.csie.org/mangoDB

http://opensvn.csie.org/MangosDatabaseProject

3:参照我的图下载吧!!!!
(1)先创建个文件夹.然后右键选SVN取出,在里面输入上面的源码地址!
(2)然后确定后就自动下载了.下载完毕要导出下载的文件...参照最后张图导出就OK啦!

1) 首先,你需要有2个软件,第一个就是下载及更新MANGOS源码用的TortoiseSVN.第二个就是VS2003或者VS2005.
注意:这里推荐使用VS2003.因为在使用VS2005时,出现了很多错误...导致编译失败...
而用VS2003时,没有出现任何问题.

MaNGOS

2) 在作完第一步后,我们可以在电脑里新建一个文件夹.这里以C:\盘来举例.例如C:\mangos.
在建好的文件夹上点击右键.你会看到"SVN Checkout..." , 点了...跳出来一个框框,
在"URL of repository"里面输入"https://mangos.svn.sourceforge.net/svnroot/mangos"
在这个下面有一个选项,你需要选中"HEAD",好了...点击OK吧.

3) 作完上面这一步后, 用右键点击MANGOS文件夹,你会看到"SVN Update",点击他吧,这会让你的MANGOS源码升级,以后每次只要点击"SVN Update",就可以让你的MANGOS源码保持在最新版.

4) 接下来,就是开始编译MANGOS了~~~是不是很兴奋了...
打开VS2003, 选择"Open Project",然后打开"C:\Mangos\win\" 选择"mangosdVC71.sln"
如果你用的是VS2005, 就要打开"mangosdVC80.sln"

5) 接下来的这一步,我建议你看一下图片来明白你需要作什么.

6) 呵呵.这一步就是等待了.编译是需要时间的...等VS帮你作好吧.

7)嗯...过了一会~~~ 编译好了.

你可以"C:\Mangos\bin\release\"里面找到编译好的MANGOS. 需要的是这几个文件

libeay32.dll, libmySQL.dll, mangosd.exe, MaNGOSScript.dll, realmd.exe

嗯, 好像还少什么

mangosd.conf和realmd.conf是不是, 我们可以在"C:\Mangos\src\mangosd\"和C:\mangos\src\realmd\"里面找到mangosd.conf.in , realmd.conf.in
啊 名字不一样 嗯 把后面的.in去掉吧.

SQL文件了可以在"C:\Mangos\sql\"里面找到...当然只有一个架构的..嘿嘿...
升级的SQL文件可以在"E:\Mangos\sql\updates"里面找到...

ScriptDev

下载最新版本的ScriptDev并将和MaNGOS一起使用,你需要以下几个步骤:
1) 新建一个文件夹,并命名。在文件夹上点右键。。。
如同上面Mangos源码的下载方式一样, 使用SVN Checkout, URL地址为 https://opensvn.csie.org/ScriptDev/trunk , 然后使用 SVN Update
2) 在作完上面的步骤后,在这个文件夹上点右键, 选择“TortoiseSVN” ,再选择 “Export“
选择一个新建文件夹.然后点OK,好了 最新版本的ScriptDev已经出来了
3) 到Export好的这个新文件夹里面,复制 SQL, SRC, WIN 这三个文件夹到Mangos文件夹里,
好了,当你编译MANGOS时,将带着最新版本的ScriptDev...
重要:当有ScriptDev的新版出来时。。。你必需重复上面的动作。。。

好了...一个最新版本的MANGOS在你手上出现了...是由你自己完成的哦...是不是特别兴奋~~~

下面的需要的软件,推荐使用2003...2005好多问题的说...
TortoiseSVN
点此下载
VC++ Express Edition
点此下载

数据库可以用990100的0712数据库...

http://bbs.99nets.com/read.php?tid=426801&fpage=2

编译好的MANGOS如何安装也可以参考990100的说明...

好了。。。带着ScriptDev的MaNGOS将成为真正的MaNGOS最新版。。。


posted @ 2012-03-06 11:39 多彩人生 阅读(1929) | 评论 (0)编辑 收藏

提高你开发效率的十五个 Visual Studio 使用技巧

相信做开发的没有不重视效率的。开发C#,VB的都知道,我们很依赖VS,或者说,我们很感谢VS。能够对一个IDE产生依赖,说明这个IDE确实 有它的独特之处。无容置疑,VS是一个非常强大的IDE,它支持多语言编辑。支持C#,VB,C/C++,HTML......它拥有强大的调试编译功 能。它让我们不用去记住那些安装,环境变量设置,服务器设置,编译的繁琐过程。高度集成化。凡事有利有弊,在敏捷开发盛行的时代,VS是否值得我们使用是 无容置疑的。但是强大的VS也拥有众多的设置,众多的技巧。记住某些小技巧可以让我们更加方便,快捷地使用VS。这是很有必要的。每个人或多或少记住了一 些小技巧。但是不可能全部都记住,我们按照我们自己的编程习惯记住一些自己比较常用的就好。

下面是鄙人在编码过程中发现而且比较经常使用的一些小技巧,希望对你有所帮助。

 

1.行编辑(复制,剪切,删除,交换)


 

当你在光标停留行使用快捷键Ctrl+C,X,L时,可以复制,剪切,删除整行内容。当然,右键也是可以的。跟平时的复制,剪切,删除就是选中和没选中代码的区别而已。

如果你想交换上下两行,你可以使用快捷键(Shift+Alt+T),前提是光标要停留在上面那一行。替换之后,光标会一直跟随原本的那一行。

 

2.注释(//TODO:...)


 

看标题的话,你可能想打我。那个程序员不知道注释啊,不就//或者/*.....*/亦或者(HTML/XML注释)。但是使用过

// TODO:注释部分

的,估计是少数吧。如果你喜欢用“任务列表”记录一些要做的事情,这个小功能最适合你了。你可以再VS 2010的菜单上找到任务列表窗,点击“菜单->视图->任务列表”,你也可以点击快捷键“Ctrl+W,T”。VS还提供了,HACK,UNTODU,UnresolvedMergeConflict标记注释,你可以在“工具->选项->环境->任务列表”找到并且编辑/添加/删除标记注释。下面是图示:

提高你开发效率的十五个 Visual Studio 使用技巧

提高你开发效率的十五个 Visual Studio 使用技巧

 

3.创建区域(#region和#endregion)


 

当代码越来越多的时候,你会期望可以隐藏一些代码,而#region 和#endregion 就是这样的功能。你可以在任何位置隐藏任何代码。即使是隐藏的内容不属于同一个函数。你可以点击#region旁边的+/-,展开/隐藏代码。在隐藏的时 候,当你的光标放放置在备注上面的时候,VS会显示出隐藏的代码内容。

提高你开发效率的十五个 Visual Studio 使用技巧


4.选择一个单词/选择一个字符串


 

如你所知双击一个单词的时候会选择整个单词。按住Ctrl键单击单词的任意位置同样可以选中单词。

双击字符串第一个引号的左侧可以选中整个字符串。按住Ctrl键单击第一个引号的前面同样可以选中整个字符串。

 

5.将代码放入工具箱


 

工具箱是拿来放控件的地方。我们在使用控件的时候,只需要从控件当中拖动控件到代码就可以了,这样可以省去大量代码的编辑工作。既然工具箱如此方便,那么是否可以将一段重用性很高的代码放入工具箱呢。答案当然是可以的。

你可以选中你的代码,然后拖入工具箱的空白处,你的代码就保存到工具箱了。就像你将控件拖 入代码页面一样,也可以将代码拖入工具箱中。以后你就可以像使用控件一样使用重用的代码。这是非常方便的。而且工具箱的内容不会因为你关闭VS而消失,在 你下次打开VS的时候工具箱同样保存了你的代码。如果你需要查看工具箱保存的代码而又不想拖到代码页面中,你只需要将光标停留在工具箱的代码图标上面。如 图所示:

提高你开发效率的十五个 Visual Studio 使用技巧

 

6.格式化代码


 

这个很重要,即使VS在你每次打完“;”回车之后会自动格式化代码。但是难免代码的格式会有所变化,譬如粘贴一段代码之后,代码的格式往往会受到影响。所以,这个还是很有必要知道的。

格式化部分代码:选中代码->Ctrl+K,F。或者Ctrl+E,F

格式化整个文档:编辑->高级->设置文档的格式。或者 Ctrl+K,D。或者Ctrl+E,D

 

7.切换设计/代码图示


 

在ASP.NET页面切换(HTML): Ctrl+PgUp/Ctrl+PgDn

在windows窗体切换:F7/Shift+F7 (代码/设计)

 

8.查找错误代码。


 

当错误列表有错误或者警告提示时,你可以双击这个错误或提示,就可以跳转到错误或警告的语句前。

 

9.跳转到指定行号


 

如果代码很多的时候,这是很有用的。在ASP.NET编程的时候,很多错误只有在运行网站的时候才能发现,而这个错误又没被在错误列表提示的时候,你就可以使用这个小技巧跳到错误的代码前面。

双击右下角状态栏的行号,会跳出一个行号跳转窗体。或者快捷键Ctrl+G调出窗体。当然,还可以从菜单栏点击“编辑->跳转..”使用这个功能。

提高你开发效率的十五个 Visual Studio 使用技巧

 

10.快速查找


 

光标停留在需要查找的词上面,使用快捷键Ctrl+F3可以跳转到下一个相同的词。按Shift+F3可以往上查找。

 

11.查找“{/}”


 

查找:你是否很烦恼有些对应的标记找的到头找不到尾,找得到尾不知道那个是头。当你把光标放在“{”的前面,VS会将相对应的”}“标记起来。你也可以将光标停留在“}”的后面,可以达到同样的效果。

提高你开发效率的十五个 Visual Studio 使用技巧

 

12.查找和替换


 

当你想查找/替换掉某个字符串的时候,你可以按快捷键“Ctrl+F”或者“Ctrl+H”,进行这一操作。另外VS支持正则表达式和通配符。

如果你想从整个项目进行查找/替换,你需要使用快捷键“Ctrl+Shift+F”或者“Ctrl+Shift+H”。当然这一切都可以在菜单栏找到。“编辑->查找和替换”。当你想中止全局替换的时候,你可以使用快捷键“Ctrl+Pause Break”。

提高你开发效率的十五个 Visual Studio 使用技巧

 

13. 书签


 

书签是很有用的功能,用过Chrome的都知道。在VS当中,书签同样适用。它可以帮你保存位置,以便你写代码。

放置书签:Ctrl+B,T

上一个书签:Ctrl+B,P

下一个书签:Ctrl+B,N

删除所有书签:Ctrl+K,C

除此之外,VS还提供了其它的书签操作。

提高你开发效率的十五个 Visual Studio 使用技巧

 

14.跳转到定义


 

当你查看代码的时候,往往需要去查看原函数,这是难免的。但是千万不要去手动寻找函数。这效率往往是很低的。你可以右键该函数,选择跳转到定义即可。当然你也可以使用快捷键F12

提高你开发效率的十五个 Visual Studio 使用技巧

 

15.以文本形式插入外部文本


 

菜单->编辑->将文件作为文本插入

好处是,你不需要打开文件去复制粘贴。

提高你开发效率的十五个 Visual Studio 使用技巧

 

或许这些小技巧你早就知道了。亦或是,你觉得这些技巧根本没啥用。当然,我们最主要的任务还是去编码而已。没有必要将心思花在这上面。但是,当你习惯使用这些小技巧的时候,这为你带来的收益觉得不会让你有所失望的。有些技巧,个人认为还是很有必要掌握的。

总之,捡你想捡的吧,让其他人折腾去吧。

posted @ 2012-03-05 21:25 多彩人生 阅读(379) | 评论 (0)编辑 收藏

服务端 点滴

@饭中淹
多谢楼主解答,不过还是有一些疑问,我说一下自己对这个架构的理解。
从用户登录开始,用户登录连接网关,发数据到loginserver校验账户密码,如果areaDB中没有账户信息,向数据中心要账户密码,插入areaDB,以后校验账户就可以直接在区域DB中做了,如果账户密码校验成功,发送本区的组列表给客户端,玩家选择某个服务器,login获取生成一个key发给userserver,同时把key发给客户端以及一个网关的ip端口,客户端使用其连接,发送key到Userserver,比对key,非法踢掉客户端连接,合法userserver向数据中心获取账户详细信息,比如账户上剩余点卡等,同时向GroupDB获取本组中的自己的角色,得到这些信息都发给客户端,客户端选择一个角色进入游戏,userserver从数据库读取该角色的完全信息,根据玩家之前的位置确定进入那个gameserver(我认为游戏服务器是根据地图划分的,不知道对不对?),角色数据传到gameserver,同时客户端根据这些数据进入场景,gameserver得到信息同时告知publicserver(publicserver连DB做什么?我猜测可以获取工会以及工会成员信息,对否?)

http://www.cppblog.com/johndragon/archive/2008/04/10/46768.html

posted @ 2012-03-05 21:02 多彩人生 阅读(166) | 评论 (0)编辑 收藏

补习 explicit

  C++提供了关键字explicit,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。声明为explicit的构造函数不能在隐式转换中使用。
  C++中, 一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数), 承担了两个角色。 1 是个构造器 2 是个默认且隐含的类型转换操作符。
  所以, 有时候在我们写下如 AAA = XXX, 这样的代码, 且恰好XXX的类型正好是AAA单参数构造器的参数类型, 这时候编译器就自动调用这个构造器, 创建一个AAA的对象。
  这样看起来好象很酷, 很方便。 但在某些情况下(见下面权威的例子), 却违背了我们(程序员)的本意。 这时候就要在这个构造器前面加上explicit修饰, 指定这个构造器只能被明确的调用,使用, 不能作为类型转换操作符被隐含的使用。 呵呵, 看来还是光明正大些比较好。
  explicit构造函数的作用
  解析:
  explicit构造函数是用来防止隐式转换的。请看下面的代码:
  class Test1
  {
  public:
  Test1(int n) { num = n; } //普通构造函数
  private:
  int num;
  };
  class Test2
  {
  public:
  explicit Test2(int n) { num = n; } //explicit(显式)构造函数
  private:
  int num;
  };
  int main()
  {
  Test1 t1 = 12; //隐式调用其构造函数, 成功
  Test2 t2 = 12; //编译错误,不能隐式调用其构造函数
  Test2 t3(12); //显示调用成功
  return 0;
  }
  Test1的构造函数带一个int型的参数,代码19行会隐式转换成调用Test1的这个构造函数。而Test2的构造函数被声明为explicit(显式),这表示不能通过隐式转换来调用这个构造函数,因此代码20行会出现编译错误。
  普通构造函数能够被隐式调用。而explicit构造函数只能被显示调用。

posted @ 2012-03-04 16:26 多彩人生 阅读(182) | 评论 (0)编辑 收藏

protobuf

接下来是定义message属性,一个message是包含了各种类型字段的聚集。有很多标准的变量类型可以使用,包 括:bool,int32,float,double和string。你也可以使用其他的message作为字段类型。正像例子中的Person包含了 PhoneNumber,而AddressBook包含了Persion。甚至可以在message内部定义message,例 如:PhoneNumber就是在Persion里面定义的。你还可以定义enum类型,正像指定电话号码类型的MOBILE、HOME、WORK。

其中“=1”,“=2”表示每个元素的标识号,它会用在二进制编码中对域的标识。标识号1-15由于使用时会比那些高的标识号少一个字节,从最优化 角度考虑,可以将其使用在一些较常用的或repeated元素上,对于16以上的则使用在不常用的或optional的元素上。对于repeated的每 个元素都需要重复编码该标识号,所以repeated的域进行优化来说是最显示的。

每个字段必须提供一个修饰词:

Ø  required:表示字段必须提供,不能为空。否则message会被认为是未初始化的,试图build未初始化的message会抛出 RuntimeException。解析未初始化的message会抛出IOException。除此之外,一个required字段与optional 字段完全相同。

Ø  optional:可选字段,可以设置也可以不设置。如果没有设置,会设置一个缺省值。可以指定一个缺省值,正像电话号码的type字段。否则,使用系统 的缺省值:数字类型缺省为0;字符类型缺省为空串;逻辑类型缺省为false;对于嵌入的message,缺省值通常是message的实例或原型。

Ø  repeated:字段可以被重复(包括0),可等同于动态数组或列表。其中存储的值列表的顺序是被保留的。

Required修饰的字段是永久性的,在使用该修饰符时一定要特别小心。如果在以后想要修改required域为optional域时会出现问 题。对于访问旧接口的用户来说没有该字段时,将会认为是不合法的访问,将会被拒绝或丢弃。其中google的一些工程师给出的建议是如果不是必须,就尽量 少用required修饰符。

posted @ 2012-03-04 15:30 多彩人生 阅读(550) | 评论 (0)编辑 收藏

字节对齐

一、什么是对齐,以及为什么要对齐:
1. 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
2. 对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况, 但是最常见的是如果不按照适合其平台的要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为 32位)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低 字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。
二、对齐的实现
通常,我们写程序的时候,不需要考虑对齐问题。编译器会替我们选择适合目标平台的对齐策略。当然,我们也可以通知给编译器传递预编译指令而改变对指定数据的对齐方法。
但是,正因为我们一般不需要关心这个问题,所以因为编辑器对数据存放做了对齐,而我们不了解的话,常常会对一些问题感到迷惑。最常见的就是struct数据结构的sizeof结果,出乎意料。为此,我们需要对对齐算法所了解。
对齐的算法:
由于各个平台和编译器的不同,现以本人使用的gcc version 3.2.2编译器(32位x86平台)为例子,来讨论编译器对struct数据结构中的各成员如何进行对齐的。
设结构体如下定义:
struct A {
    int a;
    char b;
    short c;
};
结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个。所以A用到的空间应该是7字节。但是因为编译器要对数据成员在空间上进行对齐。
所以使用sizeof(strcut A)值为8。
现在把该结构体调整成员变量的顺序。
struct B {
    char b;
    int a;
    short c;
};
这时候同样是总共7个字节的变量,但是sizeof(struct B)的值却是12。
下面我们使用预编译指令#pragma pack (value)来告诉编译器,使用我们指定的对齐值来取代缺省的。
#pragma pack (2) /*指定按2字节对齐*/
struct C {
    char b;
    int a;
    short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct C)值是8。

修改对齐值为1:
#pragma pack (1) /*指定按1字节对齐*/
struct D {
    char b;
    int a;
    short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct D)值为7。

对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
这里面有四个概念值:
1)数据类型自身的对齐值:就是上面交代的基本数据类型的自身对齐值。
2)指定对齐值:#pragma pack (value)时的指定对齐值value。
3)结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
4)数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小的那个值。
有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就 是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是 数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整 数倍,结合下面例子理解)。这样就不难理解上面的几个例子的值了。
例子分析:
分析例子B;
struct B {
    char b;
    int a;
    short c;
};
假设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。第一个成员变量b的自身对齐值是1,比指定或者默认指 定对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.第二个成员变量a,其自身对齐值为4,所以有效对齐值也为 4,所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,复核0x0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐 值为2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存 放的都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求, 0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。故B从0x0000到0x000B 共有12个字节,sizeof(struct B)=12;

同理,分析上面例子C:
#pragma pack (2) /*指定按2字节对齐*/
struct C {
    char b;
    int a;
    short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x0000%1= 0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续 字节中,符合0x0002%2=0。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放
在0x0006、0x0007中,符合0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。又C的自身对齐值为4,所以 C的有效对齐值为2。又8%2=0,C只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.

有 了以上的解释,相信你对C语言的字节对齐概念应该有了清楚的认识了吧。在网络程序中,掌握这个概念可是很重要的喔,在不同平台之间(比如在Windows 和Linux之间)传递2进制流(比如结构体),那么在这两个平台间必须要定义相同的对齐方式,不然莫名其妙的出了一些错,可是很难排查的哦^_^。

posted @ 2012-03-04 14:26 多彩人生 阅读(301) | 评论 (0)编辑 收藏

ProtocolBuffers2.4.1应用说明(一)

ProtocolBuffers2.4.1应用说明(一)
2012-02-03 12:07

 

客方的ProtocolBuffers 详细说明,可以下载最新版的ProtocolBuffers包。

我所下载的包是:protobuf-2.4.1.tar.bz2 、 protoc-2.4.1-win32.zip

 

ProtocolBuffers 首页:http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/overview.html

protobuf-2.4.1.tar.bz2  是源码包

 protoc-2.4.1-win32.zip 是编译 .proto 文件的编译器

本文使用 ProtocolBuffers 的环境

操作系统: windows 7 64位

开发工具:Visual studio 2008

开发语言:C++、MFC类库

第一步: 编译protobuf-2.4.1工程

说明:编译protobuf-2.4.1工程后生成 libprotobuf.lib, libprotobuf.lib 会在自已的工程文件中用到这个库文件

1)当前的目录结构为:

E:\ProtocolBuffers\ 此目录结构下有两个包 protobuf-2.4.1.tar.bz2 、protoc-2.4.1-win32.zip

2)解压 protobuf-2.4.1.tar.bz2 包

会生 E:\ProtocolBuffers\protobuf-2.4.1\protobuf-2.4.1目录结构

调整后的目录结构为:E:\ProtocolBuffers\protobuf-2.4.1目录结构,便于应用。

3)VS2008编译工程

找到 E:\ProtocolBuffers\protobuf-2.4.1\vsprojects\protobuf.sln文件。

用VS2008 打开,然后编译整个功程,很顺利的编译完整个功程。

如图所示:

    

编译完成后会在E:\ProtocolBuffers\protobuf-2.4.1\vsprojects\Debug 目录结构中生成libprotobuf.lib库文件。

4) 如果出现问题:

可以阅读 vsprojects\readme.txt 说明文档。  

 

第二步:编写 .proto 文件

 

1)在目录 E:\ProtocolBuffers\protobuf-2.4.1\examples 中有个示例

可以先按官方的文档来熟悉一下。

2) 编写 .proto 文件

自已编写的 shapeobject.proto 文件

 

package candee;

option java_package = "com.example.candee";

option java_outer_classname = "ShapeObjectProto";

message DrawInfoPB {


message ColorVal {

required int32r = 1;//int32  unsigned short

required int32g = 2;

required int32b = 3;

}

 

required int32toolbarState = 1;// TOOLBAR_STATE

required ColorValpenColor = 2;// 笔的颜色

required int32penLineWidth = 3;// 用户设置画笔的宽度

required ColorValwordColor = 4;// 字的颜色

required int32wordLineWidth = 5;// 用户设置字的宽度

required ColorValgraphColor = 6;// 图形的颜色

required int32graph = 7;// 图形

required int32graphLineWidth = 8;// 绘制图形的线宽

}

 

message ShapeObjectPB {

 

required DrawInfoPB drawInfoPB = 1;// 绘画信息

 

message DrawPointPB {

required int32 x1 = 1;

required int32 y1 = 2;

required int32 X2 = 3;

required int32 y2 = 4;

}

repeated DrawPointPB drawPointPB = 2;// 绘画坐标

 

optional string textPB = 3;// 编辑框文字信息

}

 

message DataPB {

repeated ShapeObjectPB shapeObjectPB = 1;

}


第三步 编译 shapeobject.proto 文件,生成C++源文件

 

1)解压 E:\ProtocolBuffers\protoc-2.4.1-win32.zip

   会生成 E:\ProtocolBuffers\protoc-2.4.1-win32\protoc.exe 编译文件。

2) 将 protoc.exe 考贝到 shapeobject.proto文件同一级目录中。

本目录为 E:\ProtocolBuffers\protobuf-2.4.1\examples

3)命令执行protoc 文件

在\examples\ 新建一个目录为 1\用来保存生成的C++源文件

在命令行下,执行protoc --cpp_out=1 shapeobject.proto

如图所示:

 4)生成的C++源文件

在E:\ProtocolBuffers\protobuf-2.4.1\examples\1 目录中保存生成的文件

生成的文件:shapeobject.pb.cc shapeobject.pb.h

将这两个文件添加到你的功程中,就可以用户ProtocolBuffer了

posted @ 2012-03-02 22:04 多彩人生 阅读(1042) | 评论 (0)编辑 收藏

仅列出标题
共25页: First 17 18 19 20 21 22 23 24 25 

导航

统计

常用链接

留言簿(3)

随笔分类

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜