<?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>Tony Bai &#187; 工作</title>
	<atom:link href="http://tonybai.com/tag/%e5%b7%a5%e4%bd%9c/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Sun, 12 Apr 2026 22:30:28 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>2014小结</title>
		<link>https://tonybai.com/2014/12/31/2014-summary/</link>
		<comments>https://tonybai.com/2014/12/31/2014-summary/#comments</comments>
		<pubDate>Wed, 31 Dec 2014 12:21:53 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[思考控]]></category>
		<category><![CDATA[2014]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[A股]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[QQ]]></category>
		<category><![CDATA[QZ8501]]></category>
		<category><![CDATA[RCS]]></category>
		<category><![CDATA[中国移动]]></category>
		<category><![CDATA[企业号]]></category>
		<category><![CDATA[创业]]></category>
		<category><![CDATA[周鸿祎]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[平台]]></category>
		<category><![CDATA[年终总结]]></category>
		<category><![CDATA[微信]]></category>
		<category><![CDATA[招行]]></category>
		<category><![CDATA[服务号]]></category>
		<category><![CDATA[生态圈]]></category>
		<category><![CDATA[用友网络]]></category>
		<category><![CDATA[用友软件]]></category>
		<category><![CDATA[短信]]></category>
		<category><![CDATA[精益创业]]></category>
		<category><![CDATA[终端技术]]></category>
		<category><![CDATA[腾讯]]></category>
		<category><![CDATA[融合通信]]></category>
		<category><![CDATA[订阅号]]></category>
		<category><![CDATA[转型]]></category>
		<category><![CDATA[运营商]]></category>
		<category><![CDATA[麦肯锡报告]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1677</guid>
		<description><![CDATA[2014年的最后一个工作日，这里写下有关2014年的一份小结。 年终总结本无固定格式，但写了若干年后，便有了自己的格式。但今年不打算遵循这个格式了，跳出自己的舒适区，随意写写。 2014年12月底，随着亚航QZ8501航班的最后一掉，航空史上都为数不多的灾难年终于画上了句号，留给人们的是久久的惊恐不安，留给遇难者 家属们的是无法释怀的悲伤。2014年12月31日15点，随着A股上证指数最后一个交易日收涨68.86点，稳稳站上3200点，让广大股民们 看到了2015年牛市持续赚钱的希望。不知为何，这个世界几乎总是同时上演着冰与火两种剧本。 短信与微信(包括其他X信)的博弈亦是如此。 短信，这一红极一时的让移动运营商赚得盆满钵满的廉价沟通工具如今却早已成明日黄花。不妨打开手机，翻看一下你的手机通信录，短信列表中是不是除 了验证码（登录、支付业务），就是各种营销垃圾广告，或者是移动运营商自有的客服信息呢。我相信我的情况应该可以代表广大群众了。 随着微信今年推出&#8220;企业号&#8221;，微信几乎完成了对短信的业务合围： 点对点短信 vs. 联系人、朋友圈、群 SP短信&#160;&#160;&#160; vs. 订阅号、服务号 行业短信&#160; vs. 服务号、企业号 （营销、售后、内部OA、CRM等） 今年年初招商银行信用卡将300以内的消费提醒短信取消，改为微信提醒，其实就是一个看高微信，看空短信的行为。只是考虑到到达率(用户未开网络时)，没 有将大额消费全部转到微信上，而是短信和微信都做提醒。一旦无线网络接入、资费门槛下降、网络速度提升、终端实时在线不再是问题，达到率也将 不是问题时，微信会对短信发起最后的总攻。 这么对比其实也不公平，因为短信和微信本不是一个重量级的对手。从出生的那天起，微信就被赋予了崇高的使命，非短信可比。微信试图连接一切，做统 一入口，建立庞大生态圈；而短信仅仅是一个通道工具罢了。 面对移动短信市场的衰败，移动运营商也在挣扎，也在试图翻盘，或至少平起平坐，但就我了解到的移动运营商产品开发与运营的风格，想和互联网巨头T 掰手腕，下场必输无疑。中国移动年初也蛮拼的，喊出了&#34;RCS（融合通信）&#34;与微信抢手机社交入口，但这都到了2014最后一天了，RCS依旧不见踪 影。 短信免费或退出历史舞台就像周鸿祎在其书《周鸿祎自述：我的互联网方法论》中说的那样是&#8220;趋势&#8221;，不可违！ 而我们就是为中国移动短信业务提供服务端软件和方案的。短信若是没了(或变成鸡肋)，我们干啥？冰冷的现实摆在大家面前，领导跟我 们说：&#8220;转型&#8221;。 2014年，至少我们依旧在转型中。老板们把&#8220;转型&#8221;依旧约束在&#8220;移动运营商&#8221;这棵大树下面，这让我们转的不那么纯粹，有些拖泥带水，可持续盈利 的业务方向并不明显。从目前来看，今年收入依旧靠传统业务渠道获得。 虽说要&#8220;转型&#8221;，但领导今年给我的任务却是做好守门员，守住现有市场份额，保证产品线上无事。这并非如我所愿，在一个业务线耕耘多年，业务和技术 能力均到达了天花板，对我个人来说，这不是一个很好的发展规划。但考虑到下面的技术负责人、员工在技术和业务火候儿还欠缺那么一些，我答应了留 守，但会投入部分精力做个人技术转型储备。 业务的转型需要技术做支撑，局限于传统后台服务系统的我们需要张开怀抱，拥抱那些&#8220;流行&#8221;的新玩意儿。我首先试水！从2014年我的博客中你也许 可以看得出来我试水过的技术，我在尝试跳出自己的各种舒适区，向一些近两年兴起的、将来比较有前途的技术方向靠拢，学习移动互联网的思维和潮流。 上半年曾尝试过终端产品开发的技术，还为此购入若干数码装备，但试过后才发现这仍然不是我的主菜，就和10年前Windows GUI程序开发不是我的菜一样。但这个过程并非没有收获，未来任何业务不与终端开发打交道是不可能的，这个接触过程让我了解到了终端开发的重点和难点，于 是总结经验，整理教训。 正当准备调整方向、重新上路之际，家里出现重大变故，耗费了我整整1个多月的时间，一切几乎都停滞了，直到10月份我才渐渐重新进入状态。 在公司内部技术社区看到公司CTO的一篇文章，讲述移动互联网正在由消费者驱动向企业驱动转变（来自麦肯锡报告），结合微信推出企业号、用友软件的转型来 看(今天听说用友软件更名为&#8220;用友网络&#8221;了，决心向互联网转型)，这个趋势也是我比较认同的，这个方向以及相关技术也是我在正在涉猎以及即将涉猎的。不过 关于企业互联网服务以及平台，自己的相关业务经验、技术和积累还是甚少，征途必然坎坷，自己还需&#8220;拼&#8221;一下！关于微信这个平台，这个入口，它是腾讯未来战 略的核心，靠着腾讯这棵大树，至少未来几年发展应该还是不错的。 公司的大BOSS这两年一直提倡&#8220;创业者的精神&#34;，学会在逆境中成长，在困境中成功。但作为在短信这个行业内浸淫了十多年的部门，我们不免产生一些惰性， 更愿意躺在现有的温床上&#8220;享受生活&#8221;，立足于现有的平台做舒服的事情。经历过2014年的严峻形势，现在的我们应该清醒的认识到这样的舒服生活，温床和平 台都可能将远离我们。如果我们再不主动站起来，我们将再无力站起了。 2014年在个人发展方面做出了&#8220;妥协&#8221;，2015我打算轻装前行，这对我、对团队成员的成长都是有好处的。年底给领导发总结时，已经和领导书面提出退出 当前业务线的想法。虽然目前还没有收到回复，不过无论怎样，我都坚定了决心，自己作为这个产品线的负责人，已经起不到领路的作用了，是时候退出了。 2015，给自己的关键字是&#8220;创业&#8221;。《精益创业》一书中作者似乎有这样一句话：&#8220;你不一定非要在车库里折腾才算是创业&#8221;，在企业内部也可以&#8220;创业&#8221;，为创造某种新产品或新服务为目的而组建的一个团队或组织内的人都是&#8220;创业者&#8221;。 以往年份的小结，我总会总结一些数据，比如blog文章、读过多少本书等等。但今年这些数据就不统计了，自己对自己的考核指标&#34;KPI&#34;有所调整，以前哪些指标已经不算数了，列出也就无意义了。 2014这一年，LP给了我很大压力！我能理解，她期望我能取得更大的成功。这让我&#8220;亚历山大&#8221;啊，这回可是真的。 要说新年的愿望是什么？希望2015年年末时能为自己2015年的所作所为，所取得的进步和成果点个赞！ &#169; [...]]]></description>
			<content:encoded><![CDATA[<p>2014年的最后一个工作日，这里写下有关2014年的一份小结。</p>
<p>年终总结本无固定格式，但写了若干年后，便有了自己的格式。但今年不打算遵循这个格式了，跳出自己的舒适区，随意写写。</p>
<p><b>2014年</b>12月底，随着亚航QZ8501航班的最后一掉，航空史上都为数不多的灾难年终于画上了句号，留给人们的是久久的惊恐不安，留给遇难者 家属们的是无法释怀的悲伤。2014年12月31日15点，随着A股上证指数最后一个交易日收涨68.86点，稳稳站上3200点，让广大股民们 看到了2015年牛市持续赚钱的希望。不知为何，这个世界几乎总是同时上演着冰与火两种剧本。</p>
<p>短信与<a href="http://weixin.qq.com">微信</a>(包括其他X信)的博弈亦是如此。</p>
<p>短信，这一红极一时的让移动运营商赚得盆满钵满的廉价沟通工具如今却早已成明日黄花。不妨打开手机，翻看一下你的手机通信录，短信列表中是不是除 了验证码（登录、支付业务），就是各种营销垃圾广告，或者是移动运营商自有的客服信息呢。我相信我的情况应该可以代表广大群众了。</p>
<p>随着微信今年推出&ldquo;<a href="http://qy.weixin.qq.com">企业号</a>&rdquo;，微信几乎完成了对短信的业务合围：</p>
<p><font face="Courier New">点对点短信 vs. 联系人、朋友圈、群<br />
	SP短信&nbsp;&nbsp;&nbsp; vs. 订阅号、服务号<br />
	行业短信&nbsp; vs. 服务号、企业号 （营销、售后、内部OA、CRM等）</font></p>
<p>今年年初招商银行信用卡将300以内的消费提醒短信取消，改为微信提醒，其实就是一个看高微信，看空短信的行为。只是考虑到到达率(用户未开网络时)，没 有将大额消费全部转到微信上，而是短信和微信都做提醒。一旦无线网络接入、资费门槛下降、网络速度提升、终端实时在线不再是问题，达到率也将 不是问题时，微信会对短信发起最后的总攻。</p>
<p>这么对比其实也不公平，因为短信和微信本不是一个重量级的对手。从出生的那天起，微信就被赋予了崇高的使命，非短信可比。微信试图连接一切，做统 一入口，建立庞大生态圈；而短信仅仅是一个通道工具罢了。</p>
<p>面对移动短信市场的衰败，移动运营商也在挣扎，也在试图翻盘，或至少平起平坐，但就我了解到的移动运营商产品开发与运营的风格，想和互联网巨头T 掰手腕，下场必输无疑。中国移动年初也蛮拼的，喊出了&quot;<a href="http://zh.wikipedia.org/wiki/RCS">RCS</a>（融合通信）&quot;与微信抢手机社交入口，但这都到了2014最后一天了，RCS依旧不见踪 影。</p>
<p>短信免费或退出历史舞台就像周鸿祎在其书《<a href="http://book.douban.com/subject/25928983/">周鸿祎自述：我的互联网方法论</a>》中说的那样是&ldquo;趋势&rdquo;，不可违！</p>
<p><b>而</b>我们就是为中国移动短信业务提供服务端软件和方案的。短信若是没了(或变成鸡肋)，我们干啥？冰冷的现实摆在大家面前，领导跟我 们说：<b>&ldquo;转型</b>&rdquo;。</p>
<p><b>2014年</b>，至少我们依旧在转型中。老板们把&ldquo;转型&rdquo;依旧约束在&ldquo;移动运营商&rdquo;这棵大树下面，这让我们转的不那么纯粹，有些拖泥带水，可持续盈利 的业务方向并不明显。从目前来看，今年收入依旧靠传统业务渠道获得。</p>
<p>虽说要&ldquo;转型&rdquo;，但领导今年给我的任务却是做好守门员，守住现有市场份额，保证产品线上无事。这并非如我所愿，在一个业务线耕耘多年，业务和技术 能力均到达了天花板，对我个人来说，这不是一个很好的发展规划。但考虑到下面的技术负责人、员工在技术和业务火候儿还欠缺那么一些，我答应了留 守，但会投入部分精力做个人技术转型储备。</p>
<p>业务的转型需要技术做支撑，局限于传统后台服务系统的我们需要张开怀抱，拥抱那些&ldquo;流行&rdquo;的新玩意儿。我首先试水！从2014年我的博客中你也许 可以看得出来我试水过的技术，我在尝试跳出自己的各种舒适区，向一些近两年兴起的、将来比较有前途的技术方向靠拢，学习移动互联网的思维和潮流。</p>
<p>上半年曾尝试过终端产品开发的技术，还为此购入若干数码装备，但试过后才发现这仍然不是我的主菜，就和10年前Windows GUI程序开发不是我的菜一样。但这个过程并非没有收获，未来任何业务不与终端开发打交道是不可能的，这个接触过程让我了解到了终端开发的重点和难点，于 是总结经验，整理教训。</p>
<p>正当准备调整方向、重新上路之际，家里出现重大变故，耗费了我整整1个多月的时间，一切几乎都停滞了，直到10月份我才渐渐重新进入状态。</p>
<p>在公司内部技术社区看到公司CTO的一篇文章，讲述移动互联网正在由消费者驱动向企业驱动转变（来自<a href="http://www.aliresearch.com/?m-cms-q-view-id-76846.html">麦肯锡报告</a>），结合微信推出企业号、用友软件的转型来 看(今天听说用友软件更名为&ldquo;用友网络&rdquo;了，决心向互联网转型)，这个趋势也是我比较认同的，这个方向以及相关技术也是我在正在涉猎以及即将涉猎的。不过 关于企业互联网服务以及平台，自己的相关业务经验、技术和积累还是甚少，征途必然坎坷，自己还需&ldquo;拼&rdquo;一下！关于微信这个平台，这个入口，它是腾讯未来战 略的核心，靠着腾讯这棵大树，至少未来几年发展应该还是不错的。</p>
<p>公司的大BOSS这两年一直提倡&ldquo;创业者的精神&quot;，学会在逆境中成长，在困境中成功。但作为在短信这个行业内浸淫了十多年的部门，我们不免产生一些惰性， 更愿意躺在现有的温床上&ldquo;享受生活&rdquo;，立足于现有的平台做舒服的事情。经历过2014年的严峻形势，现在的我们应该清醒的认识到这样的舒服生活，温床和平 台都可能将远离我们。如果我们再不主动站起来，我们将再无力站起了。</p>
<p>2014年在个人发展方面做出了&ldquo;妥协&rdquo;，2015我打算轻装前行，这对我、对团队成员的成长都是有好处的。年底给领导发总结时，已经和领导书面提出退出 当前业务线的想法。虽然目前还没有收到回复，不过无论怎样，我都坚定了决心，自己作为这个产品线的负责人，已经起不到领路的作用了，是时候退出了。</p>
<p>2015，给自己的关键字是&ldquo;创业&rdquo;。《<a href="http://book.douban.com/subject/10945606/">精益创业</a>》一书中作者似乎有这样一句话：&ldquo;你不一定非要在车库里折腾才算是创业&rdquo;，在企业内部也可以&ldquo;创业&rdquo;，为创造某种新产品或新服务为目的而组建的一个团队或组织内的人都是&ldquo;创业者&rdquo;。</p>
<p>以往年份的小结，我总会总结一些数据，比如blog文章、读过多少本书等等。但今年这些数据就不统计了，自己对自己的考核指标&quot;KPI&quot;有所调整，以前哪些指标已经不算数了，列出也就无意义了。</p>
<p>2014这一年，LP给了我很大压力！我能理解，她期望我能取得更大的成功。这让我&ldquo;亚历山大&rdquo;啊，这回可是真的。</p>
<p>要说新年的愿望是什么？希望2015年年末时能为自己2015年的所作所为，所取得的进步和成果<b>点个赞</b>！</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/12/31/2014-summary/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>探讨Docker容器中修改系统变量的方法</title>
		<link>https://tonybai.com/2014/10/14/discussion-on-the-approach-to-modify-system-variables-in-docker/</link>
		<comments>https://tonybai.com/2014/10/14/discussion-on-the-approach-to-modify-system-variables-in-docker/#comments</comments>
		<pubDate>Tue, 14 Oct 2014 13:56:12 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[init]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[phusion]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Redhat]]></category>
		<category><![CDATA[SharedMemory]]></category>
		<category><![CDATA[shmget]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[共享内存]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[虚拟化]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1563</guid>
		<description><![CDATA[探讨完Docker对共享内存状态持久化的支持状况后，我将遗留产品build到一个pre-production image中，测试启动是否OK。很显然，我过于乐观了，Docker之路并不平坦。我收到了shmget报出的EINVAL错误码，提示参数非法。 shmget的manual对EINVAL错误码的说明如下： EINVAL： A&#160; new&#160; segment&#160; was&#160; to&#160; be&#160; created&#160; and size &#60; SHMMIN or size &#62; SHMMAX, or no new segment was to be created, a segment with given key existed, but size is greater than the size of that segment. 显然我们要创建的shared memory的size很可能大于SHMMAX这个系统变量了。那么一个从base image创建出的容器中的系统变量到底是什么值呢？我们来查看一下，我们基于&#34;centos:centos6&#34;启动一个Docker容器，并检查其中的 系统变量值设置： $ sudo docker run -it &#34;centos:centos6&#34; /bin/bash bash-4.1# [...]]]></description>
			<content:encoded><![CDATA[<p>探讨完<a href="http://tonybai.com/2014/10/12/discussion-on-shared-mem-support-in-docker/">Docker对共享内存状态持久化的支持状况</a>后，我将遗留产品build到一个pre-production image中，测试启动是否OK。很显然，我过于乐观了，<a href="http://www.docker.com">Docker</a>之路并不平坦。我收到了shmget报出的EINVAL错误码，提示参数非法。 shmget的manual对EINVAL错误码的说明如下：</p>
<p><font face="Courier New">EINVAL：<br />
	A&nbsp; new&nbsp; segment&nbsp; was&nbsp; to&nbsp; be&nbsp; created&nbsp; and size &lt; SHMMIN or size &gt; SHMMAX, or no new segment was to be created, a segment with given key existed, but size is greater than the size of that segment.</font></p>
<p>显然我们要创建的shared memory的size很可能大于SHMMAX这个系统变量了。那么一个从base image创建出的容器中的系统变量到底是什么值呢？我们来查看一下，我们基于&quot;centos:centos6&quot;启动一个Docker容器，并检查其中的 系统变量值设置：</p>
<p><font face="Courier New">$ sudo docker run -it &quot;centos:centos6&quot; /bin/bash<br />
	bash-4.1# cat /proc/sys/kernel/shmmax<br />
	33554432<br />
	bash-4.1# sysctl -a|grep shmmax<br />
	kernel.shmmax = 33554432</font></p>
<p>可以看出默认情况下，当前容器中root账号看到的shmmax值我<font face="Courier New">33554432</font>， 我的程序要创建的shm size的确要大于这个值，报出EINVAL错误也就无可厚非了。我尝试按照物理机上的方法临时修改一下该值：</p>
<p><font face="Courier New">bash-4.1# echo 68719476736 &gt; /proc/sys/kernel/shmmax<br />
	bash: /proc/sys/kernel/shmmax: Read-only file system</font></p>
<p><font face="Courier New">/proc/sys/kernel/shmmax居然是只读的，无法修改。</font></p>
<p><font face="Courier New">我又尝试修改/etc/sysctl.conf这个持久化系统变量的地方，但打开/etc/sysctl.conf文件，我发现我又错了，这 个文件中shmmax的值如下：</font></p>
<p><font face="Courier New"># Controls the maximum shared segment size, in bytes<br />
	kernel.shmmax = 68719476736</font></p>
<p><font face="Courier New"><font face="Courier New">/etc/sysctl.conf文件 中的系统变量shmmax的值是68719476736，而系统当前的实际值则是33554432，难道是/etc /sysctl.conf中的值没有生效，于是我手工重新加载一次该文件：</font></font></p>
<p><font face="Courier New"><font face="Courier New">-bash-4.1# sysctl -p<br />
	error: &quot;Read-only file system&quot; setting key &quot;net.ipv4.ip_forward&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;net.ipv4.conf.default.rp_filter&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;net.ipv4.conf.default.accept_source_route&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;kernel.sysrq&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;kernel.core_uses_pid&quot;<br />
	error: &quot;net.ipv4.tcp_syncookies&quot; is an unknown key<br />
	error: &quot;net.bridge.bridge-nf-call-ip6tables&quot; is an unknown key<br />
	error: &quot;net.bridge.bridge-nf-call-iptables&quot; is an unknown key<br />
	error: &quot;net.bridge.bridge-nf-call-arptables&quot; is an unknown key<br />
	error: &quot;Read-only file system&quot; setting key &quot;kernel.msgmnb&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;kernel.msgmax&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;kernel.shmmax&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;kernel.shmall&quot;</font></font></p>
<p><font face="Courier New"><font face="Courier New">我得到了和之前类似的错误结果：只读文件系统，无法修改。于是乎两个问题萦绕在我的面前：<br />
	1、为什么容器内当前系统变量值与sysctl.conf中的不一致？<br />
	2、为什么无法修改当前系统变量值?</font></font></p>
<p><font face="Courier New"><font face="Courier New">在翻阅了Stackoverflow, github docker issues后，我得到了的答案如下：</font></font></p>
<p><font face="Courier New"><font face="Courier New">1、Docker的base image做的很精简，甚至都没有init进程，原本在OS启动时执行生效系统变量的过程(sysctl -p)也给省略了，导致这些系统变量依旧保留着kernel默认值。以CentOs为例，在linux kernel boot后，init都会执行/etc/rc.d/rc.sysinit，后者会加载/etc/sysctl.conf中的系统变量值。下面是 CentOs5.6中的rc.sysinit代码摘录：</font></font></p>
<p><font face="Courier New"><font face="Courier New">&#8230; &#8230;<br />
	# Configure kernel parameters<br />
	update_boot_stage RCkernelparam<br />
	sysctl -e -p /etc/sysctl.conf &gt;/dev/null 2&gt;&amp;1<br />
	&#8230; &#8230;</font></font></p>
<p><font face="Courier New"><font face="Courier New">2、Docker容器中的系统变量在non-priviledged模式下目前(我使用的时docker 1.2.0版本)就无法修改，</font></font>这 和resolv.conf、hosts等文件映射到宿主机对应的文件有不同。</p>
<p><font face="Courier New">$ mount -l<br />
	&#8230;. &#8230;.<br />
	/dev/mapper/ubuntu&#8211;Server&#8211;14&#8211;vg-root on /etc/resolv.conf type ext4 (rw,relatime,errors=remount-ro,data=ordered)<br />
	/dev/mapper/ubuntu&#8211;Server&#8211;14&#8211;vg-root on /etc/hostname type ext4 (rw,relatime,errors=remount-ro,data=ordered)<br />
	/dev/mapper/ubuntu&#8211;Server&#8211;14&#8211;vg-root on /etc/hosts type ext4 (rw,relatime,errors=remount-ro,data=ordered)<br />
	&#8230; &#8230;</font></p>
<p>那么我们该如何修改系统变量值来满足遗留产品的需求呢？</p>
<p><b>一、使用&#8211;privileged选项</b></p>
<p>我们使用&#8211;privileged这个特权选项来启动一个基于centos:centos6的新容器，看看是否能对shmmax这样的系统变量值 进行修改：</p>
<p><font face="Courier New">$ sudo docker run -it &#8211;privileged&nbsp; &quot;centos:centos6&quot; /bin/bash<br />
	bash-4.1# cat /proc/sys/kernel/shmmax<br />
	33554432<br />
	bash-4.1# echo 68719476736 &gt; /proc/sys/kernel/shmmax<br />
	bash-4.1# cat /proc/sys/kernel/shmmax<br />
	68719476736</font><br />
	<font face="Courier New">bash-4.1# sysctl -p<br />
	net.ipv4.ip_forward = 0<br />
	net.ipv4.conf.default.rp_filter = 1<br />
	net.ipv4.conf.default.accept_source_route = 0<br />
	kernel.sysrq = 0<br />
	kernel.core_uses_pid = 1<br />
	&#8230; &#8230;<br />
	kernel.msgmnb = 65536<br />
	kernel.msgmax = 65536<br />
	kernel.shmmax = 68719476736<br />
	kernel.shmall = 4294967296</font></p>
<p>可以看出，通过&#8211;privileged选项，容器获得了额外的特权，并且可以对系统变量的值进行修改了。不过这样的修改是不能保存在容器里的， 我们stop 容器，再重启该容器就能看出来：</p>
<p><font face="Courier New">$ sudo docker start 3e22d65a7845<br />
	$ sudo docker attach 3e22d65a7845<br />
	bash-4.1# cat /proc/sys/kernel/shmmax<br />
	33554432</font></p>
<p>shmmax的值在容器重启后又变回了原先的那个默认值。不过重启后的容器依旧具有privileged的特权，我们还可以重新手工执行命令对系 统变量进行修改：</p>
<p><font face="Courier New">bash-4.1# echo 68719476736 &gt; /proc/sys/kernel/shmmax<br />
	bash-4.1# cat /proc/sys/kernel/shmmax<br />
	68719476736</font></p>
<p>但即便这样，也无法满足我们的需求，我们总不能每次都在容器中手工执行系统变量值修改的操作吧。privileged选项的能力能否带到 image中呢？答案是目前还不能，我们无法在build image时通过privileged选项修改系统变量值。</p>
<p>这样一来，我们能做的只有把产品启动与系统变量值修改放在一个脚本中了，并将该脚本作为docker 容器的cmd命令来执行，比如我们构建一个Dockerfile：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	RUN yum install python-setuptools -y<br />
	RUN easy_install supervisor<br />
	RUN mkdir -p /var/log/supervisor<br />
	COPY ./supervisord.conf /etc/supervisord.conf<br />
	COPY ./start.sh /bin/start.sh<br />
	RUN chmod +x /bin/start.sh<br />
	CMD ["/bin/start.sh]</font></p>
<p><font face="Courier New">//start.sh<br />
	sysctl -p<br />
	/usr/bin/supervisord</font></p>
<p>这样，start.sh在supervisord启动前将系统变量值重新加载，而supervisord后续启动的程序就可以看到这些新系统变量 的值了。不过别忘了利用这个image启动容器时要加上&#8211;priviledged选项，否则容器启动就会失败。</p>
<p><b>二、使用phusion/baseimage</b></p>
<p>前面说过/etc/sysctl.conf中的值没有生效是因为docker官方提供的centos:centos6把init进程的初始化过程给精 简掉了。<a href="https://registry.hub.docker.com/u/phusion/baseimage/">phusion/baseimage</a>是目前docker registery上仅次于ubuntu和centos两个之后的base image，其提供了/sbin/my_init这个init进程，用于在container充当init进程的角色。那么my_init是否可以用于执行sysctl -p呢？我们试验一下：</p>
<p>我们先pull这个base image下来：<font face="Courier New">sudo docker pull phusion/baseimage。pull成功后，我们先基于&ldquo;phusion/baseimage&rdquo;启动一个容器做一些explore工作：</font></p>
<p><font face="Courier New">$ sudo docker run -i -t &quot;phusion/baseimage&quot;<br />
	*** Running /etc/my_init.d/00_regen_ssh_host_keys.sh&#8230;<br />
	No SSH host key available. Generating one&#8230;<br />
	Creating SSH2 RSA key; this may take some time &#8230;<br />
	Creating SSH2 DSA key; this may take some time &#8230;<br />
	Creating SSH2 ECDSA key; this may take some time &#8230;<br />
	Creating SSH2 ED25519 key; this may take some time &#8230;<br />
	invoke-rc.d: policy-rc.d denied execution of restart.<br />
	*** Running /etc/rc.local&#8230;<br />
	*** Booting runit daemon&#8230;<br />
	*** Runit started as PID 100</font></p>
<p><font face="Courier New">通过nsenter进去，查看一下/sbin/my_init的源码，我们发现这是一个python脚本，不过从头到尾浏览一遍，没有发现sysctl加载/etc/sysctl.conf系统变量的操作。</font></p>
<p><font face="Courier New">不过，phusion文档中说my_init可以在初始化过程中执行/etc/my_init.d下的脚本。那是不是我们将一个执行sysctl -p的脚本放入/etc/my_init.d下就可以实现我们的目的了呢？试试。</font></p>
<p><font face="Courier New">我们编写一个脚本：load_sys_varibles.sh</font></p>
<p><font face="Courier New">#!/bin/sh<br />
	sysctl -p &gt; init.txt</font></p>
<p><font face="Courier New">下面是制作image的Dockerfile:</font></p>
<p><font face="Courier New">FROM phusion/baseimage:latest<br />
	MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	RUN echo &quot;kernel.shmmax = 68719476736&quot; &gt;&gt; /etc/sysctl.conf<br />
	RUN mkdir -p /etc/my_init.d<br />
	ADD load_sys_varibles.sh /etc/my_init.d/load_sys_varibles.sh<br />
	RUN chmod +x /etc/my_init.d/load_sys_varibles.sh<br />
	CMD ["/sbin/my_init"]</font></p>
<p><font face="Courier New">phusion/baseimage是基于ubuntu的OS，其sysctl.conf默认情况下没啥内容，所以我们在Dockerfile中向这个文件写入我们需要的系统变量值。构建image并启动容器：</font></p>
<p><font face="Courier New">$ sudo docker build -t &quot;myphusion:v1&quot; ./<br />
	Sending build context to Docker daemon 13.12 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM phusion/baseimage:latest<br />
	&nbsp;&#8212;&gt; cf39b476aeec<br />
	Step 1 : MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; d0e9b51a3e4f<br />
	Step 2 : RUN echo &quot;kernel.shmmax = 68719476736&quot; &gt;&gt; /etc/sysctl.conf<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; 2c800687cc83<br />
	Step 3 : RUN mkdir -p /etc/my_init.d<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; fe366eea5eb4<br />
	Step 4 : ADD load_sys_varibles.sh /etc/my_init.d/load_sys_varibles.sh<br />
	&nbsp;&#8212;&gt; a641bb595fb9<br />
	Removing intermediate container c381b9f001c2<br />
	Step 5 : RUN chmod +x /etc/my_init.d/load_sys_varibles.sh<br />
	&nbsp;&#8212;&gt; Running in 764866552f25<br />
	&nbsp;&#8212;&gt; eae3d7f1eac5<br />
	Removing intermediate container 764866552f25<br />
	Step 6 : CMD ["/sbin/my_init"]<br />
	&nbsp;&#8212;&gt; Running in 9ab8d0b717a7<br />
	&nbsp;&#8212;&gt; 8be4e7b6b174<br />
	Removing intermediate container 9ab8d0b717a7<br />
	Successfully built 8be4e7b6b174</font></p>
<p><font face="Courier New">$ sudo docker run -it &quot;myphusion:v1&quot;<br />
	*** Running /etc/my_init.d/00_regen_ssh_host_keys.sh&#8230;<br />
	No SSH host key available. Generating one&#8230;<br />
	Creating SSH2 RSA key; this may take some time &#8230;<br />
	Creating SSH2 DSA key; this may take some time &#8230;<br />
	Creating SSH2 ECDSA key; this may take some time &#8230;<br />
	Creating SSH2 ED25519 key; this may take some time &#8230;<br />
	invoke-rc.d: policy-rc.d denied execution of restart.<br />
	*** Running /etc/my_init.d/load_sys_varibles.sh&#8230;<br />
	sysctl: setting key &quot;kernel.shmmax&quot;: Read-only file system<br />
	*** /etc/my_init.d/load_sys_varibles.sh failed with status 255</font></p>
<p><font face="Courier New">*** Killing all processes&#8230;</font></p>
<p><font face="Courier New">唉，还是老问题！即便是在my_init中执行，依旧无法逾越Read-only file system，查看Phusion/baseimage的Dockerfile才知道，它也是From ubuntu:14.04的，根不变，上层再怎么折腾也没用。</font></p>
<p><font face="Courier New">换一种容器run方法吧，加上&#8211;privileged：</font></p>
<p><font face="Courier New">$ sudo docker run -it &#8211;privileged&nbsp; &quot;myphusion:v1&quot;<br />
	*** Running /etc/my_init.d/00_regen_ssh_host_keys.sh&#8230;<br />
	No SSH host key available. Generating one&#8230;<br />
	Creating SSH2 RSA key; this may take some time &#8230;<br />
	Creating SSH2 DSA key; this may take some time &#8230;<br />
	Creating SSH2 ECDSA key; this may take some time &#8230;<br />
	Creating SSH2 ED25519 key; this may take some time &#8230;<br />
	invoke-rc.d: policy-rc.d denied execution of restart.<br />
	*** Running /etc/my_init.d/load_sys_varibles.sh&#8230;<br />
	*** Running /etc/rc.local&#8230;<br />
	*** Booting runit daemon&#8230;<br />
	*** Runit started as PID 102</font></p>
<p><font face="Courier New">这回灵光了。enter到容器里看看设置的值是否生效了：</font></p>
<p><font face="Courier New">root@9e399f46372a:~#cat /proc/sys/kernel/shmmax<br />
	68719476736</font></p>
<p><font face="Courier New">结果如预期。这样来看phusion/baseimage算是为sysctl -p加载系统变量值提供了一个便利，但依旧无法脱离&#8211;privileged，且依旧无法在image中持久化这个设置。</font></p>
<p><font face="Courier New">在Docker github的issue中有人提出建议在Dockerfile中加入类似RUNP这样的带有特权的指令语法，但不知何时才能在Docker中加入这一功能。</font></p>
<p><font face="Courier New">总而言之，基于目前docker官网提供的base image，我们很难找到特别理想的修改系统变量值的方法，除非自己制作base image，这个还没尝试过，待后续继续研究。</font></p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/10/14/discussion-on-the-approach-to-modify-system-variables-in-docker/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>探讨docker容器对共享内存的支持情况</title>
		<link>https://tonybai.com/2014/10/12/discussion-on-shared-mem-support-in-docker/</link>
		<comments>https://tonybai.com/2014/10/12/discussion-on-shared-mem-support-in-docker/#comments</comments>
		<pubDate>Sat, 11 Oct 2014 21:23:34 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[IPC]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[mmap]]></category>
		<category><![CDATA[POSIX]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[SharedMemory]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[共享内存]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[程序员]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1560</guid>
		<description><![CDATA[我们的遗留系统广泛使用了性能最佳的IPC方式 &#8211; 共享内存，而且用到了两种共享内存的实现方式：System V共享内存(shmget、shmat、shmdt)以及Mmap映射Regular File。System V共享内存支持一定程度上的内存数据持久化，即当程序创建共享内存对象后，如果不显式删除或物理主机重启，该IPC对象会一直保留，其中的数据也不会丢 失；mmap映射Regular File的方式支持内存数据持久化到文件中，即便物理主机重启，这部分数据依旧不会丢失，除非显式删除文件。这两个共享内存机制，尤其是其持久化的特性是 我们的系统所依赖的。但是在Docker容器中，这两种共享内存机制依旧能被很好的支持吗？我们通过试验来分析一下。 一、System V共享内存 一个启动的Docker容器就是一个拥有了自己的内核名字空间的进程，其pid、net、ipc、mnt、uts、user等均与其他进程隔离，对于运行于该容器内的程序而言，它仿佛会觉得它独占了一台&#8220;主机&#8221;。对于这类&#8220;主机&#8221;，我们首先来测试一下其中的system v共享内存是否依旧能像物理主机上一样，在程序退出后依旧能保持持久化？在容器退出后能保持么？ 我们先来写两个测试程序，一个用于创建system v共享内存，并写入一些数据，另外一个程序则映射该共享内存并尝试读出内存中的数据。由于Golang目前仍未提供对System V共享内存的高级封装接口，通过syscall包的Syscall调用又太繁琐，因此我们直接使用C代码与Go代码结合的方式实现这两个测试程序。之前写 过一篇名为《Go与C语言互操作》的博文，看不懂下面代码的朋友，可以先阅读一下这篇文章。 //systemv_shm_wr.go package main //#include &#60;sys/types.h&#62; //#include &#60;sys/ipc.h&#62; //#include &#60;sys/shm.h&#62; //#include &#60;stdio.h&#62; // //#define SHMSZ&#160;&#160;&#160;&#160; 27 // //int shm_wr() { //&#160;&#160;&#160; char c; //&#160;&#160;&#160; int shmid; //&#160;&#160;&#160; key_t key; //&#160;&#160;&#160; char *shm, *s; // //&#160;&#160;&#160; key = 5678; // [...]]]></description>
			<content:encoded><![CDATA[<p>我们的遗留系统广泛使用了性能最佳的IPC方式 &#8211; <a href="http://en.wikipedia.org/wiki/Shared_memory">共享内存</a>，而且用到了两种共享内存的实现方式：System V共享内存(shmget、shmat、shmdt)以及Mmap映射Regular File。System V共享内存支持一定程度上的内存数据持久化，即当程序创建共享内存对象后，如果不显式删除或物理主机重启，该IPC对象会一直保留，其中的数据也不会丢 失；mmap映射Regular File的方式支持内存数据持久化到文件中，即便物理主机重启，这部分数据依旧不会丢失，除非显式删除文件。这两个共享内存机制，尤其是其持久化的特性是 我们的系统所依赖的。但是在<a href="http://docker.com">Docker</a>容器中，这两种共享内存机制依旧能被很好的支持吗？我们通过试验来分析一下。</p>
<p><b>一、System V共享内存</b></p>
<p>一个启动的Docker容器就是一个拥有了自己的内核名字空间的进程，其<b>pid</b><b>、net</b><b>、ipc</b><b>、mnt</b><b>、uts</b><b>、user</b>等均与其他进程隔离，对于运行于该容器内的程序而言，它仿佛会觉得它独占了一台&ldquo;主机&rdquo;。对于这类&ldquo;主机&rdquo;，我们首先来测试一下其中的system v共享内存是否依旧能像物理主机上一样，在程序退出后依旧能保持持久化？在容器退出后能保持么？</p>
<p>我们先来写两个测试程序，一个用于创建system v共享内存，并写入一些数据，另外一个程序则映射该共享内存并尝试读出内存中的数据。由于Golang目前仍未提供对System V共享内存的高级封装接口，通过syscall包的Syscall调用又太繁琐，因此我们直接使用C代码与Go代码结合的方式实现这两个测试程序。之前写 过一篇名为《<a href="http://tonybai.com/2012/09/26/interoperability-between-go-and-c/">Go与C语言互操作</a>》的博文，看不懂下面代码的朋友，可以先阅读一下这篇文章。</p>
<p><font face="Courier New">//systemv_shm_wr.go<br />
	package main</font></p>
<p><font face="Courier New">//#include &lt;sys/types.h&gt;<br />
	//#include &lt;sys/ipc.h&gt;<br />
	//#include &lt;sys/shm.h&gt;<br />
	//#include &lt;stdio.h&gt;<br />
	//<br />
	//#define SHMSZ&nbsp;&nbsp;&nbsp;&nbsp; 27<br />
	//<br />
	//int shm_wr() {<br />
	//&nbsp;&nbsp;&nbsp; char c;<br />
	//&nbsp;&nbsp;&nbsp; int shmid;<br />
	//&nbsp;&nbsp;&nbsp; key_t key;<br />
	//&nbsp;&nbsp;&nbsp; char *shm, *s;<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; key = 5678;<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; if ((shmid = shmget(key, SHMSZ, IPC_CREAT | 0666)) &lt; 0) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br />
	//&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; if ((shm = shmat(shmid, NULL, 0)) == (char *) -1) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -2;<br />
	//&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; s = shm;<br />
	//&nbsp;&nbsp;&nbsp; for (c = &#39;a&#39;; c &lt;= &#39;z&#39;; c++)<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *s++ = c;<br />
	//&nbsp;&nbsp;&nbsp; s = NULL;<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; return 0;<br />
	//}<br />
	import &quot;C&quot;</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i := C.shm_wr()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if i != 0 {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;SystemV Share Memory Create and Write Error:&quot;, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;SystemV Share Memory Create and Write Ok&quot;)<br />
	}</font></p>
<p><font face="Courier New">//systemv_shm_rd.go</font></p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">//#include &lt;sys/types.h&gt;<br />
	//#include &lt;sys/ipc.h&gt;<br />
	//#include &lt;sys/shm.h&gt;<br />
	//#include &lt;stdio.h&gt;<br />
	//<br />
	//#define SHMSZ&nbsp;&nbsp;&nbsp;&nbsp; 27<br />
	//<br />
	//int shm_rd() {<br />
	//&nbsp;&nbsp;&nbsp; char c;<br />
	//&nbsp;&nbsp;&nbsp; int shmid;<br />
	//&nbsp;&nbsp;&nbsp; key_t key;<br />
	//&nbsp;&nbsp;&nbsp; char *shm, *s;<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; key = 5678;<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; if ((shmid = shmget(key, SHMSZ, 0666)) &lt; 0) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br />
	//&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; if ((shm = shmat(shmid, NULL, 0)) == (char *) -1) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -2;<br />
	//&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; s = shm;<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; int i = 0;<br />
	//&nbsp;&nbsp;&nbsp; for (i = 0; i &lt; SHMSZ-1; i++)<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;%c &quot;, *(s+i));<br />
	//&nbsp;&nbsp;&nbsp; printf(&quot;\n&quot;);<br />
	//&nbsp;&nbsp;&nbsp; s = NULL;<br />
	//<br />
	//&nbsp;&nbsp;&nbsp; return 0;<br />
	//}<br />
	import &quot;C&quot;</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i := C.shm_rd()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if i != 0 {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;SystemV Share Memory Create and Read Error:&quot;, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;SystemV Share Memory Create and Read Ok&quot;)<br />
	}</font></p>
<p>我们通过go build构建上面两个程序，得到两个测试用可执行程序：systemv_shm_wr和systemv_shm_rd。下面我们来构建我们的测试用docker image，Dockerfile内容如下：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	COPY ./systemv_shm_wr /bin/<br />
	COPY ./systemv_shm_rd /bin/</font></p>
<p>构建Docker image：&ldquo;shmemtest:v1&rdquo;：</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;shmemtest:v1&quot; ./<br />
	Sending build context to Docker daemon 16.81 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./systemv_shm_wr /bin/<br />
	&nbsp;&#8212;&gt; ea59fb767573<br />
	Removing intermediate container 4ce91720897b<br />
	Step 3 : COPY ./systemv_shm_rd /bin/<br />
	&nbsp;&#8212;&gt; 1ceb207b1009<br />
	Removing intermediate container 7ace7ad53a3f<br />
	Successfully built 1ceb207b1009</font></p>
<p>启动一个基于该image的容器：<br />
	<font face="Courier New">$ sudo docker run -it &quot;shmemtest:v1&quot; /bin/bash</font></p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	0a2f37bee6eb&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; shmemtest:v1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/bash&quot;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 28 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 28 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; elegant_hawking</font></p>
<p>进入容器，先后执行systemv_shm_wr和systemv_shm_rd，我们得到如下结果：</p>
<p><font face="Courier New">bash-4.1# systemv_shm_wr<br />
	SystemV Share Memory Create and Write Ok<br />
	bash-4.1# systemv_shm_rd<br />
	a b c d e f g h i j k l m n o p q r s t u v w x y z<br />
	SystemV Share Memory Create and Read Ok</font></p>
<p>在容器运行过程中，SystemV共享内存对象是可以持久化的。systemv_shm_wr退出后，数据依旧得以保留。我们接下来尝试一下重启container后是否还能读出数据：</p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	0a2f37bee6eb&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; shmemtest:v1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/bash&quot;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 8 minutes ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 8 minutes&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; elegant_hawking&nbsp;&nbsp;&nbsp;&nbsp;<br />
	$ sudo docker stop 0a2f37bee6eb<br />
	0a2f37bee6eb<br />
	$ sudo docker start 0a2f37bee6eb<br />
	0a2f37bee6eb<br />
	$ sudo docker attach 0a2f37bee6eb<br />
	bash-4.1# systemv_shm_rd<br />
	SystemV Share Memory Create and Read Error: -1</font></p>
<p>程序返回-1，显然在shmget时就出错了，系统已经没有了key为&quot;5678&quot;的这个共享内存IPC对象了。也就是说当容器stop时，就好比我们的物理主机关机，docker将该容器对应的共享内存IPC对象删除了。</p>
<p>从原理上分析，似乎我们也能得出此结论：毕竟Docker container是通过kernel namespace隔离的，容器中的进程在IPC资源申请时需要加入namespace信息。打个比方，如果我们启动容器的进程pid(物理主机视角)是 1234，那么这容器内进程申请的共享内存IPC资源（比如key=5678）的标识应该类似于&ldquo;1234:5678&rdquo;这样的形式。重启容器 后，Docker Daemon无法给该容器分配与上次启动相同的pid，因此pid发生了变化，之前容器中的&quot;1234:5678&quot;保留下来也是毫无意义的，还无端占用系 统资源。因此，System V IPC在Docker容器中的运用与物理机有不同，这方面要小心，目前似乎没有很好的方法，也许以后Docker会加入全局IPC，这个我们只能等待。</p>
<p><b>二、Mmap映射共享内存</b></p>
<p>接下来我们探讨mmap共享内存在容器中的支持情况。mmap常见的有两类共享内存映射方式，一种映射到/dev/zero，另外一种则是映射到 Regular Fiile。前者在程序退出后数据自动释放，后者则保留在映射的文件中。后者对我们更有意义，这次测试的也是后者。</p>
<p>同样，我们也先来编写两个测试程序。</p>
<p><font face="Courier New">//mmap_shm_wr.go</font><br />
	<font face="Courier New">package main</font></p>
<p><font face="Courier New">//#include &lt;stdio.h&gt;<br />
	//#include &lt;sys/types.h&gt;<br />
	//#include &lt;sys/mman.h&gt;<br />
	//#include &lt;fcntl.h&gt;<br />
	//<br />
	//#define SHMSZ&nbsp;&nbsp;&nbsp;&nbsp; 27<br />
	//<br />
	//int shm_wr()<br />
	//{<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char c;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *shm = NULL;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *s = NULL;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int fd;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ((fd = open(&quot;./shm.txt&quot;, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR)) == -1)&nbsp; {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lseek(fd, 500, SEEK_CUR);<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; write(fd, &quot;\0&quot;, 1);<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lseek(fd, 0, SEEK_SET);<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; shm = (char*)mmap(shm, SHMSZ, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!shm) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -2;<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(fd);<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s = shm;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (c = &#39;a&#39;; c &lt;= &#39;z&#39;; c++) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *(s+(int)(c &#8211; &#39;a&#39;)) = c;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br />
	//}<br />
	import &quot;C&quot;</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i := C.shm_wr()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if i != 0 {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Mmap Share Memory Create and Write Error:&quot;, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Mmap Share Memory Create and Write Ok&quot;)<br />
	}</font></p>
<p><font face="Courier New">//mmap_shm_rd.go</font><br />
	<font face="Courier New">package main</font></p>
<p><font face="Courier New">//#include &lt;stdio.h&gt;<br />
	//#include &lt;sys/types.h&gt;<br />
	//#include &lt;sys/mman.h&gt;<br />
	//#include &lt;fcntl.h&gt;<br />
	//<br />
	//#define SHMSZ&nbsp;&nbsp;&nbsp;&nbsp; 27<br />
	//<br />
	//int shm_rd()<br />
	//{<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char c;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *shm = NULL;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *s = NULL;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int fd;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ((fd = open(&quot;./shm.txt&quot;, O_RDONLY)) == -1)&nbsp; {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; shm = (char*)mmap(shm, SHMSZ, PROT_READ, MAP_SHARED, fd, 0);<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!shm) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -2;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(fd);<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s = shm;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int i = 0;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (i = 0; i &lt; SHMSZ &#8211; 1; i++) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;%c &quot;, *(s + i));<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;\n&quot;);<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br />
	//}<br />
	import &quot;C&quot;</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i := C.shm_rd()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if i != 0 {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Mmap Share Memory Read Error:&quot;, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Mmap Share Memory Read Ok&quot;)<br />
	}</font></p>
<p>我们通过go build构建上面两个程序，得到两个测试用可执行程序：mmap_shm_wr和mmap_shm_rd。下面我们来构建我们的测试用docker image，Dockerfile内容如下：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	COPY ./mmap_shm_wr /bin/<br />
	COPY ./mmap_shm_rd /bin/</font></p>
<p>构建Docker image：&ldquo;shmemtest:v2&rdquo;：</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;shmemtest:v2&quot; ./<br />
	Sending build context to Docker daemon 16.81 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./mmap_shm_wr /bin/<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; 01e2f6bc7606<br />
	Step 3 : COPY ./mmap_shm_rd /bin/<br />
	&nbsp;&#8212;&gt; 0de95503c851<br />
	Removing intermediate container 0c472e92809f<br />
	Successfully built 0de95503c851</font></p>
<p>启动一个基于该image的容器：<br />
	<font face="Courier New">$ sudo docker run -it &quot;shmemtest:v2&quot; /bin/bash</font></p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	1182f9eca367&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; shmemtest:v2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/bash&quot;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 11 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 11 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; distracted_elion</font></p>
<p>进入容器，先后执行mmap_shm_wr和mmap_shm_rd，我们得到如下结果：</p>
<p><font face="Courier New">bash-4.1# mmap_shm_wr<br />
	Mmap Share Memory Create and Write Ok<br />
	bash-4.1# mmap_shm_rd<br />
	a b c d e f g h i j k l m n o p q r s t u v w x y z<br />
	Mmap Share Memory Read Ok</font></p>
<p>我们接下来尝试一下重启container后是否还能读出数据：</p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	1182f9eca367&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; shmemtest:v2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/bash&quot;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; About a minute ago&nbsp;&nbsp; Up About a minute&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; distracted_elion&nbsp;&nbsp;&nbsp;<br />
	$ sudo docker stop 1182f9eca367<br />
	1182f9eca367<br />
	$ sudo docker start 1182f9eca367<br />
	1182f9eca367<br />
	$ sudo docker attach 1182f9eca36</font><font face="Courier New">7</font></p>
<p><font face="Courier New">bash-4.1# mmap_shm_rd<br />
	a b c d e f g h i j k l m n o p q r s t u v w x y z<br />
	Mmap Share Memory Read Ok</font></p>
<p>通过执行结果可以看出，通过mmap映射文件方式，共享内存的数据即便在容器重启后依旧可以得到保留。从原理上看，shm.txt是容器内 的一个文件，该文件存储在容器的可写文件系统layer中，从物理主机上看，其位置在/var/lib/docker/aufs/mnt /container_full_id/下，即便容器重启，该文件也不会被删除，而是作为容器文件系统的一部分：</p>
<p><font face="Courier New">$ sudo docker inspect -f &#39;{{.Id}}&#39; 1182f9eca367<br />
	1182f9eca36756219537f9a1c7cd1b62c6439930cc54bc69e87915c5dc8f7b97<br />
	$ sudo ls /var/lib/docker/aufs/mnt/1182f9eca36756219537f9a1c7cd1b62c6439930cc54bc69e87915c5dc8f7b97<br />
	bin&nbsp; dev&nbsp; etc&nbsp; home&nbsp; lib&nbsp; lib64&nbsp; lost+found&nbsp; media&nbsp; mnt&nbsp; opt&nbsp; proc&nbsp; root&nbsp; sbin&nbsp;&nbsp;&nbsp; selinux&nbsp;<big><b> shm.txt&nbsp;</b></big> srv&nbsp; sys&nbsp; tmp&nbsp; usr&nbsp; var</font></p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/10/12/discussion-on-shared-mem-support-in-docker/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>docker容器内服务程序的优雅退出</title>
		<link>https://tonybai.com/2014/10/09/gracefully-shutdown-app-running-in-docker/</link>
		<comments>https://tonybai.com/2014/10/09/gracefully-shutdown-app-running-in-docker/#comments</comments>
		<pubDate>Thu, 09 Oct 2014 13:58:49 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[BestPractice]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[Dockerfile]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[image]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Namespace]]></category>
		<category><![CDATA[nsenter]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Redhat]]></category>
		<category><![CDATA[Signal]]></category>
		<category><![CDATA[supervisor]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[vm]]></category>
		<category><![CDATA[yum]]></category>
		<category><![CDATA[信号]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[命令行]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[映像]]></category>
		<category><![CDATA[最佳实践]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[虚拟化]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1555</guid>
		<description><![CDATA[近期在试验如何将我们的产品部署到docker容器中去，这其中涉及到一个技术环节，那就是如何让docker容器退出时其内部运行的服务程序也 可以优雅的退出。所谓优雅退出，指的就是程序在退出前有清理资源（比如关闭文件描述符、关闭socket），保存必要中间状态，持久化内存数据 （比如将内存中的数据flush到文件中）的机会。docker作为目前最火的轻量级虚拟化技术，其在后台服务领域的应用是极其广泛的，其设计者 在程序优雅退出方面是有考虑的。下面我们由简单到复杂逐一考量一下。 一、优雅退出的原理 对于服务程序而言，一般都是以daemon形式运行在后台的。通知这些服务程序退出需要使用到系统的signal机制。一般服务程序都会监听某个 特定的退出signal，比如SIGINT、SIGTERM等（通过kill -l命令你可以查看到几十种signal）。当我们使用kill + 进程号时，系统会默认发送一个SIGTERM给相应的进程。该进程通过signal handler响应这一信号，并在这个handler中完成相应的&#8220;优雅退出&#8221;操作。 与&#8220;优雅退出&#8221;对立的是&#8220;暴力退出&#8221;，也就是我们常说的使用kill -9，也就是kill -s SIGKILL + 进程号，这个行为不会给目标进程任何时间空隙，而是直接将进程杀死，无论进程当前在做何种操作。这种操作常常导致&#8220;不一致&#8221;状态的出现。SIGKILL这 个信号比较特殊，进程无法有效监听该信号，无法有效针对该信号设置handler，无法改变其信号的默认处理行为。 二、测试用&#8220;服务程序&#8221; 为了测试docker容器对优雅退出的支持，我们编写如下&#8220;服务程序&#8221;用于放在docker容器中运行： //dockerapp1.go package main import &#34;fmt&#34; import &#34;time&#34; import &#34;os&#34; import &#34;os/signal&#34; import &#34;syscall&#34; type signalHandler func(s os.Signal, arg interface{}) type signalSet struct { &#160;&#160;&#160;&#160;&#160;&#160;&#160; m map[os.Signal]signalHandler } func signalSetNew() *signalSet { &#160;&#160;&#160;&#160;&#160;&#160;&#160; ss := new(signalSet) [...]]]></description>
			<content:encoded><![CDATA[<p><span style="line-height: 1.6em;">近期在试验如何将我们的产品部署到</span><a href="http://docker.com" style="line-height: 1.6em;">docker容器</a><span style="line-height: 1.6em;">中去，这其中涉及到一个技术环节，那就是如何让docker容器退出时其内部运行的服务程序也 可以优雅的退出。所谓优雅退出，指的就是程序在退出前有清理资源（比如关闭文件描述符、关闭socket），保存必要中间状态，持久化内存数据 （比如将内存中的数据flush到文件中）的机会。docker作为目前最火的轻量级虚拟化技术，其在后台服务领域的应用是极其广泛的，其设计者 在程序优雅退出方面是有考虑的。下面我们由简单到复杂逐一考量一下。</span></p>
<p><b>一、优雅退出的原理</b></p>
<p>对于服务程序而言，一般都是以daemon形式运行在后台的。通知这些服务程序退出需要使用到系统的signal机制。一般服务程序都会监听某个 特定的退出signal，比如SIGINT、SIGTERM等（通过kill -l命令你可以查看到几十种signal）。当我们使用kill + 进程号时，系统会默认发送一个SIGTERM给相应的进程。该进程通过signal handler响应这一信号，并在这个handler中完成相应的&ldquo;优雅退出&rdquo;操作。</p>
<p>与&ldquo;优雅退出&rdquo;对立的是&ldquo;暴力退出&rdquo;，也就是我们常说的使用kill -9，也就是kill -s SIGKILL + 进程号，这个行为不会给目标进程任何时间空隙，而是直接将进程杀死，无论进程当前在做何种操作。这种操作常常导致&ldquo;不一致&rdquo;状态的出现。SIGKILL这 个信号比较特殊，进程无法有效监听该信号，无法有效针对该信号设置handler，无法改变其信号的默认处理行为。</p>
<p><b>二、</b><b>测试用&ldquo;服务程序&rdquo;</b></p>
<p>为了测试docker容器对优雅退出的支持，我们编写如下&ldquo;服务程序&rdquo;用于放在docker容器中运行：</p>
<p><font face="Courier New">//dockerapp1.go</font></p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;<br />
	import &quot;time&quot;<br />
	import &quot;os&quot;<br />
	import &quot;os/signal&quot;<br />
	import &quot;syscall&quot;</font></p>
<p><font face="Courier New">type signalHandler func(s os.Signal, arg interface{})</font></p>
<p><font face="Courier New">type signalSet struct {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m map[os.Signal]signalHandler<br />
	}</font></p>
<p><font face="Courier New">func signalSetNew() *signalSet {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss := new(signalSet)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.m = make(map[os.Signal]signalHandler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return ss<br />
	}</font></p>
<p><font face="Courier New">func (set *signalSet) register(s os.Signal, handler signalHandler) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if _, found := set.m[s]; !found {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; set.m[s] = handler<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">func (set *signalSet) handle(sig os.Signal, arg interface{}) (err error) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if _, found := set.m[sig]; found {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; set.m[sig](sig, arg)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return nil<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return fmt.Errorf(&quot;No handler available for signal %v&quot;, sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; panic(&quot;won&#39;t reach here&quot;)<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go sysSignalHandleDemo()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Hour) // make the main goroutine wait!<br />
	}</font></p>
<p><font face="Courier New">func sysSignalHandleDemo() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss := signalSetNew()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; handler := func(s os.Signal, arg interface{}) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;handle signal: %v\n&quot;, s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s == syscall.SIGTERM {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;signal termiate received, app exit normally\n&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(0)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGINT, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGUSR1, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGUSR2, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGTERM, handler)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c := make(chan os.Signal)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var sigs []os.Signal<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for sig := range ss.m {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sigs = append(sigs, sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; signal.Notify(c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sig := &lt;-c</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; err := ss.handle(sig, nil)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;unknown signal received: %v, app exit unexpectedly\n&quot;, sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>关于<a href="http://tonybai.com/tag/golang">Go语言</a>对系统Signal的处理，可以参考《<a href="http://tonybai.com/2012/09/21/signal-handling-in-go/">Go中的系统Signal处理</a>》一文。</p>
<p><b>三、制作测试用docker image</b></p>
<p>在《 <a href="http://tonybai.com/2014/09/26/install-docker-on-ubuntu-server-1404/">Ubuntu Server 14.04安装docker</a>》一文中，我们完成了在ubuntu 14.04上安装docker的步骤。要制作测试用docker image，我们首先需要pull一个base image。我们以CentOS6.5为例：</p>
<p>在Ubuntu 14.04上执行：<br />
	&nbsp;&nbsp;&nbsp; <font face="Courier New">sudo&nbsp; docker pull centos:centos6</font></p>
<p>docker会自动从<a href="https://registry.hub.docker.com">官方仓库</a>下载一个制作好的docker image。下载成功后，我们可以run一下试试，像这样：</p>
<p><font face="Courier New">$&gt; sudo docker run -t -i centos:centos6 /bin/bash</font></p>
<p>我们查看一下CentOS6的小版本：<br />
	<font face="Courier New">$&gt; cat /etc/centos-release<br />
	CentOS release 6.5 (Final)</font></p>
<p>这是一个极其精简的CentOS，各种工具均未安装：<br />
	<font face="Courier New">bash-4.1# telnet<br />
	bash: telnet: command not found<br />
	bash-4.1# ssh<br />
	bash: ssh: command not found<br />
	bash-4.1# ftp<br />
	bash: ftp: command not found<br />
	bash-4.1# echo $PATH<br />
	/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin</font></p>
<p>如果你要安装一些必要的工具，可以直接使用yum install，默认的base image已经将yum配置好了，可以直接使用。如果通过公司代理访问外部网络，别忘了先export http_proxy。另外docker直接使用宿主机的/etc/resolv.conf作为容器的DNS，我们也无需额外设置DNS。</p>
<p>接下来，我们就制作我们的第一个测试用image。安装官方推荐的Best Practice，我们使用Dockerfile来bulid一个测试用image。步骤如下：</p>
<p>- 建立~/ImagesFactory目录<br />
	- 将构建好的dockerapp1拷贝到~/ImagesFactory目录下<br />
	- 进入~/ImagesFactory目录，创建Dockerfile文件，Dockerfile内容如下：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	COPY ./dockerapp1 /bin<br />
	CMD /bin/dockerapp1</font></p>
<p>- 执行docker build，结果如下：</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:v1&quot; ./<br />
	Sending build context to Docker daemon 7.496 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./dockerapp1 /bin<br />
	2014/10/09 16:05:25 lchown /var/lib/docker/aufs/mnt/fb0e864d3f07ca17ef8b6b69f034728e1f1158fd3f9c83fa48243054b2f26958/bin/dockerapp1: not a directory</font></p>
<p>居然build失败，提示什么not a directory。于是各种Search，终于发现问题所在，原来是&ldquo;<font face="Courier New">COPY ./dockerapp1 /bin</font>&rdquo;这条命令错了，少了个&ldquo;/&rdquo;，将&quot; /bin&quot;改为&ldquo;/bin/&rdquo;就OK了，Docker真是奇怪啊，这块明显应该做得更兼容些。新的Dockerfile如下：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	COPY ./dockerapp1 /bin/<br />
	CMD /bin/dockerapp1</font></p>
<p>构建结果如下：</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:v1&quot; ./<br />
	Sending build context to Docker daemon 7.496 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./dockerapp1 /bin/<br />
	&nbsp;&#8212;&gt; 20c3783c42ab<br />
	Removing intermediate container cab639ab4321<br />
	Step 3 : CMD /bin/dockerapp1<br />
	&nbsp;&#8212;&gt; Running in 31875d3c37f9<br />
	&nbsp;&#8212;&gt; 21a720a808a7<br />
	Removing intermediate container 31875d3c37f9<br />
	Successfully built 21a720a808a7</font></p>
<p><font face="Courier New">$ sudo docker images<br />
	REPOSITORY&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TAG&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; VIRTUAL SIZE<br />
	test&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; v1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 21a720a808a7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 59 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 214.6 MB</font></p>
<p><b>四、第一个测试容器</b></p>
<p>我们基于image &quot;test:v1&quot;启动一个测试容器：</p>
<p><font face="Courier New">$ sudo docker run -d &quot;test:v1&quot;<br />
	daf3ae88fec23a31cde9f6b9a3f40057953c87b56cca982143616f738a84dcba</font></p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	daf3ae88fec2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:v1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/sh -c /bin/doc&nbsp;&nbsp; 17 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 16 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; condescending_sammet&nbsp;&nbsp; </font></p>
<p>通过docker run命令，我们基于image&quot;test:v1&quot;启动了一个容器。通过docker ps命令可以看到容器成功启动，容器id：<font face="Courier New">daf3ae88fec2，别名为：</font><font face="Courier New">condescending_sammet。</font></p>
<p><font face="Courier New">根据Dockerfile我们知道，容器启动后将执行&quot;/bin/dockerapp1&quot;这个程序，dockerapp1退出，容器即退出。 run命令的&quot;-d&quot;选项表示容器将以daemon的形式运行，我们在前台无法看到容器的输出。那么我们怎么查看容器的输出呢？我们可以通过 docker logs + 容器id的方式查看容器内应用的标准输出或标准错误。我们也可以进入容器来查看。</font></p>
<p><font face="Courier New">进入容器有多种方法，比如用sudo docker attach </font><font face="Courier New"><font face="Courier New">daf3ae88fec2</font>。attach后，就好比将daemon方式运行的容器 拿到了前台，你可以Ctrl + C一下，可以看到如下dockerapp1的输出:</font></p>
<p><font face="Courier New">^Chandle signal: interrupt</font></p>
<p><font face="Courier New">另外一种方式是利用nsenter工具进入我们容器的namespace空间。ubuntu 14.04下可以通过如下方式安装该工具：</font></p>
<p><font face="Courier New">$ wget <a class="moz-txt-link-freetext" href="https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz">https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz</a>; tar xzvf util-linux-2.24.tar.gz<br />
	$ cd util-linux-2.24<br />
	$ ./configure &#8211;without-ncurses &amp;&amp; make nsenter<br />
	$ sudo cp nsenter /usr/local/bin</font></p>
<p>安装后，我们通过如下方式即可进入上面的容器：</p>
<p><font face="Courier New">$ echo $(sudo docker inspect &#8211;format &quot;{{ .State.Pid }}&quot; daf3ae88fec2)<br />
	5494<br />
	$ sudo nsenter &#8211;target 5494 &#8211;mount &#8211;uts &#8211;ipc &#8211;net &#8211;pid<br />
	-bash-4.1# ps -ef<br />
	UID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PID&nbsp; PPID&nbsp; C STIME TTY&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TIME CMD<br />
	root&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp; 0 09:20 ?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 00:00:00 /bin/dockerapp1<br />
	root&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 16&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp; 0 09:32 ?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 00:00:00 -bash<br />
	root&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 27&nbsp;&nbsp;&nbsp; 16&nbsp; 0 09:32 ?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 00:00:00 ps -ef<br />
	-bash-4.1# </font></p>
<p>进入容器后通过ps命令可以看到正在运行的dockerapp1程序。在容器内，我们可以通过kill来测试dockerapp1的运行情况：</p>
<p><font face="Courier New">-bash-4.1# kill -s SIGINT 1</font></p>
<p>通过前面的attach窗口，我们可以看到dockerapp1输出:</p>
<p><font face="Courier New">handle signal: interrupt</font></p>
<p>如果你发送SIGTERM信号，那么dockerapp1将终止运行，容器也就停止了。</p>
<p><font face="Courier New">-bash-4.1# kill 1</font></p>
<p>attach窗口显示：</p>
<p><font face="Courier New">signal termiate received, app exit normally</font></p>
<p>我们可以看到容器启动后默认执行的时Dockerfile中的CMD命令，如果Dockerfile中有多行CMD命令，Docker在启动容器 时只会执行最后一条CMD命令。如果在docker run中指定了命令，docker则会执行命令行中的命令而不会执行dockerapp1，比如：</p>
<p><font face="Courier New">$ sudo docker run -t -i &quot;test:v1&quot; /bin/bash<br />
	bash-4.1# </font></p>
<p>这里我们看到直接执行的时bash，dockerapp1并未执行。</p>
<p><b>五、docker stop的行为</b></p>
<p>我们先来看看docker stop的manual：</p>
<p><font face="Courier New">$ sudo docker stop &#8211;help<br />
	Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]<br />
	Stop a running container by sending SIGTERM and then SIGKILL after a grace period<br />
	&nbsp; -t, &#8211;time=10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Number of seconds to wait for the container to stop before killing it. Default is 10 seconds.</font></p>
<p>可以看出当我们执行docker stop时，docker会首先向容器内的当前主程序发送一个SIGTERM信号，用于容器内程序的退出。如果容器在收到SIGTERM后没有马上退出， 那么stop命令会在等待一段时间（默认是10s）后，再向容器发送SIGKILL信号，将容器杀死，变为退出状态。</p>
<p>我们来验证一下docker stop的行为。启动刚才那个容器：</p>
<p><font face="Courier New">$ sudo docker start daf3ae88fec2<br />
	daf3ae88fec2</font></p>
<p><font face="Courier New">attach到容器daf3ae88fec2<br />
	$ sudo docker attach daf3ae88fec2</font></p>
<p>新打开一个窗口，执行docker stop命令：<br />
	<font face="Courier New">$ sudo docker stop daf3ae88fec2<br />
	daf3ae88fec2</font></p>
<p>可以看到attach窗口输出：<br />
	<font face="Courier New">handle signal: terminated<br />
	signal termiate received, app exit normally</font></p>
<p>通过docker ps查看，发现容器已经退出。</p>
<p>也许通过上面的例子还不能直观的展示stop命令的<b>两阶段行为</b>，因为dockerapp1收到SIGTERM后直接就退出 了，stop命令无需等待容器慢慢退出，也无需发送SIGKILL。我们改造一下dockerapp1这个程序。</p>
<p>我们复制一下dockerapp1.go为dockerapp2.go，编辑dockerapp2.go，将handler中对SIGTERM的 处理注释掉，其他不变：</p>
<p><font face="Courier New">handler := func(s os.Signal, arg interface{}) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;handle signal: %v\n&quot;, s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /*<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s == syscall.SIGTERM {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;signal termiate received, app exit normally\n&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(0)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p>我们使用dockerapp2来构建一个新image：test:v2，将Dockerfile中得dockerapp1换成 dockerapp2即可。</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:v2&quot; ./<br />
	Sending build context to Docker daemon 9.369 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./dockerapp2 /bin/<br />
	&nbsp;&#8212;&gt; 27cd613a9bd7<br />
	Removing intermediate container 07c760b6223b<br />
	Step 3 : CMD /bin/dockerapp2<br />
	&nbsp;&#8212;&gt; Running in 1aac086452a7<br />
	&nbsp;&#8212;&gt; 82eb876fefd2<br />
	Removing intermediate container 1aac086452a7<br />
	Successfully built 82eb876fefd2</font></p>
<p>利用image &quot;test:v2&quot;创建一个容器来测试stop。</p>
<p><font face="Courier New">$ sudo docker run -d &quot;test:v2&quot;<br />
	29f3ec1af3c355458cbbd802a5e8a53da28e9f51a56ce822c7bba2a772edceac</font></p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	29f3ec1af3c3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:v2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/sh -c /bin/doc&nbsp;&nbsp; 7 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 6 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; romantic_feynman&nbsp;</font>&nbsp;&nbsp;</p>
<p>Attach到这个容器并观察，在另外一个窗口stop该container。我们在attach窗口只看到如下输出：</p>
<p><font face="Courier New">handle signal: terminated</font></p>
<p>stop命令的执行没有立即返回，而是等待容器退出。等待10s后，容器退出，stop命令执行结束。从这个例子我们可以明显看出stop的两阶 段行为。</p>
<p>如果我们以<font face="Courier New">sudo docker run -i -t &quot;test:v1&quot; /bin/bash</font>形式启动容器，那stop命令会将SIGTERM发送给bash这个程序，即使你通过nsenter进入容 器，启动了dockerapp1，dockerapp1也不会收到SIGTERM，dockerapp1会随着容器的退出而被强行终止，就像被 kill -9了一样。</p>
<p><b>六、多进程容器服务</b>程序</p>
<p>上面无论是dockerapp1还是dockerapp2，都是一个单进程服务程序。如果我们在容器内执行一个多进程程序，我们该如何优雅退出 呢？我们先来编写一个多进程的服务程序dockerapp3：</p>
<p>在dockerapp1.go的基础上对main和sysSignalHandleDemo进行修改形成dockerapp3.go，修改后这两 个函数的代码如下：</p>
<p><font face="Courier New">//dockerapp3.go<br />
	&#8230; &#8230;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go sysSignalHandleDemo()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pid, _, err := syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if err != 0 {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;err fork process, err: %v\n&quot;, err)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if pid == 0 {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;i am in child process, pid = %v\n&quot;, syscall.Getpid())<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Hour) // make the child process wait<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;i am parent process, pid = %v\n&quot;, syscall.Getpid())<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;fork ok, childpid = %v\n&quot;, pid)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Hour) // make the main goroutine wait!<br />
	}</font></p>
<p><font face="Courier New">func sysSignalHandleDemo() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss := signalSetNew()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; handler := func(s os.Signal, arg interface{}) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%v: handle signal: %v\n&quot;, syscall.Getpid(), s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s == syscall.SIGTERM {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%v: signal termiate received, app exit normally\n&quot;, syscall.Getpid())<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(0)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGINT, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGUSR1, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGUSR2, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGTERM, handler)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c := make(chan os.Signal)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var sigs []os.Signal<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for sig := range ss.m {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sigs = append(sigs, sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; signal.Notify(c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sig := &lt;-c</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; err := ss.handle(sig, nil)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%v: unknown signal received: %v, app exit unexpectedly\n&quot;, syscall.Getpid(), sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>dockerapp3利用fork创建了一个子进程，这样dockerapp3实际上是两个进程在运行，各自有自己的signal监听 goroutine，goroutine的处理逻辑是相同的。注意：由于Windows和Mac OS X不具备fork语义，因此在这两个平台上运行dockerapp3不会得到预期结果。</p>
<p>利用dockerapp3，我们创建image &quot;test:v3&quot;:</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:v3&quot; ./<br />
	[sudo] password for tonybai:<br />
	Sending build context to Docker daemon 11.24 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./dockerapp3 /bin/<br />
	&nbsp;&#8212;&gt; 6ccf97065853<br />
	Removing intermediate container 6d85fe241939<br />
	Step 3 : CMD /bin/dockerapp3<br />
	&nbsp;&#8212;&gt; Running in 75d76380992a<br />
	&nbsp;&#8212;&gt; c9e7bf361ed7<br />
	Removing intermediate container 75d76380992a<br />
	Successfully built c9e7bf361ed7</font></p>
<p>启动基于test:v3 image的容器：</p>
<p><font face="Courier New">$ sudo docker run -d &quot;test:v3&quot;<br />
	781cecb4b3628cb33e1b104ea57e506ad5cb4a44243256ebd1192af86834bae6<br />
	$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	781cecb4b362&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:v3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/sh -c /bin/doc&nbsp;&nbsp; 5 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 4 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; insane_bohr&nbsp;&nbsp;&nbsp;</font>&nbsp;&nbsp;&nbsp;</p>
<p>通过docker logs查看dockerapp3的输出：</p>
<p><font face="Courier New">$ sudo docker logs 781cecb4b362<br />
	i am parent process, pid = 1<br />
	fork ok, childpid = 13<br />
	i am in child process, pid = 13</font></p>
<p>可以看出主进程pid为1，子进程pid为13。我们通过stop停止该容器：</p>
<p><font face="Courier New">$ sudo docker stop 781cecb4b362<br />
	781cecb4b362</font></p>
<p>再次通过docker logs查看：</p>
<p><font face="Courier New">$ sudo docker logs 781cecb4b362<br />
	i am parent process, pid = 1<br />
	fork ok, childpid = 13<br />
	i am in child process, pid = 13<br />
	1: handle signal: terminated<br />
	1: signal termiate received, app exit normally</font></p>
<p>我们可以看到主进程收到了stop发来的SIGTERM并退出，主进程的退出导致容器退出，导致子进程13也无法生存，并且没有优雅退出。而在非 容器状态下，子进程是可以被init进程接管的。</p>
<p>因此对于docker容器内运行的多进程程序，stop命令只会将SIGTERM发送给容器主进程，要想让其他进程也能优雅退出，需要在主进程与 其他进程间建立一种通信机制。在主进程退出前，等待其他子进程退出。待所有其他进程退出后，主进程再退出，容器停止。这样才能保证服务程序的优雅 退出。</p>
<p><b>七、容器内启动多个服务程序</b></p>
<p>虽说docker <a href="https://docs.docker.com/articles/dockerfile_best-practices/">best practice</a>建议一个container内只放置一个服务程序，但对已有的一些遗留系统，在架构没有做出重构之前，很可能会有在一个 container中部署两个以上服务程序的情况和需求。而docker Dockerfile只允许执行一个CMD，这种情况下，我们就需要借助类似supervisor这样的进程监控管理程序来启动和管理container 内的多个程序了。</p>
<p>下面我们来自制作一个基于centos:centos6的安装了supervisord以及两个服务程序的image。我们将dockerapp1拷贝一份，并将拷贝命名为dockerapp1-brother。下面是我们的Dockerfile：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	RUN yum install python-setuptools -y<br />
	RUN easy_install supervisor<br />
	RUN mkdir -p /var/log/supervisor<br />
	COPY ./supervisord.conf /etc/supervisord.conf<br />
	COPY ./dockerapp1 /bin/<br />
	COPY ./dockerapp1-brother /bin/<br />
	CMD ["/usr/bin/supervisord"]</font></p>
<p>supervisord的配置文件supervisord.conf内容如下：</p>
<p><font face="Courier New">; supervisor config file</font></p>
<p><font face="Courier New">[unix_http_server]<br />
	file=/var/run/supervisor.sock&nbsp;&nbsp; ; (the path to the socket file)<br />
	chmod=0700&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; sockef file mode (default 0700)</font></p>
<p><font face="Courier New">[supervisord]<br />
	logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)<br />
	pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)<br />
	childlogdir=/var/log/supervisor&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; (&#39;AUTO&#39; child log dir, default $TEMP)</font></p>
<p><font face="Courier New">[rpcinterface:supervisor]<br />
	supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface</font></p>
<p><font face="Courier New">[supervisorctl]<br />
	serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL&nbsp; for a unix socket</font></p>
<p><font face="Courier New">[supervisord]<br />
	nodaemon=false</font></p>
<p><font face="Courier New">[program:dockerapp1]<br />
	command=/bin/dockerapp1<br />
	stdout_logfile=/tmp/dockerapp1.log<br />
	stopsignal=TERM<br />
	stopwaitsecs=10</font></p>
<p><font face="Courier New">[program:dockerapp1-brother]<br />
	command=/bin/dockerapp1-brother<br />
	stdout_logfile=/tmp/dockerapp1-brother.log<br />
	stopsignal=QUIT<br />
	stopwaitsecs=10</font></p>
<p>开始build镜像：<br />
	&nbsp;&nbsp;&nbsp; <font face="Courier New">$&gt; sudo docker build -t=&quot;test:supervisor-v1&quot; ./<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; Successfully built d006b9ad10eb</font></p>
<p>基于该镜像，启动一个容器：<br />
	<font face="Courier New">$&gt; sudo docker run -d &quot;test:supervisor-v1&quot;<br />
	05ded2b898c90059d4c9b5c6ccc8603b6848ae767360c42bd9b36ff87fb4b9df</font></p>
<p>执行ps命令查看镜像id：<br />
	<font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES</font></p>
<p>怎么回事？Container没有启动起来？</p>
<p><font face="Courier New">$ sudo docker ps -a<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	05ded2b898c9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:supervisor-v1&nbsp;&nbsp;&nbsp; &quot;/usr/bin/supervisor&nbsp;&nbsp; 22 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Exited (0) 21 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hungry_engelbart</font></p>
<p>通过ps -a查看，container启动是成功了，但是成功退出了。于是尝试查看一下log：</p>
<p><font face="Courier New">sudo docker logs 05ded2b898c9<br />
	/usr/lib/python2.6/site-packages/supervisor-3.1.2-py2.6.egg/supervisor/options.py:296: UserWarning: Supervisord is running as root and it is searching for its configuration file in default locations (including its current working directory); you probably want to specify a &quot;-c&quot; argument specifying an absolute path to a configuration file for improved security.<br />
	&nbsp; &#39;Supervisord is running as root and it is searching &#39;</font></p>
<p>似乎是supervisord转为daemon程序，容器主进程退出了，容器随之终止了。</p>
<p>看来容器内的supervisord不能以daemon形式运行，应该以前台形式run。修改一下supervisord.conf中得配置：</p>
<p>将<br />
	<font face="Courier New">[supervisord]<br />
	nodaemon=false</font></p>
<p>改为</p>
<p><font face="Courier New">[supervisord]<br />
	nodaemon=true</font></p>
<p>重新制作镜像:</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:supervisor-v2&quot; ./<br />
	Sending build context to Docker daemon 13.12 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : RUN yum install python-setuptools -y<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; e09c66a1ea8c<br />
	Step 3 : RUN easy_install supervisor<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; 9c8797e8c27e<br />
	Step 4 : RUN mkdir -p /var/log/supervisor<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; 9bfc67f8517d<br />
	Step 5 : COPY ./supervisord.conf /etc/supervisord.conf<br />
	&nbsp;&#8212;&gt; 8c514f998363<br />
	Removing intermediate container 4a185856e6ed<br />
	Step 6 : COPY ./dockerapp1 /bin/<br />
	&nbsp;&#8212;&gt; 0317bd4914d3<br />
	Removing intermediate container ac5738380854<br />
	Step 7 : COPY ./dockerapp1-brother /bin/<br />
	&nbsp;&#8212;&gt; d89711888bdf<br />
	Removing intermediate container eadc9444e716<br />
	Step 8 : CMD ["/usr/bin/supervisord"]<br />
	&nbsp;&#8212;&gt; Running in aaa042ac3914<br />
	&nbsp;&#8212;&gt; 9655256bbfed<br />
	Removing intermediate container aaa042ac3914<br />
	Successfully built 9655256bbfed</font></p>
<p>有了前面的铺垫，这次build image瞬间完成。启动容器，查看容器启动状态，查看容器内supervisord的运行日志如下：</p>
<p><font face="Courier New">$ sudo docker run -d &quot;test:supervisor-v2&quot;<br />
	61916f1c82338b28ced101b6bde119e4afb7c7fa349b4332ed51a43a4586b1b9</font></p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	61916f1c8233&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:supervisor-v2&nbsp;&nbsp; &quot;/usr/bin/supervisor&nbsp;&nbsp; 16 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 16 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; prickly_einstein</font></p>
<p><font face="Courier New">$ sudo docker logs 8eb3e9892e66</font></p>
<p><font face="Courier New">/usr/lib/python2.6/site-packages/supervisor-3.1.2-py2.6.egg/supervisor/options.py:296: UserWarning: Supervisord is running as root and it is searching for its configuration file in default locations (including its current working directory); you probably want to specify a &quot;-c&quot; argument specifying an absolute path to a configuration file for improved security.<br />
	&nbsp; &#39;Supervisord is running as root and it is searching &#39;<br />
	2014-10-09 14:36:02,334 CRIT Supervisor running as root (no user in config file)<br />
	2014-10-09 14:36:02,349 INFO RPC interface &#39;supervisor&#39; initialized<br />
	2014-10-09 14:36:02,349 CRIT Server &#39;unix_http_server&#39; running without any HTTP authentication checking<br />
	2014-10-09 14:36:02,349 INFO supervisord started with pid 1<br />
	2014-10-09 14:36:03,354 INFO spawned: &#39;dockerapp1&#39; with pid 14<br />
	2014-10-09 14:36:03,363 INFO spawned: &#39;dockerapp1-brother&#39; with pid 15<br />
	2014-10-09 14:36:04,368 INFO success: dockerapp1 entered RUNNING state, process has stayed up for &gt; than 1 seconds (startsecs)<br />
	2014-10-09 14:36:04,369 INFO success: dockerapp1-brother entered RUNNING state, process has stayed up for &gt; than 1 seconds (startsecs)</font></p>
<p>可以看到supervisord已经将dockerapp1和dockerapp1-brother启动起来了。</p>
<p>现在我们尝试停止容器，我们预期是supervisord在退出前通知dockerapp1和dockerapp1-brother先退出，我们可以通过 查看容器内的/tmp/dockerapp1.log和/tmp/dockerapp1-brother.log来确认supervisord是否做了通 知。</p>
<p><font face="Courier New">$ sudo docker stop 61916f1c8233<br />
	61916f1c8233</font></p>
<p><font face="Courier New">$ sudo docker logs 61916f1c8233<br />
	&#8230; &#8230;<br />
	2014-10-09 14:37:52,253 WARN received SIGTERM indicating exit request<br />
	2014-10-09 14:37:52,254 INFO waiting for dockerapp1, dockerapp1-brother to die<br />
	2014-10-09 14:37:52,254 INFO stopped: dockerapp1-brother (exit status 0)<br />
	2014-10-09 14:37:52,256 INFO stopped: dockerapp1 (exit status 0)</font></p>
<p>通过容器的log，我们看出supervisord是等待两个程序退出后才退出的，不过我们还是要看看两个程序的输出日志以最终确认。重新启动容器，通过nsenter进入到容器中。</p>
<p><font face="Courier New">-bash-4.1# vi /tmp/dockerapp1.log</font></p>
<p><font face="Courier New">handle signal: terminated<br />
	signal termiate received, app exit normally</font></p>
<p><font face="Courier New">-bash-4.1# vi /tmp/dockerapp1-brother.log</font></p>
<p><font face="Courier New">handle signal: terminated<br />
	signal termiate received, app exit normally</font></p>
<p>两个程序的标准输出日志证实了我们的预期。</p>
<p>BTW，在物理机上测试supervisord以daemon形式运行，当kill掉supervisord时，supervisord是不会通知其监控 和管理的程序退出的。只有在以non-daemon形式运行时，supervisord才会在退出前先通知下面的程序退出。如果在一段时间内下面程序没有 退出，supervisord在退出前会kill -9强制杀死这些程序的进程。</p>
<p>最后要说的时，在验证一些想法时，没有必要build image，我们可以直接将本地文件copy到容器中，下面是一个例子，我们将dockerapp1和dockerapp1-brother拷贝到镜像中：<br />
	<font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	4d8982bfccc7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; centos:centos6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/bash&quot;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 26 minutes ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 26 minutes&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sharp_thompson&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	$ sudo docker inspect -f &#39;{{.Id}}&#39; 4d8982bfccc7<br />
	4d8982bfccc79dea762b41f8a6f669bda1ec73c8881b6ca76e7a7917c62972c4<br />
	$ sudo cp dockerapp1&nbsp; /var/lib/docker/aufs/mnt/4d8982bfccc79dea762b41f8a6f669bda1ec73c8881b6ca76e7a7917c62972c4/bin/dockerapp1<br />
	$ sudo cp dockerapp1-brother&nbsp; /var/lib/docker/aufs/mnt/4d8982bfccc79dea762b41f8a6f669bda1ec73c8881b6ca76e7a7917c62972c4/bin/dockerapp1-brother</font></p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/10/09/gracefully-shutdown-app-running-in-docker/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Golang Channel用法简编</title>
		<link>https://tonybai.com/2014/09/29/a-channel-compendium-for-golang/</link>
		<comments>https://tonybai.com/2014/09/29/a-channel-compendium-for-golang/#comments</comments>
		<pubDate>Mon, 29 Sep 2014 09:02:43 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Actor]]></category>
		<category><![CDATA[BestPractice]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[Concurrent]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[CSP]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[GopherCon]]></category>
		<category><![CDATA[LXC]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[TIOBE]]></category>
		<category><![CDATA[内存模型]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[布道师]]></category>
		<category><![CDATA[并发]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编程]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1551</guid>
		<description><![CDATA[在进入正式内容前，我这里先顺便转发一则消息，那就是Golang 1.3.2已经正式发布了。国内的golangtc已经镜像了golang.org的安装包下载页面，国内go程序员与爱好者们可以到&#34;Golang中 国&#34;，即golangtc.com去下载go 1.3.2版本。 Go这门语言也许你还不甚了解，甚至是完全不知道，这也有情可原，毕竟Go在TIOBE编程语言排行榜上位列30开外。但近期使用Golang 实现的一杀手级应用 Docker你却不该不知道。docker目前火得是一塌糊涂啊。你去国内外各大技术站点用眼轻瞥一下，如 果没有涉及到&#8220;docker&#8221;字样新闻的站点建 议你以后就不要再去访问了^_^。Docker是啥、怎么用以及基础实践可以参加国内一位仁兄的经验之作：《 Docker &#8211; 从入门到实践》。 据我了解，目前国内试水Go语言开发后台系统的大公司与初创公司日益增多，比如七牛、京东、小米，盛大，金山，东软，搜狗等，在这里我们可以看到一些公司的Go语言应用列表，并且目前这个列表似乎依旧在丰富中。国内Go语言的推广与布道也再稳步推进中，不过目前来看多以Go入 门与基础为主题，Go idioms、tips或Best Practice的Share并不多见，想必国内的先行者、布道师们还在韬光养晦，积攒经验，等到时机来临再厚积薄发。另外国内似乎还没有一个针对Go的 布道平台，比如Golang技术大会之类的的平台。 在国外，虽然Go也刚刚起步，但在Golang share的广度和深度方面显然更进一步。Go的国际会议目前还不多，除了Golang老东家Google在自己的各种大会上留给Golang展示自己的 机会外，由 Gopher Academy 发起的GopherCon 会议也于今年第一次举行，并放出诸多高质量资料，在这里可以下载。欧洲的Go语言大会.dotgo也即将开幕，估计后续这两个大会将撑起Golang技术分享 的旗帜。 言归正传，这里要写的东西并非原创，自己的Go仅仅算是入门级别，工程经验、Best Practice等还谈不上有多少，因此这里主要是针对GopherCon2014上的&#8220;舶来品&#8221;的学习心得。来自CloudFlare的工程师John Graham-Cumming谈了关于 Channel的实践经验，这里针对其分享的内容，记录一些学习体会和理解，并结合一些外延知识，也可以算是一种学习笔记吧，仅供参考。 一、Golang并发基础理论 Golang在并发设计方面参考了C.A.R Hoare的CSP，即Communicating Sequential Processes并发模型理论。但就像John Graham-Cumming所说的那样，多数Golang程序员或爱好者仅仅停留在&#8220;知道&#8221;这一层次，理解CSP理论的并不多，毕竟多数程序员是搞工程 的。不过要想系统学习CSP的人可以从这里下载到CSP论文的最新版本。 维基百科中概要罗列了CSP模型与另外一种并发模型Actor模型的区别： Actor模型广义上讲与CSP模型很相似。但两种模型就提供的原语而言，又有一些根本上的不同之处： &#160;&#160;&#160; &#8211; CSP模型处理过程是匿名的，而Actor模型中的Actor则具有身份标识。 &#160;&#160;&#160; &#8211; CSP模型的消息传递在收发消息进程间包含了一个交会点，即发送方只能在接收方准备好接收消息时才能发送消息。相反，actor模型中的消息传递是异步 的，即消息的发送和接收无需在同一时间进行，发送方可以在接收方准备好接收消息前将消息发送出去。这两种方案可以认为是彼此对偶的。在某种意义下，基于交 会点的系统可以通过构造带缓冲的通信的方式来模拟异步消息系统。而异步系统可以通过构造带消息/应答协议的方式来同步发送方和接收方来模拟交会点似的通信 方式。 &#160;&#160;&#160; &#8211; CSP使用显式的Channel用于消息传递，而Actor模型则将消息发送给命名的目的Actor。这两种方法可以被认为是对偶的。某种意义下，进程可 以从一个实际上拥有身份标识的channel接收消息，而通过将actors构造成类Channel的行为模式也可以打破actors之间的名字耦合。 二、Go Channel基本操作语法 Go Channel的基本操作语法如下： [...]]]></description>
			<content:encoded><![CDATA[<p>在进入正式内容前，我这里先顺便转发一则消息，那就是Golang 1.3.2已经正式发布了。国内的<a href="http://golangtc.com">golangtc</a>已经镜像了golang.org的安装包下载页面，国内go程序员与爱好者们可以到&quot;Golang中 国&quot;，即golangtc.com去下载go 1.3.2版本。</p>
<p><a href="http://golang.org">Go</a>这门语言也许你还不甚了解，甚至是完全不知道，这也有情可原，毕竟Go在<a href="http://www.tiobe.com">TIOBE</a>编程语言排行榜上位列30开外。但近期使用Golang 实现的一杀手级应用<a href="http://docker.com"> <b>Docker</b></a>你却不该不知道。docker目前火得是一塌糊涂啊。你去国内外各大技术站点用眼轻瞥一下，如 果没有涉及到&ldquo;docker&rdquo;字样新闻的站点建 议你以后就不要再去访问了^_^。Docker是啥、怎么用以及基础实践可以参加国内一位仁兄的经验之作：《 <a href="https://www.gitbook.io/book/yeasy/docker_practice">Docker &#8211; 从入门到实践</a>》。</p>
<p>据我了解，目前国内试水Go语言开发后台系统的大公司与初创公司日益增多，比如七牛、京东、小米，盛大，金山，东软，搜狗等，在<a href="https://github.com/qiniu/go/issues/15#issuecomment-55568731">这里</a>我们可以看到一些公司的Go语言应用列表，并且目前这个列表似乎依旧在丰富中。国内Go语言的推广与布道也再稳步推进中，不过目前来看多以Go入 门与基础为主题，Go idioms、tips或Best Practice的Share并不多见，想必国内的先行者、布道师们还在韬光养晦，积攒经验，等到时机来临再厚积薄发。另外国内似乎还没有一个针对Go的 布道平台，比如Golang技术大会之类的的平台。</p>
<p>在国外，虽然Go也刚刚起步，但在Golang share的广度和深度方面显然更进一步。Go的国际会议目前还不多，除了Golang老东家Google在自己的各种大会上留给Golang展示自己的 机会外，由 <span style="font-family: Ubuntu, Tahoma, sans-serif; font-size: 14px; line-height: 20px;"><a href="http://gopheracademy.com/">Gopher Academy</a> 发起的</span><a href="http://www.gophercon.com">GopherCon</a> 会议也于今年第一次举行，并放出诸多高质量资料，在<a href="https://github.com/gophercon/2014-talks">这里</a>可以下载。欧洲的Go语言大会<a href="http://www.dotgo.eu/">.dotgo</a>也即将开幕，估计后续这两个大会将撑起Golang技术分享 的旗帜。</p>
<p>言归正传，这里要写的东西并非原创，自己的Go仅仅算是入门级别，工程经验、Best Practice等还谈不上有多少，因此这里主要是针对GopherCon2014上的&ldquo;舶来品&rdquo;的学习心得。来自CloudFlare的工程师John Graham-Cumming谈了关于 Channel的实践经验，这里针对其分享的内容，记录一些学习体会和理解，并结合一些外延知识，也可以算是一种学习笔记吧，仅供参考。</p>
<p><b>一、Golang并发基础理论</b></p>
<p>Golang在并发设计方面参考了C.A.R Hoare的CSP，即Communicating Sequential Processes并发模型理论。但就像John Graham-Cumming所说的那样，多数Golang程序员或爱好者仅仅停留在&ldquo;知道&rdquo;这一层次，理解CSP理论的并不多，毕竟多数程序员是搞工程 的。不过要想系统学习CSP的人可以从<a href="http://www.usingcsp.com">这里</a>下载到CSP论文的最新版本。</p>
<p><a href="http://en.wikipedia.org/wiki/Communicating_sequential_processes">维基百科</a>中概要罗列了CSP模型与另外一种并发模型Actor模型的区别：</p>
<p>Actor模型广义上讲与CSP模型很相似。但两种模型就提供的原语而言，又有一些根本上的不同之处：<br />
	&nbsp;&nbsp;&nbsp; &#8211; CSP模型处理过程是匿名的，而Actor模型中的Actor则具有身份标识。<br />
	&nbsp;&nbsp;&nbsp; &#8211; CSP模型的消息传递在收发消息进程间包含了一个交会点，即发送方只能在接收方准备好接收消息时才能发送消息。相反，actor模型中的消息传递是异步 的，即消息的发送和接收无需在同一时间进行，发送方可以在接收方准备好接收消息前将消息发送出去。这两种方案可以认为是彼此对偶的。在某种意义下，基于交 会点的系统可以通过构造带缓冲的通信的方式来模拟异步消息系统。而异步系统可以通过构造带消息/应答协议的方式来同步发送方和接收方来模拟交会点似的通信 方式。<br />
	&nbsp;&nbsp;&nbsp; &#8211; CSP使用显式的Channel用于消息传递，而Actor模型则将消息发送给命名的目的Actor。这两种方法可以被认为是对偶的。某种意义下，进程可 以从一个实际上拥有身份标识的channel接收消息，而通过将actors构造成类Channel的行为模式也可以打破actors之间的名字耦合。</p>
<p><b>二、Go Channel基本操作语法</b></p>
<p>Go Channel的基本操作语法如下：</p>
<p><font face="Courier New">c := make(chan bool) //创建一个无缓冲的bool型Channel <br />
	c &lt;- x&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //向一个Channel发送一个值<br />
	&lt;- c&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //从一个Channel中接收一个值<br />
	x = &lt;- c&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //从Channel c接收一个值并将其存储到x中<br />
	x, ok = &lt;- c&nbsp; //从Channel接收一个值，如果channel关闭了或没有数据，那么ok将被置为false</font></p>
<p><i>不带缓冲的Channel</i>兼具通信和同步两种特性，颇受青睐。</p>
<p><b>三、Channel用作信号(Signal)的场景</b></p>
<p>1、等待一个事件(Event)</p>
<p><font face="Courier New">等待一个事件，有时候通过close一个Channel就足够了。例如：</font></p>
<p><font face="Courier New">//testwaitevent1.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Begin doing something!&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c := make(chan bool)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Doing something&#8230;&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <i>close(c)</i><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <i>&lt;-c</i><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Done!&quot;)<br />
	}</font></p>
<p>这里main goroutine通过&quot;<font face="Courier New">&lt;-c</font>&quot;来等待sub goroutine中的&ldquo;完成事件&rdquo;，sub goroutine通过close channel促发这一事件。当然也可以通过向Channel写入一个bool值的方式来作为事件通知。main goroutine在channel c上没有任何数据可读的情况下会阻塞等待。</p>
<p>关于输出结果：</p>
<p>根据《<a href="http://golang.org/ref/mem">Go memory model</a>》中关于close channel与recv from channel的order的定义：<span style="color: rgb(34, 34, 34); font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-style: italic; line-height: normal;"><font face="Courier New">The closing of a channel happens before a receive that returns a zero value because the channel is closed.</font></span></p>
<p>我们可以很容易判断出上面程序的输出结果：</p>
<p><font face="Courier New">Begin doing something!<br />
	Doing something&#8230;<br />
	Done!</font></p>
<p>如果将<font face="Courier New">close(c)</font>换成<font face="Courier New">c&lt;-true</font>，则根据《Go memory model》中的定义：<font face="Courier New"><span style="color: rgb(34, 34, 34); font-size: 16px; font-style: italic; line-height: normal;">A receive from an unbuffered channel happens before the send on that channel completes.</span></font><br />
	&quot;<font face="Courier New">&lt;-c</font>&quot;要先于&quot;<font face="Courier New">c&lt;-true</font>&quot;完成，但也不影响日志的输出顺序，输出结果仍为上面三行。</p>
<p>2、协同多个Goroutines</p>
<p>同上，close channel还可以用于协同多个Goroutines，比如下面这个例子，我们创建了100个Worker Goroutine，这些Goroutine在被创建出来后都阻塞在&quot;<font face="Courier New">&lt;-start&quot;</font>上，直到我们在main goroutine中给出<u>开工</u>的信号：&quot;<font face="Courier New">close(start)&quot;</font>，这些goroutines才开始真正的并发运行起来。</p>
<p><font face="Courier New">//testwaitevent2.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func worker(start chan bool, index int) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;-start<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;This is Worker:&quot;, index)<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; start := make(chan bool)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for i := 1; i &lt;= 100; i++ {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go worker(start, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(start)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {} //deadlock we expected<br />
	}</font></p>
<p>3、Select</p>
<p>【select的基本操作】<br />
	select是Go语言特有的操作，使用select我们可以同时在多个channel上进行发送/接收操作。下面是select的基本操作。</p>
<p><font face="Courier New">select {<br />
	case x := &lt;- somechan:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; 使用x进行一些操作</font></p>
<p><font face="Courier New">case y, ok := &lt;- someOtherchan:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; 使用y进行一些操作，<br />
	&nbsp;&nbsp;&nbsp; // </font><font face="Courier New"><font face="Courier New">检查ok值判断someOtherchan是否已经关闭</font></font></p>
<p><font face="Courier New">case outputChan &lt;- z:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; z值被成功发送到Channel上时</font></p>
<p><font face="Courier New">default:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; 上面case均无法通信时，执行此分支<br />
	}</font></p>
<p>【惯用法：for/select】</p>
<p>我们在使用select时很少只是对其进行一次evaluation，我们常常将其与for {}结合在一起使用，并选择适当时机从for{}中退出。</p>
<p><font face="Courier New">for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x := &lt;- somechan:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; 使用x进行一些操作</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case y, ok := &lt;- someOtherchan:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; 使用y进行一些操作，<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 检查ok值判断someOtherchan是否已经关闭</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case outputChan &lt;- z:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; z值被成功发送到Channel上时</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; default:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; 上面case均无法通信时，执行此分支<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	} </font></p>
<p>【终结workers】</p>
<p>下面是一个常见的终结sub worker goroutines的方法，每个worker goroutine通过select监视一个die channel来及时获取main goroutine的退出通知。</p>
<p><font face="Courier New">//testterminateworker1.go</font><br />
	<font face="Courier New">package main</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp; &quot;fmt&quot;<br />
	&nbsp;&nbsp;&nbsp; &quot;time&quot;<br />
	)</font></p>
<p><font face="Courier New">func worker(die chan bool, index int) {<br />
	&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Begin: This is Worker:&quot;, index)<br />
	&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //case xx：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //做事的分支<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case &lt;-die:<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; fmt.Println(&quot;Done: This is Worker:&quot;, index)<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp; die := make(chan bool)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; for i := 1; i &lt;= 100; i++ {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; go worker(die, i)<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 5)<br />
	&nbsp;&nbsp;&nbsp; close(die)<br />
	&nbsp;&nbsp;&nbsp; select {} </font><font face="Courier New"><font face="Courier New">//deadlock we expected</font><br />
	}</font></p>
<p>【终结验证】</p>
<p>有时候终结一个worker后，main goroutine想确认worker routine是否真正退出了，可采用下面这种方法：</p>
<p><font face="Courier New">//testterminateworker2.go<br />
	package main</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp; &quot;fmt&quot;<br />
	&nbsp;&nbsp;&nbsp; //&quot;time&quot;<br />
	)</font></p>
<p><font face="Courier New">func worker(die chan bool) {<br />
	&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Begin: This is Worker&quot;)<br />
	&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //case xx：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //做事的分支<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case &lt;-die:<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; fmt.Println(&quot;Done: This is Worker&quot;)<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; die &lt;- true<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp; die := make(chan bool)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; go worker(die)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; die &lt;- true<br />
	&nbsp;&nbsp;&nbsp; &lt;-die<br />
	&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Worker goroutine has been terminated&quot;)<br />
	}</font></p>
<p>【关闭的Channel永远不会阻塞】</p>
<p>下面演示在一个已经关闭了的channel上读写的结果：</p>
<p><font face="Courier New">//testoperateonclosedchannel.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cb := make(chan bool)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(cb)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x := &lt;-cb<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%#v\n&quot;, x)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x, ok := &lt;-cb<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%#v %#v\n&quot;, x, ok)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ci := make(chan int)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(ci)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; y := &lt;-ci<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%#v\n&quot;, y)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cb &lt;- true<br />
	}</font></p>
<p><font face="Courier New">$go run </font><font face="Courier New"><font face="Courier New">testoperateonclosedchannel.go</font><br />
	false<br />
	false false<br />
	0<br />
	panic: runtime error: send on closed channel</font></p>
<p>可以看到在一个已经close的unbuffered channel上执行读操作，回返回channel对应类型的零值，比如bool型channel返回false，int型channel返回0。但向close的channel写则会触发panic。不过无论读写都不会导致阻塞。</p>
<p>【关闭带缓存的channel】</p>
<p>将unbuffered channel换成buffered channel会怎样？我们看下面例子：</p>
<p><font face="Courier New">//testclosedbufferedchannel.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c := make(chan int, 3)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 15<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 34<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 65<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 1<br />
	}</font></p>
<p><font face="Courier New">$go run testclosedbufferedchannel.go<br />
	15<br />
	34<br />
	65<br />
	0<br />
	panic: runtime error: send on closed channel</font></p>
<p>可以看出带缓冲的channel略有不同。尽管已经close了，但我们依旧可以从中读出关闭前写入的3个值。第四次读取时，则会返回该channel类型的零值。向这类channel写入操作也会触发panic。</p>
<p>【range】</p>
<p>Golang中的range常常和channel并肩作战，它被用来从channel中读取所有值。下面是一个简单的实例：</p>
<p><font face="Courier New">//testrange.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func generator(strings chan string) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;Five hour&#39;s New York jet lag&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;and Cayce Pollard wakes in Camden Town&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;to the dire and ever-decreasing circles&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;of disrupted circadian rhythm.&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(strings)<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings := make(chan string)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go generator(strings)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for s := range strings {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%s\n&quot;, s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;\n&quot;)<br />
	}</font></p>
<p><b>四、隐藏状态</b></p>
<p>下面通过一个例子来演示一下channel如何用来隐藏状态：</p>
<p>1、例子：唯一的ID服务</p>
<p><font face="Courier New">//testuniqueid.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func newUniqueIDService() &lt;-chan string {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; id := make(chan string)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var counter int64 = 0<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; id &lt;- fmt.Sprintf(&quot;%x&quot;, counter)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; counter += 1<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return id<br />
	}<br />
	func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; id := newUniqueIDService()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for i := 0; i &lt; 10; i++ {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&lt;-id)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">$ go run testuniqueid.go<br />
	0<br />
	1<br />
	2<br />
	3<br />
	4<br />
	5<br />
	6<br />
	7<br />
	8<br />
	9</font></p>
<p>newUniqueIDService通过一个channel与main goroutine关联，main goroutine无需知道uniqueid实现的细节以及当前状态，只需通过channel获得最新id即可。</p>
<p><b>五、默认情况</b></p>
<p>我想这里John Graham-Cumming主要是想告诉我们select的default分支的实践用法。</p>
<p>1、select&nbsp; for non-blocking receive</p>
<p><font face="Courier New">idle:= make(chan []byte, 5) //用一个带缓冲的channel构造一个简单的队列</font></p>
<p><font face="Courier New">select {<br />
	case b = &lt;-idle:  //尝试从idle队列中读取<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	default:&nbsp; //队列空，分配一个新的buffer<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; makes += 1<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = make([]byte, size)<br />
	}</font></p>
<p>2、select for non-blocking send</p>
<p><font face="Courier New">idle:= make(chan []byte, 5) </font><font face="Courier New"><font face="Courier New">//用一个带缓冲的channel构造一个简单的队列</font></font></p>
<p><font face="Courier New">select {<br />
	case idle &lt;- b: //尝试向队列中插入一个buffer<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&#8230;<br />
	default: //队列满？</font></p>
<p><font face="Courier New">}</font></p>
<p><b>六、Nil Channels</b></p>
<p>1、nil channels阻塞</p>
<p>对一个没有初始化的channel进行读写操作都将发生阻塞，例子如下：</p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var c chan int<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;-c<br />
	}</font></p>
<p><font face="Courier New">$go run testnilchannel.go<br />
	fatal error: all goroutines are asleep &#8211; deadlock!</font></p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var c chan int<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 1<br />
	}</font></p>
<p><font face="Courier New">$go run testnilchannel.go<br />
	fatal error: all goroutines are asleep &#8211; deadlock!</font></p>
<p>2、nil channel在select中很有用</p>
<p>看下面这个例子：</p>
<p><font face="Courier New">//testnilchannel_bad.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;<br />
	import &quot;time&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var c1, c2 chan int = make(chan int), make(chan int)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 5)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c1 &lt;- 5<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 7)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c2 &lt;- 7<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c2)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x := &lt;-c1:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x := &lt;-c2:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;over&quot;)<br />
	}</font></p>
<p>我们原本期望程序交替输出5和7两个数字，但实际的输出结果却是：</p>
<p><font face="Courier New">5<br />
	0<br />
	0<br />
	0<br />
	&#8230; &#8230; 0死循环</font></p>
<p>再仔细分析代码，原来select每次按case顺序evaluate：<br />
	&nbsp;&nbsp;&nbsp; &#8211; 前5s，select一直阻塞；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 第5s，c1返回一个5后被close了，&ldquo;case x := &lt;-c1&rdquo;这个分支返回，select输出5，并重新select<br />
	&nbsp;&nbsp;&nbsp; &#8211; 下一轮select又从&ldquo;case x := &lt;-c1&rdquo;这个分支开始evaluate，由于c1被close，按照前面的知识，close的channel不会阻塞，我们会读出这个 channel对应类型的零值，这里就是0；select再次输出0；这时即便c2有值返回，程序也不会走到c2这个分支<br />
	&nbsp;&nbsp;&nbsp; &#8211; 依次类推，程序无限循环的输出0</p>
<p>我们利用nil channel来改进这个程序，以实现我们的意图，代码如下：</p>
<p><font face="Courier New">//testnilchannel.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;<br />
	import &quot;time&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var c1, c2 chan int = make(chan int), make(chan int)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 5)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c1 &lt;- 5<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 7)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c2 &lt;- 7<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c2)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x, ok := &lt;-c1:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if !ok {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c1 = nil<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x, ok := &lt;-c2:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if !ok {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c2 = nil<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if c1 == nil &amp;&amp; c2 == nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;over&quot;)<br />
	}</font></p>
<p><font face="Courier New">$go run testnilchannel.go<br />
	5<br />
	7<br />
	over</font></p>
<p>可以看出：通过将已经关闭的channel置为nil，下次select将会阻塞在该channel上，使得select继续下面的分支evaluation。</p>
<p><b>七、Timers</b></p>
<p>1、超时机制Timeout</p>
<p>带超时机制的select是常规的tip，下面是示例代码，实现30s的超时select：</p>
<p><font face="Courier New">func worker(start chan bool) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; timeout := time.After(30 * time.Second)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; do some stuff<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case &lt;- timeout:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	} </font></p>
<p>2、心跳HeartBeart</p>
<p>与timeout实现类似，下面是一个简单的心跳select实现：</p>
<p><font face="Courier New">func worker(start chan bool) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; heartbeat := time.Tick(30 * time.Second)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; do some stuff<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case &lt;- heartbeat:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&#8230; do heartbeat stuff<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	} </font></p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/09/29/a-channel-compendium-for-golang/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Ubuntu Server 14.04安装docker</title>
		<link>https://tonybai.com/2014/09/26/install-docker-on-ubuntu-server-1404/</link>
		<comments>https://tonybai.com/2014/09/26/install-docker-on-ubuntu-server-1404/#comments</comments>
		<pubDate>Fri, 26 Sep 2014 07:09:52 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[SSH]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[virtualbox]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[学习]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[端口转发]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1547</guid>
		<description><![CDATA[近期在研究docker这一轻量级容器引擎，研究docker对日常开发测试工作以及产品部署运维工作能带来哪些便利。前些时候刚刚将工作环境从 Ubuntu搬到了Mac Air上，对Mac OS X的一切均不甚熟悉，给docker研究带来了不便，于是打算在VirtualBox中安装一Ubuntu Server作为docker之承载平台。这里记录一下安装配置过程，主要为了备忘，如果能给其他人带来帮助，我会甚感欣慰。 docker官方对ubuntu的支持是蛮好的。docker对Linux内核版本有要求，要&#62;=3.8，Ubuntu Server目前最新版本14.04.1恰符合这一要求，其kernel version = 3.13.0-32。 一、VirtualBox安装Ubuntu Server 14.04.1 VirtualBox安装Ubuntu OS做过了不止一遍，即便是换成最新的14.04.1 Server版，差别也没有太多，无非是按照安装提示，逐步Next。这里给Ubuntu Server 14.04分配了1G Memory, 32G动态硬盘空间。 【配置源】 &#160; 默认情况下，/etc/apt/sources.list中只有一组源：cn.archive.ubuntu.com/ubuntu。这个国外源的下载速度显然无法满足我的要求，于是我把我常用的sohu源加入sources.list中，并且放在前面： &#160; deb http://mirrors.sohu.com/ubuntu/ trusty main restricted &#160; deb http://mirrors.sohu.com/ubuntu/ trusty-security main restricted &#160; deb http://mirrors.sohu.com/ubuntu/ trusty-updates main restricted &#160; deb http://mirrors.sohu.com/ubuntu/ trusty-proposed main restricted &#160; deb http://mirrors.sohu.com/ubuntu/ trusty-backports main restricted &#160; [...]]]></description>
			<content:encoded><![CDATA[<p>近期在研究<a href="http://docker.com">docker</a>这一轻量级容器引擎，研究docker对日常开发测试工作以及产品部署运维工作能带来哪些便利。前些时候刚刚将工作环境从 <a href="http://tonybai.com/tag/ubuntu">Ubuntu</a>搬到了Mac Air上，对Mac OS X的一切均不甚熟悉，给docker研究带来了不便，于是打算在<a href="http://virtualbox.org">VirtualBox</a>中安装一Ubuntu Server作为docker之承载平台。这里记录一下安装配置过程，主要为了备忘，如果能给其他人带来帮助，我会甚感欣慰。</p>
<p>docker官方对<a href="http://ubuntu.com">ubuntu</a>的支持是蛮好的。docker对Linux内核版本有要求，要&gt;=3.8，Ubuntu Server目前最新版本14.04.1恰符合这一要求，其kernel version = 3.13.0-32。</p>
<p><strong>一、VirtualBox安装Ubuntu Server 14.04.1</strong></p>
<p>VirtualBox安装Ubuntu OS做过了不止一遍，即便是换成最新的14.04.1 Server版，差别也没有太多，无非是按照安装提示，逐步Next。这里给Ubuntu Server 14.04分配了1G Memory, 32G动态硬盘空间。</p>
<p>【配置源】</p>
<p>&nbsp; 默认情况下，/etc/apt/sources.list中只有一组源：cn.archive.ubuntu.com/ubuntu。这个国外源的下载速度显然无法满足我的要求，于是我把我常用的sohu源加入sources.list中，并且放在前面：</p>
<p><font face="Courier New">&nbsp; deb http://mirrors.sohu.com/ubuntu/ trusty main restricted<br />
	&nbsp; deb http://mirrors.sohu.com/ubuntu/ trusty-security main restricted<br />
	&nbsp; deb http://mirrors.sohu.com/ubuntu/ trusty-updates main restricted<br />
	&nbsp; deb http://mirrors.sohu.com/ubuntu/ trusty-proposed main restricted<br />
	&nbsp; deb http://mirrors.sohu.com/ubuntu/ trusty-backports main restricted</font></p>
<p><font face="Courier New">&nbsp; deb-src http://mirros.sohu.com/ubuntu/ trusty main restricted<br />
	&nbsp; deb-src http://mirrors.sohu.com/ubuntu/ trusty-security main restricted<br />
	&nbsp; deb-src http://mirrors.sohu.com/ubuntu/ trusty-updates main restricted<br />
	&nbsp; deb-src http://mirrors.sohu.com/ubuntu/ trusty-proposed main restricted<br />
	&nbsp; deb-src http://mirrors.sohu.com/ubuntu/ trusty-backports main restricted</font></p>
<p>&nbsp; 公司采用代理访问外网，于是还得在/etc/apt/apt.conf中加上代理的设置，否则无法更新源，也就无法安装第三方软件：</p>
<p><font face="Courier New">&nbsp; Acquire::http::Proxy &quot;http://username:passwd@proxyhost:proxyport&quot;;</font></p>
<p>&nbsp;【乱码处理】</p>
<p>&nbsp; 由于安装时候选择了中国区域（locale zh_CN.UTF-8），因此在VirtualBox的窗口中直接执行命令的提示信息可能是乱码。对于Server，我们一般是不会直接通过其主机显示 器登录使用的，都是通过终端访问，但在未安装和开启ssh服务和未配置端口转发前，我们只能先凑合这个窗口了。可先将/etc/default /locale中的LANGUAGE由&quot;zh_CN:zh&quot;改为&quot;en_US:en&quot;， logout后重新登录就可以看到非乱码的英文提示信息了。</p>
<p>【安装VirtualBox增强组件】</p>
<p>&nbsp; Ubuntu Server默认是不安装图形桌面的，只有一个命令行窗口，连鼠标都无法使用。因此增强组件安装的意义没有桌面系统那么强烈。我能想到的只有&ldquo;共享目录&rdquo;这一个功能有些用处。</p>
<p>&nbsp; 安装方法也不难，按下面步骤逐步操作即可：</p>
<p><font face="Courier New">&nbsp; sudo apt-get install build-essential linux-headers-$(uname -r) dkms gcc g++<br />
	&nbsp; sudo mnt /dev/cdrom /mnt<br />
	&nbsp; cd /mnt<br />
	&nbsp; sudo bash ./VBoxLinuxAdditions.run</font></p>
<p>&nbsp; 如果结果都是&quot;done&quot;，重启后就ok了。</p>
<p>【安装ssh服务】</p>
<p>&nbsp;&nbsp;&nbsp; ssh服务由openssh-server提供：<br />
	&nbsp;&nbsp;&nbsp; <font face="Courier New">sudo apt-get openssh-server</font><br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp; 安装成功后，ssh server服务就会自动启动起来。</p>
<p>&nbsp;&nbsp; 不过我们还是需要修改一些配置，比如允许Root登录：打开/etc/ssh/sshd_config，将PermitRootLogin后面的内容改为yes。<br />
	&nbsp;&nbsp;&nbsp;<br />
	【设置端口转发】</p>
<p>&nbsp; 前面说过，对于Server，我们更多是在其他主机上通过ssh或telnet远程访问该Server并执行各种操作。由于这里是VirtualBox安 装的虚拟机，其他主机无法看到这台Server，我们需要设置端口转发将外部访问的数据转发给这个内部虚拟Server。</p>
<p>&nbsp; 我们通过VirtualBox软件提供的图形界面即可完成这个操作：<br />
	&nbsp;&nbsp;&nbsp; 1、&ldquo;设置&rdquo;这个虚拟机<br />
	&nbsp;&nbsp;&nbsp; 2、在&ldquo;网络&rdquo;标签中，点击&ldquo;端口转发&rdquo;按钮，进入端口转发规则添加窗口。<br />
	&nbsp;&nbsp;&nbsp; 3、添加一条规则：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 名称：ssh-rules<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 协议：TCP<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 主机IP、子系统IP可以为空。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 主机端口：2222<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 子系统端口：22<br />
	&nbsp;&nbsp; 4、配置结束</p>
<p>&nbsp;&nbsp;&nbsp; 配置结束后，我们在宿主机上netstat -an|grep 2222，可以看到VirtualBox增加了该端口2222的监听。</p>
<p>&nbsp; 现在我们就可以在其他机器上通过ssh -l tonybai 宿主机ip -p 2222的方式登录到我们新安装的这台虚拟Server了。</p>
<p>&nbsp;&nbsp;<br />
	<b>二、安装docker</b></p>
<p>docker目前的最新版本号是1.2.0，但14.04源中的docker还是正式稳定版1.0之前的版本，显然这是无法满足我的要求的。我们只能另外添加docker源来安装最新版docker。</p>
<p>&nbsp; 【安装docker】</p>
<p>&nbsp;&nbsp;&nbsp; 我们在/etc/apt/sources.list中加入下面这个源：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">deb <a class="moz-txt-link-freetext" href="http://mirror.yandex.ru/mirrors/docker/">http://mirror.yandex.ru/mirrors/docker/</a> docker main</font><br />
	&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 执行apt-get update。</p>
<p>&nbsp;&nbsp;&nbsp; sudo apt-get install lxc-docker</p>
<p><font face="Courier New">正在读取软件包列表&#8230; 完成<br />
	正在分析软件包的依赖关系树&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	正在读取状态信息&#8230; 完成&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	将会安装下列额外的软件包：<br />
	&nbsp; aufs-tools cgroup-lite git git-man liberror-perl lxc-docker-1.2.0<br />
	建议安装的软件包：<br />
	&nbsp; git-daemon-run git-daemon-sysvinit git-doc git-el git-email git-gui gitk<br />
	&nbsp; gitweb git-arch git-bzr git-cvs git-mediawiki git-svn<br />
	下列新软件包将被安装：<br />
	&nbsp; aufs-tools cgroup-lite git git-man liberror-perl lxc-docker lxc-docker-1.2.0<br />
	升级了 0 个软件包，新安装了 7 个软件包，要卸载 0 个软件包，有 59 个软件包未被升级。<br />
	需要下载 7,477 kB 的软件包。<br />
	解压缩后会消耗掉 35.4 MB 的额外空间。<br />
	您希望继续执行吗？ [Y/n] y</font></p>
<p>&nbsp; 这个源里的docker居然是最新版。于是安装之。安装后，我们执行docker version来确认一下安装是否成功。</p>
<p><font face="Courier New">&nbsp; tonybai@ubuntu-Server-14:~$ docker version<br />
	Client version: 1.2.0<br />
	Client API version: 1.14<br />
	Go version (client): go1.3.1<br />
	Git commit (client): fa7b24f<br />
	OS/Arch (client): linux/amd64<br />
	2014/09/26 13:56:53 Get http:///var/run/docker.sock/v1.14/version: dial unix /var/run/docker.sock: permission denied</font></p>
<p>&nbsp; 【为docker设置http代理】</p>
<p>&nbsp;&nbsp;&nbsp; 在公司内使用代理才能访问到外网，于是我们也需要为docker命令设置代理以使其顺利执行命令。</p>
<p>&nbsp;&nbsp;&nbsp; 我们安装的docker实际上分为两部分，docker命令行和docker daemon。两者是C/S结构，docker命令行将用户的请求转发给docker daemon，后者会真正与外部通信完成各种操作。</p>
<p>&nbsp;&nbsp;&nbsp; 于是我们可以这样为docker daemon设置http_proxy:<br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; sudo service docker stop<br />
	&nbsp;&nbsp;&nbsp; sudo http_proxy=&#39;<a class="moz-txt-link-freetext" href="http://baim:xxx@proxy.neusoft.com:8080">http://user:passwd@proxyhost:port</a>&#39; docker -d &amp;</font></p>
<p>&nbsp;&nbsp;&nbsp; 这样设置启动后，我们可以通过下面命令测试设置是否ok：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New"> sudo docker search ubuntu</font></p>
<p>&nbsp;&nbsp;&nbsp; 如果你看到下面信息，说明设置成功了：</p>
<p>&nbsp;<font face="Courier New">&nbsp;&nbsp; tonybai@ubuntu-Server-14:~$ sudo docker search ubuntu<br />
	[info] GET /v1.14/images/search?term=ubuntu<br />
	[b36518a9] +job search(ubuntu)<br />
	[b36518a9] -job search(ubuntu) = OK (0)<br />
	NAME&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DESCRIPTION&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STARS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OFFICIAL&nbsp;&nbsp; AUTOMATED<br />
	ubuntu&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Official Ubuntu base image&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 709&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [OK]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	dockerfile/ubuntu&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Trusted automated Ubuntu (http://www.ubunt&#8230;&nbsp;&nbsp; 24&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [OK]<br />
	crashsystems/gitlab-docker&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A trusted, regularly updated build of GitL&#8230;&nbsp;&nbsp; 20&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [OK]<br />
	ubuntu-upstart&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Upstart is an event-based replacement for &#8230;&nbsp;&nbsp; 13&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [OK]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font><br />
	&#8230; &#8230;.</p>
<p>&nbsp;</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/09/26/install-docker-on-ubuntu-server-1404/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Cocos2d-x屏幕适配之Sprite绘制原理</title>
		<link>https://tonybai.com/2014/05/13/sprite-draw-principles-of-cocos2dx-screen-adaptation/</link>
		<comments>https://tonybai.com/2014/05/13/sprite-draw-principles-of-cocos2dx-screen-adaptation/#comments</comments>
		<pubDate>Mon, 12 May 2014 17:04:14 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[android-native-app-glue]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CGL]]></category>
		<category><![CDATA[Cocos2d]]></category>
		<category><![CDATA[Cocos2d-x]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[Frustum]]></category>
		<category><![CDATA[Game-Engine]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Mobile]]></category>
		<category><![CDATA[MobileGame]]></category>
		<category><![CDATA[NativeActivity]]></category>
		<category><![CDATA[NDK]]></category>
		<category><![CDATA[opengl]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[projection]]></category>
		<category><![CDATA[Screen-Adaptation]]></category>
		<category><![CDATA[Sprite]]></category>
		<category><![CDATA[viewport]]></category>
		<category><![CDATA[内存计数]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[安卓]]></category>
		<category><![CDATA[屏幕适配]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[平头截体]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[性能优化]]></category>
		<category><![CDATA[手游]]></category>
		<category><![CDATA[投影变换]]></category>
		<category><![CDATA[架构]]></category>
		<category><![CDATA[模型视图变换]]></category>
		<category><![CDATA[游戏引擎]]></category>
		<category><![CDATA[源码分析]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[精灵]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[视口变换]]></category>
		<category><![CDATA[触控科技]]></category>
		<category><![CDATA[跨平台]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1533</guid>
		<description><![CDATA[手机(智能终端)游戏绝大多数为全屏(Full Screen)显示，这样开发人员在制作游戏时势必要考虑不同手机(智能终端）屏幕大小、宽高比的不同给游戏画面带来的影响，并且要将这种影响降低到最 小，努力使用不同终端的游戏玩家拥有几乎相同的游戏画面体验。为此各种游戏引擎在屏幕适配方面都给出了自己的方案，Cocos2d-x也不例外。 在Cocos2d-x官网Wiki上特地撰写了一篇讲解Cocos2d-x多屏幕适配原理的文章&#8220;Detailed explanation of Cocos2d-x Multi-resolution adaptation&#8221;。 这里我们以Cocos2d-x引擎（基于2.2.2版本）自带的Sample项目HelloCpp(cocos2d-x-2.2.2/samples/Cpp/HelloCpp）为例，直观的看看这个方案带来的好 处。首先，我们对HelloCpp项目做些许改造： &#160;&#160;&#160; &#8211; 注释掉AppDelegate.cpp中applicationDidFinishLaunching下的pEGLView-&#62;setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, kResolutionNoBorder); &#160;&#160;&#160; &#8211; 仅使用Resource/iphone下的资源，即仅searchPath.push_back(smallResource.directory)； 这里我们有一张480&#215;320分辨率大小PNG文件。 &#160;&#160;&#160; &#8211; 通过改变proj.linux/main.cpp中的eglView-&#62;setFrameSize(960, 640);来改变屏幕参数。（用linux工程模拟甚为方便，编译和运行占用资源小，极为迅捷，效果与Android平台是等 效的） 我们对比一下以下三种条件下的游戏Demo显示结果： &#160;&#160;&#160; 1) 屏幕大小480&#215;320，未做任何屏幕适配工作，不调用pEGLView-&#62;setDesignResolutionSize。 &#160;&#160;&#160; 2) 屏幕大小960&#215;640，未做任何屏幕适配工作，不调用pEGLView-&#62;setDesignResolutionSize。 &#160;&#160;&#160; 3) 屏幕大小同为960&#215;640，按照上面Cocos2d-x屏幕适配指南Wiki中的做法，调用pEGLView-&#62;setDesignResolutionSize(480, 320); 如我们所料，我们得到三个截然不同的结果。 第一种情况，我们所得到的游戏屏幕截图如下： 第二种情况，我们所得到的游戏屏幕截图如下： 第三种情况，我们所得到的游戏屏幕截图如下： 第一种情况是最理想的情况，屏幕大小与背景图片大小相同，如我们所愿，屏幕与背景图片吻合的天衣无缝。 第二种情况显然是模拟我们初次遇到问题的场景。屏幕Size扩大为原先的二倍，在资源没有变化的情况下，我们发现480&#215;320大小的背景图片没 有铺满屏幕，仅仅是居中显示，并在四周露出较多&#8221;黑边&#8220;，这显然不是我们想要的。 第三种情况，也就是我们按照官方屏幕适配方案调整后得到的结果，在资源依旧不变的情况下，我们得到了相对令人满意的结果：背景图片恰如其分的铺满 整个屏幕，比例正确。这样我们用一套资源就可以同时适配两个屏幕了：480&#215;320、960&#215;640。这两种终端的玩家至少不会对我们的游戏心生 抱怨之情^_^。 当然在遇到第二种情况的时候，你也大可再准备一套新资源，比如一张960&#215;640的背景图片。在480&#215;320手机上，使用480&#215;320的图 片；在960&#215;640的手机上，使用960&#215;640的背景图片。但这种方法的弊端至少有三： &#160;&#160;&#160; &#8211; 包大了：游戏的安装包Size急剧变大。 &#160;&#160;&#160; &#8211; 活儿多了：因适配屏幕种类太多而制作大量的图片。 &#160;&#160;&#160; [...]]]></description>
			<content:encoded><![CDATA[<p>手机(智能终端)游戏绝大多数为全屏(Full Screen)显示，这样开发人员在制作游戏时势必要考虑不同手机(智能终端）屏幕大小、宽高比的不同给游戏画面带来的影响，并且要将这种影响降低到最 小，努力使用不同终端的游戏玩家拥有几乎相同的游戏画面体验。为此各种游戏引擎在屏幕适配方面都给出了自己的方案，<a href="http://www.cocos2d-x.org">Cocos2d-x</a>也不例外。 在Cocos2d-x官网Wiki上特地撰写了一篇讲解Cocos2d-x多屏幕适配原理的文章&ldquo;<a href="http://www.cocos2d-x.org/wiki /Detailed_explanation_of_Cocos2d-x_Multi-resolution_adaptation">Detailed explanation of Cocos2d-x Multi-resolution adaptation</a>&rdquo;。</p>
<p>这里我们以Cocos2d-x引擎（基于2.2.2版本）自带的Sample项目HelloCpp(<font face="Courier&lt;br /&gt;&lt;br /&gt;<br />
      New">cocos2d-x-2.2.2/samples/Cpp/HelloCpp</font>）为例，直观的看看这个方案带来的好 处。首先，我们对HelloCpp项目做些许改造：<br />
	&nbsp;&nbsp;&nbsp; &#8211; 注释掉AppDelegate.cpp中applicationDidFinishLaunching下的<font face="Courier New">pEGLView-&gt;setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, kResolutionNoBorder)</font>;<br />
	&nbsp;&nbsp;&nbsp; &#8211; 仅使用Resource/iphone下的资源，即仅<font face="Courier New">searchPath.push_back(smallResource.directory)</font>； 这里我们有一张480&#215;320分辨率大小PNG文件。<br />
	&nbsp;&nbsp;&nbsp; &#8211; 通过改变proj.linux/main.cpp中的<font face="Courier New">eglView-&gt;setFrameSize(960, 640)</font>;来改变屏幕参数。（用linux工程模拟甚为方便，编译和运行占用资源小，极为迅捷，效果与Android平台是等 效的）</p>
<p>我们对比一下以下三种条件下的游戏Demo显示结果：<br />
	&nbsp;&nbsp;&nbsp; 1) 屏幕大小480&#215;320，未做任何屏幕适配工作，不调用pEGLView-&gt;setDesignResolutionSize。<br />
	&nbsp;&nbsp;&nbsp; 2) 屏幕大小960&#215;640，未做任何屏幕适配工作，不调用pEGLView-&gt;setDesignResolutionSize。<br />
	&nbsp;&nbsp;&nbsp; 3) 屏幕大小同为960&#215;640，按照上面Cocos2d-x屏幕适配指南Wiki中的做法，调用<font face="Courier New">pEGLView-&gt;setDesignResolutionSize(480, 320);</font></p>
<p>如我们所料，我们得到三个截然不同的结果。</p>
<p>第一种情况，我们所得到的游戏屏幕截图如下：<br />
	<img alt="" src="/wp-content/uploads/origin-size.jpg" style="height: 320px; width: 441px;" /></p>
<p>第二种情况，我们所得到的游戏屏幕截图如下：<br />
	<img alt="" src="/wp-content/uploads/before-setdesignresolution.jpg" style="height: 320px; width: 459px;" /></p>
<p>第三种情况，我们所得到的游戏屏幕截图如下：</p>
<p><img alt="" src="/wp-content/uploads/after-setdesignresolution.jpg" style="width: 480px; height: 334px;" /></p>
<p>第一种情况是最理想的情况，屏幕大小与背景图片大小相同，如我们所愿，屏幕与背景图片吻合的天衣无缝。<br />
	第二种情况显然是模拟我们初次遇到问题的场景。屏幕Size扩大为原先的二倍，在资源没有变化的情况下，我们发现480&#215;320大小的背景图片没 有铺满屏幕，仅仅是居中显示，并在四周露出较多&rdquo;黑边&ldquo;，这显然不是我们想要的。<br />
	第三种情况，也就是我们按照官方屏幕适配方案调整后得到的结果，在资源依旧不变的情况下，我们得到了相对令人满意的结果：背景图片恰如其分的铺满 整个屏幕，比例正确。这样我们用一套资源就可以同时适配两个屏幕了：480&#215;320、960&#215;640。这两种终端的玩家至少不会对我们的游戏心生 抱怨之情^_^。</p>
<p>当然在遇到第二种情况的时候，你也大可再准备一套新资源，比如一张960&#215;640的背景图片。在480&#215;320手机上，使用480&#215;320的图 片；在960&#215;640的手机上，使用960&#215;640的背景图片。但这种方法的弊端至少有三：<br />
	&nbsp;&nbsp;&nbsp; &#8211; 包大了：游戏的安装包Size急剧变大。<br />
	&nbsp;&nbsp;&nbsp; &#8211; 活儿多了：因适配屏幕种类太多而制作大量的图片。<br />
	&nbsp;&nbsp;&nbsp; &#8211; 新屏幕出来咋办：如果某个厂家突然于某天出品一款手机，其分辨率与以往市面上的所有手机均不同，那你的游戏因没有对应的资源，肯定无法很好适配该手机，导 致较差用户体验。</p>
<p>为此，适配屏幕唯一的出路似乎只有按照官方推荐的方案进行了，当然适当结合有限种类的资源也许可以更好的提升游戏体验。</p>
<p>如果仅仅从游戏制作角度来看，我们找到了可以适配屏幕的方法就可以了，没有必要刨根问底。甚至当有人问起来：为何 setDesignResolutionSize后，背景图片就可以充满屏幕了呢？我们可以回答：&ldquo;引擎对精灵进行了缩放，就是这样&rdquo;。但对于上 面的背景精灵来说，真的是我们理解的普通意义上的&ldquo;精灵缩放(Scale)吗？本着&ldquo;知其然，也要知其所以然&rdquo;的精神，这里对引擎如何对 Sprite进行绘制进行了一番研究，我还真发现了一些与我之前理解差异较大的&ldquo;深奥&rdquo;原理，这里与大家一起分享一下。</p>
<p><b>一、绘制参数初始化</b></p>
<p>我们还是从代码开始，了解一下引擎绘制参数的初始化工作是如何做的、在哪里做的，为后续的分析做些铺垫。这里以Cocos2d-x 2.2.2 Android平台为例。关于Cocos2d-x 2.2.2 Android平台的引擎粗线条启动流程分析，可以参考《<a href="http://tonybai.com/2014/03/11/hello-cocos2dx/">Hello，Cocos2d-x</a>》这篇文章。看完这篇文章，你就会知道我们这次应该从<font face="Courier New">Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit</font>开 始。</p>
<p><font face="Courier New">// samples/Cpp/HelloCpp/proj.android/jni/hellocpp/main.cpp<br />
	void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JNIEnv*&nbsp; env, jobject thiz, jint w, jint h)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; if (!CCDirector::<b>sharedDirector</b>()-&gt;getOpenGLView())<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCEGLView *view = CCEGLView::sharedOpenGLView();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>view-&gt;setFrameSize(w, h);</b></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AppDelegate *pAppDelegate = new AppDelegate();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCApplication::sharedApplication()-&gt;run();<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p><font face="Courier New">这里是引擎部分初始化的起点:CCDirector和CCEGLView先后完成创建与初始化。接下来我们分别看一下这两个过程，我们主要关 注与绘制参数设置相关的内容：</font></p>
<p><font face="Courier New">bool CCDirector::init(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; <b>setDefaultValues</b>();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; m_obWinSizeInPoints = CCSizeZero;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; m_pobOpenGLView = NULL;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; m_fContentScaleFactor = 1.0f;<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; return true;<br />
	}</font></p>
<p><font face="Courier New">void CCDirector::setDefaultValues(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; CCConfiguration *conf =<br />
	&nbsp;&nbsp;&nbsp;&nbsp; CCConfiguration::sharedConfiguration();<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; // GL projection<br />
	&nbsp;&nbsp;&nbsp; const char *projection =<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf-&gt;getCString(&quot;cocos2d.x.gl.projection&quot;,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &quot;<b>3d</b>&quot;);<br />
	&nbsp;&nbsp;&nbsp; if( strcmp(projection, &quot;3d&quot;) == 0 )<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_eProjection = <b>kCCDirectorProjection3D</b>;<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p><font face="Courier New">由于conf中没有配置&ldquo;cocos2d.x.gl.projection&rdquo;，因此projection使用了 getCString传入的默认值：&quot;3d&quot;，m_eProjection则被赋值为kCCDirectorProjection3D。</font></p>
<p><font face="Courier New">CCEGLView的创建更为简单：</font></p>
<p><font face="Courier New">CCEGLView::CCEGLView()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; initExtensions();<br />
	}</font></p>
<p><font face="Courier New">但背后真正发挥关键作用的是其父类CCEGLViewProtocol。</font></p>
<p><font face="Courier New">CCEGLViewProtocol::CCEGLViewProtocol()<br />
	: m_pDelegate(NULL)<br />
	, m_fScaleX(1.0f)<br />
	, m_fScaleY(1.0f)<br />
	, m_eResolutionPolicy(kResolutionUnKnown)<br />
	{<br />
	}</font></p>
<p><font face="Courier New">这里我们看到了三个重要的字段：m_fScaleX、m_fScaleY以及m_eResolutionPolicy，这三个字段对于后续屏 幕适配起到至关重要的作用。</font></p>
<p><font face="Courier New">nativeInit中的view-&gt;SetFrameSize(w, h)用于设置的屏幕物理分辨率，如果你的手机是960&#215;640分辨率的，那FrameSize就是960&#215;640。</font></p>
<p><font face="Courier New">void CCEGLViewProtocol::setFrameSize(float width,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float height)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; m_obDesignResolutionSize<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = m_obScreenSize<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = CCSizeMake(width, height);<br />
	}<br />
	初始情况下，CCEGLViewProtocol将&ldquo;设计分辨率&rdquo;m_obDesignResolutionSize也设置为与 FrameSize or m_obScreenSize同等大小。</font></p>
<p><font face="Courier New">我们回到游戏逻辑层代码AppDelegate.cpp，我们知道游戏逻辑的入口在这里，最初的参数初始化是在为Director设置 GLView实例时进行的：</font></p>
<p><font face="Courier New">bool AppDelegate::applicationDidFinishLaunching() {<br />
	&nbsp;&nbsp;&nbsp; // initialize director<br />
	&nbsp;&nbsp;&nbsp; CCDirector* pDirector = CCDirector::sharedDirector();<br />
	&nbsp;&nbsp;&nbsp; CCEGLView* pEGLView = CCEGLView::sharedOpenGLView();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; pDirector-&gt;<b>setOpenGLView</b>(pEGLView);<br />
	&nbsp;&nbsp;&nbsp; CCSize frameSize = pEGLView-&gt;getFrameSize();<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p><font face="Courier New">void CCDirector::setOpenGLView(CCEGLView *pobOpenGLView)<br />
	{<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pobOpenGLView = pobOpenGLView;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // set size<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_obWinSizeInPoints =<br />
	<b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pobOpenGLView-&gt;getDesignResolutionSize()</b>;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (m_pobOpenGLView)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>setGLDefaultValues();</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CHECK_GL_ERROR_DEBUG();<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">由于尚未调用setDesignResolutionSize，因此m_obWinSizeInPoints的值与FrameSize大小相 同。</font></p>
<p><font face="Courier New">setGLDefaultValues最为关键，这是我们第一次遇到该函数，该方法用于初始化一些OpenGL的参数，建立好后续 OpenGL操作时所需要的各种数据结构。</font></p>
<p><font face="Courier New">void CCDirector::setGLDefaultValues(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; setAlphaBlending(true);<br />
	&nbsp;&nbsp;&nbsp; setDepthTest(false);<br />
	&nbsp;&nbsp;&nbsp; <b>setProjection(</b>m_eProjection);<br />
	&nbsp;&nbsp;&nbsp; // set other opengl default values<br />
	&nbsp;&nbsp;&nbsp; glClearColor(0.0f, 0.0f, 0.0f, 1.0f);<br />
	}</font></p>
<p><font face="Courier New">glClearColor(0.0f, 0.0f, 0.0f, 1.0f);设置初始颜色为黑色，alpha为1.0f，即完全不透明。setProjection是实际上绘制参数设置的核心。</font></p>
<p><font face="Courier New">void CCDirector::setProjection(ccDirectorProjection kProjection)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; CCSize size = m_obWinSizeInPoints;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; <b>setViewport</b>();<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; switch (kProjection)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp; case kCCDirectorProjection3D:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float zeye = this-&gt;<b>getZEye</b>();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmMat4 matrixPerspective, matrixLookup;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>kmGLMatrixMode(KM_GL_PROJECTION);</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmGLLoadIdentity();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // issue #1334<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>kmMat4PerspectiveProjection</b>( &amp;matrixPerspective,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; 60,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (GLfloat)size.width/size.height,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; 0.1f, zeye*2);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmGLMultMatrix(&amp;matrixPerspective);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>kmGLMatrixMode(KM_GL_MODELVIEW);</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmGLLoadIdentity();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmVec3 eye, center, up;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmVec3Fill( &amp;eye, size.width/2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; size.height/2, zeye );<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmVec3Fill( &amp;center, size.width/2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; size.height/2, 0.0f );<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmVec3Fill( &amp;up, 0.0f, 1.0f, 0.0f);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>kmMat4LookAt</b>(&amp;matrixLookup, &amp;eye,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;center, &amp;up);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmGLMultMatrix(&amp;matrixLookup);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; m_eProjection = kProjection;<br />
	&nbsp;&nbsp;&nbsp; ccSetProjectionMatrixDirty();<br />
	}</font></p>
<p><font face="Courier New">由于前面m_eProjection已经被赋值为kCCDirectorProjection3D，因此我们只分析 kCCDirectorProjection3D这个case分支。该函数大致进行设置的顺序是：设置视口变换（ViewPort)、设置投影变换矩阵和 设置模型视图变换矩阵。我们分别来看：</font></p>
<p><font face="Courier New">&nbsp;<b>* 设置视口(ViewPort)</b></font></p>
<p><font face="Courier New">void CCDirector::setViewport()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; if (m_pobOpenGLView)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pobOpenGLView-&gt;<b>setViewPortInPoints</b>(0, 0,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_obWinSizeInPoints.width,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_obWinSizeInPoints.height);<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">void CCEGLViewProtocol::setViewPortInPoints(float x ,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; float y , float w , float h)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; <b>glViewport</b>((GLint)(x * m_fScaleX<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + m_obViewPortRect.origin.x),<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (GLint)(y * m_fScaleY<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + m_obViewPortRect.origin.y),<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (GLsizei)(w * m_fScaleX),<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (GLsizei)(h * m_fScaleY));<br />
	}</font></p>
<p><font face="Courier New">这是我们遇到的第一个OpenGL概念：设置视口变换，关于视口变换究竟起到什么作用，后续会细说。</font></p>
<p><font face="Courier New">&nbsp;<b>* 设置&ldquo;投影变换&rdquo;矩阵参数</b></font></p>
<p><font face="Courier New">&nbsp;kmMat4PerspectiveProjection( &amp;<b>matrixPerspective</b>, 60,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (GLfloat)size.width/size.height, 0.1f, zeye*2);<br />
	&nbsp;kmGLMultMatrix(&amp;matrixPerspective);</font></p>
<p><font face="Courier New">&nbsp;<b>* 设置&ldquo;模型视图变换&rdquo;矩阵参数</b></font></p>
<p><font face="Courier New">&nbsp;kmVec3 eye, center, up;<br />
	&nbsp;kmVec3Fill( &amp;eye, size.width/2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; size.height/2, zeye );<br />
	&nbsp;kmVec3Fill( &amp;center, size.width/2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; size.height/2, 0.0f );<br />
	&nbsp;kmVec3Fill( &amp;up, 0.0f, 1.0f, 0.0f);<br />
	&nbsp;kmMat4LookAt(&amp;<b>matrixLookup</b>, &amp;eye,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;center, &amp;up);</font></p>
<p><font face="Courier New">至此，引擎的绘制参数初始化设置就OK了，在你调用setDesignResolutionSize之前，这些参数不会被改变。</font></p>
<p><b>二、kazmath</b></p>
<p>Cocos2d-x引擎最底层采用OpenGL ES 2.0进行图形绘制，这样要想搞清楚前面的问题缘由，对OpenGL那一套技术体系至少要有一些直观认识才行。在这之前，我们还要先了解一些 Cocos2d-x深度使用的kazmath库。<font face="Courier New">根据《<a href="http://book.douban.com/subject/24704115/">Cocos2d-x高级开发教程</a>》书 中说: &ldquo;因为在Cocos2d-x 2.0采用的OpenGL ES 2.0中，而那些OpenGL ES 1.0函数已经不可使用了。但OpenGL ES 2.0已经放弃了固定的渲染流水线，取而代之的是自定义的各种着色器，在这种情况下变换操作通常需要由开发者来维护。所幸引擎也引入了一套第三方库 Kazmath，它使得我们几乎可以按照原来OpenGL ES 1.0所采用的方式进行开发&rdquo;。</font></p>
<p><font face="Courier New">至此，我们大致知道了Kazmath库是用来辅助我们按照OpenGL ES 1.0的方式管理变换矩阵以及做变换操作的，接下来我们一起来看看kazmath库的结构吧：</font></p>
<p><font face="Courier New">//cocos2d-x-2.2.2/cocos2dx/kazmath/src/GL/matrix.c</font></p>
<p><font face="Courier New">km_mat4_stack modelview_matrix_stack;<br />
	km_mat4_stack projection_matrix_stack;<br />
	km_mat4_stack texture_matrix_stack;<br />
	km_mat4_stack* current_stack = NULL;<br />
	static unsigned char initialized = 0;</font></p>
<p><font face="Courier New">以上是Cocos2d-x整个引擎生命周期内会用到的与opengl变换矩阵相关的一些全局变量。</font></p>
<p><font face="Courier New">kazmath声明了三个变换矩阵的栈，modelview_matrix_stack（模型视图矩阵栈）、 projection_matrix_stack（投影矩阵栈）以及texture_matrix_stack（纹理矩阵栈）。不过Cocos2d-x引 擎只用到了前两个变化矩阵栈。current_stack指向当前所使用的那个变换矩阵栈。</font></p>
<p><font face="Courier New">这些栈的初始化在lazyInitialize中：</font></p>
<p><font face="Courier New">void lazyInitialize()<br />
	{</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; if (!initialized) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kmMat4 identity; //Temporary identity matrix</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Initialize all 3 stacks<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //modelview_matrix_stack =<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (km_mat4_stack*) malloc(sizeof(km_mat4_stack));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; km_mat4_stack_initialize(&amp;modelview_matrix_stack);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //projection_matrix_stack =<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (km_mat4_stack*) malloc(sizeof(km_mat4_stack));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; km_mat4_stack_initialize(&amp;projection_matrix_stack);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //texture_matrix_stack =<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (km_mat4_stack*) malloc(sizeof(km_mat4_stack));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; km_mat4_stack_initialize(&amp;texture_matrix_stack);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; current_stack = &amp;<b>modelview_matrix_stack</b>;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; initialized = 1;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>kmMat4Identity(&amp;identity);</b></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Make sure that each stack has the identity matrix<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; km_mat4_stack_push(&amp;modelview_matrix_stack, &amp;identity);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; km_mat4_stack_push(&amp;projection_matrix_stack, &amp;identity);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; km_mat4_stack_push(&amp;texture_matrix_stack, &amp;identity);<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">kmMat4Identify用于初始化&ldquo;单位矩阵(Indentify Matrix)&rdquo;，所谓&quot;单位矩阵&quot;，指的是对脚线上元素都为1的矩阵。从kmMat4Identify的实现，我们也可以看出这一点：</font></p>
<p><font face="Courier New">kmMat4* const kmMat4Identity(kmMat4* pOut)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; memset(pOut-&gt;mat, 0, sizeof(float) * 16);<br />
	&nbsp;&nbsp;&nbsp; <b>pOut-&gt;mat[0] = pOut->mat[5]<br />
	&nbsp; &nbsp;&nbsp; = pOut-&gt;mat[10]<br />
	&nbsp;&nbsp;&nbsp;&nbsp; = pOut-&gt;mat[15] = 1.0f;</b><br />
	&nbsp;&nbsp;&nbsp; return pOut;<br />
	}</font></p>
<p><font face="Courier New">最后，lazyInitialize函数将单位矩阵分别圧入（km_mat4_stack_push）不同的matrix stack。</font></p>
<p><font face="Courier New">再回顾一下CCDirector::setProjection，该函数通过kazmath先后设置了 projection_matrix_stack和modelview_matrix_stack的top元素。</font></p>
<p><font face="Courier New">&nbsp;&nbsp; kmGLMatrixMode(<b>KM_GL_PROJECTION</b>);<br />
	&nbsp;&nbsp; kmGLLoadIdentity();<br />
	&nbsp;&nbsp; <b>kmMat4PerspectiveProjection</b>( &amp;matrixPerspective, 60,<br />
	&nbsp;&nbsp;&nbsp;&nbsp; (GLfloat)size.width/size.height, 0.1f, zeye*2);<br />
	&nbsp;&nbsp; kmGLMultMatrix(&amp;matrixPerspective);<br />
	&nbsp;&nbsp;<br />
	&nbsp;&nbsp; kmGLMatrixMode(<b>KM_GL_MODELVIEW</b>);<br />
	&nbsp;&nbsp; kmGLLoadIdentity();<br />
	&nbsp;&nbsp; kmVec3 eye, center, up;<br />
	&nbsp;&nbsp; kmVec3Fill( &amp;eye, size.width/2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; size.height/2, zeye );<br />
	&nbsp;&nbsp; kmVec3Fill( &amp;center, size.width/2,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; size.height/2, 0.0f );<br />
	&nbsp;&nbsp; kmVec3Fill( &amp;up, 0.0f, 1.0f, 0.0f);<br />
	&nbsp;&nbsp; <b>kmMat4LookAt</b>(&amp;matrixLookup, &amp;eye,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;center, &amp;up);<br />
	&nbsp;&nbsp; kmGLMultMatrix(&amp;matrixLookup);</font></p>
<p><font face="Courier New"><b>三、精灵绘制</b></font></p>
<p><font face="Courier New">由《<a href="http://tonybai.com/2014/03/11/hello-cocos2dx/">Hello，Cocos2d-x</a>》一文我们知道，一旦引擎初始化完毕，就开始了每帧图像的绘制工作，Render Thread在一个&ldquo;死循环&rdquo;中反复调用CCDirector的drawScene方法 （CCDisplayLinkDirector::mainLoop中调用了drawScene）：</font></p>
<p><font face="Courier New">void CCDirector::drawScene(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; glClear(GL_COLOR_BUFFER_BIT<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | GL_DEPTH_BUFFER_BIT);<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; <b>kmGLPushMatrix</b>();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // draw the scene<br />
	&nbsp;&nbsp;&nbsp; if (m_pRunningScene)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pRunningScene-&gt;<b>visit</b>();<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; <b>kmGLPopMatrix</b>();<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p><font face="Courier New">Cocos2d-x采用&ldquo;渲染树&rdquo;的方式进行绘制，即先从场景(Scene)的顶层根节点开始，深度优先的递归绘制Child Node。而整个绘制的顶层节点是CCScene。绘制从m_pRunningScene-&gt;visit()真正开始。visit是Scene、 Layer、Sprite的共同父类CCNode实现的方法：</font></p>
<p><font face="Courier New">void CCNode::visit()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; if (!m_bVisible)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; <b>kmGLPushMatrix</b>();<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; this-&gt;<b>transform</b>();<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; if(m_pChildren &amp;&amp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pChildren-&gt;count() &gt; 0)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; sortAllChildren();<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // <b>draw children zOrder</b> &lt; 0<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8230; ..<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // self draw<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this-&gt;<b>draw</b>();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // <b>draw other children nodes</b><br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this-&gt;draw();<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; <b>kmGLPopMatrix</b>();<br />
	}<br />
	&nbsp;&nbsp;&nbsp;<br />
	Visit大致做了这么几件事：<br />
	&nbsp;&nbsp;&nbsp; &#8211; 向当前OpenGL变换矩阵栈Push元素<br />
	&nbsp;&nbsp;&nbsp; &#8211; 用当前OpenGL变换矩阵栈栈顶元素的变换参数做节点变换<br />
	&nbsp;&nbsp;&nbsp; &#8211; 递归绘制zOrder &lt; 0 的子节点<br />
	&nbsp;&nbsp;&nbsp; &#8211; 绘制自己<br />
	&nbsp;&nbsp;&nbsp; &#8211; 递归绘制其他子节点<br />
	&nbsp;&nbsp;&nbsp; &#8211; 从当前OpenGL变换矩阵栈Pop元素</font></p>
<p><font face="Courier New">如果你想知道为什么父节点缩放(Scale)、旋转(Rotate)、扭曲(Skew)后，子节点也会跟着父节点同样缩放(Scale)、旋 转(Rotate)、扭曲？其原理就在这里的transform方法中：</font></p>
<p><font face="Courier New">void CCNode::transform()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; kmMat4 transfrom4x4;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // Convert 3&#215;3 into 4&#215;4 matrix<br />
	&nbsp;&nbsp;&nbsp; CCAffineTransform tmpAffine<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = this-&gt;nodeToParentTransform();<br />
	&nbsp;&nbsp;&nbsp; CGAffineToGL(&amp;tmpAffine,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; transfrom4x4.mat);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // Update Z vertex manually<br />
	&nbsp;&nbsp;&nbsp; transfrom4x4.mat[14] = m_fVertexZ;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; kmGLMultMatrix( &amp;transfrom4x4 );<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p><font face="Courier New">在进入tranform以前，Cocos2d-x做了啥？对了，kmGLPushMatrix()：</font></p>
<p><font face="Courier New">void kmGLPushMatrix(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; kmMat4 top;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; lazyInitialize();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; //<b>Duplicate the top of the stack (i.e the current matrix)</b><br />
	&nbsp;&nbsp;&nbsp; kmMat4Assign(&amp;top, current_stack-&gt;top);<br />
	&nbsp;&nbsp;&nbsp; km_mat4_stack_push(current_stack, &amp;top);<br />
	}</font></p>
<p><font face="Courier New">在引擎初始化后，我们的current_stack是模型视图矩阵栈modelview_matrix_stack。所有设置的初始参数都保 存在该栈的栈顶元素中。在每次Node绘制前，Node都会创建自己的变换矩阵，但这个矩阵不是凭空创造的，从kmGLPushMatrix 可以看出，在当前Node将新创建的矩阵元素圧栈前，它复制了原栈顶元素，也就<b>携带有父节点所有的初始变换信息</b>，也就是说在 km_mat4_stack_push后，栈顶放置的元素其实是原栈顶元素的复制品，而后续所有操作都是基于这个复制品的。这样一来，如果父 节点做了缩放或旋转或扭曲，那这些信息都会作为初始信息作为子节点变换的基础，后续子节点自身的变换参数也都是在这个基础上做出的，最终的矩 阵是transform方法中的kmGLMultMatrix后得出的。真正的矩阵变换计算都在nodeToParentTransform 中，不过要想看懂这个函数，需要对OpenGL有更深入的了解才行，这里略过^_^。</font></p>
<p><font face="Courier New">真正绘制Node的方法是CCNode::draw的override方法。CCNode::draw是一个空函数，各个子类 override该方法进行各自的绘制。以CCSprite::draw为例：</font></p>
<p><font face="Courier New">void CCSprite::draw(void)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; CC_NODE_DRAW_SETUP();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; ccGLBlendFunc( m_sBlendFunc.src, m_sBlendFunc.dst );</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; ccGLBindTexture2D( m_pobTexture-&gt;getName() );<br />
	&nbsp;&nbsp;&nbsp; ccGLEnableVertexAttribs( kCCVertexAttribFlag_PosColorTex );</font></p>
<p><font face="Courier New">#define kQuadSize sizeof(m_sQuad.bl)<br />
	&nbsp;&nbsp;&nbsp; long offset = (long)&amp;m_sQuad;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // vertex<br />
	&nbsp;&nbsp;&nbsp; int diff = offsetof( ccV3F_C4B_T2F, vertices);<br />
	&nbsp;&nbsp;&nbsp; glVertexAttribPointer(kCCVertexAttrib_Position, 3,<br />
	&nbsp;&nbsp;&nbsp;&nbsp; GL_FLOAT, GL_FALSE, kQuadSize, (void*) (offset + diff));</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // texCoods<br />
	&nbsp;&nbsp;&nbsp; diff = offsetof( ccV3F_C4B_T2F, texCoords);<br />
	&nbsp;&nbsp;&nbsp; glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2,<br />
	&nbsp; &nbsp; &nbsp; GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff));</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; // color<br />
	&nbsp;&nbsp;&nbsp; diff = offsetof( ccV3F_C4B_T2F, colors);<br />
	&nbsp;&nbsp;&nbsp; glVertexAttribPointer(kCCVertexAttrib_Color, 4,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GL_UNSIGNED_BYTE, GL_TRUE,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kQuadSize, (void*)(offset + diff));</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p><font face="Courier New">这里的draw是一个典型的OpenGL绘制工序。CC_NODE_DRAW_SETUP()将之前的经过若干准备而得到的最终各类变换矩阵 整合并传给OpenGL：</font></p>
<p><font face="Courier New">/** @def CC_NODE_DRAW_SETUP<br />
	&nbsp;Helpful macro that setups the GL server state,<br />
	&nbsp;the correct GL program and sets the Model View<br />
	&nbsp;Projection matrix<br />
	&nbsp;@since v2.0<br />
	&nbsp;*/<br />
	#define CC_NODE_DRAW_SETUP() \<br />
	do { \<br />
	&nbsp;&nbsp;&nbsp; ccGLEnable(m_eGLServerState); \<br />
	&nbsp;&nbsp;&nbsp; CCAssert(getShaderProgram(), &quot;No shader program set for this node&quot;); \<br />
	&nbsp;&nbsp;&nbsp; { \<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getShaderProgram()-&gt;use(); \<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getShaderProgram()-&gt;<b>setUniformsForBuiltins</b>(); \<br />
	&nbsp;&nbsp;&nbsp; } \<br />
	} while(0)</font></p>
<p><font face="Courier New">void CCGLProgram::setUniformsForBuiltins()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; kmMat4 matrixP;<br />
	&nbsp;&nbsp;&nbsp; kmMat4 matrixMV;<br />
	&nbsp;&nbsp;&nbsp; kmMat4 matrixMVP;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; kmGLGetMatrix(KM_GL_PROJECTION, &amp;matrixP);<br />
	&nbsp;&nbsp;&nbsp; kmGLGetMatrix(KM_GL_MODELVIEW, &amp;matrixMV);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; kmMat4Multiply(&amp;matrixMVP, &amp;matrixP, &amp;matrixMV);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; setUniformLocationWithMatrix4fv(m_uUniforms[kCCUniformPMatrix],<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; matrixP.mat, 1);<br />
	&nbsp;&nbsp;&nbsp; setUniformLocationWithMatrix4fv(m_uUniforms[kCCUniformMVMatrix],<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; matrixMV.mat, 1);<br />
	&nbsp;&nbsp;&nbsp; setUniformLocationWithMatrix4fv(m_uUniforms[kCCUniformMVPMatrix],<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; matrixMVP.mat, 1);<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p><font face="Courier New">经过计算顶点、绑定纹理等步骤后，最终由glDrawArrays完成Node绘制。</font></p>
<p><font face="Courier New"><b>四、m_fScaleX和m_fScaleY都是1.0，背景精灵为何被放大？</b></font></p>
<p><font face="Courier New">根据上面的分析，我们了解到&ldquo;子节点将跟随父节点的缩放而缩放&rdquo;。据此，我们来分析一下前面提到的屏幕适配例子中的第三种情况，即屏幕大小为 960&#215;640，按照Cocos2d-x屏幕适配指南Wiki中的做法，调用 pEGLView-&gt;setDesignResolutionSize(480, 320)。在该情况中，我们得到的结果是480&#215;320大小的背景图片充满了大小为960&#215;640的屏幕窗口，这给我们的直观印象就是背景图片被放大了一 倍。下面我们就尝试用上面的分析来解释一下这个现象。</font></p>
<p><font face="Courier New">在这个例子中，渲染树结构如下：<br />
	&nbsp;&nbsp; CCScene<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; CCLayer<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; CCSprite &#8211; 背景图精灵</font></p>
<p><font face="Courier New">按照之前的理论，背景图精灵自身或父类应该有缩放的设置，比如m_fScaleX = 2.0之类的设置，于是我在代码中输出了Scene、Layer以及Sprite的m_fScaleX和m_fScaleY值。但出乎预料的是，这些 Node子类的两个轴向缩放值都保持了默认值，即1.0f。在代码里翻了半天，也的确没有找到改写Scene、Layer或Sprite Scale的地方。又一想：代码中调用了setDesignResolutionSize，这样CCEGLView的m_fScaleX = m_fScaleY = 2.0f，难道是CCEGLView的m_fScale传递给了CCScene等Node子类，但事实总是残酷的，代表这一联系的代码也始终未被我所找 到，看来继续纠结m_fScale的值设置是无法搞清楚真正原因，应该换换思路了。这里背景图的放大不应该是Node scale值设置的问题，也就是说关键环节不应该在绘制流程，而是在之前的OpenGL变换矩阵参数设置，看来不再深入学习点OpenGL知识，这个问题 就很难搞定了，于是开始翻看《<a href="http://book.douban.com/subject/4311129/">OpenGL编程指南</a>7th》（号称OpenGL红宝书）和《<a href="http://book.douban.com/subject/10774590/">OpenGL超级宝典</a>》（号称OpenGL蓝宝 书）。虽然我的阅读是粗粒度的，但还是收获到了一些答案。</font></p>
<p><font face="Courier New"><b>五、OpenGL基础</b></font></p>
<p><font face="Courier New">OpenGL是帮助我们将三维世界的物体转换到二维屏幕上的一组接口。在新技术尚未出现之前，我们的屏幕永远是二维的，即便是现在的3D电影 也是双眼视角二维图像叠加的结果。我们知道&ldquo;</font><font face="Courier New">将大象装进冰箱总共分三 步&rdquo;，将一个三维模型转换到二维屏幕上，OpenGL也规定了相对流水线般的步骤。</font></p>
<p><font face="Courier New"><img alt="" src="/wp-content/uploads/opengl-display-process.jpg" style="width: 480px; height: 111px;" /></font></p>
<p><font face="Courier New">OpenGL三维图形的显示流程</font></p>
<p><font face="Courier New">三维图形显示流程中，涉及到OpenGL的一个重要操作，那就是&ldquo;变换(Transformation)&rdquo;，主要的变换包括模型视图变换 （model-view transformation）、投影变换(projection transformation)以及视口变换(ViewPort transformation)。</font><font face="Courier New">我们经常用相机模拟来对比OpenGL解决这一问题的过程以及相关概念。</font></p>
<p><font face="Courier New"><img alt="" src="/wp-content/uploads/opengl-transforms.jpg" style="width: 480px; height: 765px;" /></font></p>
<p><font face="Courier New">回顾一下我们自己用相机拍照的步骤吧。</font></p>
<p><font face="Courier New">第零步，选景。景就是所谓的三维模型或三维物体，或简称<b>模型</b>(Model)，就是我们要显示到屏幕上的物体；<br />
	第一步，确定相机位置。让相机以一定的距离、高度、角度对准模型。在这里，相机的位置变换，对应OpenGL的&ldquo;视图变换或叫视点变换 (View Transformation)&rdquo;。在这一步里（对应上面图中的第二步），我们还可以调整三维物体的相对位置、角度与相机的距离，这就是模型变换 （Modeling Transformation），两种变换达成的效果是相同的，因此总称模型视图变换(Model-View Transformation)。<br />
	第二步，选镜头，并调焦。确定图像投影在胶片上的范围以及景深等。这一步叫投影变换（Projection Transformation）。<br />
	第三步，冲洗照片。拍摄好的图像放在底片上，但我们需要选择冲洗后最终是放在6寸相纸还是20寸相纸上，显然在不同大小相纸上，图像的显示效 果不同（比如大小）。这个过程叫视口变换（Viewport Transformation）。</font></p>
<p><font face="Courier New"><tt>三维空间的物体都是用三维坐标描述的，谈到坐标就离不开坐标系，OpenGL中的坐标系就有多种，我们最常用的就是世界坐标系。</tt></font></p>
<p><font face="Courier New"><tt>世界坐标系是以屏幕中心为原点(0, 0, 0)，你面对屏幕，你的右边是x正轴，上面是y正轴，屏幕指向你的为z正轴。</tt></font><font face="Courier New"><tt>无论如何变换，世界坐标系都不动。</tt></font>我们在Cocos2d-x中设置 初始参数时，参数的单位多为世界坐标系中的单位。</p>
<p>视点变换时会涉及到视点坐标系，但这个变换由opengl接口来负责，我们不用过多关心。</p>
<p>绘图坐标系（局部坐标系），当前绘图坐标系是绘制物体时的坐标系。程序刚初始化时，世界坐标系和当前绘图坐标系是重合的，当用 glTranslatef()等变换函数做移动和旋转时，都是改变的当前绘图坐标系，改变的位置都是当前绘图坐标系相对自己的x,y,z轴所做的 改变，改变以后，再绘图时，都是在当前绘图坐标系进行绘图，所有的函数参数也都是相对当前绘图坐标系来讲的。</p>
<p>屏幕坐标系，即终端屏幕上的坐标系，与世界坐标系有不同，它以屏幕左上角的点为原点，向右是x正轴，向下是y正轴，屏幕指向你的为z正轴。</p>
<p>注意视口(Viewport)的设置是以实际屏幕坐标定义了窗口中的区域，长度宽度都是以<b>实际像素</b>为单位。当然引擎在精灵绘图时用 的是绘图坐标系，我们理解原点在左下角即可。</p>
<p><b>六、Cocos2d-x各种变换矩阵的初始参数设置</b></p>
<p>前面说过，Cocos2d-x在CCDirector::setProjection中完成了对变换矩阵的初始参数设置，我们逐一来看看这些设置对模型映射后的二维图像有何影响，这也是理解篇头几个问题的关键环节。</p>
<p>&nbsp; <b>* 投影变换</b><br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 前面提到过，投影变换相当于调节相机镜头。OpenGL中提供了两种投影方式，一种是正射投影，另一种是透视投影。Cocos2d-x使用的是透视投影 （Perspective Projection)。透视投影是实际人们观察事物的真实反馈，即离视点近的物体大，离视点远的物体小，远到极点即为消失，成为灭点。Cocos2d- x使用的是kmMat4PerspectiveProjection，对应OpenGL中的gluPerspective，该方法创建一个对称透视视景体 (View Volumn)，见下图：</p>
<p><img alt="" src="/wp-content/uploads/projection.jpg" style="width: 480px; height: 208px;" /></p>
<p>gluPerspective的函数原型如下：<font face="Courier New">void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear, GLdouble zFar);</font></p>
<p>&nbsp;&nbsp;&nbsp; 参数fovy定义视野在X-Z平面的角度，范围是[0.0, 180.0]，也就是上图中的&ldquo;视角&rdquo;；<br />
	&nbsp;&nbsp;&nbsp; 参数aspect是投影平面宽度与高度的比率；<br />
	&nbsp;&nbsp;&nbsp; 参数zNear和Far分别是近远裁剪面沿Z负轴到视点的距离，它们总为正值。<br />
	&nbsp;&nbsp;<br />
	Cocos2d-x中是这么设置投影变换矩阵的：</p>
<p><font face="Courier New">&nbsp; float zeye = this-&gt;getZEye();<br />
	&nbsp; kmMat4PerspectiveProjection( &amp;matrixPerspective, 60, (GLfloat)size.width/size.height, 0.1f, zeye*2);</font></p>
<p><font face="Courier New">&nbsp; float CCDirector::getZEye(void)<br />
	&nbsp; {<br />
	&nbsp;&nbsp;&nbsp; return (m_obWinSizeInPoints.height / 1.1566f);<br />
	&nbsp; }</font></p>
<p><font face="Courier New">从参数上来看，<br />
	&nbsp;&nbsp;&nbsp; 视角 = 60度<br />
	&nbsp;&nbsp;&nbsp; 宽高比 = 设计分辨率的宽高比，<br />
	&nbsp;&nbsp;&nbsp; 近平面 = 距离视点0.1f，几乎与视点重合<br />
	&nbsp;&nbsp;&nbsp; 远平面 = 距离视点zeye * 2距离。<br />
	&nbsp;&nbsp;&nbsp; 视点位置 = 设计分辨率.height / 1.1566f</font></p>
<p><font face="Courier New">投影是用来对模型进行截取的，只有在投影变换所建立的平头截体（Frustum，投影的近、远两个截面以及其他四个面构成的立体体）内的模型部分才会被最终映射和显示。我们用下面的图来直观了解一下各个参数在三维空间的概念吧。</font></p>
<p><font face="Courier New"><img alt="" src="/wp-content/uploads/projection-1.jpg" style="width: 480px; height: 366px;" /></font></p>
<p><font face="Courier New">显然引擎如此设置投影矩阵的参数是有考虑的：<br />
	首先就是投影平头截体的宽高比 = 设计分辨率的宽高比，这样设置使得一切符合设计分辨率宽高比的模型都可以被理想截取。<br />
	其次，视角60度，zEye的在Z轴正方向距离世界原点的距离 = (m_obWinSizeInPoints.height / 1.1566f),这里的1.1566f是怎么来的呢？我们沿着X轴负方向向zy平面投影，得到下图：</font></p>
<p><font face="Courier New"><img alt="" src="/wp-content/uploads/projection-2.jpg" style="width: 480px; height: 388px;" /></font></p>
<p><font face="Courier New">看这个图，让我想起了初中几何，通过60度的视角，我们可以推断由eye、XZ截断上平面与Y轴的交点、XZ截断下平面与Y轴的交点组成一个等边三角形， 现在我们已知在Zy平面投影中视点与原点的距离为m_obWinSizeInPoints.height / 1.1566f, 我们还知道夹角是60度，我们求一下投影在(z=0，XY平面)的截面高度h。</font></p>
<p><font face="Courier New">cos30 = (</font><font face="Courier New">m_obWinSizeInPoints.height / 1.1566f)/ h<br />
	h = (m_obWinSizeInPoints.height / 1.1566f)/cos30 = m_obWinSizeInPoints.height;</font></p>
<p><font face="Courier New">我们计算出来的结果是 h = m_obWinSizeInPoints.height = 设计分辨率中的高度分量。这意味这什么呢？Cocos2d-x是2D游戏渲染引擎，针对该引擎的模型的z坐标都是0，因此模型实际上就在xy平面内，也就 是说eye与原点的距离恰好就是eye与模型的距离，而模型可显示区域的最大高度也就是h，即m_obWinSizeInPoints.height。这 个结论会在后续问题分析时发挥作用。</font></p>
<p><font face="Courier New">注意虽然这里知道eye在Z轴正方向距离世界原点的距离，但eye的(x, y)坐标在投影设置后依旧无法确认，我们需要在设置模型视图变换时得到eye的(x, y)坐标。</font></p>
<p><font face="Courier New">&nbsp; <b>* 视图变换</b></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; kmGLMatrixMode(KM_GL_MODELVIEW);<br />
	&nbsp;&nbsp;&nbsp; kmGLLoadIdentity();<br />
	&nbsp;&nbsp;&nbsp; kmVec3 eye, center, up;<br />
	&nbsp;&nbsp;&nbsp; kmVec3Fill( &amp;eye, size.width/2, size.height/2, zeye );<br />
	&nbsp;&nbsp;&nbsp; kmVec3Fill( &amp;center, size.width/2, size.height/2, 0.0f );<br />
	&nbsp;&nbsp;&nbsp; kmVec3Fill( &amp;up, 0.0f, 1.0f, 0.0f);<br />
	&nbsp;&nbsp;&nbsp; kmMat4LookAt(&amp;matrixLookup, &amp;eye, &amp;center, &amp;up);<br />
	&nbsp;&nbsp;&nbsp; kmGLMultMatrix(&amp;matrixLookup);</font></p>
<p><font face="Courier New">OpenGL原生的视图变换参数设置方法是gluLookAt，在kazmath中对应的方法为kmMat4LookAt。gluLookAt的函数原型是：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; void gluLookAt(GLdouble eyex, GLdouble exey, GLdouble eyez,<br />
	&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; GLdouble centrex, GLdouble centrey, GLdouble centrez,<br />
	&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; GLdouble upx, GLdouble upy, GLdouble upz);</font></p>
<p><font face="Courier New">eye的坐标(eyex, eyey, eyez), Cocos2d-x中是这么设置的kmVec3Fill( &amp;eye, size.width/2, size.height/2, zeye )。可以看出eye在xy平面的投影恰好是以屏幕分辨率构成的矩形的中心。</font></p>
<p><font face="Courier New">centre坐标，表示的是视线方向，该方向矢量是由eye坐标、centre坐标共同构成的，由eye指向center。Cocos2d-x的设置 kmVec3Fill( &amp;center, size.width/2, size.height/2, 0.0f )。x, y坐标与eye的相同，因此视线平行于Z轴。</font></p>
<p><font face="Courier New">最后的up参数可以理解为头顶方向，这里设置为Y轴方向。</font></p>
<p><font face="Courier New"><img alt="" src="/wp-content/uploads//view-transform.jpg" style="width: 480px; height: 328px;" /></font></p>
<p><font face="Courier New">可以看出，eye就在投影区的中心，由于投影区的高度为size.height(投影变换时分析得到的)，这样根据投影矩阵设置的宽高比，得出该投影区的宽度也恰为size.width。</font></p>
<p><font face="Courier New"><b>七、再分析</b></font></p>
<p><font face="Courier New">有了以上关于Cocos2d-x引擎的了解，我们再回过头来用OpenGL的变换原理对篇头的三种情况做分析。</font></p>
<p><font face="Courier New">&nbsp;1) 屏幕大小480&#215;320，未做任何屏幕适配工作，不调用pEGLView-&gt;setDesignResolutionSize。结果：背景图充满窗口。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 在这种情况下，各个OpenGL变换矩阵参数值如下：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; eye视点坐标(240, 160, 320/1.1566f);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 投影变换矩阵在xy平面的截面区域恰好是480&#215;320；<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 背景图锚点位置(240, 160, 0)；</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 在这种情况下，截面区域恰与背景图重合，显示在屏幕上后，背景图恰充满窗口，见下图：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; <img alt="" src="/wp-content/uploads/case1.jpg" style="width: 480px; height: 328px;" /><br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;2) 屏幕大小960&#215;640，未做任何屏幕适配工作，不调用pEGLView-&gt;setDesignResolutionSize。结果：背景图未充满窗口，四周有较大黑边。<br />
	&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 在这种情况下，各个OpenGL变换矩阵参数值如下：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; eye视点坐标(480, 320, 480/1.1566f);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 投影变换矩阵在xy平面的截面区域是960&#215;640；<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 而背景图锚点位置(480, 320, 0)；</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 因此背景图(480&#215;320)未能完整充满截面区域(960&#215;640)，背景图周围将有较大黑边，见下图：<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;<img alt="" src="/wp-content/uploads/case2.jpg" style="width: 480px; height: 328px;" />&nbsp;</font></p>
<p><font face="Courier New">&nbsp;3) 屏幕大小同为960&#215;640，按照上面Cocos2d-x屏幕适配指南Wiki中的做法，调用pEGLView-&gt;setDesignResolutionSize(480, 320)。结果：背景图放大为原来2倍，充满屏幕窗口。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 在这种情况下，各个OpenGL变换矩阵参数值如下：<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; eye视点坐标(240, 160, 320/1.1566f);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 投影变换矩阵在xy平面的截面区域是480&#215;320；<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 而背景图锚点位置(240, 160, 0)；</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 在这种情况下，截面区域恰与背景图重合。但这里需要注意的是现在屏幕是960&#215;640，而截面区域仅仅是480&#215;320，为何映射后，背景图充满屏幕了呢？这里就不能不提到视口的作用了。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 前面说过视口相当于相片，现在我们拍摄出的图片是480&#215;320的，但我们选择的底片Viewport却是960&#215;640的，怎么办，在视口转换 时，OpenGL自动将480&#215;320的图片映射到960&#215;640的底片上，相当于对图像进行的放大。而960&#215;640的视口恰好与屏幕窗口大小一致且坐 标重叠，于是我们就在屏幕上看到了一个铺满屏幕的背景图，见下图：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; <img alt="" src="/wp-content/uploads/case3.jpg" style="width: 480px; height: 500px;" /></font></p>
<p><font face="Courier New">&nbsp;4) 我们再来说两个有关视口的例子</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 以第三种情况为基础，我们修改一下引擎代码，看看视口的作用。<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 我们手工将CCDirector::setViewport()中的：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; m_pobOpenGLView-&gt;setViewPortInPoints(0, 0, m_obWinSizeInPoints.width, m_obWinSizeInPoints.height);<br />
	&nbsp;&nbsp;&nbsp; 改为：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; m_pobOpenGLView-&gt;setViewPortInPoints(0, 0, m_obWinSizeInPoints.width/2, m_obWinSizeInPoints.height/2);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 这样修改后，Viewport从point(0,0), rect (960&#215;640)变成了point(0,0), rect (480&#215;320)。也就是说用照相机拍出的景物大小是480&#215;320，底片也是480&#215;320，但屏幕是960&#215;640，我们可以将屏幕理解为相框，把 一张480&#215;320的照片，放到960&#215;640大小的相框里，相片只能占据相框的四分之一。这个例子的最终屏幕显示结果见下图：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; <img alt="" src="/wp-content/uploads/case4.jpg" style="width: 480px; height: 500px;" /></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 前面的例子中背景图片size均小于屏幕大小，我们再来举一个资源图片大于屏幕大小的例子，看看经过一系列变换会得到什么样的结果。<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 首先将CCDirector::setViewport()中的代码恢复原先状态。然后我们准备一张1024&#215;768(&gt;屏幕的960&#215;640)的 背景图片&quot;HelloWorld-1024&#215;768.jpg&quot;，修改HelloWorldScene.cpp，将：<br />
	&nbsp;&nbsp;&nbsp; CCSprite* pSprite = CCSprite::create(&quot;HelloWorld.png&quot;);<br />
	&nbsp;&nbsp;&nbsp; 修改为：<br />
	&nbsp;&nbsp;&nbsp; CCSprite* pSprite = CCSprite::create(&quot;<b>HelloWorld-1024&#215;768.png</b>&quot;);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 注释掉AppDelegate.cpp中的pEGLView-&gt;setDesignResolutionSize调用，这样更直观。</font></p>
<p><font face="Courier New">&nbsp; &nbsp; 这样修改后，各参数如下：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; eye视点坐标(480, 320, 640/1.1566f);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 投影变换矩阵在xy平面的截面区域是960&#215;640；<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 而背景图锚点位置(480, 320, 0)；<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Viewport point(0,0), rect (960&#215;640)<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 由于背景资源图片太大(1024&#215;768)，大于我们的投影截面区域960&#215;640，因此模型真正能显示的部分仅仅是投影截面区域中的那960&#215;640范围内的图片。于是显示结果如下：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; <img alt="" src="/wp-content/uploads//case-5-transform.jpg" style="width: 480px; height: 334px;" /></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 矩阵变换过程如下：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; <img alt="" src="/wp-content/uploads/case5.jpg" style="width: 480px; height: 325px;" /></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 投影截面区域与视口区域重叠，这里就不再赘述了。</font></p>
<p><font face="Courier New"><b>八、CCDirector::m_fContentScaleFactor</b></font></p>
<p><font face="Courier New">决定图像在屏幕上的最终显示结果的因素还有一个，那就是CCDirector::m_fContentScaleFactor。在最初的HelloCpp例子中，我们能看到这样的代码：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; if (frameSize.height &gt; mediumResource.size.height)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; searchPath.push_back(largeResource.directory);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pDirector-&gt;setContentScaleFactor(<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MIN(largeResource.size.height/designResolutionSize.height,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; largeResource.size.width/designResolutionSize.width));<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 可以看出这个contentScaleFactor存储的是资源分辨率与设计分辨率的比值。我们还是用例子来看看该元素对显示的影响。我们在第一种情况的基础上验证。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 第一种情况：屏幕480&#215;320，未调用setDesignResolutionSize，资源大小480&#215;320。结果：图片充满屏幕。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 现在我们增加并使用一个新资源：HelloWorld-960&#215;640.png，这个图片大小960&#215;640，是屏幕大小的二倍，根据上面的分析，我们很容易猜测到最终结果是：只有图片中央区域(480&#215;320)可以显示出来，其余部分被投影矩阵截掉。</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 现在我们使用setContentScaleFactor，在AppDelegate.cpp中做如下调用：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; pDirector-&gt;setContentScaleFactor(MIN(960/480, 640/320));</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 这样我们得到的m_fContentScaleFactor = 2。而我们编译运行后得到的结果是：图片铺满整个屏幕。为什么会这样呢？</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 我们在代码中搜索contentScaleFactor，我们找到一些宏和调用：</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; </font><br />
	<font face="Courier New">#define CC_CONTENT_SCALE_FACTOR() CCDirector::sharedDirector()-&gt;getContentScaleFactor()</font></p>
<p><font face="Courier New">CCSize CCTexture2D::getContentSize()</font><br />
	<font face="Courier New">{</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; CCSize ret;<br />
	&nbsp;&nbsp;&nbsp; ret.width = <b>m_tContentSize.width / CC_CONTENT_SCALE_FACTOR();</b><br />
	&nbsp;&nbsp;&nbsp; ret.height = <b>m_tContentSize.height / CC_CONTENT_SCALE_FACTOR();</b></font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; return ret;<br />
	}</font></p>
<p><font face="Courier New">#define CC_RECT_PIXELS_TO_POINTS(__rect_in_pixels__)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \</font><br />
	<font face="Courier New">&nbsp;&nbsp;&nbsp; CCRectMake( (__rect_in_pixels__).origin.x / CC_CONTENT_SCALE_FACTOR(), (__rect_in_pixels__).origin.y / CC_CONTENT_SCALE_FACTOR(),&nbsp;&nbsp;&nbsp; \<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (__rect_in_pixels__).size.width / CC_CONTENT_SCALE_FACTOR(), (__rect_in_pixels__).size.height / CC_CONTENT_SCALE_FACTOR() )</font><br />
	&#8230; &#8230;</p>
<p><font face="Courier New">bool CCSprite::initWithTexture(CCTexture2D *pTexture)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; CCAssert(pTexture != NULL, &quot;Invalid texture for sprite&quot;);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; CCRect rect = CCRectZero;<br />
	&nbsp;&nbsp;&nbsp; rect.size = pTexture-&gt;getContentSize();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; return initWithTexture(pTexture, rect);<br />
	}</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; 这些代码都在告诉我们，如果m_fContentScaleFactor = 2，那代码会对Sprite的纹理进行缩放，让上面得到的数据是经过contentScaleFactor变换的，我们可以认为我们所用的实际资源大小是 原资源的1/m_fContentScaleFactor即可。</font></p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/05/13/sprite-draw-principles-of-cocos2dx-screen-adaptation/feed/</wfw:commentRss>
		<slash:comments>17</slash:comments>
		</item>
		<item>
		<title>Cocos2d-x 3.0rc0集成Google AdMob SDK</title>
		<link>https://tonybai.com/2014/05/01/integrate-cocos2dx3rc0-with-admob/</link>
		<comments>https://tonybai.com/2014/05/01/integrate-cocos2dx3rc0-with-admob/#comments</comments>
		<pubDate>Wed, 30 Apr 2014 18:23:16 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AdMob]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[android-native-app-glue]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CGL]]></category>
		<category><![CDATA[Cocos2d]]></category>
		<category><![CDATA[Cocos2d-x]]></category>
		<category><![CDATA[Cocos2d-x-3.0]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[EGL]]></category>
		<category><![CDATA[Game-Engine]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Jni]]></category>
		<category><![CDATA[Mobile]]></category>
		<category><![CDATA[MobileGame]]></category>
		<category><![CDATA[NativeActivity]]></category>
		<category><![CDATA[NDK]]></category>
		<category><![CDATA[opengl]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Pthread]]></category>
		<category><![CDATA[SDK]]></category>
		<category><![CDATA[内存计数]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[多线程]]></category>
		<category><![CDATA[安卓]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[广告平台]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[性能优化]]></category>
		<category><![CDATA[手游]]></category>
		<category><![CDATA[架构]]></category>
		<category><![CDATA[游戏引擎]]></category>
		<category><![CDATA[源码分析]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[触控科技]]></category>
		<category><![CDATA[跨平台]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1531</guid>
		<description><![CDATA[话说Cocos2d-x 3.0上一周迫不及待地发布了正式版，本是一件值得庆幸的事情。但由于不可解决的技术问题，引擎无奈将Android平台的NativeActivity 实现重新回退到了Cocos2d-x 2.2.x版本的实现方案。由于之前已经将 GameDemo移植到了Cocos2d-x 3.0rc0版，直观感受到了NativeActivity方案带来的游戏操作体验上的提升（触屏事件的响应），因此心里总是&#8220;挂念&#8221;引擎的 NativeActivity方案。按照Cocos2d-x官方人士的说法，只要NDK的问题修复，NativeActivity还是未来引擎在 Android平台上的第一选择。我的理解，将来Cocos2d-x的某个版本中还会恢复NativeActivity的实现方案。 上次只是将GameDemo的核心逻辑移植到了Cocos2d-x 3.0rc0上，但一些外部SDK集成尚未做完，这两天闲遐时开始着手研究如何做，而首先要移植的就是Google AdMob SDK。关于Cocos2d-x 3.0rc0集成Google AdMob SDK，网上已经有了技术方案原型，最初应该是一个外国开发者提出的方案，后来被CocoaChina cocos2d-x社区版主翻译成了中文教程，这里基本上也是参考的这个方案，只是做了局部细化和进一步说明，希望能帮助大家进一步解惑。 一、功能说明 GameDemo游戏分为三个场景：WelcomeScene、GameScene以及EndScene。集成AdMob SDK的功能要求如下： &#160;&#160;&#160; &#8211; 当游戏进入到WelcomeScene时，AdMob SDK完成初始化，发出Ad Request，当收到Ad的时候才会在屏幕上方显示带有Ad的窗口； &#160;&#160;&#160; &#8211; 当点击Start进入到GameScene时，隐藏Ad窗口，以不干扰玩家的游戏操作为优先； &#160;&#160;&#160; &#8211; 当游戏Over进入到EndScene的时候，恢复显示Ad窗口； &#160;&#160;&#160; &#8211; 当玩家点击&#8220;Retry&#8221;回到GameScene时，隐藏Ad窗口。 二、方案原理 这个方案就是通过android.widget.PopupWindow实现广告窗口悬浮在当前窗口之上。按照Android官方Doc中的说 明，PopupWindow用于实现一个弹出框，它可以使用任意布局的View作为其内容，这个弹出框是悬浮在当前activity之上的。 至于PopupWindow为何能显示在当前Activity之上，可以查看PopupWindow的源码，大致思路就是PopupWindow通过 new时传入的context(当前Activity)获得了当前WindowManager，并将AdView作为子View添加到该窗口中。这里简要列出一些关键源码，大家可粗略理解一下： &#160;&#160;&#160; public PopupWindow(Context context, AttributeSet attrs, &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; int defStyleAttr, int defStyleRes) { &#160;&#160;&#160;&#160;&#160;&#160;&#160; mContext = context; &#160;&#160;&#160;&#160;&#160;&#160;&#160; [...]]]></description>
			<content:encoded><![CDATA[<p>话说<a href="http://www.cocos2d-x.org">Cocos2d-x</a> 3.0上一周迫不及待地发布了<a href="http://www.cocos2d-x.org/news/215">正式版</a>，本是一件值得庆幸的事情。但由于不可解决的<a href="http://www.cocos2d-x.org/forums/6/topics/48163">技术问题</a>，引擎无奈将Android平台的NativeActivity 实现重新<a href="http://tonybai.com/2014/04/23/changes-in-cocos2dx-3-rc2-for-android/">回退</a>到了Cocos2d-x 2.2.x版本的实现方案。由于之前已经将 GameDemo移植到了<a href="http://tonybai.com/2014/04/22/hello-cocos2dx-3-rc0/">Cocos2d-x 3.0rc0版</a>，直观感受到了NativeActivity方案带来的游戏操作体验上的提升（触屏事件的响应），因此心里总是&ldquo;挂念&rdquo;引擎的 NativeActivity方案。按照Cocos2d-x官方人士的说法，只要NDK的问题修复，NativeActivity还是未来引擎在 Android平台上的第一选择。我的理解，将来Cocos2d-x的某个版本中还会恢复NativeActivity的实现方案。</p>
<p>	上次只是将GameDemo的核心逻辑移植到了Cocos2d-x 3.0rc0上，但一些外部SDK集成尚未做完，这两天闲遐时开始着手研究如何做，而首先要移植的就是<a href="https://developers.google.com/mobile-ads-sdk/">Google AdMob SDK</a>。关于Cocos2d-x 3.0rc0集成Google AdMob SDK，网上已经有了技术方案原型，最初应该是一个<a href="http://www.dynadream.com/ddweb/index.php/Special_Blog">外国开发者提出的方案</a>，后来被<a href="http://www.cocoachina.com">CocoaChina</a> cocos2d-x社区版主翻译成了<a href="http://www.cocoachina.com/bbs/read.php?tid=194999">中文教程</a>，这里基本上也是参考的这个方案，只是做了局部细化和进一步说明，希望能帮助大家进一步解惑。</p>
<p>	<b>一、功能说明</b></p>
<p>	GameDemo游戏分为三个场景：WelcomeScene、GameScene以及EndScene。集成AdMob SDK的功能要求如下：</p>
<p>	&nbsp;&nbsp;&nbsp; &#8211; 当游戏进入到WelcomeScene时，AdMob SDK完成初始化，发出Ad Request，当收到Ad的时候才会在屏幕上方显示带有Ad的窗口；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 当点击<b>Start</b>进入到GameScene时，隐藏Ad窗口，以不干扰玩家的游戏操作为优先；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 当游戏Over进入到EndScene的时候，恢复显示Ad窗口；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 当玩家点击&ldquo;<b>Retry</b>&rdquo;回到GameScene时，隐藏Ad窗口。</p>
<p>	<b>二、方案原理</b></p>
<p>	这个方案就是通过android.widget.PopupWindow实现广告窗口悬浮在当前窗口之上。按照Android<a href="http://developer.android.com/reference/android/widget/PopupWindow.html">官方Doc</a>中的说 明，PopupWindow用于实现一个弹出框，它可以使用任意布局的View作为其内容，这个弹出框是悬浮在当前activity之上的。</p>
<p>	至于PopupWindow为何能显示在当前Activity之上，可以查看PopupWindow的源码，大致思路就是PopupWindow通过 new时传入的context(当前Activity)获得了当前WindowManager，并将AdView作为子View添加到该窗口中。这里简要列出一些关键源码，大家可粗略理解一下：</p>
<p>	&nbsp;&nbsp;&nbsp; <font face="Courier New">public PopupWindow(Context context, AttributeSet attrs,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int defStyleAttr, int defStyleRes) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mContext = context;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>mWindowManager</b> = (WindowManager)context<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .getSystemService(Context.WINDOW_SERVICE);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230;. &#8230;<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public void setContentView(View contentView) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (isShowing()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>mContentView</b> = contentView;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (mContext == null &amp;&amp; mContentView != null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mContext = mContentView.getContext();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (mWindowManager == null &amp;&amp; mContentView != null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mWindowManager = (WindowManager) mContext<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .getSystemService(Context.WINDOW_SERVICE);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public void showAtLocation(View parent, int gravity, int x, int y) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; showAtLocation(parent.getWindowToken(), gravity, x, y);<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public void showAtLocation(IBinder token, int gravity, int x, int y) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (isShowing() || mContentView == null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; unregisterForScrollChanged();</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mIsShowing = true;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mIsDropdown = false;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WindowManager.LayoutParams p = createPopupLayout(token);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p.windowAnimations = computeAnimationResource();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>preparePopup</b>(p);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (gravity == Gravity.NO_GRAVITY) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gravity = Gravity.TOP | Gravity.START;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p.gravity = gravity;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p.x = x;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p.y = y;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (mHeightMode &lt; 0) p.height = mLastHeight = mHeightMode;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (mWidthMode &lt; 0) p.width = mLastWidth = mWidthMode;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>invokePopup</b>(p);<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; private void invokePopup(WindowManager.LayoutParams p) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (mContext != null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p.packageName = mContext.getPackageName();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mPopupView.setFitsSystemWindows(mLayoutInsetDecor);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; setLayoutDirectionFromAnchor();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>mWindowManager.addView(mPopupView, p);</b><br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	UI线程负责更新窗口，因此popupWindow的创建与操作都应该通过runOnUiThread传递给UI线程执行。</p>
<p>	游戏逻辑的C++层代码通过Jni控制PopupWindow的Show和dismiss。别忘了C++层的代码是在渲染线程执行的哦，这也是为何要用handler和runOnUIThread的原因。</p>
<p>	在Cocos2d-x 2.2.2版本集成AdMob时，AdMob只是在收到ad后才显示出广告Banner，但是在cocos2d-x 3.0rc0中，当广告加载未成功时，android.widget.PopupWindow会显示一个小黑框，特难看，因此我们需要自己来控制。</p>
<p>	<b>三、集成步骤</b></p>
<p>	【AdMob准备】</p>
<p>	&nbsp; 到Google AdMob注册一个帐号，如果你有Google帐号，那直接开通AdMob（需填写更加详细的信息）。<br />
	&nbsp; 下载Google AdMob SDK，之前一直用GoogleAdMobAdsSdk-6.4.1.jar，这里还以这个版本为例。但Google官方已经不推荐这个版本了，你可以下 载Google Play Services版本的Mobile Ads API，但代码与这里会稍有不同。将下载的jar包放到GameDemo/proj.android/libs中。<br />
	&nbsp;<br />
	【修改AndroidManifest.xml】</p>
<p>	&nbsp; 在&lt;Application&gt;标签里添加：</p>
<p>	<font face="Courier New">&nbsp; &lt;activity<br />
	&nbsp;&nbsp;&nbsp;&nbsp; android:name=&quot;com.google.ads.AdActivity&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp; android:configChanges=&quot;keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize&quot; /&gt;<br />
	&nbsp; &lt;meta-data<br />
	&nbsp;&nbsp;&nbsp;&nbsp; android:name=&quot;ADMOB_PUBLISHER_ID&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp; android:value=&quot;<i>YOUR_PUBLISHER_ID_VALUE</i>&quot; /&gt;</font></p>
<p>	&nbsp; 权限方面至少包含如下两个：<br />
	<font face="Courier New">&nbsp;&nbsp; &lt;uses-permission android:name=&quot;android.permission.INTERNET&quot;/&gt;<br />
	&nbsp;&nbsp; &lt;uses-permission android:name=&quot;android.permission.ACCESS_NETWORK_STATE&quot;/&gt;</font></p>
<p>	<b>【Java层代码集成】</b></p>
<p>	我们只需要修改GameDemoActivity.java这个文件。</p>
<p>	<font face="Courier New">import android.app.NativeActivity;<br />
	import android.os.Bundle;<br />
	import android.util.Log;</p>
<p>	import android.os.Handler;<br />
	import android.os.Message;<br />
	import android.view.Gravity;<br />
	import android.view.View;<br />
	import android.view.Gravity;<br />
	import android.view.ViewGroup.LayoutParams;<br />
	import android.view.ViewGroup.MarginLayoutParams;<br />
	import android.view.WindowManager;<br />
	import android.widget.LinearLayout;</p>
<p>	import android.widget.PopupWindow;<br />
	import com.google.ads.AdRequest;<br />
	import com.google.ads.AdSize;<br />
	import com.google.ads.AdView;<br />
	import com.google.ads.AdListener;<br />
	import com.google.ads.Ad;</font></p>
<p>	<font face="Courier New">public class GameDemoActivity extends NativeActivity{<br />
	&nbsp;&nbsp;&nbsp; private static GameDemoActivity context;</p>
<p>	&nbsp;&nbsp;&nbsp; private AdView adView;<br />
	&nbsp;&nbsp;&nbsp; private PopupWindow popUpWindow;<br />
	&nbsp;&nbsp;&nbsp; private LinearLayout popupWindowLayout;<br />
	&nbsp;&nbsp;&nbsp; private LinearLayout mainActivityLayout;<br />
	&nbsp;&nbsp;&nbsp; private boolean hasAdReceived;</p>
<p>	&nbsp;&nbsp;&nbsp; private static Handler adHandler = new Handler() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void handleMessage(Message msg) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!context.hasAdReceived)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; switch (msg.what) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case 0:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (View.VISIBLE ==<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.adView.getVisibility()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.adView.setVisibility(View.GONE);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.popUpWindow.dismiss();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case 1:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (View.VISIBLE !=<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.adView.getVisibility()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.adView.setVisibility(View.VISIBLE);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.popUpWindow.showAtLocation(<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.mainActivityLayout, Gravity.TOP, 0, 0);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; };</p>
<p>	&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp; protected void onCreate(Bundle savedInstanceState) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super.onCreate(savedInstanceState);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context = this;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adView = new AdView(this, AdSize.BANNER, &quot;a1533d6de900e31&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adView.setAdListener(new AdmobListener());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //初始时，广告View隐藏起来<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adView.setVisibility(View.GONE);<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public static void setupAds(){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.initAdPopupWindow();<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public void initAdPopupWindow() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (adView != null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.runOnUiThread(new Runnable() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void run() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MarginLayoutParams params = new MarginLayoutParams(<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LayoutParams.WRAP_CONTENT,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LayoutParams.WRAP_CONTENT);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; params.setMargins(0, 0, 0, 0);</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popupWindowLayout = new LinearLayout(context);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popupWindowLayout.setPadding(-5, -5, -5, -5);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popupWindowLayout.setOrientation(LinearLayout.VERTICAL);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popupWindowLayout.<b>addView</b>(adView, params);</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popUpWindow = new PopupWindow(context);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popUpWindow.setWidth(320);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popUpWindow.setHeight(50);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popUpWindow.setWindowLayoutMode(LayoutParams.WRAP_CONTENT,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LayoutParams.WRAP_CONTENT);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popUpWindow.setClippingEnabled(false);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popUpWindow.<b>setContentView</b>(popupWindowLayout);</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mainActivityLayout = new LinearLayout(context);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.<b>setContentView</b>(mainActivityLayout, params);</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.hasAdReceived = false;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AdRequest adRequest = new AdRequest();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //测试Admob时使用测试Device，发布版需要去掉这行代码<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adRequest.addTestDevice(&quot;CCE4220B2509A406B515E7C9A205AEE1&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.adView.loadAd(adRequest);</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; popUpWindow.update();<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; });<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; //GoogleAdMobAdsSdk-6.4.1版本中的AdListener是interface，因此<br />
	&nbsp;&nbsp;&nbsp; //我们需要override所有接口，但只有onReceiveAd是我们关心的。<br />
	&nbsp;&nbsp;&nbsp; private class AdmobListener implements AdListener {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void onReceiveAd(Ad ad) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //只有第一次成功接收Ad后，我们后续才显示广告窗口，否则<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //popupWindow会显示为一个小黑框，特难看。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Log.d(&quot;GameDemo&quot;, &quot;onReceiveAd&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!hasAdReceived){<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; hasAdReceived = true;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void onFailedToReceiveAd(Ad ad,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AdRequest.ErrorCode error) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Log.d(&quot;GameDemo&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;failed to receive ad (&quot; + error+ &quot;)&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void onPresentScreen(Ad ad) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Log.d(&quot;GameDemo&quot;, &quot;onPresentScreen&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void onDismissScreen(Ad ad) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Log.d(&quot;GameDemo&quot;, &quot;onDismisScreen&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void onLeaveApplication(Ad ad) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Log.d(&quot;GameDemo&quot;, &quot;onLeaveApp&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public static void setAdVisible(boolean b) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Message msg = new Message();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (b) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.what = 1;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.what = 0;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adHandler.sendMessage(msg);<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>	【C++层代码集成】</p>
<p>	在AppDelegate.cpp中添加setupAds方法：</p>
<p>	<font face="Courier New">void AppDelegate::setupAds()<br />
	{<br />
	#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)<br />
	&nbsp;&nbsp;&nbsp; JniMethodInfo t;<br />
	&nbsp;&nbsp;&nbsp; if (JniHelper::getStaticMethodInfo(t, &quot;com/tonybai/game/GameDemoActivity&quot;, &quot;setupAds&quot;, &quot;()V&quot;)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;CallStaticVoidMethod(t.classID, t.methodID);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (t.env-&gt;ExceptionOccurred()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;ExceptionDescribe();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;ExceptionClear();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;DeleteLocalRef(t.classID);<br />
	&nbsp;&nbsp;&nbsp; }<br />
	#endif<br />
	}</font></p>
<p>	在WelcomeScene的init方法中调用setupAds：</p>
<p>	<font face="Courier New">bool WelcomeScene::init()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; bool bRet = false;<br />
	&nbsp;&nbsp;&nbsp; do {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; AppDelegate *app = (AppDelegate*)Application::getInstance();<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; app-&gt;<b>setupAds</b>();</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bRet=true;<br />
	&nbsp;&nbsp;&nbsp; } while(0);</p>
<p>	&nbsp;&nbsp;&nbsp; return bRet;<br />
	}</font></p>
<p>	在点击Start按钮时，隐藏Ad：</p>
<p>	<font face="Courier New">void WelcomeScene::menuStartCallback(Ref* pSender)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; AppDelegate *app = (AppDelegate*)Application::getInstance();</p>
<p>	&nbsp;&nbsp;&nbsp; <b>app-&gt;setAdVisible(false);</b><br />
	&nbsp;&nbsp;&nbsp; GameScene *gameScene = GameScene::create();<br />
	&nbsp;&nbsp;&nbsp; Director::getInstance()-&gt;replaceScene(gameScene);<br />
	}</font></p>
<p>	集成步骤到此就结束了，编译ok就可以部署到模拟器上运行测试一番了。</p>
<p>	<b>四、使用Cocos2d-x 3.0rc0引擎遇到的两个问题</b></p>
<p>	【问题1】用cocos2d-x 3.0rc0的cocos编译高版本引擎生成的工程遇到的问题</p>
<p>	&nbsp;cocos2d-x 3.0rc0生成的proj.android/build-cfg.json与高版本(rc1～正式版)&nbsp; cocos生成的工程的build-cfg.json稍有不同，用cocos2d-x 3.0rc0版cocos编译高版本cocos生成的project，会提示build_android组件无法找到&ldquo;copy_to_assets&rdquo;。 因此需要手动修改proj.android/build-cfg.json，将其中的：</p>
<p>	<font face="Courier New">&nbsp;&nbsp;&nbsp; &quot;copy_resources&quot;: [<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;from&quot;: &quot;../Resources&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;to&quot;: &quot;&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; ],</font></p>
<p>	改为：</p>
<p>	<font face="Courier New">&nbsp;&nbsp; &quot;copy_to_assets&quot; :[<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;../Resources/&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ],</font></p>
<p>	【问题2】 W/dalvikvm(1194): dvmFindClassByName rejecting &#39;org/cocos2dx/lib/Cocos2dxHelper&#39;</p>
<p>	使用cocos2d-x 3.0rc0编译的项目，总是出现如下问题，但似乎这个错误还不影响程序的运行：</p>
<p>	<font face="Courier New">04-29 09:44:34.968: D/JniHelper(1194): JniHelper::setJavaVM(0xb8835730), pthread_self() = B897B518<br />
	04-29 09:44:34.968: W/dalvikvm(1194): dvmFindClassByName rejecting &#39;org/cocos2dx/lib/Cocos2dxHelper&#39;</font></p>
<p>	Google了一下，这个问题很多童鞋都遇到了，但给出的solution却不甚令我满意，于是我就求甚解的细致挖掘了一下，终于找到了一个让我还算满意 的答案。我们分析一下这两条日志，rejecting那条日志总是伴随setJavaVM后面出现的。可引擎什么时候setJavaVM了呢？显然是在 native_app_glue创建的子进程中，引擎需要attachCurrentThread，获得JniEnv时才做的。于是我们打开<font face="Courier New">cocos2d-x-3.0rc0/cocos/2d/platform/android/nativeactivity.cpp</font>一看究竟。</p>
<p>	在nativeactivity.cpp中有两处&quot;org/cocos2dx/lib/Cocos2dxHelper&quot;，我们先看engine_handle_cmd中的那处：</p>
<p>	<font face="Courier New">static void engine_handle_cmd(struct android_app* app, int32_t cmd)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8230;. &#8230;.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case APP_CMD_INIT_WINDOW:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LOG_RENDER_DEBUG(&quot;android_main : APP_CMD_INIT_WINDOW&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // The window is being shown, get it ready.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (engine-&gt;app-&gt;window != NULL) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cocos_dimensions d = engine_init_display(engine);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ((d.w &gt; 0) &amp;&amp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (d.h &gt; 0)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>cocos2d::JniHelper::setJavaVM(app-&gt;activity-&gt;vm);</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cocos2d::JniHelper::setClassLoaderFrom(app-&gt;activity-&gt;clazz);</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // call Cocos2dxHelper.init()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cocos2d::JniMethodInfo ccxhelperInit;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!cocos2d::JniHelper::getStaticMethodInfo(ccxhelperInit,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;<b>org/cocos2dx/lib/Cocos2dxHelper</b>&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;init&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;(Landroid/app/Activity;)V&quot;)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LOGI(&quot;cocos2d::JniHelper::getStaticMethodInfo(ccxhelperInit) FAILED&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ccxhelperInit.env-&gt;CallStaticVoidMethod(ccxhelperInit.classID,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ccxhelperInit.methodID,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app-&gt;activity-&gt;clazz);</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cocos_init(d, app);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; engine-&gt;animating = 1;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; engine_draw_frame(engine);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p>	初步可以断定，那两条日志就是执行到这里输出的，但为何dvmFindClassByName方法会rejecting &ldquo;org/cocos2dx/lib/Cocos2dxHelper&rdquo;这个类名呢？我们还得翻看dalvik虚拟机源码：</p>
<p>	<font face="Courier New">&nbsp;/dalvik/vm/native/InternalNative.cpp</p>
<p>	/*<br />
	&nbsp;* Find a class by name, initializing it if requested.<br />
	&nbsp;*/<br />
	ClassObject* dvmFindClassByName(StringObject* nameObj, Object* loader,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bool doInit)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; ClassObject* clazz = NULL;<br />
	&nbsp;&nbsp;&nbsp; char* name = NULL;<br />
	&nbsp;&nbsp;&nbsp; char* descriptor = NULL;<br />
	&nbsp;&nbsp;&nbsp; if (nameObj == NULL) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dvmThrowNullPointerException(&quot;name == null&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto bail;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; name = dvmCreateCstrFromString(nameObj);<br />
	&nbsp;&nbsp;&nbsp; /*<br />
	&nbsp;&nbsp;&nbsp;&nbsp; * We need to validate and convert the name (from x.y.z to x/y/z). This<br />
	&nbsp;&nbsp;&nbsp;&nbsp; * is especially handy for array types, since we want to avoid<br />
	&nbsp;&nbsp;&nbsp;&nbsp; * auto-generating bogus array classes.<br />
	&nbsp;&nbsp;&nbsp;&nbsp; */<br />
	&nbsp;&nbsp;&nbsp; if (!dexIsValidClassName(name, true)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>ALOGW(&quot;dvmFindClassByName rejecting &#39;%s&#39;&quot;, name);</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dvmThrowClassNotFoundException(name);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto bail;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p>	我们的确找到了输出rejecting日志的地方，通过注释我们可以看到这个方法是用来验证名字对象，并将x.y.z形式的名字转换成x/y/z的。但引 擎中传入的就是&ldquo;x/y/z&rdquo;格式，因此这个方法输出了错误日志。我尝试将上面engine_handle_cmd中的&quot;<font face="Courier New">org/cocos2dx/lib/Cocos2dxHelper</font>&quot;改成&quot;<font face="Courier New">org.cocos2dx.lib.Cocos2dxHelper</font>&quot;，错误日志果然消失了。</p>
<p>	不过目前仍不能解释一点：为何在其他位置，比如在前面的AppDelegate::setupAds中，使用x/y/z格式的jni调用参数却没有输出错误日志！难道两个位置dalvik虚拟机使用的是不同的名字对象查找和加载方法？这个目前尚无定论。</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/05/01/integrate-cocos2dx3rc0-with-admob/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Cocos2d-x 3.0多线程异步资源加载</title>
		<link>https://tonybai.com/2014/04/28/multithreaded-resource-loading-in-cocos2dx-3/</link>
		<comments>https://tonybai.com/2014/04/28/multithreaded-resource-loading-in-cocos2dx-3/#comments</comments>
		<pubDate>Sun, 27 Apr 2014 23:13:23 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[android-native-app-glue]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CGL]]></category>
		<category><![CDATA[Cocos2d]]></category>
		<category><![CDATA[Cocos2d-x]]></category>
		<category><![CDATA[Cocos2d-x-3.0]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[EGL]]></category>
		<category><![CDATA[Game-Engine]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Jni]]></category>
		<category><![CDATA[Mobile]]></category>
		<category><![CDATA[MobileGame]]></category>
		<category><![CDATA[NativeActivity]]></category>
		<category><![CDATA[NDK]]></category>
		<category><![CDATA[opengl]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Pthread]]></category>
		<category><![CDATA[内存计数]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[多线程]]></category>
		<category><![CDATA[安卓]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[性能优化]]></category>
		<category><![CDATA[手游]]></category>
		<category><![CDATA[架构]]></category>
		<category><![CDATA[游戏引擎]]></category>
		<category><![CDATA[源码分析]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[触控科技]]></category>
		<category><![CDATA[跨平台]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1525</guid>
		<description><![CDATA[Cocos2d-x从2.x版本到上周刚刚才发布的Cocos2d-x 3.0 Final版，其引擎驱动核心依旧是一个单线程的&#8220;死循环&#8221;，一旦某一帧遇到了&#8220;大活儿&#8221;，比如Size很大的纹理资源加载或网络IO或大量计算，画面将 不可避免出现卡顿以及响应迟缓的现象。从古老的Win32 GUI编程那时起，Guru们就告诉我们：别阻塞主线程(UI线程)，让Worker线程去做那些&#8220;大活儿&#8221;吧。 手机游戏，即便是休闲类的小游戏，往往也涉及大量纹理资源、音视频资源、文件读写以及网络通信，处理的稍有不甚就会出现画面卡顿，交互不畅的情况。虽然引 擎在某些方面提供了一些支持，但有些时候还是自己祭出Worker线程这个法宝比较灵活，下面就以Cocos2d-x 3.0 Final版游戏初始化为例（针对Android平台），说说如何进行多线程资源加载。 我们经常看到一些手机游戏，启动之后首先会显示一个带有公司Logo的闪屏画面(Flash Screen)，然后才会进入一个游戏Welcome场景，点击&#8220;开始&#8221;才正式进入游戏主场景。而这里Flash Screen的展示环节往往在后台还会做另外一件事，那就是加载游戏的图片资源，音乐音效资源以及配置数据读取，这算是一个&#8220;障眼法&#8221;吧，目的就是提高用 户体验，这样后续场景渲染以及场景切换直接使用已经cache到内存中的数据即可，无需再行加载。 一、为游戏添加FlashScene 在游戏App初始化时，我们首先创建FlashScene，让游戏尽快显示FlashScene画面： // AppDelegate.cpp bool AppDelegate::applicationDidFinishLaunching() { &#160;&#160;&#160; &#8230; &#8230; &#160;&#160;&#160; FlashScene* scene = FlashScene::create(); &#160;&#160;&#160; pDirector-&#62;runWithScene(scene); &#160;&#160;&#160; return true; } 在FlashScene init时，我们创建一个Resource Load Thread，我们用一个ResourceLoadIndicator作为渲染线程与Worker线程之间交互的媒介。 //FlashScene.h struct ResourceLoadIndicator { &#160;&#160;&#160; pthread_mutex_t mutex; &#160;&#160;&#160; bool load_done; &#160;&#160;&#160; void *context; }; class FlashScene : [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.cocos2d-x.org">Cocos2d-x</a>从<a href="http://tonybai.com/2014/03/11/hello-cocos2dx/">2.x版</a>本到上周刚刚才发布的<a href="http://www.cocos2d-x.org/news/215">Cocos2d-x 3.0 Final版</a>，其引擎驱动核心依旧是一个单线程的&ldquo;死循环&rdquo;，一旦某一帧遇到了&ldquo;大活儿&rdquo;，比如Size很大的纹理资源加载或网络IO或大量计算，画面将 不可避免出现卡顿以及响应迟缓的现象。从古老的Win32 GUI编程那时起，Guru们就告诉我们：别阻塞主线程(UI线程)，让Worker线程去做那些&ldquo;大活儿&rdquo;吧。</p>
<p>手机游戏，即便是休闲类的小游戏，往往也涉及大量纹理资源、音视频资源、文件读写以及网络通信，处理的稍有不甚就会出现画面卡顿，交互不畅的情况。虽然引 擎在某些方面提供了一些支持，但有些时候还是自己祭出Worker线程这个法宝比较灵活，下面就以Cocos2d-x 3.0 Final版游戏初始化为例（针对Android平台），说说如何进行多线程资源加载。</p>
<p>我们经常看到一些手机游戏，启动之后首先会显示一个带有公司Logo的闪屏画面(Flash Screen)，然后才会进入一个游戏Welcome场景，点击&ldquo;开始&rdquo;才正式进入游戏主场景。而这里Flash Screen的展示环节往往在后台还会做另外一件事，那就是加载游戏的图片资源，音乐音效资源以及配置数据读取，这算是一个&ldquo;障眼法&rdquo;吧，目的就是提高用 户体验，这样后续场景渲染以及场景切换直接使用已经cache到内存中的数据即可，无需再行加载。</p>
<p><b>一、为游戏添加FlashScene</b></p>
<p>在游戏App初始化时，我们首先创建FlashScene，让游戏尽快显示FlashScene画面：</p>
<p><font face="Courier New">// AppDelegate.cpp<br />
	bool AppDelegate::applicationDidFinishLaunching() {<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; FlashScene* scene = FlashScene::create();<br />
	&nbsp;&nbsp;&nbsp; pDirector-&gt;runWithScene(scene);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; return true;<br />
	}</font></p>
<p>在FlashScene init时，我们创建一个Resource Load Thread，我们用一个ResourceLoadIndicator作为渲染线程与Worker线程之间交互的媒介。</p>
<p><font face="Courier New">//FlashScene.h</font></p>
<p><font face="Courier New">struct ResourceLoadIndicator {<br />
	&nbsp;&nbsp;&nbsp; pthread_mutex_t mutex;<br />
	&nbsp;&nbsp;&nbsp; bool load_done;<br />
	&nbsp;&nbsp;&nbsp; void *context;<br />
	};</font></p>
<p><font face="Courier New">class FlashScene : public Scene<br />
	{<br />
	public:<br />
	&nbsp;&nbsp;&nbsp; FlashScene(void);<br />
	&nbsp;&nbsp;&nbsp; ~FlashScene(void);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; virtual bool init();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; CREATE_FUNC(FlashScene);<br />
	&nbsp;&nbsp;&nbsp; bool getResourceLoadIndicator();<br />
	&nbsp;&nbsp;&nbsp; void setResourceLoadIndicator(bool flag);</font></p>
<p><font face="Courier New">private:<br />
	&nbsp;&nbsp;&nbsp;&nbsp; void updateScene(float dt);</font></p>
<p><font face="Courier New">private:<br />
	&nbsp;&nbsp;&nbsp;&nbsp; ResourceLoadIndicator rli;<br />
	};</font></p>
<p><font face="Courier New">// FlashScene.cpp<br />
	bool FlashScene::init()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; bool bRet = false;<br />
	&nbsp;&nbsp;&nbsp; do {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CC_BREAK_IF(!CCScene::init());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Size winSize = Director::getInstance()-&gt;getWinSize();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //FlashScene自己的资源只能同步加载了<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Sprite *bg = Sprite::create(&quot;FlashSceenBg.png&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CC_BREAK_IF(!bg);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bg-&gt;setPosition(ccp(winSize.width/2, winSize.height/2));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this-&gt;addChild(bg, 0);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this-&gt;schedule(schedule_selector(FlashScene::updateScene)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; , 0.01f);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //start the resource loading thread<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rli.load_done = false;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rli.context = (void*)this;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pthread_mutex_init(&amp;rli.mutex, NULL);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pthread_attr_t attr;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pthread_attr_init(&amp;attr);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pthread_attr_setdetachstate(&amp;attr, PTHREAD_CREATE_DETACHED);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pthread_t thread;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pthread_create(&amp;thread, &amp;attr,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; resource_load_thread_entry, &amp;rli);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bRet=true;<br />
	&nbsp;&nbsp;&nbsp; } while(0);</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; return bRet;<br />
	}</font></p>
<p><font face="Courier New">static void* resource_load_thread_entry(void* param)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; AppDelegate *app = (AppDelegate*)Application::getInstance();<br />
	&nbsp;&nbsp;&nbsp; ResourceLoadIndicator *rli = (ResourceLoadIndicator*)param;<br />
	&nbsp;&nbsp;&nbsp; FlashScene *scene = (FlashScene*)rli-&gt;context;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; //load music effect resource<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; //init from config files<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; //load images data in worker thread<br />
	&nbsp;&nbsp;&nbsp; SpriteFrameCache::getInstance()-&gt;addSpriteFramesWithFile(<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;All-Sprites.plist&quot;);<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; //set loading done<br />
	&nbsp;&nbsp;&nbsp; scene-&gt;setResourceLoadIndicator(true);<br />
	&nbsp;&nbsp;&nbsp; return NULL;<br />
	}</font></p>
<p><font face="Courier New">bool FlashScene::getResourceLoadIndicator()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; bool flag;<br />
	&nbsp;&nbsp;&nbsp; pthread_mutex_lock(&amp;rli.mutex);<br />
	&nbsp;&nbsp;&nbsp; flag = rli.load_done;<br />
	&nbsp;&nbsp;&nbsp; pthread_mutex_unlock(&amp;rli.mutex);<br />
	&nbsp;&nbsp;&nbsp; return flag;<br />
	}</font></p>
<p><font face="Courier New">void FlashScene::setResourceLoadIndicator(bool flag)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; pthread_mutex_lock(&amp;rli.mutex);<br />
	&nbsp;&nbsp;&nbsp; rli.load_done = flag;<br />
	&nbsp;&nbsp;&nbsp; pthread_mutex_unlock(&amp;rli.mutex);<br />
	&nbsp;&nbsp;&nbsp; return;<br />
	}</font></p>
<p>我们在定时器回调函数中对indicator标志位进行检查，当发现加载ok后，切换到接下来的游戏开始场景：</p>
<p><font face="Courier New">void FlashScene::updateScene(float dt)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; if (getResourceLoadIndicator()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Director::getInstance()-&gt;replaceScene(<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WelcomeScene::create());<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>到此，FlashScene的初始设计和实现完成了。Run一下试试吧。</p>
<p><b>二、崩溃</b></p>
<p>在<a href="http://genymotion.com/">GenyMotion</a>的4.4.2模拟器上，游戏运行的结果并没有如我期望，FlashScreen显现后游戏就异常崩溃退出了。</p>
<p>通过monitor分析游戏的运行日志，我们看到了如下一些异常日志：</p>
<p><font face="Courier New">threadid=24: thread exiting, not yet detached (count=0)<br />
	threadid=24: thread exiting, not yet detached (count=1)<br />
	threadid=24: native thread exited without detaching</font></p>
<p>很是奇怪啊，我们在创建线程时，明明设置了 PTHREAD_CREATE_DETACHED属性了啊：</p>
<p><font face="Courier New">pthread_attr_setdetachstate(&amp;attr, PTHREAD_CREATE_DETACHED);</font></p>
<p>怎么还会出现这个问题，而且居然有三条日志。翻看了一下引擎内核的代码TextureCache::addImageAsync，在线程创建以及线程主函数中也没有发现什么特别的设置。为何内核可以创建线程，我自己创建就会崩溃呢。Debug多个来回，问题似乎聚焦在<font face="Courier New">resource_load_thread_entry</font>中执行的任务。在我的代码里，我利用SimpleAudioEngine加载了音效资源、利用UserDefault读取了一些持久化的数据，把这两个任务去掉，游戏就会进入到下一个环节而不会崩溃。</p>
<p>SimpleAudioEngine和UserDefault能有什么共同点呢？Jni调用。没错，这两个接口底层要适配多个平台，而对于Android 平台，他们都用到了Jni提供的接口去调用Java中的方法。而Jni对多线程是有约束的。<a href="http://developer.android.com/training/articles/perf-jni.html">Android开发者官网</a>上有这么一段话：</p>
<p><font face="Courier New">All threads are Linux threads, scheduled by the kernel. They&#39;re usually started from managed code (using Thread.start), but they can also be created elsewhere and then attached to the JavaVM. For example, a thread started with pthread_create can be attached with the JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. <b>Until a thread is attached, it has no JNIEnv, and cannot make JNI calls</b>.</font></p>
<p>由此看来pthread_create创建的新线程默认情况下是不能进行Jni接口调用的，除非Attach到Vm，获得一个JniEnv对象，并且在线 程exit前要Detach Vm。好，我们来尝试一下，Cocos2d-x引擎提供了一些JniHelper方法，可以方便进行Jni相关操作。</p>
<p><font face="Courier New">#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)<br />
	#include &quot;platform/android/jni/JniHelper.h&quot;<br />
	#include &lt;jni.h&gt;<br />
	#endif</font></p>
<p><font face="Courier New">static void* resource_load_thread_entry(void* param)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; JavaVM *vm;<br />
	&nbsp;&nbsp;&nbsp; JNIEnv *env;<br />
	&nbsp;&nbsp;&nbsp; vm = JniHelper::getJavaVM();</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; JavaVMAttachArgs thread_args;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; thread_args.name = &quot;Resource Load&quot;;<br />
	&nbsp;&nbsp;&nbsp; thread_args.version = JNI_VERSION_1_4;<br />
	&nbsp;&nbsp;&nbsp; thread_args.group = NULL;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; vm-&gt;<b>AttachCurrentThread</b>(&amp;env, &amp;thread_args);<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; //Your Jni Calls<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; vm-&gt;<b>DetachCurrentThread</b>();<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; return NULL;<br />
	}</font></p>
<p>关于什么是JavaVM，什么是JniEnv，Android Developer官方文档中是这样描述的：</p>
<p><font face="Courier New">The JavaVM provides the &quot;invocation interface&quot; functions, which allow you to create and destroy a JavaVM. In theory you can have multiple JavaVMs per process, but <b>Android only allows one</b>.<br />
	The JNIEnv provides most of the JNI functions. Your native functions all receive a JNIEnv as the first argument.<br />
	The JNIEnv is used for thread-local storage. For this reason, you cannot share a JNIEnv between threads. </font></p>
<p><b>三、黑屏</b></p>
<p>上面的代码成功解决了线程崩溃的问题，但问题还没完，因为接下来我们又遇到了&ldquo;黑屏&rdquo;事件。所谓的&ldquo;黑屏&rdquo;，其实并不是全黑。但进入游戏 WelcomScene时，只有Scene中的LabelTTF实例能显示出来，其余Sprite都无法显示。显然肯定与我们在Worker线程加载纹理 资源有关了：</p>
<p><font face="Courier New">SpriteFrameCache::getInstance()-&gt;addSpriteFramesWithFile(&quot;All-Sprites.plist&quot;);</font></p>
<p>我们通过碎图压缩到一张大纹理的方式建立SpriteFrame，这是Cocos2d-x推荐的优化手段。但要想找到这个问题的根源，还得看monitor日志。我们的确发现了一些异常日志：</p>
<p><font face="Courier New">libEGL: call to OpenGL ES API with no current context (logged once per thread)</font></p>
<p>通过Google得知，只有Renderer Thread才能进行egl调用，因为egl的context是在Renderer Thread创建的，Worker Thread并没有EGL的context，在进行egl操作时，无法找到context，因此操作都是失败的，纹理也就无法显示出来。要解决这个问题就 得查看一下TextureCache::addImageAsync是如何做的了。</p>
<p>TextureCache::addImageAsync只是在worker线程进行了image数据的加载，而纹理对象Texture2D instance则是在addImageAsyncCallBack中创建的。也就是说纹理还是在Renderer线程中创建的，因此不会出现我们上面的 &ldquo;黑屏&rdquo;问题。模仿addImageAsync，我们来修改一下代码：</p>
<p><font face="Courier New">static void* resource_load_thread_entry(void* param)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; allSpritesImage = new Image();<br />
	&nbsp;&nbsp;&nbsp; allSpritesImage-&gt;initWithImageFile(&quot;All-Sprites.png&quot;);<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	}</font></p>
<p><font face="Courier New">void FlashScene::updateScene(float dt)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; if (getResourceLoadIndicator()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // construct texture with preloaded images<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Texture2D *allSpritesTexture = TextureCache::getInstance()-&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; addImage(allSpritesImage, &quot;All-Sprites.png&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; allSpritesImage-&gt;release();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SpriteFrameCache::getInstance()-&gt;addSpriteFramesWithFile(<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;All-Sprites.plist&quot;, allSpritesTexture);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Director::getInstance()-&gt;replaceScene(WelcomeScene::create());<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>完成这一修改后，游戏画面就变得一切正常了，多线程资源加载机制正式生效。</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/04/28/multithreaded-resource-loading-in-cocos2dx-3/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Cocos2d-x 3.0rc2集成ShareSDK</title>
		<link>https://tonybai.com/2014/04/25/integrate-cocos2dx3rc2-with-sharesdk/</link>
		<comments>https://tonybai.com/2014/04/25/integrate-cocos2dx3rc2-with-sharesdk/#comments</comments>
		<pubDate>Thu, 24 Apr 2014 16:21:11 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[android-native-app-glue]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CGL]]></category>
		<category><![CDATA[Cocos2d]]></category>
		<category><![CDATA[Cocos2d-x]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[Game-Engine]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Mobile]]></category>
		<category><![CDATA[MobileGame]]></category>
		<category><![CDATA[NativeActivity]]></category>
		<category><![CDATA[NDK]]></category>
		<category><![CDATA[opengl]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[ShareSDK]]></category>
		<category><![CDATA[Wechat]]></category>
		<category><![CDATA[weibo]]></category>
		<category><![CDATA[内存计数]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[安卓]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[微信]]></category>
		<category><![CDATA[微博]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[性能优化]]></category>
		<category><![CDATA[手游]]></category>
		<category><![CDATA[架构]]></category>
		<category><![CDATA[游戏引擎]]></category>
		<category><![CDATA[源码分析]]></category>
		<category><![CDATA[社交分享]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[触控科技]]></category>
		<category><![CDATA[跨平台]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1521</guid>
		<description><![CDATA[给自己的手机游戏增加些社交分享功能，有助于游戏宣传和提升知名度，是一种不错的社交营销手段。国内这方面的第三方插件有不少，比如ShareSDK、友 盟分享组件、Baidu分享组件等，之前在研究2.2.2版本时，集成了ShareSDK这个组件，这次迁移到Cocos2d-x 3.0rc2依旧选择集成ShareSDK，这里就来说说集成的过程，遇到的一些问题以及解决方法。这里仅以Android平台游戏集成为例。 一、功能描述、SDK版本和帐号准备 功能大致是这样的：在游戏中设置一个按钮，点击这个按钮，弹出知名社交平台的分享图标集窗口，用户选择分享目标后，相关信息分享到对应的社交平台。分享结果通知通过Toast显示在屏幕的下方。 这次依旧使用ShareSDK for Android 2.3.7版本（ShareSDK-Android-2.3.7），Cocos2d-x的版本为3.0rc2。 集成前，你需要有一个基于Cocos2d-x 3.0rc2的可运行的Android平台游戏project，我们的集成就基于该project，这里我们的project名为GameDemo，GameDemo的源码结构大致是： GameDemo/ &#160;&#160;&#160; &#8211; Classes/ &#160;&#160;&#160; &#8211; proj.android/ &#160;&#160;&#160; &#8211; Resources/ &#160;&#160;&#160; &#8211; cocos2d/ &#160;&#160;&#160; &#8211; CMakeLists.txt &#160;&#160;&#160; &#8211; &#8230; &#8230; 使用ShareSDK前，你需要在各大主流社交平台（微信、微博）申请开发者帐号以及游戏接入权限(app_key、app_secret)等，当然在ShareSDK站点也应该有自己的帐号和应用AppKey，这些申请的审核需要几个工作日，甚至更长。 二、ShareSDK集成步骤 按照ShareSDK官方manual说法，Cocos2d-x集成ShareSDK有三种方式，之前在Cocos2d-x 2.2.2引擎中采用的是专用组件集成的方式，该组件(C2DXShareSDKSample)可以在这里下载（该组件近期已经fix了我之前发现的bug）。 1.&#160; jar包集成 这次我们主要做微博、微信的社交分享，因此只需要微博、微信相关jar包。在C2DXShareSDKSample/proj.android/libs下，我们找到以下几个jar包： &#160; -rw-rw-r&#8211; 1 tonybai tonybai&#160; 97K&#160; 4月&#160; 8 18:10 mframework.jar &#160; -rw-rw-r&#8211; 1 tonybai tonybai 112K&#160; 4月&#160; [...]]]></description>
			<content:encoded><![CDATA[<p>给自己的手机游戏增加些社交分享功能，有助于游戏宣传和提升知名度，是一种不错的社交营销手段。国内这方面的第三方插件有不少，比如<a href="http://sharesdk.cn">ShareSDK</a>、<a href="http://www.umeng.com/component_social">友 盟分享组件</a>、<a href="http://developer.baidu.com/soc/share">Baidu分享组件</a>等，之前在研究<a href="http://tonybai.com/2014/03/11/hello-cocos2dx/">2.2.2版本</a>时，<a href="http://tonybai.com/2014/04/17/a-bug-from-sharesdk-componet-for-cocos2dx/">集成了ShareSDK</a>这个组件，这次迁移到<a href="http://tonybai.com/2014/04/23/changes-in-cocos2dx-3-rc2-for-android/">Cocos2d-x 3.0rc2</a>依旧选择集成ShareSDK，这里就来说说集成的过程，遇到的一些问题以及解决方法。这里仅以Android平台游戏集成为例。</p>
<p>	<b>一、功能描述、</b><b>SDK版本和帐号准备</b></p>
<p>	功能大致是这样的：在游戏中设置一个按钮，点击这个按钮，弹出知名社交平台的分享图标集窗口，用户选择分享目标后，相关信息分享到对应的社交平台。分享结果通知通过Toast显示在屏幕的下方。</p>
<p>	这次依旧使用ShareSDK for Android 2.3.7版本（ShareSDK-Android-2.3.7），Cocos2d-x的版本为<a href="http://www.cocos2d-x.org">3.0rc2</a>。</p>
<p>	集成前，你需要有一个基于Cocos2d-x 3.0rc2的可运行的Android平台游戏project，我们的集成就基于该project，这里我们的project名为GameDemo，GameDemo的源码结构大致是：</p>
<p>	<font face="Courier New">GameDemo/<br />
	&nbsp;&nbsp;&nbsp; &#8211; Classes/<br />
	&nbsp;&nbsp;&nbsp; &#8211; proj.android/<br />
	&nbsp;&nbsp;&nbsp; &#8211; Resources/<br />
	&nbsp;&nbsp;&nbsp; &#8211; cocos2d/<br />
	&nbsp;&nbsp;&nbsp; &#8211; CMakeLists.txt<br />
	&nbsp;&nbsp;&nbsp; &#8211; &#8230; &#8230;</font></p>
<p>	使用ShareSDK前，你需要在各大主流社交平台（<a href="http://open.weixin.qq.com/">微信</a>、<a href="http://open.weibo.com">微博</a>）申请开发者帐号以及游戏接入权限(app_key、app_secret)等，当然在ShareSDK站点也应该有自己的帐号和应用AppKey，这些申请的审核需要几个工作日，甚至更长。</p>
<p>	<b>二、ShareSDK集成步骤</b></p>
<p>	按照ShareSDK官方manual说法，Cocos2d-x集成ShareSDK有三种方式，之前在Cocos2d-x 2.2.2引擎中采用的是专用组件集成的方式，该组件(C2DXShareSDKSample)可以在<a href="https://github.com/ShareSDKPlatform/C2DXShareSDKSample">这里</a>下载（该组件近期已经fix了<a href="http://tonybai.com/2014/04/17/a-bug-from-sharesdk-componet-for-cocos2dx/">我之前发现的bug</a>）。</p>
<p>	<b>1.&nbsp; jar包集成</b></p>
<p>	这次我们主要做微博、微信的社交分享，因此只需要微博、微信相关jar包。在C2DXShareSDKSample/proj.android/libs下，我们找到以下几个jar包：</p>
<p>	<font face="Courier New">&nbsp; -rw-rw-r&#8211; 1 tonybai tonybai&nbsp; 97K&nbsp; 4月&nbsp; 8 18:10 mframework.jar<br />
	&nbsp; -rw-rw-r&#8211; 1 tonybai tonybai 112K&nbsp; 4月&nbsp; 8 17:39 ShareSDK-Core-2.3.7.jar<br />
	&nbsp; -rw-rw-r&#8211; 1 tonybai tonybai&nbsp; 19K&nbsp; 4月&nbsp; 8 17:39 ShareSDK-SinaWeibo-2.3.7.jar<br />
	&nbsp; -rw-rw-r&#8211; 1 tonybai tonybai 4.3K&nbsp; 4月&nbsp; 8 17:39 ShareSDK-Wechat-2.3.7.jar<br />
	&nbsp; -rw-rw-r&#8211; 1 tonybai tonybai&nbsp; 29K&nbsp; 4月&nbsp; 8 17:39 ShareSDK-Wechat-Core-2.3.7.jar<br />
	&nbsp; -rw-rw-r&#8211; 1 tonybai tonybai 4.6K&nbsp; 4月&nbsp; 8 17:39 ShareSDK-Wechat-Favorite-2.3.7.jar<br />
	&nbsp; -rw-rw-r&#8211; 1 tonybai tonybai 4.4K&nbsp; 4月&nbsp; 8 17:39 ShareSDK-Wechat-Moments-2.3.7.jar</font></p>
<p>	把这些jar包文件Copy到GameDemo/proj.android/libs下。</p>
<p>
	<b>2. 配置文件与资源部分集成</b></p>
<p>	修改GameDemo/proj.android/AndroidManifest.xml文件，在application标签下，添加如下Activity标签：</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">&lt;activity<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:name=&quot;cn.sharesdk.framework.ShareSDKUIShell&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:configChanges=&quot;keyboardHidden|orientation|screenSize&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:screenOrientation=&quot;portrait&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:theme=&quot;@android:style/Theme.Translucent.NoTitleBar&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:windowSoftInputMode=&quot;stateHidden|adjustResize&quot; &gt;<br />
	&nbsp;&nbsp;&nbsp; &lt;/activity&gt;<br />
	&nbsp;&nbsp;&nbsp; &lt;activity<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:name=&quot;.wxapi.WXEntryActivity&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:configChanges=&quot;keyboardHidden|orientation|screenSize&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:exported=&quot;true&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:screenOrientation=&quot;portrait&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:theme=&quot;@android:style/Theme.Translucent.NoTitleBar&quot; /&gt;</font></p>
<p>	将C2DXShareSDKSample/proj.android/res下的如下目录中的文件复制到GameDemo/proj.android/res下：</p>
<p>	<font face="Courier New">&nbsp;&nbsp; drawable-hdpi/&nbsp; drawable-ldpi/&nbsp; drawable-mdpi/&nbsp;<br />
	&nbsp;&nbsp; drawable-xhdpi/&nbsp; layout/&nbsp; values/&nbsp; values-en/</font></p>
<p>	注意，类似icon.png这种文件就不要复制了，自己做一下判断就好。</p>
<p>
	<b>3. C++部分代码集成</b></p>
<p>	将C2DXShareSDKSample/Classes下的C2DXShareSDK文件夹Copy到GameDemo/Classes下面。</p>
<p>	由于Cocos2d-x 3.0rc2的类命名发生了变化，我们需要对C2DXShareSDK中使用到的引擎中的类名以及方法名进行修改。但实际上Cocos2d-x 3.0rc2考虑到了一些兼容性的问题，大部分名字通过<font face="Courier New">cocos2d/cocos/deprecated/CCDeprecated.h</font>中定义的typedef得以保留，虽然这些名字已经被建议deprecated了。rc2中CCObject被改名为Ref了，这个我们需要手工在C2DXShareSDK进行修改。</p>
<p>	另外ShareSDK组件在实现时大量使用了<font face="Courier New">CCDictionary</font>、<font face="Courier New">CCArray</font>和<font face="Courier New">CCString</font>，而这三个类在Cocos2d-x 3.0rc2中均被deprecated了，但我们依然可以使用，所以我们可以不做修改。但以后随着cocos2d-x版本的演进，这些类很可能被彻底移除出引擎，我们就需要重新使用其替代品进行实现了。</p>
<p>	此外我们还需要手工修改一下C2DXShareSDK/Android/JSON/CCJSONConverter.cpp文件中的<font face="Courier New">getObjJson</font>方 法，因为rc2中CCDictionary、CCString、CCArray这些类的真实名称都已经换成了__Dictionary、__String 和__Array，CCDictionary、CCString、CCArray只是些typedef，因此要像下面这样做些修改(如果你是集成 cocos2d-x 2.x.x版本，则无需做下面修改)：</p>
<p>	<font face="Courier New">cJSON * CCJSONConverter::getObjJson(Ref * obj)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; std::string s = typeid(*obj).name();<br />
	&nbsp;&nbsp;&nbsp; if(s.find(&quot;<b>__Dictionary</b>&quot;)!=std::string::npos){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cJSON * json = cJSON_CreateObject();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; convertDictionaryToJson((CCDictionary *)obj, json);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return json;<br />
	&nbsp;&nbsp;&nbsp; }else if(s.find(&quot;<b>__Array</b>&quot;)!=std::string::npos){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cJSON * json = cJSON_CreateArray();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; convertArrayToJson((CCArray *)obj, json);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return json;<br />
	&nbsp;&nbsp;&nbsp; }else if(s.find(&quot;<b>__String</b>&quot;)!=std::string::npos){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCString * s = (CCString *)obj;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cJSON * json = cJSON_CreateString(s-&gt;getCString());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return json;<br />
	&nbsp;&nbsp;&nbsp; }else if(s.find(&quot;<b>CCNumber</b>&quot;)!=std::string::npos){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCNumber * n = (CCNumber *)obj;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cJSON * json = cJSON_CreateNumber(n-&gt;getDoubleValue());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return json;<br />
	&nbsp;&nbsp;&nbsp; }else if(s.find(&quot;<b>CCNull</b>&quot;)!=std::string::npos){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cJSON * json = cJSON_CreateNull();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return json;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; CCLog(&quot;CCJSONConverter encountered an unrecognized type&quot;);<br />
	&nbsp;&nbsp;&nbsp; return NULL;<br />
	}</font></p>
<p>	CCNumber和CCNull是ShareSDK组件自己实现的类名，这里无需修改。</p>
<p>	接下来我们需要在AppDelegate.cpp中对ShareSDK做初始化了：</p>
<p>	<font face="Courier New">bool AppDelegate::applicationDidFinishLaunching() {<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; initShareSDK();<br />
	&nbsp;&nbsp;&nbsp; &#8230; ..<br />
	}</p>
<p>	void AppDelegate::initShareSDK()<br />
	{<br />
	&nbsp;&nbsp;&nbsp; // sina weibo<br />
	&nbsp;&nbsp;&nbsp; CCDictionary *sinaConfigDict = CCDictionary::create();<br />
	&nbsp;&nbsp;&nbsp; sinaConfigDict-&gt;setObject(CCString::create(&quot;<i>YOUR_WEIBO_APPKEY</i>&quot;), &quot;app_key&quot;);<br />
	&nbsp;&nbsp;&nbsp; sinaConfigDict-&gt;setObject(CCString::create(&quot;<i>YOUR_WEBIO_APPSECRET</i>&quot;), &quot;app_secret&quot;);<br />
	&nbsp;&nbsp;&nbsp; sinaConfigDict-&gt;setObject(CCString::create(&quot;http://www.sharesdk.cn&quot;), &quot;redirect_uri&quot;);<br />
	&nbsp;&nbsp;&nbsp; C2DXShareSDK::setPlatformConfig(C2DXPlatTypeSinaWeibo, sinaConfigDict);</p>
<p>	&nbsp;&nbsp;&nbsp; // wechat<br />
	&nbsp;&nbsp;&nbsp; CCDictionary *wcConfigDict = CCDictionary::create();<br />
	&nbsp;&nbsp;&nbsp; wcConfigDict-&gt;setObject(CCString::create(&quot;<i>YOUR_WECHAT_APPID</i>&quot;), &quot;app_id&quot;);<br />
	&nbsp;&nbsp;&nbsp; C2DXShareSDK::setPlatformConfig(C2DXPlatTypeWeixiSession, wcConfigDict);<br />
	&nbsp;&nbsp;&nbsp; C2DXShareSDK::setPlatformConfig(C2DXPlatTypeWeixiTimeline, wcConfigDict);<br />
	&nbsp;&nbsp;&nbsp; C2DXShareSDK::setPlatformConfig(C2DXPlatTypeWeixiFav, wcConfigDict);</p>
<p>	&nbsp;&nbsp;&nbsp; C2DXShareSDK::open(CCString::create(&quot;YOUR_SHARESDK_APPKEY&quot;), false);<br />
	}</font></p>
<p>	在Share按钮的事件回调函数中调用ShareSDK的接口进行社交平台分享：</p>
<p>	<font face="Courier New">void GameScene::menuShareCallback(Ref* sender)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; Dictionary *content = Dictionary::create();</p>
<p>	&nbsp;&nbsp;&nbsp; content-&gt;setObject(String::create(&quot;ShareSDK for Cocos2d-x 3.0rc2社交分享测试。&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; , &quot;content&quot;);<br />
	&nbsp;&nbsp;&nbsp; content-&gt;setObject(String::create(&quot;ShareSDK分享测试&quot;), &quot;title&quot;);<br />
	&nbsp;&nbsp;&nbsp; content-&gt;setObject(String::create(&quot;http://tonybai.com&quot;), &quot;titleUrl&quot;);<br />
	&nbsp;&nbsp;&nbsp; content-&gt;setObject(String::create(&quot;http://tonybai.com&quot;), &quot;url&quot;);<br />
	&nbsp;&nbsp;&nbsp; content-&gt;setObject(String::create(&quot;Tony Bai&quot;), &quot;site&quot;);<br />
	&nbsp;&nbsp;&nbsp; content-&gt;setObject(String::create(&quot;http://tonybai.com&quot;), &quot;siteUrl&quot;);<br />
	&nbsp;&nbsp;&nbsp; content-&gt;setObject(String::createWithFormat(&quot;%s&quot;, <i>YOUR_LOCAL_IMAGE_PAT</i>H)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; , &quot;image&quot;);<br />
	&nbsp;&nbsp;&nbsp; content-&gt;setObject(String::createWithFormat(&quot;%d&quot;, C2DXContentTypeNews)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; , &quot;type&quot;);</p>
<p>	&nbsp;&nbsp;&nbsp; C2DXShareSDK::showShareMenu(NULL, content, CCPointMake(100, 100),<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; C2DXMenuArrowDirectionLeft, shareResultHandler);<br />
	}</font></p>
<p>	<font face="Courier New">void shareResultHandler(C2DXResponseState state,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; C2DXPlatType platType,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Dictionary *shareInfo,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Dictionary *error)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; AppDelegate *app = (AppDelegate*)Application::getInstance();<br />
	&nbsp;&nbsp;&nbsp; switch (state) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case C2DXResponseStateSuccess:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCLog(&quot;Share Ok&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app-&gt;showShareResultToast(&quot;分享成功&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case C2DXResponseStateFail:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app-&gt;showShareResultToast(&quot;分享失败&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CCLog(&quot;Share Failed&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; default:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>	showShareResultToast实现如下：</p>
<p>	<font face="Courier New">void AppDelegate::showShareResultToast(const char *msg)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; JniMethodInfo t;<br />
	&nbsp;&nbsp;&nbsp; if (JniHelper::getStaticMethodInfo(t, &quot;YOUR_ACTIVITY_NAME&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;showShareResultToast&quot;, &quot;(Ljava/lang/String;)V&quot;)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; jstring jmsg = t.env-&gt;NewStringUTF(msg);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;CallStaticVoidMethod(t.classID, t.methodID, jmsg);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (t.env-&gt;ExceptionOccurred()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;ExceptionDescribe();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;ExceptionClear();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;DeleteLocalRef(t.classID);<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>
	<b>4. Java部分代码集成</b></p>
<p>	在GameDemo/proj.android/src下面建立cn/sharesdk路径，将C2DXShareSDKSample /proj.android/src/cn/sharesdk下的onekeyshare和ShareSDKUtils.java Copy到GameDemo/proj.android/src/cn/sharesdk下面。</p>
<p>	将ShareSDK-Android-2.3.7.zip解压后的ShareSDK for Android/Src/wxapi Copy到GameDemo/proj.android/src/com.tonybai.game/下。</p>
<p>	修改GameDemo/proj.android/src/com.tonybai.game/GameDemoActivity.java文件：</p>
<p>	<font face="Courier New">import android.widget.Toast;<br />
	import cn.sharesdk.ShareSDKUtils;<br />
	&#8230;</p>
<p>	public class GameDemoActivity extends Cocos2dxActivity {</p>
<p>	&nbsp;&nbsp;&nbsp; private static Context context;</p>
<p>	&nbsp;&nbsp;&nbsp; private static Handler notifyHandler = new Handler() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void handleMessage(Message msg) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; switch (msg.what) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case 1:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String message = (String) msg.obj;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Toast.makeText(context, message,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Toast.LENGTH_SHORT).show();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; default:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; };</p>
<p>
	&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp; protected void onCreate(Bundle savedInstanceState) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super.onCreate(savedInstanceState);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context = this;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ShareSDKUtils.prepare();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ShareSDKUtils.initSDK(&quot;YOUR_SHARESDK_APPKEY&quot;, true);<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public static void showShareResultToast(String result) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Message msg = new Message();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.what = 1;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.obj = result;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; notifyHandler.sendMessage(msg);<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp; public void onDestroy() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ShareSDKUtils.stopSDK();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super.onDestroy();<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>	<b>三、问题与解决方法</b></p>
<p>	按照上面的集成方法修改后，通过cocos编译app，在模拟器运行GameDemo，点击Share，理论上屏幕下方会出现ShareSDK的分享窗口，选择&ldquo;新浪微博&rdquo;图标，会打开&ldquo;图文分享&rdquo;内容窗口，点击窗口右上角的&ldquo;分享&rdquo;即可。</p>
<p>	【<b>问题1</b>】&ldquo;图文分享&rdquo;窗口内容可编辑，并且总是弹出软键盘，影响体验。<br />
	&nbsp;<br />
	&nbsp;期望：内容不可编辑，默认不弹出软键盘<br />
	&nbsp;解决方法：<br />
	&nbsp;&nbsp;&nbsp; &nbsp; 打开proj.android/src/cn/sharesdk/onekeyshare/EditPage.java，做如下修改：</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 将窗口的软输入方式默认改为SOFT_INPUT_STATE_HIDDEN。</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp; <font face="Courier New">public void setActivity(Activity activity) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super.setActivity(activity);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (dialogMode) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; activity.setTheme(android.R.style.Theme_Dialog);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; activity.requestWindowFeature(Window.FEATURE_NO_TITLE);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; activity.getWindow().setSoftInputMode(<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <u><b>WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);//default: hidden</b></u><br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	<font face="Courier New">&nbsp;&nbsp;&nbsp; 在initPageView中增加一行：etContent.setKeyListener(null)。让窗口内容无法修改。<br />
	&nbsp;&nbsp;&nbsp; private void initPageView() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 文字输入区域<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; etContent = new EditText(getContext());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; etContent.setGravity(Gravity.LEFT | Gravity.TOP);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; etContent.setBackgroundDrawable(null);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; etContent.setText(String.valueOf(reqData.get(&quot;text&quot;)));<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>etContent.setKeyListener(null);//make the edittext uneditable</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; etContent.setLayoutParams(lpEt);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	<b>【问题2】</b>向微博分享，点击&ldquo;分享&rdquo;后，过一会程序异常停止。</p>
<p>	&nbsp;原因分析：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 通过调试观察，发现ShareSDK在解析从Weibo收到的Json包时出现内存违法访问。具体位置是在解析一个数组对象时出现的问题。 ShareSDK用CCArray来存储Json中的数组对象。该问题在cocos2d-x 2.2.2版本中不会出现，但在cocos2d-x 3.0rc2版本中会出现。经代码对比发现，3.0rc2版本中的CCArray的实现与2.2.2 CCArray实现有很大不同，似乎是做了较大重构，暂不能确定是否是3.0rc2版本中CCArray实现的bug。</p>
<p>	&nbsp;解决方法：由于后续的分享结果通知成功与否只需要根据分享的状态来决定，因此我们只需解析出&quot;status&quot;、&ldquo;action&rdquo;和&ldquo;platform&rdquo; 这三个CCNumber类型字段的值即可。CCArray类型的对象我们并不需要，因此我们只需绕过对Array类型字段的解析和存储即可，修改如下：</p>
<p>	<font face="Courier New">// Classes/C2DXShareSDK/Android/JSON/CCJSONConverter.cpp</p>
<p>	void CCJSONConverter::convertJsonToDictionary(cJSON *json, CCDictionary *dictionary)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; dictionary-&gt;removeAllObjects();<br />
	&nbsp;&nbsp;&nbsp; cJSON * j = json-&gt;child;<br />
	&nbsp;&nbsp;&nbsp; while (j) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>if (j-&gt;type == cJSON_Number) {</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Ref * obj = getJsonObj(j);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dictionary-&gt;setObject(obj, j-&gt;string);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>}</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; j = j-&gt;next;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>	<b>四、其他</b></p>
<p>	在使用ShareSDK做社交分享时，注意下面两个现象：<br />
	1) 第一次进行微博或微信分享时，会打开授权页面，授权后才能分享成功；<br />
	2) 微信分享窗口只有在手机联网状态下才能打开。如果手机无法联网，那微信好友、朋友圈和收藏分享将无法打开分享窗口，也不会有什么提示。</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/04/25/integrate-cocos2dx3rc2-with-sharesdk/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
