<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Programming</title>
	<atom:link href="http://blog.developnotes.info/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.developnotes.info</link>
	<description>Learning, Working, Writing</description>
	<lastBuildDate>Wed, 07 Nov 2012 13:08:06 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.4.2</generator>
<image>
<link>http://blog.developnotes.info</link>
<url>http://blog.developnotes.info/wp-content/plugins/maxblogpress-favicon/icons/favicon-31.ico</url>
<title>Programming</title>
</image>
		<item>
		<title>pkg_resources.DistributionNotFound</title>
		<link>http://blog.developnotes.info/pkg_resources-distributionnotfound/</link>
		<comments>http://blog.developnotes.info/pkg_resources-distributionnotfound/#comments</comments>
		<pubDate>Wed, 07 Nov 2012 12:54:23 +0000</pubDate>
		<dc:creator>huubby</dc:creator>
				<category><![CDATA[默认]]></category>
		<category><![CDATA[python]]></category>

		<guid isPermaLink="false">http://blog.developnotes.info/?p=469</guid>
		<description><![CDATA[前段时间升级到Mountain Lion，之后很久没写过Python。昨天准备折腾一下才发现，我的Python竟然不工作了。python &#8211;version有输出，但pip报错： $ pip --version Traceback (most recent call last): File "/usr/local/bin/pip", line 5, in &#60;module&#62; from pkg_resources import load_entry_point File "/Library/Python/2.7/site-packages/distribute-0.6.30-py2.7.egg/pkg_resources.py", line 2807, in &#60;module&#62; working_set.require(__requires__) File "/Library/Python/2.7/site-packages/distribute-0.6.30-py2.7.egg/pkg_resources.py", line 690, in require needed = self.resolve(parse_requirements(requirements)) File "/Library/Python/2.7/site-packages/distribute-0.6.30-py2.7.egg/pkg_resources.py", line 588, in resolve raise DistributionNotFound(req) pkg_resources.DistributionNotFound: pip==1.0.2 google了一晚上找到了解决方案 在python的setuptools页面下载对应版本的Python 打开终端，运行： sh ./setuptools-0.6c11-py2.7.egg 完成之后运行： easy_install -U pip 这个问题似乎是因为ML升级了Python，却没有做附带的升级site-packages工作。]]></description>
			<content:encoded><![CDATA[<p>前段时间升级到Mountain Lion，之后很久没写过Python。昨天准备折腾一下才发现，我的Python竟然不工作了。python &#8211;version有输出，但pip报错：</p>
<blockquote><p><code>$ pip --version<br />
Traceback (most recent call last):<br />
File "/usr/local/bin/pip", line 5, in &lt;module&gt;<br />
from pkg_resources import load_entry_point<br />
File "/Library/Python/2.7/site-packages/distribute-0.6.30-py2.7.egg/pkg_resources.py", line 2807, in &lt;module&gt;<br />
working_set.require(__requires__)<br />
File "/Library/Python/2.7/site-packages/distribute-0.6.30-py2.7.egg/pkg_resources.py", line 690, in require<br />
needed = self.resolve(parse_requirements(requirements))<br />
File "/Library/Python/2.7/site-packages/distribute-0.6.30-py2.7.egg/pkg_resources.py", line 588, in resolve<br />
raise DistributionNotFound(req)<br />
pkg_resources.DistributionNotFound: pip==1.0.2</code></p></blockquote>
<p>google了一晚上找到了解决方案</p>
<ul>
<li>在python的<a href="http://pypi.python.org/pypi/setuptools">setuptools页面</a>下载对应版本的Python</li>
<li>打开终端，运行：<br />
<blockquote><p><code>sh ./setuptools-0.6c11-py2.7.egg</code></p></blockquote>
</li>
<li>完成之后运行：<br />
<blockquote><p><code>easy_install -U pip</code></p></blockquote>
</li>
</ul>
<p>这个问题似乎是因为ML升级了Python，却没有做附带的升级site-packages工作。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.developnotes.info/pkg_resources-distributionnotfound/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>标准输入／输出流的缓冲</title>
		<link>http://blog.developnotes.info/buffering-of-standard-input-output/</link>
		<comments>http://blog.developnotes.info/buffering-of-standard-input-output/#comments</comments>
		<pubDate>Mon, 09 May 2011 14:30:39 +0000</pubDate>
		<dc:creator>huubby</dc:creator>
				<category><![CDATA[默认]]></category>
		<category><![CDATA[buffering]]></category>
		<category><![CDATA[standard I/O]]></category>

		<guid isPermaLink="false">http://blog.developnotes.info/?p=397</guid>
		<description><![CDATA[本文内容大部分内容来自《Advanced Programming in the UNIX Environment》第5章第4节 Buffering。 题目： 下面的代码输出结果是什么？ class A { private: int m_value; public: A(int value) { m_value = value; } void Print1() { printf("hello world"); } void Print2() { printf("%d", m_value); } }; int main(void) { A* pA = NULL; pA-&#62;Print1(); pA-&#62;Print2(); return 0; } 显然应该是，能正常执行Print1()而不能执行Print2()，也就是说，在程序崩溃之前至少应该能输出“hello world”。原因看这里 等一等，你可能会拷贝下来去编译执行一次，如果跟我一样在Mac下的话，你可能会发现不对劲的地方－为什么看不到Print1()的输出呢？为了确认答案，拷贝题目代码执行的结果中没有Print1()的输出，直接看到Print2()引用非法地址导致的错误： Segmentation fault 那这是什么原因呢？ printf函数定义： [...]]]></description>
			<content:encoded><![CDATA[<p>本文内容大部分内容来自《<a href="http://book.douban.com/subject/1788421/">Advanced Programming in the UNIX Environment</a>》第5章第4节 Buffering。</p>
<p>题目：</p>
<blockquote><p>下面的代码输出结果是什么？</p>
<pre>class A
{
    private:
        int m_value;
    public:
        A(int value)
        {
            m_value = value;
        }
        void Print1()
        {
            printf("hello world");
        }
        void Print2()
        {
            printf("%d", m_value);
        }
};

int main(void)
{
    A* pA = NULL;
    pA-&gt;Print1();
    pA-&gt;Print2();

    return 0;
}</pre>
</blockquote>
<p>显然应该是，能正常执行Print1()而不能执行Print2()，也就是说，在程序崩溃之前至少应该能输出“hello world”。原因看<a href="http://blog.developnotes.info/2009/12/22/0912222006/">这里</a></p>
<p>等一等，你可能会拷贝下来去编译执行一次，如果跟我一样在Mac下的话，你可能会发现不对劲的地方－为什么看不到Print1()的输出呢？为了确认答案，拷贝题目代码执行的结果中没有Print1()的输出，直接看到Print2()引用非法地址导致的错误：</p>
<pre>Segmentation fault</pre>
<p>那这是什么原因呢？</p>
<p>printf函数定义：</p>
<pre>int  printf(const char * __restrict, ...)</pre>
<p>它属于标准I/O库，标准I/O库提供了对输入输出的缓存以达到调用read和write次数最少的目的。嗯哼，“缓冲”是关键词，猜测Print1()的输出应该是被缓冲了吧。可具体是怎么缓冲的呢？</p>
<p>标准I/O库通过提供缓冲区，减少对于无缓冲的read和write方法的调用，增加效率。另一方面，缓冲区的存在也使得应用程序不用自己考虑最佳的读写块大小。不过，如上所见，缓冲区既是标准I/O库的便利之处，同时也是容易引起混乱的地方。标准I/O库提供三种缓冲方式：</p>
<ol>
<li>完全缓冲，此种情形下，所有的I/O操作都在缓冲区被填满后才真正的发生；</li>
<li>行缓冲，只有当碰到换行符时才发生真正的I/O操作；</li>
<li>无缓冲，顾名思义，标准I/O库对I/O操作不做缓存。</li>
</ol>
<p>ISO C对standard input、standard output以及standard error有以下规定：</p>
<ul>
<li>当且仅当不指向交互设备时，standard input和standard output是完全缓冲的</li>
<li>Standard error不允许完全缓冲</li>
</ul>
<p>这个模糊不清的标准根本没说实现的时候具体应该怎么做，当standard input指向交互设备时，它应该无缓冲还是行缓冲？Standard error不允许完全缓冲，那么实现的时候采用行缓冲还是无缓冲呢？大多数的实现遵循以下原则：</p>
<ul>
<li>Standard error总是实现为无缓冲流；</li>
<li>除了standard error之外的其他流，倘若指向终端设备，则采取行缓冲。否则全部采用完全缓冲；</li>
</ul>
<p>对默认的缓冲方式不满意的话，可以通过</p>
<pre>void setbuf(FILE * __restrict, char * __restrict);
int  setvbuf(FILE * __restrict, char * __restrict, int, size_t);</pre>
<p>两个函数更改缓冲区大小以及类型。</p>
<p>好了，既然知道内幕，现在修改main函数，把缓冲区设置成空试试</p>
<pre>int main(void)
{
    setbuf(stdout, NULL);

    A* pA = NULL;
    pA-&gt;Print1();
    pA-&gt;Print2();

    return 0;
}</pre>
<p>再执行，这次Print1()的输出能够正常显示了。</p>
<p>&nbsp;</p>
<p>注：上面的代码在Visual 2005下不需改动即可显示Print1()输出</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.developnotes.info/buffering-of-standard-input-output/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Win2003Server的SYN Attack Protect陷阱</title>
		<link>http://blog.developnotes.info/trap-in-syn-attack-protect/</link>
		<comments>http://blog.developnotes.info/trap-in-syn-attack-protect/#comments</comments>
		<pubDate>Thu, 17 Feb 2011 03:11:06 +0000</pubDate>
		<dc:creator>huubby</dc:creator>
				<category><![CDATA[默认]]></category>
		<category><![CDATA[socket]]></category>
		<category><![CDATA[synattackprotect]]></category>
		<category><![CDATA[TCPIP]]></category>
		<category><![CDATA[win2003server]]></category>

		<guid isPermaLink="false">http://developnotes.info/?p=381</guid>
		<description><![CDATA[发现这个陷阱源于一个自己写的HTTP服务端。因为项目里要用到支持GET请求就能满足需要的HTTP服务端，图方便就用内部封的异步Socket类实现了这么个简单的服务端。没想到，就是这个简单的HTTP服务，还是出现了几次莫名其妙的问题。第一次出现在刚完成代码功能测试时，对HTTP的一知半解导致查了几天之后才发现原来是没处理HTTP头里的Keep-Alive。 最近又碰见另一个百思不得其解铃还需系铃人人得而诛之的问题，测试服务端性能时，用Apache的AB压服务器，设置的并发数稍大就只能完成一部分请求，AB的提示是“远程主机主动关闭连接”。具体来说就是，当AB参数为请求数1000，并发数200的时候，服务端能够正常完成测试；增加并发数到300，则服务端只能完成1000个请求中的300多个，客户端抓包观察到大量请求在TCP三次握手完成后立刻被RST。 用自己封装的异步Socket类写个测试客户端，循环连接服务端，产生大量连接，同样连接数也只能到300多个，其余的大量连接得到类型为WSAENETRESET的socket错误。 用来实现HTTP服务端的异步Socket类没有什么特别操作。逻辑是这样的： 服务端首先产生一个Socket句柄（废话&#8230;），转为异步模式后监听端口，等待客户端连接。 一旦有客户端连接，服务端调用accept得到新的Socket，加入到一个Socket集合进行统一处理。 每次统一处理时，查询所有Socket的事件，对不同的事件进行不同的处理。 服务端对所有的Socket句柄不主动执行关闭操作，除非调用recv时得到的长度为0，即认为是客户端主动关闭，随后服务端进入被动关闭。 应用开发出现问题的第一反应是检查自己的程序。首先怀疑是逻辑不严密，在某特殊情况下Reset客户端连接。在服务端每个可能关闭socket的地方增加日志，再用AB压一次观察输出。但服务端日志没有记录到任何异常的关闭操作，问题似乎不是服务端关闭连接导致的。 既然没有异常关闭socket的情况，那应该是其他地方出问题。考虑listen函数的backlog参数，会不会是这里的原因呢？ listen函数原型： int listen( __in SOCKET s, __in int backlog ); 来看backlog参数在MSDN的说明 The maximum length of the queue of pending connections 再往下看，MSDN中对listen函数有一段这样的描述 If a connection request arrives and the queue is full, the client will receive an error with an indication of WSAECONNREFUSED. 就是说，如果客户端连接数过大，超过调用listen函数设置的backlog队列大小，新的连接会收到WSAECONNREFUSED的socket错误。但观察到的现象是客户端socket错误为WSAENETRESET，不符合文档的描述。 过了listen函数再往下看，代码进入日志能够记录的范围。上面说过，服务端没有异常关闭的操作。到这里仍然没找到原因。目前为止，最有可能的情况就是瞬间的连接数过多导致backlog满。就算是这个原因，客户端收到的也应该是WSAECONNREFUSED错误而不是观察到的WSAENETRESET错误，绕进死胡同了。就这个问题在stackoverflow发的询问帖，回复里也有人说可能是backlog满导致客户端无法连接，但却无法解释为什么收到的socket错误不是MSDN描述的WSAECONNREFUSED。 问题就这样被搁置下来，直到三天后我忙完手头的事情，用部分关键词google，得到了某个错误现象匹配度很高的搜索结果。这里是原帖，截取一段简单描述 We [...]]]></description>
			<content:encoded><![CDATA[<div>
<p>发现这个陷阱源于一个自己写的HTTP服务端。因为项目里要用到支持GET请求就能满足需要的HTTP服务端，图方便就用内部封的异步Socket类实现了这么个简单的服务端。没想到，就是这个简单的HTTP服务，还是出现了几次莫名其妙的问题。第一次出现在刚完成代码功能测试时，对HTTP的一知半解导致查了几天之后才发现原来是没处理HTTP头里的Keep-Alive。</p>
<p>最近又碰见另一个百思不得其解铃还需系铃人人得而诛之的问题，测试服务端性能时，用Apache的AB压服务器，设置的并发数稍大就只能完成一部分请求，AB的提示是“远程主机主动关闭连接”。具体来说就是，当AB参数为请求数1000，并发数200的时候，服务端能够正常完成测试；增加并发数到300，则服务端只能完成1000个请求中的300多个，客户端抓包观察到大量请求在TCP三次握手完成后立刻被RST。</p>
<p>用自己封装的异步Socket类写个测试客户端，循环连接服务端，产生大量连接，同样连接数也只能到300多个，其余的大量连接得到类型为WSAENETRESET的socket错误。</p>
<p>用来实现HTTP服务端的异步Socket类没有什么特别操作。逻辑是这样的：</p>
<ul>
<li>服务端首先产生一个Socket句柄（废话&#8230;），转为异步模式后监听端口，等待客户端连接。</li>
<li>一旦有客户端连接，服务端调用accept得到新的Socket，加入到一个Socket集合进行统一处理。</li>
<li>每次统一处理时，查询所有Socket的事件，对不同的事件进行不同的处理。</li>
<li>服务端对所有的Socket句柄不主动执行关闭操作，除非调用recv时得到的长度为0，即认为是客户端主动关闭，随后服务端进入被动关闭。</li>
</ul>
<p>应用开发出现问题的第一反应是检查自己的程序。首先怀疑是逻辑不严密，在某特殊情况下Reset客户端连接。在服务端每个可能关闭socket的地方增加日志，再用AB压一次观察输出。但服务端日志没有记录到任何异常的关闭操作，问题似乎不是服务端关闭连接导致的。</p>
<p>既然没有异常关闭socket的情况，那应该是其他地方出问题。考虑listen函数的backlog参数，会不会是这里的原因呢？</p>
<p>listen函数原型：</p>
<pre>int listen( __in  SOCKET s,  __in  int backlog );</pre>
<p>来看backlog参数在MSDN的说明</p>
<blockquote><p>The maximum length of the queue of pending connections</p></blockquote>
<p>再往下看，MSDN中对listen函数有一段这样的描述</p>
<blockquote><p>If a connection request arrives and the queue is full, the client will receive an error with an indication of WSAECONNREFUSED.</p></blockquote>
<p>就是说，如果客户端连接数过大，超过调用listen函数设置的backlog队列大小，新的连接会收到WSAECONNREFUSED的socket错误。但观察到的现象是客户端socket错误为WSAENETRESET，不符合文档的描述。</p>
<p>过了listen函数再往下看，代码进入日志能够记录的范围。上面说过，服务端没有异常关闭的操作。到这里仍然没找到原因。目前为止，最有可能的情况就是瞬间的连接数过多导致backlog满。就算是这个原因，客户端收到的也应该是WSAECONNREFUSED错误而不是观察到的WSAENETRESET错误，绕进死胡同了。就这个问题在<a href="http://stackoverflow.com/">stackoverflow</a>发的询问帖，回复里也有人说可能是backlog满导致客户端无法连接，但却无法解释为什么收到的socket错误不是MSDN描述的WSAECONNREFUSED。</p>
<p>问题就这样被搁置下来，直到三天后我忙完手头的事情，用部分关键词google，得到了某个错误现象匹配度很高的搜索结果。<a href="http://www.tech-archive.net/Archive/Development/microsoft.public.win32.programmer.networks/2005-10/msg00313.html">这里</a>是原帖，截取一段简单描述</p>
<blockquote><p>We started getting problems when customers implemented SP1 on their Windows Server 2003<br />
boxes. We&#8217;d see clients who thought they had successfully connected, but who would get a<br />
RESET back from the server immediately after the 3-way handshake (SYN, SYN-ACK, ACK). This<br />
seems *completely broken* to me.</p></blockquote>
<p>啊哈，跟我们的问题看起来一样！他认为是Windows 2003 Server默认开启的<a href="http://technet.microsoft.com/en-us/library/cc781167(WS.10).aspx">SYN攻击保护</a>开关在作怪，猜想可能是SYN攻击保护机制的实现影响到Winsock的accept函数行为。正常的行为是，当backlog队列满时，三次握手中第一个SYN包就会导致服务端响应RST+ACK拒绝客户端的连接。但SYN保护机制起作用后，这个行为变成直接接受新的连接再去检查backlog队列，当发现backlog队列满时丢弃此连接。服务端此时要干掉连接就只能在已完成三次握手的连接上发送一个RST，于是客户端出现莫名其妙的WSAENETRESET错误。</p>
<p>这是他的问题以及猜测，能不能解决我们的问题，还要实验一下。打开注册表才发现，我们的服务器上没有他描述的HKLM\System\CurrentControlSet\Services\Tcpip\Parameters\SynAttackProtect这一项，没关系，没有我们可以创造一个，设置键值为0，重启机器（万恶的重启！）。此时再用AB去压服务端，在客户端果然抓到的RST+ACK包。用自己写的客户端测试，socket错误也由之前百思不得其解的WSAENETRESET变成WSAECONNREFUSED。</p>
<p>问题原因到这里基本确认，由于我们的服务端实现在接受客户端连接时速度不够快，用AB压服务端时的大量连接填满backlog队列，这些连接同时也触发服务器默认开启的SYN攻击保护机制，操作系统认为受到SYN攻击，后续的其他连接于是被重置。关于这个问题，<a href="http://blogs.msdn.com/b/wndp/archive/2005/12/09/502256.aspx">这里</a>也有提及。这种跟文档描述不一致的行为害苦了广大程序员，在别人2005年就受过苦之后，2010年我这个倒霉蛋又再次遇上。不过奇怪的是，网上搜索不到多少关于这个问题的中文资料，英文倒是又不少人提过。</p>
<p>之前在跟项目组同事的讨论原因时，也考虑过系统或者路由在应用程序之下断开连接的可能性，但是由于对操作系统环境不熟悉，不知道存在这么一个SynAttackProtect机制，影响到问题原因的确认，算是一个教训吧。虽然存在文档描述与实际行为不一致的原因，但对程序运行平台的熟悉程度还是直接影响了定位问题的速度。</p>
<p>顺便说一句，测试中发现，Windows 2003 Server SP2里的backlog最大值似乎是200，不过没有查过官方文档确认，纯属推测。</p>
<p>PS：就这个问题跟带我出道的师傅讨论，又有另一个疑问。当SYN保护机制生效时，backlog队列满的情况下，新的连接应该不会再被加入到队列中，为什么服务端调用accept仍然得到了这个连接？有人了解SynAttackProtect机制实现的话，请不吝赐教！</p>
</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.developnotes.info/trap-in-syn-attack-protect/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Nagle算法和魔兽世界的延迟</title>
		<link>http://blog.developnotes.info/nagle-algorithm-and-lag-in-wow/</link>
		<comments>http://blog.developnotes.info/nagle-algorithm-and-lag-in-wow/#comments</comments>
		<pubDate>Sun, 17 Oct 2010 10:28:33 +0000</pubDate>
		<dc:creator>huubby</dc:creator>
				<category><![CDATA[默认]]></category>
		<category><![CDATA[Nagle Algorithm]]></category>
		<category><![CDATA[TCPIP]]></category>

		<guid isPermaLink="false">http://www.huubby6.tk/?p=339</guid>
		<description><![CDATA[暴雪大神的《魔兽世界》从2005年4月26号公测到现在，风靡全球已有好几个春秋。还记得公测时小白一个的我打哀嚎被副本里的一个要一点点技术跳过去的平台折腾疯，无数次的跳到空中，眼看着那个台子在眼前越升越高，然后环顾四周，全是怪，挂，跑尸，复活，找路，再跳，挂&#8230;以至于我满级之后都不敢去哀嚎 : ( 当然，玩了几年的wow，要说印象最深刻的嘛，当然要数游戏代理公司的小霸王服务器，以及小霸王那无敌的延迟（你肯定也这么想，哈）。曾经有一次，参加工会的BWL开荒，2号小红龙，只要开荒过这个怪的玩家，我相信你绝对不会忘记它的变态。那次开荒，变态的BOSS配合几百的延迟让全团一次次的扑地，直到身上的装备跟延迟变成一个颜色，还是没过，丧气解散，杯具！ 不论是以前的九城时代，还是现在的网易时代，lag一直都是wow最大的问题，当然，大家普遍认为主要的原因在于服务器质量不靠谱，故有“小霸王”之名。但同时也要了解，暴雪一直在调整程序，以期粉丝们有更好的游戏体验。在TBC 2.3.2版本的patch说明中，就有如下与游戏体验相关的内容： 综合 * /timetest命令可显示游戏性能信息。输入/timetest 0可关闭该命令。开启此命令后，系统将在玩家下一次在飞行管理员处搭乘飞行坐骑时进行性能测试，并在着陆后显示结果数据。 * 微缩地图将不再显示带蓝色问号的任务NPC位置。 * 受到爆击后触发的效果：许多技能和天赋在2.3.0中作了改变，可以在玩家处于坐下状态时正常发挥因受到爆击而触发的效果。 * 当玩家完成了NPC给予的任务后，鼠标指向该NPC时将显示为问号，而不再是感叹号。 * 取消了公会银行的管理权限对公会会长的等级要求。公会会长将始终拥有对公会银行的完整管理权限，且此完整权限不可改变。 * 在公会银行的管理中新增了“提款：仅限于维修”功能。当公会将某层级的公会成员权限设定为此时，此阶层的成员将不能从公会银行直接提款，而只能用每天的提款额度进行直接修理。 * 玩家在受到攻击时将自动站起，即使该次攻击未能命中。 * 船只和飞艇上的乘务NPC又可以正常工作了。 * 通过禁用Nagle算法降低了网络延迟。 首先赞一下暴雪的产品态度，如果我们公司也一直致力于提高客户的使用体验，就算不能到暴雪这种程度，最起码也应该会有很好的口碑和用户忠诚度。好了，马屁拍完来研究一下这句“通过禁用Nagle算法降低了网络延迟 ”。 开始blabla之前，我先说说网络延迟的概念。不知道在其他领域是是不是有别的含义，但在我们游戏界（什么时候我算游戏界的了？-_-&#124;&#124;&#124;），特别是在魔兽世界，延迟指的就是痛苦，就是被别人打而无还手之力，就是剩下一滴血的时候用不了血瓶，用不了消失，用不了冰箱，It means hell！对不起，我又变态，哦，不，失态了。 正经的说，延迟在游戏中最典型最恶心的表现是，当你打算使用某个技能，游戏角色要在你按了键盘或点了鼠标后的一段时间内才能发出技能。如果你按了一系列技能的快捷键后游戏人物一点反应没有，而是要等到你倒了杯水回来的时候才突然像快进一样做出N多动作，那么恭喜你，网络延迟非常高，你的游戏人物应该大多数情况下会躺在地板上数星星。 那么这个Nagle算法是什么万恶的东东，会增加网络延迟？而且暴雪大神竟然一直到TBC才想起来关掉这玩意儿，不给力阿！话说，在很久很久以前&#8230;好吧好吧，别嘘。所谓Nagle算法，它是个根据一定的规则减少网络发送包数量的方法，进而改善了TCP/IP网络的效率。关于这个算法，《TCP/IP详解》里是这样说的 该算法要求一个TCP连接上最多只能有一个未被确认的未完成的小分组，在该分组的确认到达之前不能发送其他的小分组。相反，TCP收集这些少量的分组，并在确认到来时以一个分组的方式发出去。 在魔兽世界游戏里，我们的操作中有相当一部分的数据量是极小的，若没有Nagle算法，1个字节的数据可能也会被作为一个分组发送出去，考虑到TCP头有20字节，IP头也有20个字节，为了发送1个字节的数据产生的分组就达到41字节。这是一种极大的浪费，更为重要的是，这同时也增加了分组的数量，给客户端网络造成压力，我想这应该正是暴雪当初没关闭Nagle算法的原因。 2.3.2里的这条patch有什么神奇的作用到这已经水落石出，如果没有关闭Nagle算法，客户端的数据包可能由于数据量太小而被TCP层缓存下来，等到上一个分组的确认收到后才把所缓存的所有数据发送到服务端，从而导致客户端感觉延迟有所增加。关闭Nagle算法后，每个数据包在TCP层便不会被缓存，直接发送到服务端，改善客户端游戏体验的目的也就达到了。 其实在其他领域对Nagle算法“意见”也是很大的，比如实时性要求很高的应用，远程桌面之类的，像鼠标移动之类的小消息必须无延迟发送出去，否则客户用起来会抓狂，后果很严重！Host Requirements RFC针对TCP的Nagle算法实现这样说的 A TCP SHOULD implement the Nagle Algorithm [TCP:9] to coalesce short segments. However, there MUST be [...]]]></description>
			<content:encoded><![CDATA[<p>暴雪大神的《魔兽世界》从2005年4月26号公测到现在，风靡全球已有好几个春秋。还记得公测时小白一个的我打哀嚎被副本里的一个要一点点技术跳过去的平台折腾疯，无数次的跳到空中，眼看着那个台子在眼前越升越高，然后环顾四周，全是怪，挂，跑尸，复活，找路，再跳，挂&#8230;以至于我满级之后都不敢去哀嚎 : (</p>
<p>当然，玩了几年的wow，要说印象最深刻的嘛，当然要数游戏代理公司的小霸王服务器，以及小霸王那无敌的延迟（你肯定也这么想，哈）。曾经有一次，参加工会的BWL开荒，2号小红龙，只要开荒过这个怪的玩家，我相信你绝对不会忘记它的变态。那次开荒，变态的BOSS配合几百的延迟让全团一次次的扑地，直到身上的装备跟延迟变成一个颜色，还是没过，丧气解散，杯具！</p>
<p>不论是以前的九城时代，还是现在的网易时代，lag一直都是wow最大的问题，当然，大家普遍认为主要的原因在于服务器质量不靠谱，故有“小霸王”之名。但同时也要了解，暴雪一直在调整程序，以期粉丝们有更好的游戏体验。在TBC 2.3.2版本的<a href="http://www.warcraftchina.com/info/patch/84553.html">patch说明</a>中，就有如下与游戏体验相关的内容：</p>
<blockquote><p>综合<br />
* <del datetime="2010-10-17T08:19:37+00:00">/timetest命令可显示游戏性能信息。输入/timetest 0可关闭该命令。开启此命令后，系统将在玩家下一次在飞行管理员处搭乘飞行坐骑时进行性能测试，并在着陆后显示结果数据。</del><br />
* <del datetime="2010-10-17T08:19:37+00:00">微缩地图将不再显示带蓝色问号的任务NPC位置。</del><br />
* <del datetime="2010-10-17T08:19:37+00:00">受到爆击后触发的效果：许多技能和天赋在2.3.0中作了改变，可以在玩家处于坐下状态时正常发挥因受到爆击而触发的效果。</del><br />
* <del datetime="2010-10-17T08:19:37+00:00">当玩家完成了NPC给予的任务后，鼠标指向该NPC时将显示为问号，而不再是感叹号。</del><br />
* <del datetime="2010-10-17T08:19:37+00:00">取消了公会银行的管理权限对公会会长的等级要求。公会会长将始终拥有对公会银行的完整管理权限，且此完整权限不可改变。</del><br />
* <del datetime="2010-10-17T08:19:37+00:00">在公会银行的管理中新增了“提款：仅限于维修”功能。当公会将某层级的公会成员权限设定为此时，此阶层的成员将不能从公会银行直接提款，而只能用每天的提款额度进行直接修理。</del><br />
* <del datetime="2010-10-17T08:19:37+00:00">玩家在受到攻击时将自动站起，即使该次攻击未能命中。</del><br />
* <del datetime="2010-10-17T08:19:37+00:00">船只和飞艇上的乘务NPC又可以正常工作了。</del><br />
* <strong>通过禁用Nagle算法降低了网络延迟。 </strong></p></blockquote>
<p>首先赞一下暴雪的产品态度，如果我们公司也一直致力于提高客户的使用体验，就算不能到暴雪这种程度，最起码也应该会有很好的口碑和用户忠诚度。好了，马屁拍完来研究一下这句“<strong>通过禁用Nagle算法降低了网络延迟 </strong>”。<br />
开始blabla之前，我先说说网络延迟的概念。不知道在其他领域是是不是有别的含义，但在我们游戏界（什么时候我算游戏界的了？-_-|||），特别是在魔兽世界，延迟指的就是痛苦，就是被别人打而无还手之力，就是剩下一滴血的时候用不了血瓶，用不了消失，用不了冰箱，It means hell！对不起，我又变态，哦，不，失态了。<br />
正经的说，延迟在游戏中最典型最恶心的表现是，当你打算使用某个技能，游戏角色要在你按了键盘或点了鼠标后的一段时间内才能发出技能。如果你按了一系列技能的快捷键后游戏人物一点反应没有，而是要等到你倒了杯水回来的时候才突然像快进一样做出N多动作，那么恭喜你，网络延迟非常高，你的游戏人物应该大多数情况下会躺在地板上数星星。</p>
<p>那么这个Nagle算法是什么万恶的东东，会增加网络延迟？而且暴雪大神竟然一直到TBC才想起来关掉这玩意儿，不给力阿！话说，在很久很久以前&#8230;好吧好吧，别嘘。所谓<a href="http://en.wikipedia.org/wiki/Nagle%27s_algorithm">Nagle算法</a>，它是个根据一定的规则减少网络发送包数量的方法，进而改善了TCP/IP网络的效率。关于这个算法，<a href="http://book.douban.com/subject/1088054/">《TCP/IP详解》</a>里是这样说的</p>
<blockquote><p>该算法要求一个TCP连接上最多只能有一个未被确认的未完成的小分组，在该分组的确认到达之前不能发送其他的小分组。相反，TCP收集这些少量的分组，并在确认到来时以一个分组的方式发出去。</p></blockquote>
<p>在魔兽世界游戏里，我们的操作中有相当一部分的数据量是极小的，若没有Nagle算法，1个字节的数据可能也会被作为一个分组发送出去，考虑到TCP头有20字节，IP头也有20个字节，为了发送1个字节的数据产生的分组就达到41字节。这是一种极大的浪费，更为重要的是，这同时也增加了分组的数量，给客户端网络造成压力，我想这应该正是暴雪当初没关闭Nagle算法的原因。</p>
<p>2.3.2里的这条patch有什么神奇的作用到这已经水落石出，如果没有关闭Nagle算法，客户端的数据包可能由于数据量太小而被TCP层缓存下来，等到上一个分组的确认收到后才把所缓存的所有数据发送到服务端，从而导致客户端感觉延迟有所增加。关闭Nagle算法后，每个数据包在TCP层便不会被缓存，直接发送到服务端，改善客户端游戏体验的目的也就达到了。</p>
<p>其实在其他领域对Nagle算法“意见”也是很大的，比如实时性要求很高的应用，远程桌面之类的，像鼠标移动之类的小消息必须无延迟发送出去，否则客户用起来会抓狂，后果很严重！<a href="http://www.rfc-editor.org/rfc/rfc1122.txt">Host Requirements RFC</a>针对TCP的Nagle算法实现这样说的</p>
<blockquote><p>A TCP SHOULD implement the Nagle Algorithm [TCP:9] to coalesce short segments.  However, there MUST be a way for<br />
an application to disable the Nagle algorithm on an individual connection.</p></blockquote>
<p>简言之，TCP必须实现Nagle算法，但同时也要提供方法能够在某个连接上关闭此算法。</p>
<p>Socket用户可以通过TCP_NODELAY选项来关闭Nagle算法，这个选项在netinet/tcp.h文件中定义。</p>
<p>over~</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.developnotes.info/nagle-algorithm-and-lag-in-wow/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>C++ Exception Handling</title>
		<link>http://blog.developnotes.info/cpp-exception-handling/</link>
		<comments>http://blog.developnotes.info/cpp-exception-handling/#comments</comments>
		<pubDate>Wed, 09 Jun 2010 12:59:07 +0000</pubDate>
		<dc:creator>huubby</dc:creator>
				<category><![CDATA[默认]]></category>
		<category><![CDATA[exception handling]]></category>

		<guid isPermaLink="false">http://www.huubby6.tk/?p=303</guid>
		<description><![CDATA[最近刚刚读完《深度探索C++对象模型》，说来惭愧，整本书都快看完才想起来要做笔记。目前笔记只有后面三章的一些重点内容，本文是异常处理(Exception Handling)这部分内容的笔记整理。 写程序的时候，对于可能出现异常的地方，通常来说我们是这样写的： try{...throw ...} catch(...) {//exception handling} 当异常发生时，代码跳转到catch块，完成异常处理后退出。为了支持这个处理流程，编译器要能够在发生异常时，找到对应的catch子句。而查找catch子句时，编译器要知道函数的当前作用范围，同时还要了解抛出异常的类型，以便进行catch子句匹配(这引出了执行期类型识别的需求，即RTTI)。 详细来看看，上面这段代码大致可以分为三个部分： throw子句，它抛出一个exception，可以是内置类型，也可以是用户自定义类型 catch子句，每个catch子句都是一个exception handler，表示其之后的代码段用来处理某种类型的exception try段，内含一些程序代码，这些代码可能引发catch子句生效 当一个exception被抛出时，程序控制权从函数中释放，转而寻找一个匹配的catch子句。若不存在匹配的catch，默认的terminate()方法将被调用。而在控制权被放弃后，堆栈中的函数被poped up，函数内局部对象的析构函数在poped up之前会被调用。 当exception发生，编译系统完成下面几件必须要做的事： 1、检查发生throw exception操作的函数 2、决定throw是否是发生在一个try段内 3、如果throw发生在try段内，编译系统开始比较发生的exception的类型和catch子句中exception的类型是否吻合 4、一旦exception类型吻合，该catch子句就得到流程控制权 5、若throw并不是发生在try段内，或者没有找到吻合的catch子句，系统要做的就是，清理当前函数范围内所有局部对象，从堆栈中pop up当前函数，进入堆栈内下一个函数，继续步骤2-5 那么当一个exception被抛出后，它经历了些什么？它并没有无家可归，睡在天桥底下。抛出的exception object放在exception数据堆栈中，从throw处传递到catch子句的是这个exception object的地址。来看个例子： catch (BaseException  newobj) {&#160;&#160; &#160;//do something&#160;&#160; &#160;throw ;} 假设这个catch子句将收到一个DerivedException类型的exception object，DerivedException继承自BaseException。那么，由于exception类型吻合，按照上面说的步骤4，这个catch子句得到控制权。 catch子句中的newobj在收到exception时，将以抛出的exception object作为初值，像函数以传值方式传递参数一样。由于newobj是一个对象而不是指针或引用，BaseException的copy constructor将会被调用(如果存在的话)，以拷贝原始exception object中属于BaseException的部分到当前的newobj中。 完成这些之后，catch子句内的代码被执行，再次抛出异常。这次抛出的是newobj还是原始的exception object？抛出的仍然是原始的exception object。newobj只是局部对象，在catch子句结束后被摧毁，任何对newobj的修改都被丢弃而不会影响到原始的exception object。这个原始的exception object将直到有一个catch子句执行完，并且不再抛出exception后，才会被摧毁。 本文内容来自《深度探索C++对象模型》第7章中的异常处理介绍。 关于C++的EH，在codeproject上有篇文章简单介绍编译系统与win32系统之间的协作关系，点这里。 that&#8217;s all, over！]]></description>
			<content:encoded><![CDATA[<p>最近刚刚读完《深度探索C++对象模型》，说来惭愧，整本书都快看完才想起来要做笔记。目前笔记只有后面三章的一些重点内容，本文是异常处理(Exception Handling)这部分内容的笔记整理。</p>
<p>写程序的时候，对于可能出现异常的地方，通常来说我们是这样写的：</p>
<div class="hl-surround"><div class="hl-main"><span style="color: Green;">try</span><span style="color: Olive;">{</span><span style="color: Gray;"><br />...<br /></span><span style="color: Green;">throw</span><span style="color: Gray;"> ...<br /></span><span style="color: Olive;">}</span><span style="color: Gray;"> </span><span style="color: Green;">catch</span><span style="color: Olive;">(</span><span style="color: Gray;">...</span><span style="color: Olive;">)</span><span style="color: Gray;"> </span><span style="color: Olive;">{</span><span style="color: Gray;"><br /></span><span style="color: #ffa500;">//exception handling</span><span style="color: Gray;"><br /></span><span style="color: Olive;">}</span></div></div>
<p>当异常发生时，代码跳转到catch块，完成异常处理后退出。为了支持这个处理流程，编译器要能够在发生异常时，找到对应的catch子句。而查找catch子句时，编译器要知道函数的当前作用范围，同时还要了解抛出异常的类型，以便进行catch子句匹配(这引出了执行期类型识别的需求，即RTTI)。</p>
<p>详细来看看，上面这段代码大致可以分为三个部分：</p>
<p>throw子句，它抛出一个exception，可以是内置类型，也可以是用户自定义类型</p>
<p>catch子句，每个catch子句都是一个exception handler，表示其之后的代码段用来处理某种类型的exception</p>
<p>try段，内含一些程序代码，这些代码可能引发catch子句生效</p>
<p>当一个exception被抛出时，程序控制权从函数中释放，转而寻找一个匹配的catch子句。若不存在匹配的catch，默认的terminate()方法将被调用。而在控制权被放弃后，堆栈中的函数被poped up，函数内局部对象的析构函数在poped up之前会被调用。</p>
<p>当exception发生，编译系统完成下面几件必须要做的事：</p>
<p>1、检查发生throw exception操作的函数</p>
<p>2、决定throw是否是发生在一个try段内</p>
<p>3、如果throw发生在try段内，编译系统开始比较发生的exception的类型和catch子句中exception的类型是否吻合</p>
<p>4、一旦exception类型吻合，该catch子句就得到流程控制权</p>
<p>5、若throw并不是发生在try段内，或者没有找到吻合的catch子句，系统要做的就是，清理当前函数范围内所有局部对象，从堆栈中pop up当前函数，进入堆栈内下一个函数，继续步骤2-5</p>
<p>那么当一个exception被抛出后，它经历了些什么？它并没有无家可归，睡在天桥底下。抛出的exception object放在exception数据堆栈中，从throw处传递到catch子句的是这个exception object的地址。来看个例子：</p>
<div class="hl-surround"><div class="hl-main"><span style="color: Green;">catch</span><span style="color: Gray;"> </span><span style="color: Olive;">(</span><span style="color: Blue;">BaseException</span><span style="color: Gray;">  </span><span style="color: Blue;">newobj</span><span style="color: Olive;">)</span><span style="color: Gray;"> </span><span style="color: Olive;">{</span><span style="color: Gray;"><br />&nbsp;&nbsp; &nbsp;</span><span style="color: #ffa500;">//do something</span><span style="color: Gray;"><br />&nbsp;&nbsp; &nbsp;</span><span style="color: Green;">throw</span><span style="color: Gray;"> ;<br /></span><span style="color: Olive;">}</span></div></div>
<p>假设这个catch子句将收到一个DerivedException类型的exception object，DerivedException继承自BaseException。那么，由于exception类型吻合，按照上面说的步骤4，这个catch子句得到控制权。</p>
<p>catch子句中的newobj在收到exception时，将以抛出的exception object作为初值，像函数以传值方式传递参数一样。由于newobj是一个对象而不是指针或引用，BaseException的copy constructor将会被调用(如果存在的话)，以拷贝原始exception object中属于BaseException的部分到当前的newobj中。</p>
<p>完成这些之后，catch子句内的代码被执行，再次抛出异常。这次抛出的是newobj还是原始的exception object？抛出的仍然是原始的exception object。newobj只是局部对象，在catch子句结束后被摧毁，任何对newobj的修改都被丢弃而不会影响到原始的exception object。这个原始的exception object将直到有一个catch子句执行完，并且不再抛出exception后，才会被摧毁。</p>
<p>本文内容来自《深度探索C++对象模型》第7章中的异常处理介绍。</p>
<p>关于C++的EH，在codeproject上有篇文章简单介绍编译系统与win32系统之间的协作关系，点<a href="http://www.codeproject.com/KB/cpp/Win32Exceptions.aspx" target="_blank">这里</a>。</p>
<p>that&#8217;s all, over！</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.developnotes.info/cpp-exception-handling/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>error C2653: &#8216;std&#8217; : is not a class or namespace name</title>
		<link>http://blog.developnotes.info/error-c2653-std/</link>
		<comments>http://blog.developnotes.info/error-c2653-std/#comments</comments>
		<pubDate>Wed, 28 Apr 2010 12:33:48 +0000</pubDate>
		<dc:creator>huubby</dc:creator>
				<category><![CDATA[默认]]></category>
		<category><![CDATA[c2653]]></category>
		<category><![CDATA[std]]></category>

		<guid isPermaLink="false">http://www.huubby6.tk/?p=286</guid>
		<description><![CDATA[Today, when I created a Win32 Console Application in VS2005, and wrote the code as follow: #include &#8220;stdafx.h&#8221; int _tmain(int argc, _TCHAR* argv[]) { std::string    str = &#8220;&#8221;; str += &#8220;helllo&#8221;; str += &#8220;world&#8221;; printf(&#8220;%s&#8221;, str); return 0; } I got compile errors: error C2653: &#8216;std&#8217; : is not a class or namespace name &#8230; [...]]]></description>
			<content:encoded><![CDATA[<p>Today, when I created a Win32 Console Application in VS2005, and wrote the code as follow:</p>
<p>#include &#8220;stdafx.h&#8221;</p>
<p>int _tmain(int argc, _TCHAR* argv[])<br />
{<br />
std::string    str = &#8220;&#8221;;<br />
str += &#8220;helllo&#8221;;<br />
str += &#8220;world&#8221;;</p>
<p>printf(&#8220;%s&#8221;, str);<br />
return 0;<br />
}</p>
<p>I got compile errors:</p>
<p><strong>error C2653: &#8216;std&#8217; : is not a class or namespace name</strong></p>
<p><strong>&#8230;</strong></p>
<p>At the beginning, I thought it caused by a static library on which my new project depended. Then, I removed the static library dependent, but it didn&#8217;t work.</p>
<p>As you know, googling is a final solution when you find something you cann&#8217;t solve. I google it and figure it out. It&#8217;s simple, as MS has a bug in this situation.</p>
<p>You can find why this happend<strong> <a href="http://support.microsoft.com/kb/243444/en-us?fr=1" target="_blank">here</a></strong>, a relational situation about the issue is<strong> <a href="http://social.msdn.microsoft.com/Forums/en/Vsexpressvc/thread/bf1b4ebd-a957-4cc3-9ca2-0bae99f41db2" target="_blank">here</a></strong>.</p>
<p>Over!</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.developnotes.info/error-c2653-std/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>restrict关键字</title>
		<link>http://blog.developnotes.info/keyword-restrict/</link>
		<comments>http://blog.developnotes.info/keyword-restrict/#comments</comments>
		<pubDate>Fri, 19 Mar 2010 07:39:51 +0000</pubDate>
		<dc:creator>huubby</dc:creator>
				<category><![CDATA[默认]]></category>
		<category><![CDATA[restrict]]></category>
		<category><![CDATA[细节]]></category>

		<guid isPermaLink="false">http://www.huubby6.tk/?p=268</guid>
		<description><![CDATA[看代码看到个restrict，头一次知道还有这么个关键字，在维基查到基本用法和解释。本着好记性不如烂笔头的原则，翻译这篇维基贴在这里。 在C语言的一个标准(C99)里，有一个用于修饰指针声明的关键字－restrict，它是程序员给编译器的一个意向声明，表明只有此指针或者基于此指针的某个值(比如 pointer+1)可以访问其指向的对象。这个关键字限制了指针别名(译注：即指向同一对象的不同指针)，目的在于进行编译器优化。如果这个程序员没有遵守这个意向声明，并且这个对象由一个独立指针访问到的话，会造成未定义的行为。( 译注：我也没搞懂这一大段话什么意思 ^_^ 不过看下面的例子应该差不多能理解这个关键字的用法) 如果知道只有一个指针指向某个内存块的话，编译器就能进行优化，以生成更高效的代码。下面这个例子能清楚解释这一点。 这段代码里面，指针ptrA , ptrB, val可能指向同一块内存，所以编译器不能对代码进行优化。 如果使用restrict关键字，函数声明为： 编译器就能知道ptrA, ptrB, val三个指针指向不同的内存块，更新其中一个不会影响到其他的，从而对代码进行优化。当然，这里要由程序员保证这三个指针不指向同一块内存。 还有一个例子是memcpy函数，memcpy(void*, void*, nbytes)的前两个指针参数被声明为restrict，告诉编译器两个数据块没有重叠，便于进行优化，使得memcpy的速度更快。如果程序员用相同的指针作为这两个参数的值，那么memcpy函数会产生未定义的行为。 (译注：memcpy的前两个参数确实声明为restrict，但是用相同的指针做参数传进去测试，没发现异常行为，此处存疑。) 另：推荐一个专栏，M. Tim Jones 专栏，Linux 内核、虚拟化及其他技术深层剖析。(你可以在这个专栏中看到restrict的身影)]]></description>
			<content:encoded><![CDATA[<p>看代码看到个restrict，头一次知道还有这么个关键字，在<a href="http://en.wikipedia.org/wiki/Restrict" target="_blank">维基</a>查到基本用法和解释。本着好记性不如烂笔头的原则，翻译<a href="http://en.wikipedia.org/wiki/Restrict" target="_blank">这篇</a>维基贴在这里。</p>
<p>在C语言的一个标准(C99)里，有一个用于修饰指针声明的关键字－restrict，它是程序员给编译器的一个意向声明，表明只有此指针或者基于此指针的某个值(比如 pointer+1)可以访问其指向的对象。这个关键字限制了指针别名(译注：即指向同一对象的不同指针)，目的在于进行编译器优化。如果这个程序员没有遵守这个意向声明，并且这个对象由一个独立指针访问到的话，会造成未定义的行为。( 译注：我也没搞懂这一大段话什么意思 ^_^ 不过看下面的例子应该差不多能理解这个关键字的用法)</p>
<p>如果知道只有一个指针指向某个内存块的话，编译器就能进行优化，以生成更高效的代码。下面这个例子能清楚解释这一点。</p>
<pre class="brush: cpp; title: ; notranslate">
void updatePtrs(size_t *ptrA, size_t *ptrB, size_t *val)
{
    *ptrA += *val;
    *ptrB += *val;
}
</pre>
<p>这段代码里面，指针ptrA , ptrB, val可能指向同一块内存，所以编译器不能对代码进行优化。</p>
<pre class="brush: cpp; title: ; notranslate">
load R1 ← *val  ; 加载val指针的
load R2 ← *ptrA ; 加载ptrA指针的值&amp;lt;/pre&amp;gt;
add  R2 += R1   ; 相加
set  R2 → *ptrA ; 更新ptrA指针的值
; 对ptrB指针的操作类似，注意，这里val指针被加载两次，因为ptrA有可能和val指向同一块内存
load R1 ← *val
load R2 ← *ptrB
add  R2 += R1
set  R2 → *ptrB
</pre>
<p>如果使用restrict关键字，函数声明为：</p>
<pre class="brush: cpp; title: ; notranslate">
void updatePtrs(size_t *restrict ptrA, size_t *restrict ptrB, size_t *restrict val);
</pre>
<p>编译器就能知道ptrA, ptrB, val三个指针指向不同的内存块，更新其中一个不会影响到其他的，从而对代码进行优化。当然，这里要由程序员保证这三个指针不指向同一块内存。</p>
<pre class="brush: cpp; title: ; notranslate">
load R1 ← *val
load R2 ← *ptrA
add  R2 += R1
set  R2 → *ptrA
;编译器知道指针val的值没有改变，所以没有重新加载它，优化了汇编代码
load R2 ← *ptrB
add  R2 += R1
set  R2 → *ptrB
</pre>
<p>还有一个例子是memcpy函数，memcpy(void*, void*, nbytes)的前两个指针参数被声明为restrict，告诉编译器两个数据块没有重叠，便于进行优化，使得memcpy的速度更快。如果程序员用相同的指针作为这两个参数的值，那么memcpy函数会产生未定义的行为。<br />
(<strong>译注：memcpy的前两个参数确实声明为restrict，但是用相同的指针做参数传进去测试，没发现异常行为，此处存疑。</strong>)<br />
<span></span></p>
<p>另：推荐一个专栏，<strong>M. Tim Jones 专栏，<span style="font-weight: normal; font-size: 12px;"><em><a href="http://www.ibm.com/developerworks/cn/linux/theme/mtj/" target="_blank">Linux 内核、虚拟化及其他技术深层剖析</a>。(你可以在这个专栏中看到restrict的身影)</em></span></strong></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.developnotes.info/keyword-restrict/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>socket流程</title>
		<link>http://blog.developnotes.info/steps-of-socket-program/</link>
		<comments>http://blog.developnotes.info/steps-of-socket-program/#comments</comments>
		<pubDate>Thu, 18 Mar 2010 09:20:09 +0000</pubDate>
		<dc:creator>huubby</dc:creator>
				<category><![CDATA[默认]]></category>
		<category><![CDATA[socket]]></category>
		<category><![CDATA[网络编程]]></category>

		<guid isPermaLink="false">http://www.huubby6.tk/?p=259</guid>
		<description><![CDATA[失败，准备写的东西放狗一搜总是一大筐，这次不写了，直接贴网上找来的图。 TCP方式的流程： UDP方式：]]></description>
			<content:encoded><![CDATA[<p>失败，<span style="font-family: Consolas, Monaco, 'Courier New', Courier, monospace;"><span style="line-height: 18px; white-space: pre;">准备写的东西放狗一搜总是一大筐，这次不写了，直接贴网上找来的图。</span></span></p>
<p><span style="font-family: Consolas, Monaco, 'Courier New', Courier, monospace;"><span style="line-height: 18px; white-space: pre;">TCP方式的流程：</span></span></p>
<p style="text-align: center;"><span style="font-family: Consolas, Monaco, 'Courier New', Courier, monospace;"><span style="line-height: 18px; white-space: pre;"><a href="http://www.huubby6.tk/wp-content/uploads/2010/03/image007.png"><img class="aligncenter size-medium wp-image-266" title="stream-socket" src="/wp-content/uploads/2010/03/image007-239x300.png" alt="" /></a><br />
</span></span></p>
<p style="text-align: center;"><span style="font-family: Consolas, Monaco, 'Courier New', Courier, monospace;"><span style="line-height: 18px; white-space: pre;"> </span></span></p>
<p><span style="font-family: Consolas, Monaco, 'Courier New', Courier, monospace;"><span style="line-height: 18px; white-space: pre;">UDP方式：</span></span></p>
<p style="text-align: center;"><span style="font-family: Consolas, Monaco, 'Courier New', Courier, monospace;"><span style="line-height: 18px; white-space: pre;"><a href="/wp-content/uploads/2010/03/image0081.png"><img class="aligncenter size-medium wp-image-261" title="dgram-socket" src="/wp-content/uploads/2010/03/image0081-300x262.png" alt="" /></a><br />
</span></span></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.developnotes.info/steps-of-socket-program/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>linux编译生成动态库</title>
		<link>http://blog.developnotes.info/linux-make-dynamic-lib/</link>
		<comments>http://blog.developnotes.info/linux-make-dynamic-lib/#comments</comments>
		<pubDate>Mon, 15 Mar 2010 09:29:12 +0000</pubDate>
		<dc:creator>huubby</dc:creator>
				<category><![CDATA[默认]]></category>
		<category><![CDATA[动态库]]></category>

		<guid isPermaLink="false">http://www.huubby6.tk/?p=251</guid>
		<description><![CDATA[工作交接，手头没事闲的慌，试了一下怎么在Linux下生成动态库。放狗一搜，铺天盖地的文章讲这个(PS：这年头，好像所有的东西都有人介绍过)。随便点了篇照着试了一下，很容易就搞定。只是有一件事不太爽，最后一步测试的时候，我发现找不到生成的那个动态库，然后又觉得这个场景似曾相识。在记忆里google了一下，发现原来N天之前我已经试验过了，结果现在又全还给google。为避免下次继续出现这种情况，还是把自己的实验过程写出来，免得再还回去。 实验开始，先准备一个要封成动态库的类。 头文件： 实现： 把文件编译成.so动态库：  g++ myclass.cpp -fPIC -shared -o libmycls.so OK，动态库有了，来看看怎么用。 编译main.cpp： g++ main.cpp -L. -lmycls -o main 看看生成的main能不能链接到我们的动态库：ldd main 如果像这样 linux-gate.so.1 =&#62;  (0x005e5000) libmycls.so =&#62; /home/user1/sourcecode/dlltest/libmycls.so (0x00ad8000) libstdc++.so.6 =&#62; /usr/lib/libstdc++.so.6 (0x0039d000) libm.so.6 =&#62; /lib/tls/i686/cmov/libm.so.6 (0&#215;00951000) libgcc_s.so.1 =&#62; /lib/libgcc_s.so.1 (0x00cdc000) libc.so.6 =&#62; /lib/tls/i686/cmov/libc.so.6 (0&#215;00698000) /lib/ld-linux.so.2 (0x00e12000) 第二行列出了之前创建的动态库，连接正常，执行main，看看结果吧。 附上-shared和-fPIC两个GCC选项的作用： -fPIC：如果支持这种目标机,编译器就输出位置无关目标码.适用于动态连接(dynamic linking),即使分支需要大范围转移. -shared：生成一个共享目标文件,他可以和其他目标文件连接产生可执行文件.只有部分系统支持该选项. 其实我在列出main的依赖项的时候碰到问题了，ldd main怎么也没列出来libmycls.so，后来设置环境变量才解决。打开~/.bashrc，增加两行($HOME是你的用户目录，$HOME/sourcecode/dlltest是动态库所在的目录)： LD_LIBRARY_PATH=$HOME/sourcecode/dlltest export [...]]]></description>
			<content:encoded><![CDATA[<p>工作交接，手头没事闲的慌，试了一下怎么在Linux下生成动态库。放狗一搜，<a href="http://www.google.cn/search?hl=zh-CN&amp;newwindow=1&amp;q=linux+动态库+创建&amp;aq=f&amp;aqi=g-g5g1g-g4&amp;aql=&amp;oq=" target="_blank">铺天盖地的文章</a>讲这个(PS：这年头，好像所有的东西都有人介绍过)。随便点了篇照着试了一下，很容易就搞定。只是有一件事不太爽，最后一步测试的时候，我发现找不到生成的那个动态库，然后又觉得这个场景似曾相识。在记忆里google了一下，发现原来N天之前我已经试验过了，结果现在又全还给google。为避免下次继续出现这种情况，还是把自己的实验过程写出来，免得再还回去。</p>
<p>实验开始，先准备一个要封成动态库的类。</p>
<p>头文件：</p>
<pre class="brush: cpp; title: ; notranslate">
//myclass.h
class CMyClass
{
public:
   int add(int x, int y);
};
</pre>
<p>实现：</p>
<pre class="brush: cpp; title: ; notranslate">
//myclass.cpp
#include &quot;myclass.h&quot;

int CMyClass::add(int x, int y)
{
   return x + y;
}
</pre>
<div>把文件编译成.so动态库：  <strong>g++ myclass.cpp -fPIC -shared -o libmycls.so</strong></div>
<div>OK，动态库有了，来看看怎么用。</div>
<div>
<pre class="brush: cpp; title: ; notranslate">
//main.cpp

#include &quot;myclass.h&quot;
#include &lt;iostream&gt;
using namespace std;

int main(void)
{
    CMyClass mycls;
    cout&lt;&lt;&quot;10 plus 3 is &quot;&lt;&lt;mycls.add(10,3)&lt;&lt;endl;
}
</pre>
</div>
<div>编译main.cpp： <strong>g++ main.cpp -L. -lmycls -o main</strong></div>
<div>看看生成的main能不能链接到我们的动态库：ldd main</div>
<div>如果像这样</div>
<div>
<div>linux-gate.so.1 =&gt;  (0x005e5000)</div>
<div><span style="white-space: pre;"> </span>libmycls.so =&gt; /home/user1/sourcecode/dlltest/libmycls.so (0x00ad8000)</div>
<div><span style="white-space: pre;"> </span>libstdc++.so.6 =&gt; /usr/lib/libstdc++.so.6 (0x0039d000)</div>
<div><span style="white-space: pre;"> </span>libm.so.6 =&gt; /lib/tls/i686/cmov/libm.so.6 (0&#215;00951000)</div>
<div><span style="white-space: pre;"> </span>libgcc_s.so.1 =&gt; /lib/libgcc_s.so.1 (0x00cdc000)</div>
<div><span style="white-space: pre;"> </span>libc.so.6 =&gt; /lib/tls/i686/cmov/libc.so.6 (0&#215;00698000)</div>
<div><span style="white-space: pre;"> </span>/lib/ld-linux.so.2 (0x00e12000)</div>
<div>第二行列出了之前创建的动态库，连接正常，执行main，看看结果吧。</div>
</div>
<div>附上-shared和-fPIC两个GCC选项的作用：</div>
<div>
<div><strong>-fPIC：如果支持这种目标机,编译器就输出位置无关目标码.适用于动态连接(dynamic linking),即使分支需要大范围转移.</strong></div>
<div>
<div><strong>-shared：生成一个共享目标文件,他可以和其他目标文件连接产生可执行文件.只有部分系统支持该选项.</strong></div>
</div>
</div>
<div>其实我在列出main的依赖项的时候碰到问题了，ldd main怎么也没列出来libmycls.so，后来设置环境变量才解决。打开~/.bashrc，增加两行($HOME是你的用户目录，$HOME/sourcecode/dlltest是动态库所在的目录)：</div>
<div>
<div>LD_LIBRARY_PATH=$HOME/sourcecode/dlltest</div>
<div>export LD_LIBRARY_PATH</div>
<div>保存退出，在命令行里面输入：source ~/.bashrc，让刚添加的环境变量生效。再来一次ldd main吧，现在应该没问题了。</div>
</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.developnotes.info/linux-make-dynamic-lib/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>cppunit中的帮助宏</title>
		<link>http://blog.developnotes.info/helpmacros-in-cppunit/</link>
		<comments>http://blog.developnotes.info/helpmacros-in-cppunit/#comments</comments>
		<pubDate>Thu, 11 Mar 2010 08:19:47 +0000</pubDate>
		<dc:creator>huubby</dc:creator>
				<category><![CDATA[默认]]></category>
		<category><![CDATA[cppunit]]></category>
		<category><![CDATA[单元测试]]></category>

		<guid isPermaLink="false">http://www.huubby6.tk/?p=230</guid>
		<description><![CDATA[本文来自include\cppunit\extensions\HelperMacros.h头文件，这里只是做简单的翻译，少数地方加入一些自己的理解。 从cppunit cookbook里，我们了解了cppunit的使用方法，其中牵涉到cppunit预定义的几个宏，CPPUNIT_TEST_SUITE()，CPPUNIT_TEST()，CPPUNIT_TEST_SUITE_END()和CPPUNIT_TEST_SUITE_NAMED_REGISTRATION()。这些宏由cppunit提供，以帮助使用者更加方便的定义一个test suite。来具体看看这几个宏的作用。 这段代码里面，CPPUNIT_TEST_SUITE()和CPPUNIT_TEST_SUITE_END()两个宏的作用就是在类CTDemoMfc_CDemo中添加两个方法。一个辅助性的addTestsToSuite方法，添加测试项到test suite里面，另一个static CppUnit::TestSuite *suite()方法，返回一个指向由CPPUNIT_TEST定义的一系列test的指针。 在类CTDemoMfc_CDemo的实现文件里面，还有一个宏：CPPUNIT_TEST_SUITE_NAMED_REGISTRATION()，它创建了一个AutoRegisterSuite类的静态对象，自动将test suite注册到一个全局的registry中。这个全局的registry可以生成一个Test实例，包含了所有的已注册的test suite。 test suite宏也可用于模板测试类 在实现文件中添加代码： 除了这几个宏外，cppunit中还有很多其他的宏，逐个来看。 CPPUNIT_TEST_SUITE(ATestFixtureType)：开始声明一个新的test suite。当你想包括父类的test suite的时候，请使用CPPUNIT_TEST_SUB_SUITE()宏代替。参数ATestFixtureType表示测试类，注意，测试类必须是TestFixture的子类； CPPUNIT_TEST_SUB_SUITE(ATestFixtureType, ASuperClass)：开始一个新的test suite，包括父类的test suite。只有当前测试类的父类使用CPPUNIT_TEST_SUITE()或CPPUNIT_TEST_SUB_SUITE()定义test suite时，才能使用这个宏。这个宏按照CPPUNIT_TEST_SUITE()一样的方式开始声明新的test suite，随后，父类的test suite被自动加入到当前的suite中。参数ATestFixtureType与CPPUNIT_TEST_SUITE中的参数意义一样，参数ASuperClass表示父类。 CPPUNIT_TEST_SUITE_END():结束test suite声明。注意的是，在这个宏之后的成员，若未重新指定访问权限，将设置为private。出于测试代码易读的考虑，建议在这个宏后面加上明确的访问权限，不论紧跟其后的成员是public,protected还是private。 CPPUNIT_TEST_SUITE_END_ABSTRACT()：结束纯虚test suite的声明。用这个宏表明此TestFixture是纯虚的，未声明静态的suite()方法。与CPPUNIT_TEST_SUITE_END()一样，在这个宏之后的成员，若未重新指定访问权限，同样将设置为private。用一个例子来说明这个宏的用法： CPPUNIT_TEST_SUITE_ADD_TEST(test)：添加一个test到suite中(用于自定义新的宏)，参数test将被添加到声明的suite中。这个宏设计用来定制新的更高级的宏，以方便对cppunit进行扩展。在CPPUNIT_TEST_SUITE()和CPPUNIT_TEST_SUITE_END()两个宏之间，可以使用两个变量 context可以用来命名test case，创建新的test fixture实例，添加test case到test suite。这个宏就是用context将test添加到suite中。 用一个例子来说明如何使用这个宏创建新的宏，将test添加到suite中。在这里例子里面，订制的新宏将一个test添加到suite中，当这个test执行超过给定的时间后，即认为失败。这个宏基于一个与TestCaller有同样接口的虚构的TimeOutTestCaller类。 CPPUNIT_TEST(testMethod)：添加一个方法testMethod到test suite中。testMethod的原型必须为void testMethod(); CPPUNIT_TEST_EXCEPTION( testMethod, ExceptionType )：添加一个test到test suite中。当指定的异常未被捕获时，这个test会失败。同样，再通过一个例子来看看这个宏的用法。 参数testMethod表示要添加到test suite当中的test的名称；参数ExceptionType即为testMethod要抛出的异常类型。这个宏在cppunit当中不建议使用，可以使用CPPUNIT_ASSERT_THROW宏代替。 CPPUNIT_TEST_SUITE_REGISTRATION( ATestFixtureType )：添加指定的fixture到未命名的registry中。这个宏声明了一个静态对象，其构造函数将一个test suite工厂加入到这类工厂的全局registry当中。可以通过调用函数CppUnit::TestFactoryRegistry::getRegistry()获取这个全局的registry。参数ATestFixtureType表示测试类。要注意的是，这个宏在一行内只能用一次，因为行号将会被用来命名一个隐藏的静态变量。 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( ATestFixtureType, suiteName )：添加指定的fixture到指定的registry中。这个宏声明了一个静态对象，其构造函数将一个test suite工厂加入到由参数suiteName指定名字的全局registry当中。可以通过调用函数CppUnit::TestFactoryRegistry::getRegistry()获取这个全局的registry。建议使用静态的方法返回字符串作为suiteName参数的值，这样比直接硬编码字符串要好，可以避免输入上的错误。 参数ATestFixtureType表示测试类。要注意的是，这个宏在一行内只能用一次，因为行号将会被用来命名一个隐藏的静态变量。 经常使用的帮助宏就介绍到这里，还有其他的一些宏，可以参照HelperMacros.h头文件中的说明。文中所涉及的宏，我并未全部使用，若翻译中有错误，欢迎指正。]]></description>
			<content:encoded><![CDATA[<div id="_mcePaste">
<div id="_mcePaste">本文来自include\cppunit\extensions\HelperMacros.h头文件，这里只是做简单的翻译，少数地方加入一些自己的理解。</div>
<div id="_mcePaste">从<a href="http://www.huubby6.tk/2010/01/27/cppunit-cookbook-chinese/" target="_blank">cppunit cookbook</a>里，我们了解了cppunit的使用方法，其中牵涉到cppunit预定义的几个宏，CPPUNIT_TEST_SUITE()，CPPUNIT_TEST()，CPPUNIT_TEST_SUITE_END()和CPPUNIT_TEST_SUITE_NAMED_REGISTRATION()。这些宏由cppunit提供，以帮助使用者更加方便的定义一个test suite。来具体看看这几个宏的作用。</div>
<pre class="brush: cpp; title: ; notranslate">
class CTDemoMfc_CDemo : public CppUnit::TestCase{
            CPPUNIT_TEST_SUITE(CTDemoMfc_CDemo);
            CPPUNIT_TEST(TestAdd);
            CPPUNIT_TEST(TestSubtration);
            CPPUNIT_TEST_SUITE_END();
public:
	CTDemoMfc_CDemo(void);
	void TestAdd();
	void TestSubtration();
	static std::string RegCaseName() { return &quot;CTDemoMfc_CDemo&quot;;}
public:
	~CTDemoMfc_CDemo(void);
};
</pre>
</div>
<div id="_mcePaste">这段代码里面，<span style="font-family: Consolas, Monaco, 'Courier New', Courier, monospace; line-height: 18px; font-size: 12px; white-space: pre;">CPPUNIT_TEST_SUITE()和CPPUNIT_TEST_SUITE_END()两个<span style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; line-height: 19px; white-space: normal; font-size: 13px;">宏的作用就是在类CTDemoMfc_CDemo中添加两个方法。一个辅助性的addTestsToSuite方法，添加测试项到test suite里面，另一个static CppUnit::TestSuite *suite()方法，返回一个指向由CPPUNIT_TEST定义的一系列test的指针。</span></span></div>
<div id="_mcePaste">在类CTDemoMfc_CDemo的实现文件里面，还有一个宏：CPPUNIT_TEST_SUITE_NAMED_REGISTRATION()，它创建了一个AutoRegisterSuite类的静态对象，自动将test suite注册到一个全局的registry中。这个全局的registry可以生成一个Test实例，包含了所有的已注册的test suite。</div>
<div id="_mcePaste">
<pre class="brush: cpp; title: ; notranslate">
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(CTDemoMfc_CDemo,CTDemoMfc_CDemo::RegCaseName());
CppUnit::MfcUi::TestRunner runner;
CppUnit::TestFactoryRegistry &amp;registry =
                 CppUnit::TestFactoryRegistry::getRegistry(CTDemoMfc_CDemo::RegCaseName());
runner.addTest(registry.makeTest());
runner.run();
</pre>
</div>
<div id="_mcePaste">test suite宏也可用于模板测试类</div>
<div id="_mcePaste">
<pre class="brush: cpp; title: ; notranslate">
template&lt;typename CharType&gt;
class StringTest : public CppUnit::TestCase {
   CPPUNIT_TEST_SUITE( StringTest );
   CPPUNIT_TEST( testAppend );
   CPPUNIT_TEST_SUITE_END();
public:
   ...
};
</pre>
</div>
<div id="_mcePaste">在实现文件中添加代码：</div>
<div id="_mcePaste">
<pre class="brush: cpp; title: ; notranslate">
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( StringTest&lt;char&gt; );
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( StringTest&lt;wchar_t&gt; );
</pre>
<p><span id="more-230"></span></p>
</div>
<div id="_mcePaste">除了这几个宏外，cppunit中还有很多其他的宏，逐个来看。</div>
<div id="_mcePaste"><em>CPPUNIT_TEST_SUITE(ATestFixtureType)</em>：开始声明一个新的test suite。当你想包括父类的test suite的时候，请使用CPPUNIT_TEST_SUB_SUITE()宏代替。参数ATestFixtureType表示测试类，注意，测试类必须是TestFixture的子类；<br />
<strong><em>CPPUNIT_TEST_SUB_SUITE(ATestFixtureType, ASuperClass)</em></strong>：开始一个新的test suite，包括父类的test suite。只有当前测试类的父类使用CPPUNIT_TEST_SUITE()或CPPUNIT_TEST_SUB_SUITE()定义test suite时，才能使用这个宏。这个宏按照CPPUNIT_TEST_SUITE()一样的方式开始声明新的test suite，随后，父类的test suite被自动加入到当前的suite中。参数ATestFixtureType与CPPUNIT_TEST_SUITE中的参数意义一样，参数ASuperClass表示父类。<br />
<strong><em>CPPUNIT_TEST_SUITE_END()</em></strong>:结束test suite声明。注意的是，在这个宏之后的成员，若未重新指定访问权限，将设置为private。出于测试代码易读的考虑，建议在这个宏后面加上明确的访问权限，不论紧跟其后的成员是public,protected还是private。<br />
<strong><em>CPPUNIT_TEST_SUITE_END_ABSTRACT()</em></strong>：结束纯虚test suite的声明。用这个宏表明此TestFixture是纯虚的，未声明静态的suite()方法。与CPPUNIT_TEST_SUITE_END()一样，在这个宏之后的成员，若未重新指定访问权限，同样将设置为private。用一个例子来说明这个宏的用法：</div>
<div id="_mcePaste">
<pre class="brush: cpp; title: ; notranslate">
//The abstract test fixture
#include &lt;cppunit/extensions/HelperMacros.h&gt;
class AbstractDocument;
class AbstractDocumentTest : public CppUnit::TestFixture {
  CPPUNIT_TEST_SUITE( AbstractDocumentTest );
  CPPUNIT_TEST( testInsertText );
  CPPUNIT_TEST_SUITE_END_ABSTRACT();
public:
  void testInsertText();
  void setUp()
  {
    m_document = makeDocument();
  }
  void tearDown()
  {
    delete m_document;
  }
protected:
  virtual AbstractDocument *makeDocument() =0;
  AbstractDocument *m_document;
};

//The concrete test fixture
class RichTextDocumentTest : public AbstractDocumentTest {
   CPPUNIT_TEST_SUB_SUITE( RichTextDocumentTest, AbstractDocumentTest );
   CPPUNIT_TEST( testInsertFormatedText );
   CPPUNIT_TEST_SUITE_END();
public:
   void testInsertFormatedText();
protected:
   AbstractDocument *makeDocument()
   {
     return new RichTextDocument();
   }
};
</pre>
</div>
<div id="_mcePaste"><em>CPPUNIT_TEST_SUITE_ADD_TEST(test)</em>：添加一个test到suite中(用于自定义新的宏)，参数test将被添加到声明的suite中。这个宏设计用来定制新的更高级的宏，以方便对cppunit进行扩展。在CPPUNIT_TEST_SUITE()和CPPUNIT_TEST_SUITE_END()两个宏之间，可以使用两个变量</div>
<div id="_mcePaste">
<pre class="brush: cpp; title: ; notranslate">
typedef TestSuiteBuilder&lt;TestFixtureType&gt; TestSuiteBuilderType;
TestSuiteBuilderType &amp;context;
</pre>
</div>
<div id="_mcePaste">context可以用来命名test case，创建新的test fixture实例，添加test case到test suite。这个宏就是用context将test添加到suite中。</div>
<div id="_mcePaste">用一个例子来说明如何使用这个宏创建新的宏，将test添加到suite中。在这里例子里面，订制的新宏将一个test添加到suite中，当这个test执行超过给定的时间后，即认为失败。这个宏基于一个与TestCaller有同样接口的虚构的TimeOutTestCaller类。</div>
<div id="_mcePaste">
<pre class="brush: cpp; title: ; notranslate">
#define CPPUNITEX_TEST_TIMELIMIT( testMethod, timeLimit )      \
     CPPUNIT_TEST_SUITE_ADD_TEST( (new TimeOutTestCaller&lt;TestFixtureType&gt;( \
                  namer.getTestNameFor( #testMethod ),         \
                  &amp;TestFixtureType::testMethod,                \
                  factory.makeFixture(),                       \
                  timeLimit ) ) )

class PerformanceTest : CppUnit::TestFixture
{
       CPPUNIT_TEST_SUITE( PerformanceTest );
       CPPUNITEX_TEST_TIMELIMIT( testSortReverseOrder, 5.0 );
       CPPUNIT_TEST_SUITE_END();
public:
       void testSortReverseOrder();
};
</pre>
</div>
<div id="_mcePaste"><em>CPPUNIT_TEST(testMethod)</em>：添加一个方法testMethod到test suite中。testMethod的原型必须为void testMethod();</div>
<div id="_mcePaste"><strong><em>CPPUNIT_TEST_EXCEPTION( testMethod, ExceptionType )</em></strong>：添加一个test到test suite中。当指定的异常未被捕获时，这个test会失败。同样，再通过一个例子来看看这个宏的用法。</div>
<div id="_mcePaste">
<pre class="brush: cpp; title: ; notranslate">
#include &lt;cppunit/extensions/HelperMacros.h&gt;
#include &lt;vector&gt;
class MyTest : public CppUnit::TestFixture {
   CPPUNIT_TEST_SUITE( MyTest );
   CPPUNIT_TEST_EXCEPTION( testVectorAtThrow, std::invalid_argument );
   CPPUNIT_TEST_SUITE_END();
public:
   void testVectorAtThrow()
   {
     std::vector&lt;int&gt; v;
     v.at(1);     // must throw exception std::invalid_argument
   }
};
</pre>
</div>
<div id="_mcePaste">参数testMethod表示要添加到test suite当中的test的名称；参数ExceptionType即为testMethod要抛出的异常类型。这个宏在cppunit当中不建议使用，可以使用CPPUNIT_ASSERT_THROW宏代替。</div>
<div id="_mcePaste"><em>CPPUNIT_TEST_SUITE_REGISTRATION( ATestFixtureType )</em>：添加指定的fixture到未命名的registry中。这个宏声明了一个静态对象，其构造函数将一个test suite工厂加入到这类工厂的全局registry当中。可以通过调用函数CppUnit::TestFactoryRegistry::getRegistry()获取这个全局的registry。参数ATestFixtureType表示测试类。要注意的是，这个宏在一行内只能用一次，因为行号将会被用来命名一个隐藏的静态变量。</div>
<div id="_mcePaste"><strong><em>CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( ATestFixtureType, suiteName )</em></strong>：添加指定的fixture到指定的registry中。这个宏声明了一个静态对象，其构造函数将一个test suite工厂加入到由参数suiteName指定名字的全局registry当中。可以通过调用函数CppUnit::TestFactoryRegistry::getRegistry()获取这个全局的registry。建议使用静态的方法返回字符串作为suiteName参数的值，这样比直接硬编码字符串要好，可以避免输入上的错误。</div>
<div id="_mcePaste">
<pre class="brush: cpp; title: ; notranslate">
// MySuites.h
namespace MySuites {
  std::string math() {
    return &quot;Math&quot;;
  }
}

// ComplexNumberTest.cpp
#include &quot;MySuites.h&quot;

CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( ComplexNumberTest, MySuites::math() );
</pre>
</div>
<div id="_mcePaste">参数ATestFixtureType表示测试类。要注意的是，这个宏在一行内只能用一次，因为行号将会被用来命名一个隐藏的静态变量。</div>
<div id="_mcePaste">经常使用的帮助宏就介绍到这里，还有其他的一些宏，可以参照HelperMacros.h头文件中的说明。文中所涉及的宏，我并未全部使用，若翻译中有错误，欢迎指正。</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.developnotes.info/helpmacros-in-cppunit/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
