﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-金庆的专栏-随笔分类-2. 网游开发</title><link>http://www.cppblog.com/jinq0123/category/7980.html</link><description /><language>zh-cn</language><lastBuildDate>Fri, 18 Nov 2022 02:36:15 GMT</lastBuildDate><pubDate>Fri, 18 Nov 2022 02:36:15 GMT</pubDate><ttl>60</ttl><item><title>How are dtLinks created in NavMesh</title><link>http://www.cppblog.com/jinq0123/archive/2022/11/18/229525.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Fri, 18 Nov 2022 02:03:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2022/11/18/229525.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/229525.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2022/11/18/229525.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/229525.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/229525.html</trackback:ping><description><![CDATA[<h1 id="how-are-dtlinks-created-in-navmesh">How are dtLinks created in NavMesh</h1>
<p>(Jin Qing's Column, Nov., 2022)</p>
<h2 id="dtlink"><code>dtLink</code></h2>
<p><code>dtLink</code> defines a link between 2 polygons.
It is an internal data structure.</p>
<p>A <code>dtLink</code> is owned by its source polygon.</p>
<h3 id="how-link-is-used">How link is used</h3>
<p>The links are used in <code>findPath()</code>, to search
all the neighbor polygons, from the current best polygon.</p>
<h3 id="fields-of-dtlink">fields of <code>dtLink</code></h3>
<p><code>dtLink</code> has 2 required fields:</p>
<ul>
<li>neighbor: the neighbor polygon reference that is linked to.</li>
<li>edge: index of the polygon edge that owns this link.
It is used in getPortalPoints() and raycast().</li>
</ul>
<p>If the link is a boundary link, which links to other tile, then it has more fields:</p>
<ul>
<li>side: defines on which side the link is. 8 sides.
It is used in findPath() and raycast().</li>
<li>bmin: defines the minimum sub-edge area.</li>
<li>bmax: defines the maximum sub-edge area.
bmin and bmax are used in portal finding.</li>
</ul>
<h2 id="how-are-links-created">How are links created</h2>
<p>Links are not saved to navmesh file. They are created on navmesh loading in <code>addTile()</code>.</p>
<p><code>dtNavMesh::addTile()</code> add a tile to the navmesh. A tile is a square block of the map.</p>
<p>The main job of <code>dtNavMesh::addTile()</code> is to create links inside this tile and between this tile and other tiles.
Actually a block indexed by (x, y) of constant size has many layers for multi-floor map.
A <code>dtMeshTile</code> is actually a layer with an index of (x, y, layer).</p>
<p>5 steps to create all links:</p>
<h3 id="1-connect-internal-links">1. Connect internal links</h3>
<p><code>connectIntLinks()</code> iterates all the polygons in the tile and create links for them
by the help of neighbors information of the polygons.</p>
<h3 id="2-base-off-mesh-connections">2. Base off-mesh connections</h3>
<p><code>baseOffMeshLinks()</code> bases off-mesh connections to their starting polygons and connect connections inside the tile.</p>
<p>Off-mesh connection is between 2 points which are inside a tile or between 2 adjacent tiles.</p>
<p>For each off-mesh connection, there creates a specific polygon consisting of 2 vertices.
See <code>DT_POLYTYPE_OFFMESH_CONNECTION</code>.</p>
<p><code>baseOffMeshLinks()</code> creates 2 links for each off-mesh connection:</p>
<ul>
<li>From the off-mesh connection polygon to the source polygon</li>
<li>From the source polygon to the off-mesh connection polygon</li>
</ul>
<p>The destinaton polygon of the off-mesh connection is skipped here.</p>
<h3 id="3-connect-external-off-mesn-links-within-this-tile">3. Connect external off-mesn links within this tile</h3>
<p><code>connectExtOffMeshLinks()</code> with the source and target tile be this tile.</p>
<p><code>connectExtOffMeshLinks()</code> searches off-mesh connections that are from this tile to the target tile
by checking the side direction of the connection.</p>
<p>It creates a link from off-mesh connection polygon to the target tile.
For bidirectional connection, it also creates a link from the target polygon to the off-mesh connection polygon.</p>
<p>So for each off-mesh connection of this tile, 3 or 4 links are created by baseOffMeshLinks() and connectExtOffMeshLinks(),
one on the source polygon, one on the destination polygon and 1/2 on the off-mesh connection polygon.</p>
<h3 id="4-connect-with-layers-in-current-tile">4. Connect with layers in current tile</h3>
<p>For each layer other than this layer in this tile:</p>
<ul>
<li>Create links from this layer to other layer</li>
<li>Create links from other layer to this layer</li>
<li>Create links of the off-mesh connection from this layer to other layer</li>
<li>Create links of the off-mesh connection from other layer to this layer</li>
</ul>
<h3 id="5-connect-with-neighbour-tiles">5. Connect with neighbour tiles</h3>
<p>Check 9 neighbor tiles' layers, and create links just like previous step:</p>
<ul>
<li>Create links from this layer to neighnor layer</li>
<li>Create links from neighnor layer to this layer</li>
<li>Create links of the off-mesh connection from this layer to neighnor layer</li>
<li>Create links of the off-mesh connection from neighnor layer to this layer</li>
</ul>
<p>By now, for every off-mesh connection of all the loaded tiles, 3/4 links are created.</p>
<h2 id="reference">Reference</h2>
<ul>
<li>https://blog.csdn.net/icebergliu1234/article/details/80322392</li>
</ul>
<img src ="http://www.cppblog.com/jinq0123/aggbug/229525.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2022-11-18 10:03 <a href="http://www.cppblog.com/jinq0123/archive/2022/11/18/229525.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>UE4 Blueprint Multiple Event BeginPlay</title><link>http://www.cppblog.com/jinq0123/archive/2021/07/31/217765.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Sat, 31 Jul 2021 07:19:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2021/07/31/217765.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217765.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2021/07/31/217765.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217765.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217765.html</trackback:ping><description><![CDATA[# Multiple Event BeginPlay<br /><br />(金庆的专栏 2021.7)<br /><br />How to do multiple actions on Event BeginPlay in UE4 Blueprints?<br /><br />Sams-Teach-Yourself-Unreal-Engine-4-Game-Development-in-24-Hours says:<br />```<br />Q. When I try to add a second event node, BeginPlay, the Editor shows me the first one already<br />placed in the Event Graph. Why does this happen?<br /><br />A. Some events, such as Event Tick and the BeginPlay event, can have only one instance per<br />Blueprint. <br />```<br /><br />https://forums.unrealengine.com/t/do-multiple-things-on-event-begin-play/411/10<br />```<br />The Sequence node allows for a single execution pulse to trigger a series of events in order. The node may have any number of outputs, all of which get called as soon as the Sequence node receives an input. They will always get called in order, but without any delay. To a typical user, the outputs will likely appear to have been triggered simultaneously.<br />```<br /><br />Youtube video: [How to do Multiple Actions on Event Begin Play Unreal Engine 4 Blueprints](https://www.youtube.com/watch?v=nqG-ztbs230)<img src ="http://www.cppblog.com/jinq0123/aggbug/217765.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2021-07-31 15:19 <a href="http://www.cppblog.com/jinq0123/archive/2021/07/31/217765.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>第9代游戏主机</title><link>http://www.cppblog.com/jinq0123/archive/2021/05/09/217680.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Sun, 09 May 2021 12:44:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2021/05/09/217680.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217680.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2021/05/09/217680.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217680.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217680.html</trackback:ping><description><![CDATA[# 第9代游戏主机<br /><br />原文：[Ninth generation of video game consoles](https://en.wikipedia.org/wiki/Ninth_generation_of_video_game_consoles)<br /><br />2020.11，微软(MS) Xbox Series X/S 和 Sony PlayStation 5 (PS5) 发布，标志着游戏主机进入第9代。<br /><br />和前代的 Xbox One 和 PS4 相比，新一代主机有可观的性能提升，支持实时光线跟踪，4K分辨率，目标帧率为60。<br />内部都使用了固态硬盘(SSD)。低配版没有光驱，仅支持网络和USB。<br /><br />定位上要胜过任天堂Switch和云游戏服务如 Stadia 和 Amazon Luna.<br /><br />## 背景<br /><br />第8代时间较长。因为摩尔定律，过去几代一般每代为5年时间，但是 MS 和 Sony 出了中间代产品 Xbox One X 和 PS4 Pro.<br /><br />2020.3 开始的 COVID-19 疫情影响也使新一代主机的发布延后了。<br /><br />## 主要主机<br /><br />### PS5<br /><br />有2个机型，基本型和数字型，数字型没有光驱较便宜，其他一样。<br />PS5和PS4的游戏兼容，只有少量游戏不支持，但是可以通过 PS Now 云游戏服务玩 PS4 游戏。<br /><br />### Xbox Series X/S<br /><br />MS 延继了双主机模式：高端的X系列和低端的S系列。S系列没有光驱。<br />两者都支持外部存储和Xbox Live在线分发。向后兼容以前的游戏，但不包括Kinect游戏。<br />MS鼓励开发商使用 Smart Delivery，把Xbox One游戏升级到Xbox Series X/S。<br /><br />## 其他主机<br /><br />* 任天堂 Switch<br />* 云游戏平台：Stadia, Amazon Luna, GeForce Now<img src ="http://www.cppblog.com/jinq0123/aggbug/217680.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2021-05-09 20:44 <a href="http://www.cppblog.com/jinq0123/archive/2021/05/09/217680.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Canvas Scaler 的3种模式</title><link>http://www.cppblog.com/jinq0123/archive/2021/03/02/217621.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Tue, 02 Mar 2021 04:00:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2021/03/02/217621.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217621.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2021/03/02/217621.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217621.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217621.html</trackback:ping><description><![CDATA[Canvas Scaler 的3种模式<br /><br />(金庆的专栏 2021.3)<br /><br />参考：<br />https://wenku.baidu.com/view/83991090336c1eb91b375db8.html<br /><br />Unity Canvas 有个 Canvas Scaler 组件，用来决定 Canvas 的 Scale(缩放)值。<br />Scale 有 X,Y,Z 3个分量，都是同一值，即在各个方向上都是同倍率缩放的。<br />它有3种Scale Mode。<br /><br />因为设计时的宽高比与目标机型宽高比不同，Canvas 需要先增减为目标高宽比，然后再缩放。<br />Canvas 缩放并不影响UI元素的相互位置，只是同比缩放。<br />但是高宽比调整时会造成UI元素的相对位置变化，如宽度增大后，UI元素会在宽度方向上相互散开。<br /><br />可以在 Unity Editor Game 页左上角分辨率下拉菜单中创建几个低分辨率屏，用来测试屏幕大小切换。<br />测试界面可放置2个按钮，一个锚定到左上，位于左上，一个锚定到右下，位于右下，不要strech。<br /><br />3种模式如下。<br /><br />## Constance Pixel Size<br /><br />Constance Pixel Size 模式下，Scale 总是为1。<br />目标机上的 Canvas 就是其分辨率大小。<br />因为设计时分辨率与实际分辨率不同，显示会不同。<br />但是图片和按钮的大小保持固定像素大小。<br />如果屏幕变大，UI元素就会相互散开，变小则聚拢。<br />如果需要缩放，只能通过程序调节。<br /><br />## Scale With Screen Size<br /><br />随屏幕大小缩放。<br />该模式下有个 Reference Resolution (参考分辨率)，应该设为主流机型的分辨率。<br />在主流机型下，Scale 为1，与设计显示相同。<br /><br />https://blog.csdn.net/u011948420/article/details/112641308<br />2020中国移动游戏质量白皮书-WeTest 报告 Android 手机 TOP300 分辨率为 2340*1080 占 31%。<br /><br />如果目标机型为 4680*2160，则 Scale 正好为2；如果目标机型为 1179*540，则Scale为0.5.<br /><br />一般情况下宽高比不同，此时Scale算法有3种，即3种Sceen Match Mode：<br /><br />### Match Width Or Height<br /><br />匹配宽度或高度。<br /><br />此时有个 Match 划动条，可以在 Width(0)..Height(1)之间。<br />如果为0，匹配宽度，则 Scale 为目标屏宽与参考屏宽之比；<br />如果为1，匹配高度，则 Scale 为目标屏高与参考屏高之比；<br />如果为0.5, 加权匹配，则是这2个比值的平均值。<br /><br />匹配宽度时，高度方向上会有UI扩散或聚拢。<br />匹配高度时，宽度方向上会有UI扩散或聚拢。<br />加权匹配时，UI在高上扩散宽上聚拢，或者在高上聚拢宽上扩散。<br /><br />### Expand<br /><br />增加。先增加宽或高到目标宽高比。该增加方向UI元素会散开。<br /><br />### Shrink<br /><br />减少。先减少宽或高到目标宽高比。该减少方向UI元素会聚拢。<br /><br />## Constant Physical Size<br /><br />固定物理大小。<br />随着4k屏的出现，屏幕DPI会很大，许多设备的像素点非常小。<br />同样的按钮，在高DPI下，如果固定像素大小，则会非常小，这时适合用该模式。<br /><br /><img src ="http://www.cppblog.com/jinq0123/aggbug/217621.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2021-03-02 12:00 <a href="http://www.cppblog.com/jinq0123/archive/2021/03/02/217621.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>帧同步是否允许客户端指定命令帧号</title><link>http://www.cppblog.com/jinq0123/archive/2020/10/08/217474.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Thu, 08 Oct 2020 03:39:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2020/10/08/217474.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217474.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2020/10/08/217474.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217474.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217474.html</trackback:ping><description><![CDATA[帧同步是否允许客户端指定命令帧号<br /><br />(金庆的专栏 2020.10)<br /><br />帧同步服务器以固定的帧率收集每个客户端的输入命令，每帧打包一个命令帧，打上服务器当前帧号，<br />然后广播该命令帧到每个客户端，由客户端执行。<br />如果没有客户端输入命令，则该命令帧可能只有一个帧号。<br /><br />客户端的输入命令分2种模式：不指定帧号和指定帧号。<br /><br />客户端不指定帧号的模式下，客户端仅告诉服务器自己的动作，由服务器为该动作打上当前的服务器帧号，然后再广播。<br />服务器实现较简单，只需要缓存当前帧的所有客户端输入，下一帧时全部打包广播。<br /><br />指定帧号的模式下，客户端要求该命令在指定帧(服务器帧)才生效。<br />服务器需要缓存该命令，运行到指定帧号时，才将该命令打包到命令帧并广播所有客户端。<br /><br />指定帧号在客户端有预测回滚时较为有利，因为客户端知道自己的命令将在确定的帧执行，即对自己的预测总是成功的。<br />而如果不指定帧号，客户端需要预测自己的命令会在哪个服务器帧到达服务器，而这在网络抖动较大时会预测失败。<br /><br />指定帧号会造成命令的延时时间较长。而不指定帧号将会立即执行。<br /><br />如果网络堵塞了一会儿，客户端发送的命令延时了较长时间才到达服务器，<br />指定的帧号可能小于服务器当前帧号，这时服务器对该命令有2种处理方式.<br />一种是直接忽略该命令，因为该命令已无法实现。<br />一种是尽量实现该命令，即在当前帧执行。<br />考虑到客户端预测，2种都是预测失败，同样需要回滚，但是在当前帧执行可能回滚造成的抖动会小一点。<br />如起跳命令，一种是已经跳起来了被拉回地面，因为起跳命令被取消了，一种是跳在空中停顿了一下，因为起跳的时间点被延后了。<br />尽量实现命令应该比丢弃命令更好一点。<br /><br />如果是在当前服务器帧执行过期帧号的命令，那么这2种模式可以合并成一种，即所有命令都是指定帧号，<br />只是有的帧号是0，表示让服务器在当前帧执行。<br /><br />通用的帧同步服务器应该让客户端指定帧号。<br /><br />指定帧号的命令可以实现严格时间间隔的命令序列，<br />客户端可以一次性发送整个命令序列，指定每个命令为不同的帧号。<br />还应该允许客户端指定命令序列的帧间隔，但是首命令是立即执行的。<br />命令序列的帧号应该是相对于首命令的。<br />如果命令序列的首命令延后执行，那么整个序列全部将同样延后。<br /><br />客户端是否需要知道自己的指定帧号命令被延后执行了？这样可以让客户端有更好的表现？<br />那么成功执行的指定帧号命令是否也应该让客户端知道？<br />服务器仅仅是将客户端命令原样广播，客户端命令中可以加入命令序号，自行判断命令是否延后了。<br /><br />是否将一个玩家的提前发送的指定帧命令提前广播给其他玩家？<br />这样其他玩家不仅对自己预测成功，对该玩家的预测也成功。<br />但是泄露了自己将要执行的动作会被其他玩家利用。<br />所以提前量不能太大，指定帧号应该尽量接近服务器帧号。<br />服务器就不用缓存客户端指令了，直接收到后广播即可。<br />这样服务器广播的命令帧中有服务器帧号，也有客户端的指定帧号。<br />服务器也就不用管客户端是否指定帧号了。<br />指定帧号和解析成为纯客户端逻辑。<br /><br /><br /><br /><img src ="http://www.cppblog.com/jinq0123/aggbug/217474.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2020-10-08 11:39 <a href="http://www.cppblog.com/jinq0123/archive/2020/10/08/217474.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>rpc应答太快造成请求超时</title><link>http://www.cppblog.com/jinq0123/archive/2020/09/17/217453.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Thu, 17 Sep 2020 07:59:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2020/09/17/217453.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217453.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2020/09/17/217453.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217453.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217453.html</trackback:ping><description><![CDATA[rpc应答太快造成请求超时<br /><br />(金庆的专栏 2020.9)<br /><br />在压测中发现总有几个请求超时，超时时长设大也会有，而成功的请求延时远小于超时时间。<br />查错的第一方向是查网络库中有消息丢失。<br />跟踪所有消息，发现超时的消息应该是正常处理并返回了。<br />于是查接收应答消息后的处理，最终找到代码：<br />```go<br />func (c *Client) onRpcRet(cbIndex uint32, ...) {<br />&nbsp;&nbsp; &nbsp;ii, ok := c.callbacks.Load(cbIndex)<br />&nbsp;&nbsp; &nbsp;if !ok {<br />&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; // logger.Errorf("onRpcRet can not find cbIndex %d", cbIndex) // 可能是超时已删<br />&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; return<br />&nbsp;&nbsp; &nbsp;}<br />```<br />打开日志，发现超时的请求对应有该条错误日志。<br />此处回调不存在的情况，正常是先超时删除回调，然后再收到应答。<br />现在是先收到了应答，发现找不到回调，然后过了一段时间会被判为超时无响应。<br /><br />将下面代码换个次序就好了：<br />```go<br />&nbsp;&nbsp; &nbsp;if err := c.Session.Send(msg); err != nil {<br />&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; ...<br />&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; return<br />&nbsp;&nbsp; &nbsp;}<br />&nbsp;&nbsp; &nbsp;c.callbacks.Store(cbIndex, ...)<br />```<br />改为<br />```go<br />&nbsp;&nbsp; &nbsp;// 必须先设回调，然后发送，因为应答可能会很快<br />&nbsp;&nbsp; &nbsp;c.callbacks.Store(cbIndex, ...)<br />&nbsp;&nbsp; &nbsp;if err := c.Session.Send(msg); err...<br />```<br /><br />压测时因为加压机CPU是满负载运转，所以 Send() 和 Store() 之间可能会间隔数毫秒，<br />足够 rpc 请求处理完成并返回，而应答返回时回调还没设置。<br /><br />先 Send() 后 Store() 写代码会稍微简单点，因为 Send() 失败后可以直接返回。<br />先 Store() 后 Send() 时，Send() 失败则需要相应 Delete().<br /><br /><img src ="http://www.cppblog.com/jinq0123/aggbug/217453.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2020-09-17 15:59 <a href="http://www.cppblog.com/jinq0123/archive/2020/09/17/217453.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>测试 tolua 例子 TestErrorStack</title><link>http://www.cppblog.com/jinq0123/archive/2020/09/03/217437.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Thu, 03 Sep 2020 08:29:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2020/09/03/217437.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217437.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2020/09/03/217437.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217437.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217437.html</trackback:ping><description><![CDATA[# 测试 tolua 例子 TestErrorStack<br /><br />(金庆的专栏 2020.9)<br /><br />## Error1<br /><br />1. 点击 "Error1" 按钮 <br />1. c# showStack.PCall()<br />1. lua ShowStack()<br />1. c# Test1()<br />1. c# try { show.PCall() }<br />1. lua Show() error('this is error')&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br /><br />```<br />LuaException: TestErrorStack:2: this is error<br />stack traceback:<br />&nbsp;&nbsp; &nbsp;[C]: in function 'error'<br />&nbsp;&nbsp; &nbsp;TestErrorStack:2: in function &lt;TestErrorStack:1&gt;<br />&nbsp;&nbsp; &nbsp;[C]: in function 'Test1'<br />&nbsp;&nbsp; &nbsp;TestErrorStack:6: in function &lt;TestErrorStack:5&gt;<br />LuaInterface.LuaState:PCall(Int32, Int32) (at Assets/ToLua/Core/LuaState.cs:758)<br />LuaInterface.LuaFunction:PCall() (at Assets/ToLua/Core/LuaFunction.cs:96)<br />TestLuaStack:Test1(IntPtr) (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:27)<br />LuaInterface.LuaDLL:lua_pcall(IntPtr, Int32, Int32, Int32)<br />LuaInterface.LuaState:PCall(Int32, Int32) (at Assets/ToLua/Core/LuaState.cs:755)<br />LuaInterface.LuaFunction:PCall() (at Assets/ToLua/Core/LuaFunction.cs:96)<br />TestLuaStack:OnGUI() (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:375)<br />```<br /><br />lua Show() 中抛 error, 在 C# 中 try-catch 得到，<br />通过 toluaL_exception() 返回 Lua 调用者 ShowStack()，<br />ShowStack() 中止执行，传递异常到 c# 调用者 OnGUI(),<br />OnGUI()中断执行，打印错误信息。<br /><br />## Instantiate Error<br /><br />1. "Instantiate Error"<br />1. c# GetFunction("Instantiate").PCall()<br />1. lua Instantiate()<br />1. c# UnityEngine.Object.Instantiate(obj)<br />1. c# TestInstantiate.Awake()<br />1. c# try { GetFunction("Show").PCall() }<br />1. lua Show()<br />1. c# state.ThrowLuaException(e)<br /><br />```<br />LuaException: TestErrorStack:2: this is error<br />stack traceback:<br />&nbsp;&nbsp; &nbsp;[C]: in function 'error'<br />&nbsp;&nbsp; &nbsp;TestErrorStack:2: in function &lt;TestErrorStack:1&gt;<br />&nbsp;&nbsp; &nbsp;[C]: in function 'Instantiate'<br />&nbsp;&nbsp; &nbsp;TestErrorStack:12: in function &lt;TestErrorStack:11&gt;<br />LuaInterface.LuaState:PCall(Int32, Int32) (at Assets/ToLua/Core/LuaState.cs:758)<br />LuaInterface.LuaFunction:PCall() (at Assets/ToLua/Core/LuaFunction.cs:96)<br />TestInstantiate:Awake() (at Assets/ToLua/Examples/TestErrorStack/TestInstantiate.cs:15)<br />UnityEngine.Object:Instantiate(Object)<br />UnityEngine_ObjectWrap:Instantiate(IntPtr) (at Assets/ToLua/BaseType/UnityEngine_ObjectWrap.cs:203)<br />LuaInterface.LuaDLL:lua_pcall(IntPtr, Int32, Int32, Int32)<br />LuaInterface.LuaState:PCall(Int32, Int32) (at Assets/ToLua/Core/LuaState.cs:755)<br />LuaInterface.LuaFunction:PCall() (at Assets/ToLua/Core/LuaFunction.cs:96)<br />TestLuaStack:OnGUI() (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:391)<br />```<br />在 lua 中实例化对象，Awake() 中向lua抛异常：state.ThrowLuaException(e)。<br />中止了 lua 调用和 OnGUI(), 但是新对象的 Start() 成功调用了。 <br />因为 Awake() 中 catch 了异常，按执行成功处理。<br />如果 Awake() 中不 catch, Awake() 执行异常，也不会有 Start() 调用，但是lua Instantiate() 执行会成功，打印出对象名。<br /><br />## Check Error<br /><br />1. "Check Error"<br />1. c# GetFunction("TestRay").PCall()<br />1. lua TestRay() return Vector3.zero<br />1. c# CheckRay();&nbsp; //返回值出错<br /><br />```<br />LuaException: bad argument #2 (Ray expected, got Vector3)<br />LuaInterface.LuaDLL:luaL_argerror(IntPtr, Int32, String) (at Assets/ToLua/Core/LuaDLL.cs:692)<br />LuaInterface.LuaDLL:luaL_typerror(IntPtr, Int32, String, String) (at Assets/ToLua/Core/LuaDLL.cs:706)<br />LuaInterface.LuaStatePtr:LuaTypeError(Int32, String, String) (at Assets/ToLua/Core/LuaStatePtr.cs:534)<br />LuaInterface.LuaState:CheckRay(Int32) (at Assets/ToLua/Core/LuaState.cs:1505)<br />LuaInterface.LuaFunction:CheckRay() (at Assets/ToLua/Core/LuaFunction.cs:781)<br />TestLuaStack:OnGUI() (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:403)<br />```<br /><br />## Push Error<br /><br />1. "Push Error"<br />1. c# func.Push(Instance);<br /><br />```<br />14:04:31.371-157: Type TestLuaStack not wrap to lua, push as UnityEngine.MonoBehaviour, the warning is only raised once<br />UnityEngine.Debug:LogWarning(Object)<br />LuaInterface.Debugger:LogWarning(String)<br />LuaInterface.Debugger:LogWarning(String, Object, Object)<br />LuaInterface.LuaState:GetMissMetaReference(Type) (at Assets/ToLua/Core/LuaState.cs:1917)<br />LuaInterface.LuaStatic:GetMissMetaReference(IntPtr, Type) (at Assets/ToLua/Core/LuaStatic.cs:39)<br />LuaInterface.ToLua:LoadPreType(IntPtr, Type) (at Assets/ToLua/Core/ToLua.cs:2608)<br />LuaInterface.ToLua:PushUserObject(IntPtr, Object) (at Assets/ToLua/Core/ToLua.cs:2622)<br />LuaInterface.ToLua:Push(IntPtr, Object) (at Assets/ToLua/Core/ToLua.cs:2636)<br />LuaInterface.LuaState:Push(Object) (at Assets/ToLua/Core/LuaState.cs:1378)<br />LuaInterface.LuaFunction:Push(Object) (at Assets/ToLua/Core/LuaFunction.cs:465)<br />TestLuaStack:OnGUI() (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:412)<br />```<br /><br />## LuaPushError<br /><br />1. "LuaPushError"<br />1. c# GetFunction("PushLuaError").PCall()<br />1. lua PushLuaError()<br />1. lua TestStack.PushLuaError()<br />1. c# PushLuaError()<br />1. c# try { testRay.Push(Instance); }<br /><br />仅是警告，没有异常<br /><br />## Check Error<br /><br />1. "Check Error"<br />1. c# GetFunction("Test5").PCall()<br />1. lua Test5()<br />1. lua TestStack.Test5()<br />1. c# Test5()<br />1. c# GetFunction("Test4").PCall()<br />1. lua TestStack.Test4()<br />1. c# Test4()<br />1. c# try { show.PCall() }<br /><br />```<br />LuaException: TestErrorStack:2: this is error<br />stack traceback:<br />&nbsp;&nbsp; &nbsp;[C]: in function 'error'<br />&nbsp;&nbsp; &nbsp;TestErrorStack:2: in function &lt;TestErrorStack:1&gt;<br />&nbsp;&nbsp; &nbsp;[C]: in function 'Test4'<br />&nbsp;&nbsp; &nbsp;TestErrorStack:30: in function &lt;TestErrorStack:29&gt;<br />&nbsp;&nbsp; &nbsp;[C]: in function 'Test5'<br />&nbsp;&nbsp; &nbsp;TestErrorStack:34: in function &lt;TestErrorStack:33&gt;<br />LuaInterface.LuaState:PCall(Int32, Int32) (at Assets/ToLua/Core/LuaState.cs:758)<br />LuaInterface.LuaFunction:PCall() (at Assets/ToLua/Core/LuaFunction.cs:96)<br />TestLuaStack:Test4(IntPtr) (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:85)<br />LuaInterface.LuaDLL:lua_pcall(IntPtr, Int32, Int32, Int32)<br />LuaInterface.LuaState:PCall(Int32, Int32) (at Assets/ToLua/Core/LuaState.cs:755)<br />LuaInterface.LuaFunction:PCall() (at Assets/ToLua/Core/LuaFunction.cs:96)<br />TestLuaStack:Test5(IntPtr) (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:102)<br />LuaInterface.LuaDLL:lua_pcall(IntPtr, Int32, Int32, Int32)<br />LuaInterface.LuaState:PCall(Int32, Int32) (at Assets/ToLua/Core/LuaState.cs:755)<br />LuaInterface.LuaFunction:PCall() (at Assets/ToLua/Core/LuaFunction.cs:96)<br />TestLuaStack:OnGUI() (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:434)<br />```<br /><br />## Test Resume<br /><br />1. "Test Resume"<br />1. c# GetFunction("Test6").PCall()<br />1. lua Test6()<br />1. lua 协程中调用 TestStack.Test6(go)<br />1. c# toluaL_exception()<br /><br />lua coroutine resume() 返回 false, 不会有错误。<br /><br />## out of bound<br /><br />1. "out of bound"<br />1. c# GetFunction("TestOutOfBound").PCall()<br />1. c# TestOutOfBound()<br />1. c# toluaL_exception(L, e)<br /><br />```<br />LuaException: Transform child out of bounds<br />stack traceback:<br />&nbsp;&nbsp; &nbsp;[C]: at 0x613c2af0<br />TestLuaStack:TestOutOfBound(IntPtr) (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:136)<br />LuaInterface.LuaDLL:lua_pcall(IntPtr, Int32, Int32, Int32)<br />LuaInterface.LuaState:PCall(Int32, Int32) (at Assets/ToLua/Core/LuaState.cs:755)<br />LuaInterface.LuaFunction:PCall() (at Assets/ToLua/Core/LuaFunction.cs:96)<br />TestLuaStack:OnGUI() (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:454)<br />```<br /><br />## TestArgError<br /><br />1. "TestArgError"<br />1. c# GetFunction("Test8").PCall()<br />1. lua Test8()<br />1. lua TestArgError()<br />1. c# TestArgError()<br />1. c# toluaL_exception(L, e)<br /><br />```<br />LuaException: TestErrorStack:69: bad argument #1 to 'TestArgError' (number expected, got string)<br />stack traceback:<br />&nbsp;&nbsp; &nbsp;[C]: in function 'TestArgError'<br />&nbsp;&nbsp; &nbsp;TestErrorStack:69: in function &lt;TestErrorStack:68&gt;<br />LuaInterface.LuaDLL:luaL_argerror(IntPtr, Int32, String) (at Assets/ToLua/Core/LuaDLL.cs:692)<br />LuaInterface.LuaDLL:luaL_typerror(IntPtr, Int32, String, String) (at Assets/ToLua/Core/LuaDLL.cs:706)<br />TestLuaStack:TestArgError(IntPtr) (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:151)<br />LuaInterface.LuaDLL:lua_pcall(IntPtr, Int32, Int32, Int32)<br />LuaInterface.LuaState:PCall(Int32, Int32) (at Assets/ToLua/Core/LuaState.cs:755)<br />LuaInterface.LuaFunction:PCall() (at Assets/ToLua/Core/LuaFunction.cs:96)<br />TestLuaStack:OnGUI() (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:463)<br />```<br /><br />## TestFuncDispose<br /><br />1. "TestFuncDispose"<br />1. c# func.Dispose();<br />1. c# func.PCall();<br /><br />```<br />LuaException: LuaFunction has been disposed<br />LuaInterface.LuaFunction:BeginPCall() (at Assets/ToLua/Core/LuaFunction.cs:73)<br />TestLuaStack:OnGUI() (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:473)<br />```<br /><br />## SendMessage<br /><br />1. "SendMessage"<br />1. c# gameObject.SendMessage("OnSendMsg");<br />1. c# OnSendMsg()<br />1. c# try { GetFunction("TestStack.Test6").PCall() }<br />1. c# Test6()<br />1. c# toluaL_exception(L, e);<br /><br />```<br />LuaException: this a lua exception<br />stack traceback:<br />&nbsp;&nbsp; &nbsp;[C]: at 0x613c2af0<br />&nbsp;&nbsp; &nbsp;[C]: in function 'TestArgError'<br />&nbsp;&nbsp; &nbsp;TestErrorStack:69: in function &lt;TestErrorStack:68&gt;<br />TestLuaStack:Test6(IntPtr) (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:122)<br />LuaInterface.LuaDLL:lua_pcall(IntPtr, Int32, Int32, Int32)<br />LuaInterface.LuaState:PCall(Int32, Int32) (at Assets/ToLua/Core/LuaState.cs:755)<br />LuaInterface.LuaFunction:PCall() (at Assets/ToLua/Core/LuaFunction.cs:96)<br />TestLuaStack:OnSendMsg() (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:277)<br />UnityEngine.GameObject:SendMessage(String)<br />TestLuaStack:OnGUI() (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:481)<br />```<br /><br />SendMessage() 等效于直接调用？<br /><br />## SendMessageInLua<br /><br />1. "SendMessageInLua"<br />1. c# GetFunction("SendMsgError").PCall()<br />1. lua SendMsgError(go)<br />1. lua go:SendMessage("OnSendMsg");<br /><br />```<br />LuaException: this a lua exception<br />stack traceback:<br />&nbsp;&nbsp; &nbsp;[C]: at 0x613c2af0<br />&nbsp;&nbsp; &nbsp;[C]: in function 'SendMessage'<br />&nbsp;&nbsp; &nbsp;TestErrorStack:38: in function &lt;TestErrorStack:37&gt;<br />&nbsp;&nbsp; &nbsp;[C]: in function 'TestArgError'<br />&nbsp;&nbsp; &nbsp;TestErrorStack:69: in function &lt;TestErrorStack:68&gt;<br />TestLuaStack:Test6(IntPtr) (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:122)<br />LuaInterface.LuaDLL:lua_pcall(IntPtr, Int32, Int32, Int32)<br />LuaInterface.LuaState:PCall(Int32, Int32) (at Assets/ToLua/Core/LuaState.cs:755)<br />LuaInterface.LuaFunction:PCall() (at Assets/ToLua/Core/LuaFunction.cs:96)<br />TestLuaStack:OnSendMsg() (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:277)<br />UnityEngine.GameObject:SendMessage(String)<br />UnityEngine_GameObjectWrap:SendMessage(IntPtr) (at Assets/Source/Generate/UnityEngine_GameObjectWrap.cs:657)<br />LuaInterface.LuaDLL:lua_pcall(IntPtr, Int32, Int32, Int32)<br />LuaInterface.LuaState:PCall(Int32, Int32) (at Assets/ToLua/Core/LuaState.cs:755)<br />LuaInterface.LuaFunction:PCall() (at Assets/ToLua/Core/LuaFunction.cs:96)<br />TestLuaStack:OnGUI() (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:488)<br />```<br /><br />和 "SendMessage" 差不多一样。<br /><br />## AddComponent<br /><br />1. "AddComponent"<br />1. c# GetFunction("TestAddComponent").PCall()<br />1. c# TestAddComponent()<br />1. c# try { go.AddComponent&lt;TestInstantiate2&gt;(); }<br />1. c# TestInstantiate2.Awake()<br />1. c# state.ThrowLuaException(e);<br /><br />```<br />Exception: Instantiate exception 2<br />LuaInterface.LuaStatePtr.ThrowLuaException (System.Exception e) (at Assets/ToLua/Core/LuaStatePtr.cs:607)<br />TestInstantiate2.Awake () (at Assets/ToLua/Examples/TestErrorStack/TestInstantiate2.cs:16)<br />UnityEngine.GameObject:AddComponent()<br />TestLuaStack:TestAddComponent(IntPtr) (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:237)<br />LuaInterface.LuaDLL:lua_pcall(IntPtr, Int32, Int32, Int32)<br />LuaInterface.LuaState:PCall(Int32, Int32) (at Assets/ToLua/Core/LuaState.cs:755)<br />LuaInterface.LuaFunction:PCall() (at Assets/ToLua/Core/LuaFunction.cs:96)<br />TestLuaStack:OnGUI() (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:498)<br />```<br /><br />## TableGetSet<br /><br />1. "TableGetSet"<br /><br />## TestTableInCo<br /><br />1. "TestTableInCo"<br />1. c# GetFunction("TestCoTable").PCall()<br />1. lua TestCoTable()<br />1. lua 运行协程 TestCo(...)<br />1. lua TestTableInCo(...)<br />1. c# TestTableInCo()<br /><br />## Instantiate2 Error<br /><br />1. "Instantiate2 Error"<br />1. c# GetFunction("Instantiate").PCall() with go2<br />1. lua Instantiate()<br />1. lua UnityEngine.Object.Instantiate(obj)<br />1. c# TestInstantiate2:Awake()<br /><br />```<br />LuaException: Instantiate exception 2<br />stack traceback:<br />&nbsp;&nbsp; &nbsp;[C]: in function 'Instantiate'<br />&nbsp;&nbsp; &nbsp;TestErrorStack:13: in function &lt;TestErrorStack:11&gt;<br />&nbsp;&nbsp; &nbsp;[C]: in function 'TestArgError'<br />&nbsp;&nbsp; &nbsp;TestErrorStack:69: in function &lt;TestErrorStack:68&gt;<br />TestInstantiate2:Awake() (at Assets/ToLua/Examples/TestErrorStack/TestInstantiate2.cs:11)<br />UnityEngine.Object:Instantiate(Object)<br />UnityEngine_ObjectWrap:Instantiate(IntPtr) (at Assets/ToLua/BaseType/UnityEngine_ObjectWrap.cs:203)<br />LuaInterface.LuaDLL:lua_pcall(IntPtr, Int32, Int32, Int32)<br />LuaInterface.LuaState:PCall(Int32, Int32) (at Assets/ToLua/Core/LuaState.cs:755)<br />LuaInterface.LuaFunction:PCall() (at Assets/ToLua/Core/LuaFunction.cs:96)<br />TestLuaStack:OnGUI() (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:573)<br />```<br /><br />## Instantiate3 Error<br /><br />1. "Instantiate3 Error"<br />1. c# UnityEngine.Object.Instantiate(go2);<br />1. c# TestInstantiate2.Awake()<br /><br />```<br />Exception: Instantiate exception 2<br />LuaInterface.LuaStatePtr.ThrowLuaException (System.Exception e) (at Assets/ToLua/Core/LuaStatePtr.cs:607)<br />TestInstantiate2.Awake () (at Assets/ToLua/Examples/TestErrorStack/TestInstantiate2.cs:16)<br />UnityEngine.Object:Instantiate(GameObject)<br />TestLuaStack:OnGUI() (at Assets/ToLua/Examples/TestErrorStack/TestLuaStack.cs:580)<br />```<br /><br />## TestCycle<br /><br />1. "TestCycle"<br />1. c# GetFunction("TestCycle").PCall()<br />1. c# TestCycle()<br /><br />测试递归调用 luaFunction<br /><br />## TestNull<br /><br />1. "TestNull"<br />1. c# StartCoroutine(TestCo(action));<br /><br />c# 中创建协程<br /><br />## TestMulti<br /><br />1. "TestMulti"<br />1. c# GetFunction("TestMulStack").PCall()<br />1. c# TestMulStack()<br />1. c# try { TestMul0(); }<br />1. c# TestMul1()<br />1. throw<br /><img src ="http://www.cppblog.com/jinq0123/aggbug/217437.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2020-09-03 16:29 <a href="http://www.cppblog.com/jinq0123/archive/2020/09/03/217437.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>lua变量缺少local造成unity死锁</title><link>http://www.cppblog.com/jinq0123/archive/2020/09/02/217435.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Wed, 02 Sep 2020 05:37:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2020/09/02/217435.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217435.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2020/09/02/217435.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217435.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217435.html</trackback:ping><description><![CDATA[# lua变量缺少local造成unity死锁<br /><br />(金庆的专栏 2020.9.1)<br /><br />这几天在开发 grpc-tolua, 可以在 unity 项目中使用 lua 发送 grpc 请求，无需生成代码.<br />现已实现 route_guide 示例中的全部4种rpc. <br />见：https://github.com/jinq0123/grpc-tolua<br /><br />昨天的开发中就发现，运行 RecordRoute 和 RouteChat 之后Unity无法再次运行，必须杀进程。<br />在 await 语句添加 `ConfigureAwait(false)` 之后，好像 RouteChat 修复了，但 RecoreRoute 依旧。<br /><br />ConfigureAwait(false) 保证了 await 之后不会抢占主线程，是解决死锁的一种方法。<br />grpc 源码中的 await 同样都跟着这句。<br /><br />今天再次测试，发现 RouteChat 并未真正修复，仍有较大可能卡死。<br /><br />最初的猜想是 Lua 协程，C# 异步造成线程死锁，因为对异步不熟，总是怀疑使用有误。<br />后来转变为查找让 Unity 卡死的阻塞式操作，但是因为日志显示不全，找遍代码都没结果。<br />除了在自己的代码中查找错误，还在 tolua 代码，grpc 代码中查找，到处怀疑。<br />今天一整天都在添加日志，重启Unity中度过。提出各种猜想，又反复验证。<br /><br />依次采用了以下方法：<br />* 比较C# route_guide 代码，查找区别<br />* 为什么是 client streaming 类型的 rpc 有错<br />* 查看 tolua 协程，Timer<br />* 添加 Delay, 更改协程的运行次序，提高或降低复现几率<br />* 函数入口和返回添加日志，证明其没有阻塞<br /><br />查找错误的方法是在代码中添加日志，找到异常之处。<br />最终的现象是Unity没有Update()调用了，全部卡死。<br /><br />错误源代码是这样的，错误版本中 awaiter 前面没有 local.<br />```lua<br />local function await(awaitable)<br />&nbsp;&nbsp;&nbsp; local awaiter = awaitable:GetAwaiter()<br />&nbsp;&nbsp;&nbsp; coroutine.wait_until(function()<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return awaiter.IsCompleted<br />&nbsp;&nbsp;&nbsp; end)<br />end<br />```<br /><br />因为想实现一个特殊版本的 await() 用于测试, 把 awaiter.IsCompleted 改为总是 false。<br />复制代码时，发现了变量缺少 local.<br />如果这个成为全局变量，第2次函数调用就会更改第1次调用的变量，我立刻意识到了这就是死锁的原因。<br />添加 local 之后测试，一切都正常了。<br /><br />分析结果是因为 awaiter 更改后，await() 在任务完成前就返回了，<br />以致于后面的读取成为阻塞式操作，阻塞了主线程。<br /><br />查错效率低的主要问题是 Unity 日志在卡死前的日志没有显示，这一点直到最后死锁复盘时才发觉。<br />因为日志缺失，根据日志得出的所有判断几乎都错了。<br />本来可以根据日志快速定位的原因，现在完全找错方向。<br /><br />如果有工具警告全局变量，就可以避免这个错误。<br /><br />Unity Profiler, VS Debugger 因为 Unity 完全卡死，所以没什么帮助。<br /><img src ="http://www.cppblog.com/jinq0123/aggbug/217435.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2020-09-02 13:37 <a href="http://www.cppblog.com/jinq0123/archive/2020/09/02/217435.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C# tolua 之间互传 byte[]</title><link>http://www.cppblog.com/jinq0123/archive/2020/08/19/217426.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Wed, 19 Aug 2020 01:03:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2020/08/19/217426.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217426.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2020/08/19/217426.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217426.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217426.html</trackback:ping><description><![CDATA[# C# tolua 之间互传 byte[]<br /><br />(金庆的专栏 2020.8)<br /><br />lua中不区分 string 和 byte[], 而在 C# 中 string 和 byte[] 之间转换涉及编码。<br /><br />C# 中一般这样转：<br /><br />string类型转成byte[]：<br />```<br />byte[] byteArray = System.Text.Encoding.Default.GetBytes(str);<br />```<br />byte[]转成string：<br />```<br />string str = System.Text.Encoding.Default.GetString(byteArray);<br />```<br /><br />Default 编码是本机当前所用编码，还可以用 ASCII, UTF8 等其他编码。<br /><br />测了在 lua 中读入一块2进制数据，调用 tolua 导出的一个方法，如：<br />```<br />public void Print(string s)<br />{<br />&nbsp;&nbsp;&nbsp; byte[] buf = Systen.Text.Encoding.Default.GetBytes(s);<br />&nbsp;&nbsp;&nbsp; Debug.Log(Bitconvert.ToString(buf));<br />}<br />```<br />测了 Default, ASCII, UTF8, ISO-8859-1(Latin-1), Unicode 发现得到的 byte[] 会出错。<br /><br />也试了 [C#中使用Buffer.BlockCopy()方法将string转换为byte array的方法](https://www.cnblogs.com/ChineseMoonGod/p/5689526.html)<br />发现 tolua 传到 C# 的 string 已经是编码过的，直接复制也是错的。<br /><br />xlua 也有相同问题，[Unity xlua 从lua传递byte[]数据到C#](https://www.jianshu.com/p/63987134c1ba)<br />使用 MemoryStream对象来传递byte[]数据，确实有点绕。<br /><br />tolua 中有个 LuaByteBuffer，可以用来传递 byte[].<br />[tolua#中的LuaByteBuffer类](http://bbs.ulua.org/article/ulua/toluazhongdeluabytebufferlei.html)<br /><br />从 lua 传 byte[] 到 C#, 只需要将参数 string 改为 LuaByteBuffer:<br />```<br />public void Print(LuaByteBuffer luaByteBuffer)<br />{<br />&nbsp;&nbsp;&nbsp; byte[] buf = luaByteBuffer.buffer;<br />&nbsp;&nbsp;&nbsp; Debug.Log(Bitconvert.ToString(buf));<br />}<br />```<br /><br />更正确又简单的方法是用 LuaByteBufferAttribute:<br />```<br />[LuaByteBufferAttribute]<br />public void Print(byte[] buf)<br />{<br />&nbsp;&nbsp;&nbsp; Debug.Log(Bitconvert.ToString(buf));<br />}<br />```<br /><br />最终发现不需要 LuaByteBufferAttribute，直接用 byte[] 就行：<br />```<br />public void Print(byte[] buf)<br />{<br />&nbsp;&nbsp;&nbsp; Debug.Log(Bitconvert.ToString(buf));<br />}<br />```<br /><br />C# 传 byte[] 到 lua, 默认为 "System.Byte[]"(userdata)，可以用 tostring() 转为 lua string:<br />```Lua<br />s = tolua.tolstring(result)<br />```<br /><br />如果可以，应该给数据加上标签[LuaByteBufferAttribute]，这样传到 lua 就是 string。<br />或者在C#建一个LuaByteBuffer把byte[]传给lua。<br /><br /><img src ="http://www.cppblog.com/jinq0123/aggbug/217426.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2020-08-19 09:03 <a href="http://www.cppblog.com/jinq0123/archive/2020/08/19/217426.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Unity使用异步grpc</title><link>http://www.cppblog.com/jinq0123/archive/2020/06/22/217366.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Mon, 22 Jun 2020 07:19:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2020/06/22/217366.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217366.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2020/06/22/217366.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217366.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217366.html</trackback:ping><description><![CDATA[# Unity使用异步grpc<br /><br />(金庆的专栏 2020.6)<br /><br />Unity 保证 async 方法运行在主线程中，所以用异步方式调用 grpc 可以大大简化网络通信的代码。<br /><br />以下示例中将 grpc 的 RouteGuide 示例移到 Unity 中运行。<br />https://github.com/grpc/grpc/tree/master/examples/csharp/RouteGuide<br /><br />其中 Main() 中的代码移到 Start() 中运行，阻塞调用改成异步调用, GetFeature() 改成 GetFeatureAsync()。<br /><br />完整代码见：https://gitee.com/jinq0123/unity-grpc-async<br /><br />```<br /><span style="color: #800000; font-family: Courier;">using Grpc.Core;</span><br /><span style="color: #800000; font-family: Courier;">using Routeguide;</span><br /><span style="color: #800000; font-family: Courier;">using System;</span><br /><span style="color: #800000; font-family: Courier;">using System.Collections;</span><br /><span style="color: #800000; font-family: Courier;">using System.Collections.Generic;</span><br /><span style="color: #800000; font-family: Courier;">using UnityEngine;</span><br /><span style="color: #800000; font-family: Courier;">using static Routeguide.Program;</span><br /><br /><span style="color: #800000; font-family: Courier;">public class Test : MonoBehaviour</span><br /><span style="color: #800000; font-family: Courier;">{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp; // Start is called before the first frame update</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp; async void Start()</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp; {</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var channel = new Channel("127.0.0.1:50052", ChannelCredentials.Insecure);</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var client = new RouteGuideClient(new RouteGuide.RouteGuideClient(channel));</span><br /><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Looking for a valid feature</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; await client.GetFeatureAsync(409146138, -746188906);</span><br /><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Feature missing.</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; await client.GetFeatureAsync(0, 0);</span><br /><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Looking for features between 40, -75 and 42, -73.</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; await client.ListFeatures(400000000, -750000000, 420000000, -730000000);</span><br /><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Record a few randomly selected points from the features file.</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; await client.RecordRoute(RouteGuideUtil.LoadFeatures(), 10);</span><br /><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Send and receive some notes.</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; await client.RouteChat();</span><br /><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; await channel.ShutdownAsync();</span><br /><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Debug.Log("End of test.");</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp; }</span><br /><span style="color: #800000; font-family: Courier;">}</span><br />```<br /><img src ="http://www.cppblog.com/jinq0123/aggbug/217366.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2020-06-22 15:19 <a href="http://www.cppblog.com/jinq0123/archive/2020/06/22/217366.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>有房间匹配和无房间匹配</title><link>http://www.cppblog.com/jinq0123/archive/2020/04/17/217238.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Fri, 17 Apr 2020 01:52:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2020/04/17/217238.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217238.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2020/04/17/217238.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217238.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217238.html</trackback:ping><description><![CDATA[# 有房间匹配和无房间匹配<br /><br />(金庆的专栏 2020.4)<br /><br />网游的匹配可以分为2种：有房间匹配和无房间匹配。<br /><br />* 有房间匹配<br />&nbsp;&nbsp;&nbsp; + 请求匹配即搜索并加入一个房间，或者开一个新房间。<br />&nbsp;&nbsp;&nbsp; + 匹配过程中可以看到房间人数增加。<br />&nbsp;&nbsp;&nbsp; + 可以列出其他房间，加入指定房间，如棋牌类大厅<br />&nbsp;&nbsp;&nbsp; + 可以无需等待人满即开始，开始后还可以继续加人<br />* 无房间匹配<br />&nbsp;&nbsp;&nbsp; + 请求匹配即进入等待状态，匹配成功才能看到所有人<br />&nbsp;&nbsp;&nbsp; + 匹配成功, 如人满了, 才可以进入游戏<br />&nbsp;&nbsp;&nbsp; + 无法加入一个已经开始的游戏<br />&nbsp;&nbsp;&nbsp; <br />有房间的用户体验好。无房间匹配可以更精确匹配。<br />有房间匹配可能需要处理长时间人不满时尝试合并房间。<br /><br /><br /><img src ="http://www.cppblog.com/jinq0123/aggbug/217238.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2020-04-17 09:52 <a href="http://www.cppblog.com/jinq0123/archive/2020/04/17/217238.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>open-match匹配流程</title><link>http://www.cppblog.com/jinq0123/archive/2019/01/31/216228.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Thu, 31 Jan 2019 02:21:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2019/01/31/216228.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/216228.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2019/01/31/216228.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/216228.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/216228.html</trackback:ping><description><![CDATA[<div><h1># open-match匹配流程</h1><br />(金庆的专栏 2019.1)<br /><br />https://github.com/GoogleCloudPlatform/open-match<br /><br />open-match 是一个通用的游戏匹配框架。<br />由游戏提供自定义的匹配算法（以docker镜像的方式提供）。<br /><br />分为多个进程，各进程之间共享一个 redis.<br /><br />* 前端, 接收玩家加入 redis，成功后通知玩家房间服地址<br />* 后端，设置一局游戏的匹配规则，设置房间服地址<br />* MMFOrc，启动匹配算法(MMF)<br />* MMF, 自定义匹配算法，读取 redis 获取玩家，匹配成功就将结果写入 redis. 仅匹配一局就退出。<br /><br />游戏服中连接 open-match 的前端与后端的进程，分别称为 frontendclient 和 Director。<br />输入分2部份，一是玩家信息，二是对局信息。<br />Director 向后端输入对局信息，就会收到一个接一个的对局人员列表.<br />Director 需要为每个对局开房间，然后通知后端房间地址。<br />后端将房间地址写入 redis, 然后前端读取到房间地址，就通知 frontendclient，让玩家进入房间。<br /><br /><h2>## test/cmd/frontendclient</h2><br />模拟大厅服或组队服，连接前端API, 请求匹配玩家/队伍。成功后将收到房间服(DGS)的地址(Assignment)。<br /><br />Player 实际上是一个队伍，其中ID字段是用空格分隔的多个ID. <br />虽然参数类型都是 Player, CreatePlayer() 参数为整个队伍，而 GetUpdates() 参数是单个玩家。<br /><br />main() 中创建多个玩家，每个玩家调用 GetUpdates() 以获取结果，go waitForResults() 中处理结果。<br />waitForResult() 读取流中的匹配结果，压入 resultsChan（但好像 resultsChan 仅用于打印）。<br />所有玩家合并到 g 实例中，然后调用 CreatePlayer() 请求匹配。<br /><br />cleanup() 调用 DeletePlayer() 来删除匹配请求，不仅需删除整个队伍，也需要删除单个玩家。<br /><br />好像最后取结果没取对地方，应该从 resultChan 中获取 Assignment, 并用该地址 udpClient().<br /><br />看了该示例就可以理解 frontend.proto<br /><br /><h2>## examples/backendclient</h2><br />MatchObject.Properties 是从 testprofile.json 读取的，应该改名为 Profile 是否更好点？<br />pbProfile 是 MatchObject，Profile 等同于 MatchObject?<br />Profile 的定义是 MMF 所需的所有参数。<br />`pbProfile.Properties = jsonProfile` 重复了2遍。<br /><br />ListMatches()列出这个Profile的所有匹配。<br />收到一个匹配后，须用CreateAssignments()将房间服地址, 称为 Assignment, 发送到所有游戏客户端。<br /><br /><h2>## cmd/frontendapi</h2><br />CreatePlayer() 将 Player 对象写入 redis, 键值为 Player.Id, 类型为 HSET。<br />对 Player 的每个 attribute，添加到 ZSET 中去。<br />此处 Player 是一组玩家。<br /><br />GetUpdates() 每隔2s读取redis, Player数据有变化时就发送。此处 Player 是单个玩家。<br /><br />如果CreatePlayer()中队伍只有一个玩家，<br />则写入的Player与GetUpdates()中读取的玩家是同一个redis键。<br /><br /><h2>## cmd/backendapi</h2><br />CreateMatch() 中 profile 类型为 MatchObject, 是一个比赛的限制条件。<br />profile 先写入 redis, 键为 profile.Id.<br />`requestKey := xid() + "." + profile.Id`,<br />并将 requestKey 加入 redis 集合 "profileq"。<br />然后每2s查询 redis, 看是否有 requestKey 键出现，并返回该值。<br /><br />ListMatch() 每2s调用一次 CreateMatch().<br /><br />DeleteMatch() 仅仅删除 Id 这个键。<br /><br />CreateAssignments() 为多个队伍设置Assignment, 即房间地址。<br />遍历所有Roster中的Player对象，在redis中设置Assignment.<br />(Assignment 更改后，会触发前端更新。)<br />将所有 Player.Id 从 "proposed" 移到 "deindexed"，这两个是 ZSET, 分值为加入时间。<br />Roster 应该是比赛中的阵营，如红方，蓝方，每个阵营中可有多个队伍。<br /><br />DeleteAssignments() 仅仅遍历所有 Player 对象来删除 Assignment 字段。<br /><br /><h2>## cmd/mmforc</h2><br />匹配流程是由 mmforc (matchmaking function orchestrator) 控制的。<br /><br />mmforc 每秒从 redis 的 profileq 中取出 100 个成员, 其中 profileq 是个set类型，<br />使用命令为`SPOP profileq 100`.<br /><br />对每个 profile, 创建一个 k8s 任务：<br /><br />```<br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;// Kick off the job asynchrnously</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;go mmfunc(ctx, profile, cfg, defaultMmfImages, clientset, &amp;pool)</span><br />```<br /><br />每隔10s, 还有所有匹配任务都完成后，需要 `checkProposals`, 即创建 evaluator 任务。<br /><br />profileq 中的元素 profile 为字符串，matchObjectID.profileID。<br />以 profileID 为键，可以从 redis 读取 profile 的内容, profile 是个 MatchObject 对象。<br /><br />profile 的内容为 json 串，其中 "jsonkeys.mmfImages" 为 mmf (matchmaking function) 镜像。<br /><br />如果profile读取失败，或者 mmfImages 为空，则使用默认的镜像。mmfImages 未来会支持多个镜像。<br /><br />通过 MMF_* 环境变量传入各种参数.<br /><br /><h2>## mmf</h2><br />示例：examples\functions\golang\manual-simple<br /><br />从环境变量 "MMF_PROFILE_ID" 解析出 profileID, 并向 redis 查询(HGETALL) profile，HSET 类型。<br /><br />从 profile 中取 pools 字段，即匹配条件。<br />pools 分为多个 pool, 每个 pool 中有多个 filter, 每个 filter 向 redis 取符合的 Player.<br /><br />profile 用到以下字段：<br /><br />* "properties.playerPool"<br />&nbsp; json串，是一些过滤条件，如&#8220;mmr: 100-999&#8221;<br />* "properties.roster"<br />&nbsp; json串, 是多个队伍大小，如 &#8220;red: 4&#8221;<br /><br />示例见：`examples\backendclient\profiles\testprofile.json`<br /><br /><h3>### 简单匹配过程</h3><br />simple mmf 的匹配过程如下：<br /><br />1. 从 redis 查询 profile，获取过滤条件和各队伍大小<br />1. 每个过滤条件向 redis 查询，所有结果的交集为可选成员<br />1. 去除 ignoreList, 即最近 800s 内已匹配成功的成员，即 proposal 和 deindexed ZSET 列表。<br />1. 如果可选成员个数太小，则 insufficient_players 并退出<br />1. 分配各个队伍成员<br />1. 向 redis 记录结果<br /><br /><h3>### 结果</h3><br />profile 中添加 roster，即各阵营成员名单，存入 prososalKey.<br />保存不分队伍的成员名单。<br />然后向 "proposalq" 添加 prososalKey<br /><br /><h3>### 细节</h3><br />poolRosters 以 (pool名, filter attribute) 为键，值为 Player ID 列表. <br />保存从 redis 查询的符合条件的 Player ID.<br /><br />overlaps 以 pool 名为键，保存符合该pool中所有filter的 Player ID 列表，去除 ignore list.<br /><br />rosters 是 profile 中的 "properties.rosters" 字段。不知何用？<br />遍历 rosters, 为每个阵营的每个player找到对应pool的PlayerID, 保存到 mo.Rosters.<br />其中 profileRosters 好像没用。<br /><br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/216228.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2019-01-31 10:21 <a href="http://www.cppblog.com/jinq0123/archive/2019/01/31/216228.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>open-match的redis数据</title><link>http://www.cppblog.com/jinq0123/archive/2018/09/28/215964.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Fri, 28 Sep 2018 06:01:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2018/09/28/215964.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/215964.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2018/09/28/215964.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/215964.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/215964.html</trackback:ping><description><![CDATA[<div><h1>open-match的redis数据</h1><br />(金庆的专栏 2018.9)<br /><br />open-match 是Google的开源游戏匹配服框架。<br /><br />https://github.com/GoogleCloudPlatform/open-match<br /><br />匹配中所用到的玩家数据会保存在 redis 中。查看<br />`open-match\internal\statestorage\redis\playerq\playerq.go`<br />可以了解redis保存的数据。<br /><br />redis中有以下键值。<br /><br /><h2>playerID</h2><br />玩家ID形如：`bfd09c94d646493f834a4abe83a5a68c`, 类型为 hash. 有以下字段：<br /><br /><h3>playerID.properties</h3><br />保存玩家数据的json串。如：<br /><br />{<br />&nbsp;&nbsp;&nbsp; "char.paladin": 1538102377,<br />&nbsp;&nbsp;&nbsp; "map.eastworld": 1538102377,<br />&nbsp;&nbsp;&nbsp; "mmr.rating": 1740,<br />&nbsp;&nbsp;&nbsp; "mode.ctf": 1538102377,<br />&nbsp;&nbsp;&nbsp; "timestamp.enter": 1538102377<br />}<br /><br /><br />玩家数据的每项值都要求为整数.<br /><br /><h2>indices</h2><br />set 类型，保存所有玩家数据键。如：<br /><br /><span style="color: #0000ff;">127.0.0.1:6379&gt; smembers indices</span><br /><span style="color: #0000ff;">1) "timestamp.enter"</span><br /><span style="color: #0000ff;">2) "map.eastworld"</span><br /><span style="color: #0000ff;">3) "mmr.rating"</span><br /><span style="color: #0000ff;">4) "mode.ctf"</span><br /><span style="color: #0000ff;">5) "char.paladin"</span><br /><br />好像没用到。<br /><br /><h2>玩家数据键</h2><br />如 `timestamp.enter`, 类型为 zset. <br />每个玩家数据键都建立了一个排序集，按该玩家数据值排序，成员为 playerID。<br /><br />例如：<br /><br /><span style="color: #0000ff;">127.0.0.1:6379&gt; zrange mmr.rating 1 3 WITHSCORES</span><br /><span style="color: #0000ff;">1) "ef3736ef2f7941f1a159f279703d5f58"</span><br /><span style="color: #0000ff;">2) "746"</span><br /><span style="color: #0000ff;">3) "17ca3bf3a2134c2c90cbe48ebc29f9cb"</span><br /><span style="color: #0000ff;">4) "891"</span><br /><span style="color: #0000ff;">5) "b6450b311f3f413595e824897015c462"</span><br /><span style="color: #0000ff;">6) "891"</span><br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/215964.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2018-09-28 14:01 <a href="http://www.cppblog.com/jinq0123/archive/2018/09/28/215964.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>手机运行 Unity Grpc</title><link>http://www.cppblog.com/jinq0123/archive/2018/08/12/215842.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Sun, 12 Aug 2018 04:24:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2018/08/12/215842.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/215842.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2018/08/12/215842.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/215842.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/215842.html</trackback:ping><description><![CDATA[<div>手机运行 Unity Grpc<br /><br />(金庆的专栏 2018.8)<br /><br />* 安装 Unit2018, 支持 .NET 4.x<br />* 创建一个项目，开启 .NET 4.x<br />&nbsp;&nbsp;&nbsp; Edit-&gt;Project Settings-&gt;Player-&gt;Other Settings<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&gt;Configuration-&gt;Script Runtime Version-&gt;.Net 4.x Equivalent<br />* 从 https://packages.grpc.io/ 的 Daily Builds 下载最新的<br />&nbsp;&nbsp;&nbsp; grpc-protoc_windows_x86-VERSION.zip<br />&nbsp;&nbsp;&nbsp; grpc_unity_package.VERSION.zip <br />* grpc_unity_package.VERSION.zip<br />&nbsp;&nbsp;&nbsp; 解压到 Assets 目录下<br />* 从 github grpc 复制 examples/protos/helloworld.proto 为 Assets/protos/helloworld.proto<br />* 创建 Assets/Scripts/Greeter/, 并在该目录下运行<br /><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp; protoc.exe -I../../../protos --csharp_out=. ../../../protos/helloworld.proto --grpc_out=. --plugin=protoc-gen-grpc=grpc_csharp_plugin.exe</span></div><div></div><div>&nbsp;&nbsp;&nbsp; + 需要先从 grpc-protoc_windows_x86-VERSION.zip 解压 protoc.exe，grpc_csharp_plugin.exe<br />&nbsp;&nbsp;&nbsp; + 生成 Helloworld.cs HelloworldGrpc.cs</div><div></div><div>* 客户端代码<br />&nbsp;&nbsp;&nbsp; + 创建Channel连接服务器<br /><span style="color: #993300;">&nbsp;&nbsp;&nbsp; channel = new Channel("127.0.0.1:50051", ChannelCredentials.Insecure);</span><br />&nbsp;&nbsp;&nbsp; + 创建客户端并发出请求<br /><span style="color: #993300;">&nbsp;&nbsp;&nbsp; var client = new Greeter.GreeterClient(channel);</span><br /><span style="color: #993300;">&nbsp;&nbsp;&nbsp; HelloReply reply = client.SayHello(new HelloRequest { Name = "Jin Qing" });</span><br />* 完整代码见：https://gitee.com/jinq0123/unity-grpc-sample<br />* 最后打包安装到手机测试通过<br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/215842.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2018-08-12 12:24 <a href="http://www.cppblog.com/jinq0123/archive/2018/08/12/215842.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>kubernetes导出有状态服务</title><link>http://www.cppblog.com/jinq0123/archive/2018/07/14/215783.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Sat, 14 Jul 2018 03:43:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2018/07/14/215783.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/215783.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2018/07/14/215783.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/215783.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/215783.html</trackback:ping><description><![CDATA[<div>kubernetes导出有状态服务<br /><br />(金庆的专栏 2018.7)<br /><br />网游服务器中的房间服务器是有状态服务器，可以用 kubernetes statefulset 开启多个实例。<br /><br />为了让客户端能够直连房间服务器，除了 statefulset 要求的 headless 服务，<br />还须为每个实例创建 NodePort 类型的服务, 并且选择Pod和禁止转发。<br /><br />下面 bootcamp.yml 先创建了 bootcamp headless 服务(clusterIP: None), <br />又创建了 bootcamp StatefulSet, 实例个数为 2.<br />然后创建 bootcamp-0,1,2 服务，分别对应 bootcamp-0,1,2 pod.<br /><br />服务个数大于实例个数，是想测试下服务没有对应的实例时的表现。<br /><br />网游中的匹配服务器将分配一个房间给客户端，列举 bootcamp-0,1,2 pod 所在节点的外网 IP,<br />连同对应服务的端口，发送给客户端，让客户端直连。<br /><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ cat bootcamp.yml </span><br /><span style="color: #0000ff; font-family: Courier;">apiVersion: v1</span><br /><span style="color: #0000ff; font-family: Courier;">kind: Service</span><br /><span style="color: #0000ff; font-family: Courier;">metadata:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; name: bootcamp</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; namespace: jq</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; labels:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; name: bootcamp</span><br /><span style="color: #0000ff; font-family: Courier;">spec:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; ports:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; - port: 8080</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; clusterIP: None&nbsp; # StatefulSet要求Headless服务</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; selector:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; app: bootcamp&nbsp; # 选择 bootcamp 应用</span><br /><br /><span style="color: #0000ff; font-family: Courier;">---</span><br /><span style="color: #0000ff; font-family: Courier;">apiVersion: apps/v1beta1</span><br /><span style="color: #0000ff; font-family: Courier;">kind: StatefulSet</span><br /><span style="color: #0000ff; font-family: Courier;">metadata:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; name: bootcamp</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; namespace: jq</span><br /><span style="color: #0000ff; font-family: Courier;">spec:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; serviceName: bootcamp&nbsp; # 上面的 Headless 服务名</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; replicas: 2</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; template:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; metadata:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; labels:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app: bootcamp&nbsp; # 应用名，与服务中的 selector 对应</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; spec:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; containers:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - name: bootcamp</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; image: docker.io/jocatalin/kubernetes-bootcamp:v1</span><br /><br /><span style="color: #0000ff; font-family: Courier;">---</span><br /><span style="color: #0000ff; font-family: Courier;">kind: Service</span><br /><span style="color: #0000ff; font-family: Courier;">apiVersion: v1</span><br /><span style="color: #0000ff; font-family: Courier;">metadata:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; name: bootcamp-0</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; namespace: jq</span><br /><span style="color: #0000ff; font-family: Courier;">spec:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; type: NodePort&nbsp; # 对外服务</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; externalTrafficPolicy: Local&nbsp; # 不要转发到其他节点</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; selector:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; app: bootcamp</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; statefulset.kubernetes.io/pod-name: bootcamp-0&nbsp; # 选择 pod</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; ports:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; - protocol: TCP</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; nodePort: 30880&nbsp; # 对外端口</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; port: 8080</span><br /><span style="color: #0000ff; font-family: Courier;">---</span><br /><span style="color: #0000ff; font-family: Courier;">kind: Service</span><br /><span style="color: #0000ff; font-family: Courier;">apiVersion: v1</span><br /><span style="color: #0000ff; font-family: Courier;">metadata:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; name: bootcamp-1</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; namespace: jq</span><br /><span style="color: #0000ff; font-family: Courier;">spec:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; type: NodePort</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; externalTrafficPolicy: Local</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; selector:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; app: bootcamp</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; statefulset.kubernetes.io/pod-name: bootcamp-1</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; ports:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; - protocol: TCP</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; nodePort: 30881</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; port: 8080</span><br /><span style="color: #0000ff; font-family: Courier;">---</span><br /><span style="color: #0000ff; font-family: Courier;">kind: Service</span><br /><span style="color: #0000ff; font-family: Courier;">apiVersion: v1</span><br /><span style="color: #0000ff; font-family: Courier;">metadata:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; name: bootcamp-2</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; namespace: jq</span><br /><span style="color: #0000ff; font-family: Courier;">spec:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; type: NodePort</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; externalTrafficPolicy: Local</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; selector:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; app: bootcamp</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; statefulset.kubernetes.io/pod-name: bootcamp-2</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; ports:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; - protocol: TCP</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; nodePort: 30882</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; port: 8080</span><br /><br />因为 statefulset 的每个实例有不同的标签，所以可以为服务选择一个实例。<br /><br />利用 externalTrafficPolicy: Local 设置来禁止转发。<br />参考 service.spec.externalTrafficPolicy 的说明：<br /><br />https://kubernetes.io/docs/tutorials/services/source-ip/#source-ip-for-services-with-type-nodeport<br /><br />Setting service.spec.externalTrafficPolicy to the value Local will only proxy requests to local endpoints, never forwarding traffic to other nodes and thereby preserving the original source IP address. If there are no local endpoints, packets sent to the node are dropped, ...<br /><br />因为有可能多个Pod开在同一节点上，所以对外端口设成了不同的 30880-30882。<br />如果限制每个节点只开一个实例，则对外端口可以设成同一个。<br /><br />创建服务：<br /><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ kubectl apply -f bootcamp.yml </span><br /><span style="color: #0000ff; font-family: Courier;">service "bootcamp" created</span><br /><span style="color: #0000ff; font-family: Courier;">statefulset.apps "bootcamp" created</span><br /><span style="color: #0000ff; font-family: Courier;">service "bootcamp-0" created</span><br /><span style="color: #0000ff; font-family: Courier;">service "bootcamp-1" created</span><br /><span style="color: #0000ff; font-family: Courier;">service "bootcamp-2" created</span><br /><br />服务如下：<br /><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ kubectl get service -n jq</span><br /><span style="color: #0000ff; font-family: Courier;">NAME&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TYPE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CLUSTER-IP&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EXTERNAL-IP&nbsp;&nbsp; PORT(S)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AGE</span><br /><span style="color: #0000ff; font-family: Courier;">bootcamp&nbsp;&nbsp;&nbsp;&nbsp; ClusterIP&nbsp;&nbsp; None&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;none&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 8080/TCP&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3m</span><br /><span style="color: #0000ff; font-family: Courier;">bootcamp-0&nbsp;&nbsp; NodePort&nbsp;&nbsp;&nbsp; 10.96.128.137&nbsp;&nbsp;&nbsp; &lt;none&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 8080:30880/TCP&nbsp;&nbsp; 3m</span><br /><span style="color: #0000ff; font-family: Courier;">bootcamp-1&nbsp;&nbsp; NodePort&nbsp;&nbsp;&nbsp; 10.109.2.56&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;none&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 8080:30881/TCP&nbsp;&nbsp; 3m</span><br /><span style="color: #0000ff; font-family: Courier;">bootcamp-2&nbsp;&nbsp; NodePort&nbsp;&nbsp;&nbsp; 10.102.181.193&nbsp;&nbsp; &lt;none&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 8080:30882/TCP&nbsp;&nbsp; 3m</span><br /><br />2个实例：<br /><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ kubectl get pod -n jq -o wide</span><br /><span style="color: #0000ff; font-family: Courier;">NAME&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; READY&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp; RESTARTS&nbsp;&nbsp; AGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IP&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NODE</span><br /><span style="color: #0000ff; font-family: Courier;">bootcamp-0&nbsp;&nbsp; 1/1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Running&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4m&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 10.244.0.42&nbsp;&nbsp; host-10-240-79-10</span><br /><span style="color: #0000ff; font-family: Courier;">bootcamp-1&nbsp;&nbsp; 1/1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Running&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4m&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 10.244.1.63&nbsp;&nbsp; host-10-240-79-11</span><br /><br />访问服务必须指定节点，不会自动转发：<br /><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ curl 10.240.79.10:30880</span><br /><span style="color: #0000ff; font-family: Courier;">Hello Kubernetes bootcamp! | Running on: bootcamp-0 | v=1</span><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ curl 10.240.79.10:30881</span><br /><span style="color: #0000ff; font-family: Courier;">curl: (7) Failed connect to 10.240.79.10:30881; Connection timed out</span><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ curl 10.240.79.11:30880</span><br /><span style="color: #0000ff; font-family: Courier;">curl: (7) Failed connect to 10.240.79.11:30880; Connection timed out</span><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ curl 10.240.79.11:30881</span><br /><span style="color: #0000ff; font-family: Courier;">Hello Kubernetes bootcamp! | Running on: bootcamp-1 | v=1</span><br /><br />没有负载均衡，30881端口总是访问 bootcamp-1：<br /><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ curl 10.240.79.11:30881</span><br /><span style="color: #0000ff; font-family: Courier;">Hello Kubernetes bootcamp! | Running on: bootcamp-1 | v=1</span><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ curl 10.240.79.11:30881</span><br /><span style="color: #0000ff; font-family: Courier;">Hello Kubernetes bootcamp! | Running on: bootcamp-1 | v=1</span><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ curl 10.240.79.11:30881</span><br /><span style="color: #0000ff; font-family: Courier;">Hello Kubernetes bootcamp! | Running on: bootcamp-1 | v=1</span><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ curl 10.240.79.11:30881</span><br /><span style="color: #0000ff; font-family: Courier;">Hello Kubernetes bootcamp! | Running on: bootcamp-1 | v=1</span><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ curl 10.240.79.11:30881</span><br /><span style="color: #0000ff; font-family: Courier;">Hello Kubernetes bootcamp! | Running on: bootcamp-1 | v=1</span><br /><br />也可以从外网访问.<br /><br />30882 端口无法连接：<br /><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ curl 10.240.79.11:30882</span><br /><span style="color: #0000ff; font-family: Courier;">curl: (7) Failed connect to 10.240.79.11:30882; Connection refused</span><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ curl 10.240.79.10:30882</span><br /><span style="color: #0000ff; font-family: Courier;">curl: (7) Failed connect to 10.240.79.10:30882; Connection refused</span><br /><br />3个端口都有监听：<br /><br /><span style="color: #0000ff; font-family: Courier;">[root@host-10-240-79-11 tmp]# netstat -ntl | grep 3088</span><br /><span style="color: #0000ff; font-family: Courier;">tcp6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0 :::30881&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; :::*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LISTEN&nbsp;&nbsp;&nbsp; &nbsp;</span><br /><span style="color: #0000ff; font-family: Courier;">tcp6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0 :::30882&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; :::*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LISTEN&nbsp;&nbsp;&nbsp; &nbsp;</span><br /><span style="color: #0000ff; font-family: Courier;">tcp6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0 :::30880&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; :::*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LISTEN&nbsp;&nbsp;&nbsp; &nbsp;</span><br /><br />iptables-save 输出如下, 其中 10.244是Pod的网段。<br /><br />没有实例运行的节点上，会丢弃请求：<br /><br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-NODEPORTS -s 127.0.0.0/8 -p tcp -m comment --comment "jq/bootcamp-1:" -m tcp --dport 30881 -j KUBE-MARK-MASQ</span><br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-NODEPORTS -p tcp -m comment --comment "jq/bootcamp-1:" -m tcp --dport 30881 -j KUBE-XLB-LJXDQ4W47M42IZBH</span><br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-NODEPORTS -s 127.0.0.0/8 -p tcp -m comment --comment "jq/bootcamp-0:" -m tcp --dport 30880 -j KUBE-MARK-MASQ</span><br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-NODEPORTS -p tcp -m comment --comment "jq/bootcamp-0:" -m tcp --dport 30880 -j KUBE-XLB-U5NEOQT6R5WSBVOH</span><br /><br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-XLB-LJXDQ4W47M42IZBH -s 10.244.0.0/16 -m comment --comment "Redirect pods trying to reach external loadbalancer VIP to clusterIP" -j KUBE-SVC-LJXDQ4W47M42IZBH</span><br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-XLB-LJXDQ4W47M42IZBH -m comment --comment "jq/bootcamp-1: has no local endpoints" -j KUBE-MARK-DROP</span><br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-XLB-U5NEOQT6R5WSBVOH -s 10.244.0.0/16 -m comment --comment "Redirect pods trying to reach external loadbalancer VIP to clusterIP" -j KUBE-SVC-U5NEOQT6R5WSBVOH</span><br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-XLB-U5NEOQT6R5WSBVOH -m comment --comment "jq/bootcamp-0: has no local endpoints" -j KUBE-MARK-DROP</span><br /><br />有实例运行的节点上会转发给 Pod 8080：<br /><br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-NODEPORTS -s 127.0.0.0/8 -p tcp -m comment --comment "jq/bootcamp-0:" -m tcp --dport 30880 -j KUBE-MARK-MASQ</span><br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-NODEPORTS -p tcp -m comment --comment "jq/bootcamp-0:" -m tcp --dport 30880 -j KUBE-XLB-U5NEOQT6R5WSBVOH</span><br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-NODEPORTS -s 127.0.0.0/8 -p tcp -m comment --comment "jq/bootcamp-1:" -m tcp --dport 30881 -j KUBE-MARK-MASQ</span><br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-NODEPORTS -p tcp -m comment --comment "jq/bootcamp-1:" -m tcp --dport 30881 -j KUBE-XLB-LJXDQ4W47M42IZBH</span><br /><br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-XLB-LJXDQ4W47M42IZBH -s 10.244.0.0/16 -m comment --comment "Redirect pods trying to reach external loadbalancer VIP to clusterIP" -j KUBE-SVC-LJXDQ4W47M42IZBH</span><br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-XLB-LJXDQ4W47M42IZBH -m comment --comment "Balancing rule 0 for jq/bootcamp-1:" -j KUBE-SEP-LJQA4WUIKJUQ5ALU</span><br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-XLB-U5NEOQT6R5WSBVOH -s 10.244.0.0/16 -m comment --comment "Redirect pods trying to reach external loadbalancer VIP to clusterIP" -j KUBE-SVC-U5NEOQT6R5WSBVOH</span><br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-XLB-U5NEOQT6R5WSBVOH -m comment --comment "jq/bootcamp-0: has no local endpoints" -j KUBE-MARK-DROP</span><br /><br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-SEP-LJQA4WUIKJUQ5ALU -s 10.244.1.63/32 -m comment --comment "jq/bootcamp-1:" -j KUBE-MARK-MASQ</span><br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-SEP-LJQA4WUIKJUQ5ALU -p tcp -m comment --comment "jq/bootcamp-1:" -m tcp -j DNAT --to-destination 10.244.1.63:8080</span><br /><br />30882 端口无法连接<br /><span style="color: #0000ff; font-family: Courier;">-A KUBE-EXTERNAL-SERVICES -p tcp -m comment --comment "jq/bootcamp-2: has no endpoints" -m addrtype --dst-type LOCAL -m tcp --dport 30882 -j REJECT --reject-with icmp-port-unreachable</span><br /><br />测试下扩容：<br /><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ kubectl get statefulset -n jq</span><br /><span style="color: #0000ff; font-family: Courier;">NAME&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DESIRED&nbsp;&nbsp; CURRENT&nbsp;&nbsp; AGE</span><br /><span style="color: #0000ff; font-family: Courier;">bootcamp&nbsp;&nbsp; 2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 45m</span><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ kubectl scale --replicas=3 statefulset/bootcamp -n jq</span><br /><span style="color: #0000ff; font-family: Courier;">statefulset.apps "bootcamp" scaled</span><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ kubectl get statefulset -n jq</span><br /><span style="color: #0000ff; font-family: Courier;">NAME&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DESIRED&nbsp;&nbsp; CURRENT&nbsp;&nbsp; AGE</span><br /><span style="color: #0000ff; font-family: Courier;">bootcamp&nbsp;&nbsp; 3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 47m</span><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ kubectl get pod -n jq -o wide</span><br /><span style="color: #0000ff; font-family: Courier;">NAME&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; READY&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp; RESTARTS&nbsp;&nbsp; AGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IP&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NODE</span><br /><span style="color: #0000ff; font-family: Courier;">bootcamp-0&nbsp;&nbsp; 1/1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Running&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 48m&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 10.244.0.42&nbsp;&nbsp; host-10-240-79-10</span><br /><span style="color: #0000ff; font-family: Courier;">bootcamp-1&nbsp;&nbsp; 1/1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Running&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 48m&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 10.244.1.63&nbsp;&nbsp; host-10-240-79-11</span><br /><span style="color: #0000ff; font-family: Courier;">bootcamp-2&nbsp;&nbsp; 1/1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Running&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 45s&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 10.244.2.60&nbsp;&nbsp; host-10-240-79-12</span><br /><span style="color: #0000ff; font-family: Courier;">[jinqing@host-10-240-79-10 statefulset]$ curl 10.240.79.12:30882</span><br /><span style="color: #0000ff; font-family: Courier;">Hello Kubernetes bootcamp! | Running on: bootcamp-2 | v=1</span><br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/215783.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2018-07-14 11:43 <a href="http://www.cppblog.com/jinq0123/archive/2018/07/14/215783.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>网游服务器 Services-based 和 Cells-based 架构</title><link>http://www.cppblog.com/jinq0123/archive/2018/03/10/215551.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Sat, 10 Mar 2018 10:03:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2018/03/10/215551.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/215551.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2018/03/10/215551.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/215551.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/215551.html</trackback:ping><description><![CDATA[<div>网游服务器 Services-based 和 Cells-based 架构<br /><br />(金庆的专栏 2018.3)<br /><br />无缝世界网游服务器架构的设计思路<br />http://blog.csdn.net/SmartTony/article/details/6842065<br />提出了2种网游服务器架构：<br /><br />* Services-based architecture 基于服务的架构<br />* Cells-based architecture 基于cell的架构<br /><br />基于服务的架构按功能划分成不同服务，不同服务实现于不同的进程。<br />而基于cell的架构使用相同的Cell进程组成一个集群，每个Cell进程包含全部的功能。<br /><br />基于服务的架构一直是主流的架构，自从 microservice 和 service mesh 概念相继流行，<br />基于微服务的架构应该会成为未来的方向。<br /><br />在无缝大地图网游中，应该用基于cell的架构来实现地图及相关功能，<br />其他与位置无关的功能则应该尽量按服务实现。<br />考虑到服务来回调用延时较大，对于实时性强的功能，如PK也应该在cell上实现。<br /><br />可以考虑客户端通过专门的网关来连接cell, 使用udp/kcp来减小时延。<br />而其他非实时性服务由Tcp网关转发。<br />服务器内部可全部统一采用 gRPC.<br /><br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/215551.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2018-03-10 18:03 <a href="http://www.cppblog.com/jinq0123/archive/2018/03/10/215551.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>网游同步中的时间感</title><link>http://www.cppblog.com/jinq0123/archive/2018/01/18/215477.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Thu, 18 Jan 2018 13:28:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2018/01/18/215477.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/215477.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2018/01/18/215477.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/215477.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/215477.html</trackback:ping><description><![CDATA[<div>网游同步中的时间感<br /><br />(金庆的专栏 2018.1)<br /><br />时间知觉（temporal perception）:对客观现象的延续性和顺序性的反应。实际上是对事件和运动的知觉。<br />-- http://dict.youdao.com/w/temporal_perception<br /><br />网游时间以服务器的为准。<br />网游客户端落后服务器例如1帧时间，所以表现的是过去的场景。<br />因为玩家自身的行为是已知的，所以本地可以预测自身角色的状态，超前于其他角色。<br />处于超前时间的主角与处于过去时间的其他角色共处。<br /><br />This raises the problem of interaction between objects displayed in present time space (the player's avatar) and objects displayed in a past time space (remote characters, AI entities). One solution is to make the LCT vary according to the distance from the player's avatar. This idea is called temporal perception, or presentation time or sometimes local perception filters and comes from the analogy with the appearance of the stars in the sky: the farther the distance, the longer the time the light takes to come to us [Singhal-Zyda].<br /><br />-- http://www.xlgps.com/article/99968.html 带宽限制下的视觉实体属性传播<br /><br />主角可以与过去时间的其他角色交互，距离越远，可以允许的滞后时间就越长。<br />这个概念称作时间感知过滤器(temporal perception filter)，<br />或者本地感知过滤器(local perception filter)。<br /><br />可以用星星作类比，我们看到的一光年远的星星其实是一年前的星星。<br /><br />Local Perception Filter Demo<br />http://mikolalysenko.github.io/local-perception-filter-demo/<br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/215477.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2018-01-18 21:28 <a href="http://www.cppblog.com/jinq0123/archive/2018/01/18/215477.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Lua和C++之间调用效率测试</title><link>http://www.cppblog.com/jinq0123/archive/2017/08/30/215209.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Wed, 30 Aug 2017 09:25:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2017/08/30/215209.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/215209.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2017/08/30/215209.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/215209.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/215209.html</trackback:ping><description><![CDATA[<div>Lua和C++之间调用效率测试<br /><br />(金庆的专栏 2017.8)<br /><br />仿照 http://www.cnblogs.com/archy_yu/p/3185608.html 对 Lua 和 C++ 调用进行测试。<br /><br />代码见：https://github.com/jinq0123/TimerLuaIntf<br /><br />使用 LuaIntf 绑定 Lua 和 C++。用 boost timer 计时。<br />依赖库 lua-cpp, lua-intf, boost-timer 用 conan 安装。<br />conan 会下载源码，编译，然后生成 conanbuildinfo.props 给 VS 导入，<br />其中设好了所有 include, lib 目录，链接库，运行库。<br /><br />代码大概如下：<br /><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;cout &lt;&lt; "C++ calls lua add() many times:\n";</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;boost::timer::auto_cpu_timer t;</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;for (int i = 0; i &lt; COUNT; ++i)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;test.dispatchStatic("add", 123, 456);</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;}</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;cout &lt;&lt; "C++ calls lua add_times() once:\n";</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;boost::timer::auto_cpu_timer t;</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;test.dispatchStatic("add_times", 123, 456, COUNT);</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;}</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;cout &lt;&lt; "Lua calls C++ add() many times:\n";</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;boost::timer::auto_cpu_timer t;</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;test.dispatchStatic("test_c_add", 123, 456, COUNT);</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;}</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;cout &lt;&lt; "Lua calls C++ add_times() once:\n";</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;boost::timer::auto_cpu_timer t;</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;test.dispatchStatic("test_c_add_times", 123, 456, COUNT);</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;}</span><br /><br />测试4种调用：<br />* C++ 调用 1kw 次 lua add()<br />* C++ 调用 1 次 lua add_times(), 其中调用 add() 1kw 次<br />* Lua 调用 C++ add() 1kw 次<br />* Lua 调用 C++ add_times() 1 次，其中调用 add() 1kw 次<br /><br />输出如：<br /><span style="color: #0000ff;">C++ calls lua add() many times:</span><br /><span style="color: #0000ff;">&nbsp;2.759473s wall, 2.761218s user + 0.000000s system = 2.761218s CPU (100.1%)</span><br /><span style="color: #0000ff;">C++ calls lua add_times() once:</span><br /><span style="color: #0000ff;">&nbsp;0.436400s wall, 0.436803s user + 0.000000s system = 0.436803s CPU (100.1%)</span><br /><span style="color: #0000ff;">Lua calls C++ add() many times:</span><br /><span style="color: #0000ff;">&nbsp;0.535802s wall, 0.530403s user + 0.000000s system = 0.530403s CPU (99.0%)</span><br /><span style="color: #0000ff;">Lua calls C++ add_times() once:</span><br /><span style="color: #0000ff;">&nbsp;0.000005s wall, 0.000000s user + 0.000000s system = 0.000000s CPU (n/a%)</span><br />&nbsp;<br />结论是：<br />* C++ 调用 Lua 可达 3百万次/s<br />* Lua 内部调用函数可达 2千万次强/s<br />* Lua 调用 C++ 函数可达 2千万次弱/s<br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/215209.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2017-08-30 17:25 <a href="http://www.cppblog.com/jinq0123/archive/2017/08/30/215209.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>protobuf定义低带宽的移动消息</title><link>http://www.cppblog.com/jinq0123/archive/2017/08/23/215189.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Wed, 23 Aug 2017 03:50:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2017/08/23/215189.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/215189.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2017/08/23/215189.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/215189.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/215189.html</trackback:ping><description><![CDATA[<div>protobuf定义低带宽的移动消息<br /><br />(金庆的专栏 2017.8)<br /><br />用protobuf定义协议时，可利用protobuf的编码方式将移动消息压缩到尽量小。<br /><br /><span style="color: #800000;">service Battle {</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;// 移动变化量。将广播给其他玩家，包括自己。</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;// 移动受步长限制。自动转向移动方向。</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;rpc Move(Movement) returns (rpc.EmptyMsg);</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;// 转向最终面向。将广播给其他玩家，包括自己。</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;rpc TurnTo(Rotation) returns (rpc.EmptyMsg);</span><br /><span style="color: #800000;">}</span><br /><br /><span style="color: #800000;">// 位置，移动和角度都用整数向量表示，单位为分米.</span><br /><span style="color: #800000;">// 使用整数占用的带宽比浮点数少。</span><br /><span style="color: #800000;">// U3d使用以米为单位的浮点数，需要转化。</span><br /><span style="color: #800000;">message Position {</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;optional sint32 x = 1;</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;optional sint32 y = 2;</span><br /><span style="color: #800000;">}</span><br /><br /><span style="color: #800000;">// 移动时发送移动量。不是发送绝对位置，使用更小的数字，占用更少带宽。</span><br /><span style="color: #800000;">message Movement {</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;optional sint32 x = 1;</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;optional sint32 y = 2;</span><br /><span style="color: #800000;">}</span><br /><br /><span style="color: #800000;">// 角度向量在满足精度的前提下，应该尽量使用绝对值小的数据表示，占用更少带宽.</span><br /><span style="color: #800000;">message Rotation {</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;optional sint32 x = 1;</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;optional sint32 y = 2;</span><br /><span style="color: #800000;">}</span><br /><br /><span style="color: #800000;">// 位置和面向。用于初始化对象。</span><br /><span style="color: #800000;">message PosAndRot {</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;optional Position position = 1;</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;optional Rotation rotation = 2;</span><br /><span style="color: #800000;">}</span><br /><br />说明<br /><br />* 名字短点。因为RPC消息中需带服务全名和方法名，所以package, service, method名字要短点<br />* 坐标用整数表示。浮点数会固定占用4个字节，而接近0值的整数值只占用不到一个字节。<br />* 使用移动变化量。地图可能很大，绝对坐标值较大会占用变多字节，而移动量总是接近0<br />* 角度使用向量更容易处理。也可使用整数的角度值(0-360)表示，但处理麻烦。<br />* 移动隐含了转向。所以转向消息不多，不必太在意大小。<br />* sint编码压缩最大。要求地图坐标应该都在0附近，正负各半。<br />* 单位选择不要太细，分米到厘米都可以。</div><img src ="http://www.cppblog.com/jinq0123/aggbug/215189.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2017-08-23 11:50 <a href="http://www.cppblog.com/jinq0123/archive/2017/08/23/215189.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Lua区分公有私有接口</title><link>http://www.cppblog.com/jinq0123/archive/2017/08/18/215169.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Fri, 18 Aug 2017 10:51:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2017/08/18/215169.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/215169.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2017/08/18/215169.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/215169.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/215169.html</trackback:ping><description><![CDATA[<div>Lua区分公有私有接口<br /><br />(金庆的专栏 2017.8)<br /><br />Lua语言没有提供public, private的概念，所有模块或类中的接口都是公有的。<br /><br />可以通过注释来区分公有私有接口。<br /><br />ldoc 提供了一个 @section 标签，可用作此功能<br /><br /><span style="color: #800000; font-family: Courier;">--- Test module.</span><br /><span style="color: #800000; font-family: Courier;">-- @module my_mod</span><br /><span style="color: #800000; font-family: Courier;">local M = {}</span><br /><br /><span style="color: #800000; font-family: Courier;">--- Public functions</span><br /><span style="color: #800000; font-family: Courier;">-- @section public</span><br /><br /><span style="color: #800000; font-family: Courier;">--- foo.</span><br /><span style="color: #800000; font-family: Courier;">-- @int a a test input</span><br /><span style="color: #800000; font-family: Courier;">-- @treturn int result</span><br /><span style="color: #800000; font-family: Courier;">function M.foo(a)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp; return a + 1</span><br /><span style="color: #800000; font-family: Courier;">end</span><br /><br /><span style="color: #800000; font-family: Courier;">--- Private functions</span><br /><span style="color: #800000; font-family: Courier;">-- @section private</span><br /><br /><span style="color: #800000; font-family: Courier;">--- goo.</span><br /><span style="color: #800000; font-family: Courier;">-- @int a a test input</span><br /><span style="color: #800000; font-family: Courier;">-- @treturn int result</span><br /><span style="color: #800000; font-family: Courier;">function M.goo(a)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp; return a + 2</span><br /><span style="color: #800000; font-family: Courier;">end</span><br /><br /><span style="color: #800000; font-family: Courier;">return M</span><br /><br />ldoc 生成文档后是这样的：</div><img src="http://www.cppblog.com/images/cppblog_com/jinq0123/my_mod.png" alt="" width="395" height="698" /><img src ="http://www.cppblog.com/jinq0123/aggbug/215169.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2017-08-18 18:51 <a href="http://www.cppblog.com/jinq0123/archive/2017/08/18/215169.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Unity2017可以使用grpc</title><link>http://www.cppblog.com/jinq0123/archive/2017/08/11/215153.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Fri, 11 Aug 2017 03:30:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2017/08/11/215153.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/215153.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2017/08/11/215153.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/215153.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/215153.html</trackback:ping><description><![CDATA[<div>Unity2017可以使用grpc<br /><br />(金庆的专栏 2017.8)<br /><br />gRPC 是 Google 发布的多语言 RPC 库，见 https://github.com/grpc/grpc。<br /><br />Unity 2017.1 .Net4.6 支持 async, await, 原来 grpc 在 Unity 上的限制已经消除。<br /><br />http://www.sohu.com/a/156779221_280780?qq-pf-to=pcqq.group<br /><img src="http://img.blog.csdn.net/20170811112351389?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvanEwMTIz/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" /><br />示例可以运行：https://github.com/ahakits/unity-grpc-sample<br /><img src="http://img.blog.csdn.net/20170811112420439?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvanEwMTIz/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" /><br /><br />示例自带Server, 也可以运行 grpc 的 hello world server 示例。<br />输入 127.0.0.1, 点"Ch" 创建 Channel.<br />然后输入一个名字，点 "Send" 即可收到应答并显示。<br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/215153.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2017-08-11 11:30 <a href="http://www.cppblog.com/jinq0123/archive/2017/08/11/215153.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>网游开发目录结构</title><link>http://www.cppblog.com/jinq0123/archive/2017/04/27/214889.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Thu, 27 Apr 2017 02:01:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2017/04/27/214889.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/214889.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2017/04/27/214889.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/214889.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/214889.html</trackback:ping><description><![CDATA[<div>网游开发目录结构<br /><br />(金庆的专栏 2017.4)<br /><br />合理设置网游开发目录结构，可以方便目录权限设置。<br /><br />以前分为3大块，分别对应美术，程序，策划的目录。<br /><br />Art<br />Code<br />Design<br /><br />也有可能是分成3个SVN库。<br />个人倾向于分成多个库，SVN应该只保存文本文件。<br /><br />对于U3d开发，美术和策划需要更改客户端开发目录。<br /><br />Art<br />Design<br />Client<br />Server<br />Common<br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/214889.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2017-04-27 10:01 <a href="http://www.cppblog.com/jinq0123/archive/2017/04/27/214889.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>建议proto文件按包名分子目录</title><link>http://www.cppblog.com/jinq0123/archive/2017/04/17/214862.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Mon, 17 Apr 2017 06:40:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2017/04/17/214862.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/214862.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2017/04/17/214862.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/214862.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/214862.html</trackback:ping><description><![CDATA[<div>建议proto文件按包名分子目录<br /><br />(金庆的专栏 2017.4)<br /><br />服务器客户端之间的protobuf协议定义在客户端与服务器公共目录下，包名为rpc.<br />服务器内部协议定义在服务器目录下，包名为svr.<br />rpc.EmptyMsg 和 svr.EmptyMsg 分别定义在各自的根目录，文件名都是 empty_msg.proto.<br />运行时就会报错：<br /><br /><span style="color: #0000ff;">[libprotobuf ERROR E:\deps\protobuf-3.2.0\protobuf-3.2.0\src\google\protobuf\</span><br /><span style="color: #0000ff;">descriptor_database.cc:57] File already exists in database : empty_msg.proto</span><br /><span style="color: #0000ff;">[libprotobuf FATAL E:\deps\protobuf-3.2.0\protobuf-3.2.0\src\google\protobuf\</span><br /><span style="color: #0000ff;">descriptor.cc:1275] CHECK failed: generated_database_-&gt;Add(encoded_file_descriptor, size):</span><br /><br />原因为试图用同一个文件名"empty_msg.proto"往descriptor_database添加descriptor。<br /><br />如果按包名分子目录，文件名就可以分开为 "rpc/empty_msg.proto" 和 "svr/empty_msg.proto".<br /><br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/214862.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2017-04-17 14:40 <a href="http://www.cppblog.com/jinq0123/archive/2017/04/17/214862.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>behaviac动态库运行出错</title><link>http://www.cppblog.com/jinq0123/archive/2017/03/16/214755.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Thu, 16 Mar 2017 03:40:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2017/03/16/214755.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/214755.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2017/03/16/214755.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/214755.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/214755.html</trackback:ping><description><![CDATA[<div>behaviac动态库运行出错<br /><br />(金庆的专栏 2017.3.16)<br /><br />游戏是静态链接的运行库，添加behaviac动态库后，运行出错：<br /><br />&gt;&nbsp;&nbsp; &nbsp;ucrtbased.dll!free_dbg_nolock(void * const block, const int block_use) 行 996&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;ucrtbased.dll!_free_dbg(void * block, int block_use) 行 1030&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;libbehaviac_msvc_debug.dll!operator delete(void * block) 行 17&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;libbehaviac_msvc_debug.dll!std::_Deallocate(void * _Ptr, unsigned int _Count, unsigned int _Sz) 行 132&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;libbehaviac_msvc_debug.dll!std::allocator&lt;std::_Container_proxy&gt;::deallocate(std::_Container_proxy * _Ptr, unsigned int _Count) 行 720&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;libbehaviac_msvc_debug.dll!std::_Wrap_alloc&lt;std::allocator&lt;std::_Container_proxy&gt; &gt;::deallocate(std::_Container_proxy * _Ptr, unsigned int _Count) 行 988&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;libbehaviac_msvc_debug.dll!std::_String_alloc&lt;std::_String_base_types&lt;char,std::allocator&lt;char&gt; &gt; &gt;::_Free_proxy() 行 661&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;libbehaviac_msvc_debug.dll!std::_String_alloc&lt;std::_String_base_types&lt;char,std::allocator&lt;char&gt; &gt; &gt;::~_String_alloc&lt;std::_String_base_types&lt;char,std::allocator&lt;char&gt; &gt; &gt;() 行 629&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;libbehaviac_msvc_debug.dll!std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt;::~basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt;() 行 1018&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;libbehaviac_msvc_debug.dll!behaviac::CTagObject::Save(behaviac::IIONode * node, const char * szClassName) 行 100&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;libbehaviac_msvc_debug.dll!behaviac::Agent::InitVariableRegistry() 行 476&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;libbehaviac_msvc_debug.dll!behaviac::Agent::Init_(int contextId, behaviac::Agent * pAgent, short priority, const char * agentInstanceName) 行 227&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;giant_test_client.exe!behaviac::Agent::InitAgent(behaviac::Agent * pAgent, const char * agentInstanceName, const char * agentInstanceNameAny, bool bToBind, int contextId, short priority) 行 56&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;giant_test_client.exe!behaviac::Agent::Create&lt;PlaneAgent&gt;(const char * agentInstanceName, int contextId, short priority) 行 88&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;giant_test_client.exe!Client::InitPlayer() 行 36&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;giant_test_client.exe!Client::Client() 行 23&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;giant_test_client.exe!main() 行 50&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;giant_test_client.exe!invoke_main() 行 64&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;giant_test_client.exe!__scrt_common_main_seh() 行 253&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;giant_test_client.exe!__scrt_common_main() 行 296&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;giant_test_client.exe!mainCRTStartup() 行 17&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;kernel32.dll!@BaseThreadInitThunk@12()&nbsp;&nbsp; &nbsp;未知<br />&nbsp;&nbsp;&nbsp; &nbsp;ntdll.dll!___RtlUserThreadStart@8()&nbsp;&nbsp; &nbsp;未知<br />&nbsp;&nbsp;&nbsp; &nbsp;ntdll.dll!__RtlUserThreadStart@8()&nbsp;&nbsp; &nbsp;未知<br /><br />看来是string跨越动态库析构。<br />如果string在动态库中构造又析构，动态库的运行时库可以与主程序不同。<br />但如果用主程序的运行时申请内存，在动态库中用另一个运行时库释放，就会产生上面的错误。<br /><br />可以建立一个最小化的程序来复现上面的错误。<br />新建一个行为树，为Agent创建一个属性。<br />然后主程序运行库设为：多线程调试 (/MTd)，而不是：多线程调试 DLL (/MDd)。<br />调试运行创建Agent时就会出上面错误。 <br /><br />疑问是这是动态库内部调用，怎么会产生跨库析构呢？<br /><br />断点进入string的构造过程可以发现，string不是动态库构造的，而是主程序构造的：<br /><br />&gt;&nbsp;&nbsp; &nbsp;test_client.exe!behaviac::StringUtils::internal::ToString(const std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &amp; val) 行 138&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;test_client.exe!behaviac::StringUtils::Detail::ToStringPtrHanler&lt;std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt;,0&gt;::ToString(const std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &amp; v) 行 192&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;test_client.exe!behaviac::StringUtils::Detail::ToStringEnumHanler&lt;std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt;,0&gt;::ToString(const std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &amp; v) 行 228&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;test_client.exe!behaviac::StringUtils::Detail::ToStringStructHanler&lt;std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt;,0&gt;::ToString(const std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &amp; val) 行 242&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;test_client.exe!behaviac::StringUtils::ToString&lt;std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;(const std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &amp; val) 行 256&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;test_client.exe!behaviac::SetFromString_t&lt;std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt;,0&gt;::Get(std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &amp; value) 行 790&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;test_client.exe!behaviac::CProperty&lt;std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; &gt;::GetValueToString(const behaviac::Agent * self) 行 848&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;libbehaviac_msvc_debug.dll!behaviac::CTagObject::Save(behaviac::IIONode * node, const char * szClassName) 行 97&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;libbehaviac_msvc_debug.dll!behaviac::Agent::InitVariableRegistry() 行 476&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;libbehaviac_msvc_debug.dll!behaviac::Agent::Init_(int contextId, behaviac::Agent * pAgent, short priority, const char * agentInstanceName) 行 227&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;test_client.exe!behaviac::Agent::InitAgent(behaviac::Agent * pAgent, const char * agentInstanceName, const char * agentInstanceNameAny, bool bToBind, int contextId, short priority) 行 56&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;test_client.exe!behaviac::Agent::Create&lt;PlaneAgent&gt;(const char * agentInstanceName, int contextId, short priority) 行 88&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;test_client.exe!Client::InitPlayer() 行 36&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;test_client.exe!Client::Client() 行 23&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;test_client.exe!main() 行 50&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;test_client.exe!invoke_main() 行 64&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;test_client.exe!__scrt_common_main_seh() 行 253&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;test_client.exe!__scrt_common_main() 行 296&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;test_client.exe!mainCRTStartup() 行 17&nbsp;&nbsp; &nbsp;C++<br />&nbsp;&nbsp;&nbsp; &nbsp;kernel32.dll!75c3336a()&nbsp;&nbsp; &nbsp;未知<br />&nbsp;&nbsp;&nbsp; &nbsp;[下面的框架可能不正确和/或缺失，没有为 kernel32.dll 加载符号]&nbsp;&nbsp; &nbsp;<br />&nbsp;&nbsp;&nbsp; &nbsp;ntdll.dll!76fa9902()&nbsp;&nbsp; &nbsp;未知<br />&nbsp;&nbsp;&nbsp; &nbsp;ntdll.dll!76fa98d5()&nbsp;&nbsp; &nbsp;未知<br /><br />原因为 IProperty::GetValueToString()是虚函数，实际实现是由主程序实现模板类。<br />&nbsp;&nbsp; &nbsp;behaviac::CProperty&lt;&gt;::GetValueToString()<br /><br />解决方案为使用behaviac静态库并且静态链接运行时库。<br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/214755.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2017-03-16 11:40 <a href="http://www.cppblog.com/jinq0123/archive/2017/03/16/214755.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>跨服Lua调用</title><link>http://www.cppblog.com/jinq0123/archive/2017/03/02/214722.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Thu, 02 Mar 2017 09:12:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2017/03/02/214722.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/214722.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2017/03/02/214722.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/214722.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/214722.html</trackback:ping><description><![CDATA[<div>跨服Lua调用<br /><br />(金庆的专栏 2017.3)<br /><br />跨服Lua调用是指服务器集群内部A服调用B服上的脚本。<br /><br />服务器之间已经实现RPC调用，Lua调用是Rpc调用的简化方式。<br /><br />示例：<br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;-- Tell remote server svr_id that game_clt_id is disconnected.</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;local arguments = { "Remote.ClientDisconnected", game_clt_id }</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;remote_runner.run_mfa(svr_id, "event.dispatcher",</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;"dispatch", arguments)</span><br /><br />以上例子相当于调用了其他服务器上的代码：<br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;require("event.dispatcher").dispatch("Remote.ClientDisconnected", game_clt_id)</span><br /><br />需要参数为：<br />* 远程服务器ID<br />* Lua模块名<br />* Lua模块中的函数名<br />* 调用参数列表<br /><br />remote_runner这样实现：<br /><br /><span style="color: #800000; font-family: Courier;">-- 运行另一服务器上的Lua代码</span><br /><span style="color: #800000; font-family: Courier;">-- 也支持本服运行(本服RPC调用)</span><br /><br /><span style="color: #800000; font-family: Courier;">local M = {}</span><br /><br /><span style="color: #800000; font-family: Courier;">local log = require("log"):new("remote_runner")</span><br /><span style="color: #800000; font-family: Courier;">local pb = require("protobuf")</span><br /><span style="color: #800000; font-family: Courier;">local serpent = require("serpent")</span><br /><br /><span style="color: #800000; font-family: Courier;">-- on_result(result) 生成 rpc 回调函数 cb(resp_str)</span><br /><span style="color: #800000; font-family: Courier;">local function get_mfa_cb(on_result)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;if (not on_result) then return nil end</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;assert("function" == type(on_result))</span><br /><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;local cb = function(resp_str)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;assert("string" == type(resp_str))</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;local resp = pb.decode("svr.RunLuaMfaResponse", resp_str)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;local ok, copy = serpent.load(resp.returned_dump)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;assert(ok, "Run mfa returns invalid value.")</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;log:debug("RunLuaMfaResponse: %s", serpent.line(copy))</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;on_result(table.unpack(copy)) -- 回调时执行</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;end&nbsp; -- cb</span><br /><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;return cb</span><br /><span style="color: #800000; font-family: Courier;">end&nbsp; -- get_mfa_cb()</span><br /><br /><span style="color: #800000; font-family: Courier;">-- Run module function with arguments on remote server.</span><br /><span style="color: #800000; font-family: Courier;">-- 示例 rum_mfa(123, "event.dispatcher", "dispatch", {"EventName", 1,2,3}, nil)</span><br /><span style="color: #800000; font-family: Courier;">function M.run_mfa(svr_id, module_name, function_name, arguments, on_result)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;assert("number" == type(svr_id))</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;assert("string" == type(module_name))</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;assert("string" == type(function_name))</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;assert("table" == type(arguments))</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;assert(nil == on_result or "function" == type(on_result))</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;log:debug("Request to call Svr_%s %s.%s()", svr_id, module_name, function_name)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;local req = {</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;module_name = module_name,</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;function_name = function_name,</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;arguments_dump = serpent.dump(arguments)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;}</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;local req_str = pb.encode("svr.RunLuaMfaRequest", req)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;local cb = get_mfa_cb(on_result)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;c_rpc.request_svr(svr_id, "svr.RunLua", "RunMfa", req_str, cb)</span><br /><span style="color: #800000; font-family: Courier;">end&nbsp; -- run()</span><br /><br /><span style="color: #800000; font-family: Courier;">return M</span><br /><br />通过Rpc服务RunLua.RunMfa实现。run_lua.proto如下定义<br /><br /><span style="color: #800000; font-family: Courier;">syntax = "proto3";</span><br /><span style="color: #800000; font-family: Courier;">package svr;</span><br /><br /><span style="color: #800000; font-family: Courier;">// 服务器内部跨服调用Lua</span><br /><span style="color: #800000; font-family: Courier;">service RunLua {</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp; // 运行 module.function(...arguments...)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp; rpc RunMfa(RunLuaMfaRequest) returns (RunLuaMfaResponse);</span><br /><span style="color: #800000; font-family: Courier;">}</span><br /><br /><span style="color: #800000; font-family: Courier;">message RunLuaMfaRequest {</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp; string module_name = 1;</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp; string function_name = 2;</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp; // arguments_dump = serpent.dump({1,2,3})</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp; string arguments_dump = 3;</span><br /><span style="color: #800000; font-family: Courier;">}</span><br /><br /><span style="color: #800000; font-family: Courier;">message RunLuaMfaResponse {</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp; // Get returned table copy:</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp; // local ok, copy = serpent.load(returned_dump)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp;&nbsp; string returned_dump = 1;</span><br /><span style="color: #800000; font-family: Courier;">}</span><br /><br />服务这样实现：<br /><br /><span style="color: #800000; font-family: Courier;">-- svc_run_lua.lua</span><br /><span style="color: #800000; font-family: Courier;">-- Run lua by other servers.</span><br /><br /><span style="color: #800000; font-family: Courier;">local M = {}</span><br /><br /><span style="color: #800000; font-family: Courier;">local log = require("log"):new("svc_run_lua")</span><br /><span style="color: #800000; font-family: Courier;">local pb = require("protobuf")</span><br /><br /><span style="color: #800000; font-family: Courier;">-- Run module.function(...arguments...)</span><br /><span style="color: #800000; font-family: Courier;">function M.RunMfa(ctx, content)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;local req = pb.decode("svr.RunLuaMfaRequest", content)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;log:debug("RunMfa %s.%s", req.module_name, req.function_name)&nbsp; -- todo: from where?</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;local mod = require(req.module_name)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;local fun = mod[req.function_name]</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;local ok, arguments = serpent.load(serpent.dump(req.arguments_dump))</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;assert(ok, "Illegal arguments.")</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;local result_table = table.pack(fun(table.unpack(arguments)))</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;local resp = { returned_dump = serpent.dump(result_table) }</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;local resp_str = pb.encode("svr.RunLuaMfaResponse", resp)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;c_rpc.reply_to(ctx, resp_str)</span><br /><span style="color: #800000; font-family: Courier;">end&nbsp; -- Run()</span><br /><br /><span style="color: #800000; font-family: Courier;">return M </span><br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/214722.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2017-03-02 17:12 <a href="http://www.cppblog.com/jinq0123/archive/2017/03/02/214722.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>网游服务器csv配置设计</title><link>http://www.cppblog.com/jinq0123/archive/2017/02/15/214681.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Wed, 15 Feb 2017 08:18:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2017/02/15/214681.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/214681.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2017/02/15/214681.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/214681.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/214681.html</trackback:ping><description><![CDATA[<div>网游服务器csv配置设计<br /><br />(金庆的专栏 2017.2)<br /><br />巨人网络Lua手游服务器中csv配置系统的设计<br /><br />配置文件为 csv 格式。<br />csv文件全部放置于 csv/ 目录。<br />csv/可以有子目录。<br />csv目录下所有csv文件可以热更新。<br /><br />策划数据也会导出到csv.<br />csv编码为utf8带BOM。<br />如果不带BOM，excel打开中文显示乱码。<br /><br />xlsx批量转为utf8的csv<br />http://blog.csdn.net/jq0123/article/details/49512877<br /><br />要求csv解析器允许多行字段，允许双引号，忽略BOM。<br />csv解析采用：https://github.com/jinq0123/csv_parser_RFC4180<br /><br />csv按以下规范建立：<br />第1行：字段说明，可以多行，可以中文，仅供注释用。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 说明放第1行的好处是：多行说明不影响下面的数据显示，征途的csv也是说明在先。<br />第2行：字段名，英文，区分大小写，尽量不用特殊字符。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 同一表内各字段名必须不同。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 建议全部小写，用下划线连接单词，如"server_type", "equip_id".<br />第3行：类型，有3种：string, int, float, 缺省为string, 无法识别也按string处理。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int, float数据缺省值0, 解析有错时也按0处理。<br /><br />所有csv加载后，通过以下接口查询<br /><br />* 获取表格 get_table("test/test.csv") -&gt; csv/test/test.csv<br />* 取全部记录 table.get_all_records()<br />* 按索引取记录（可能多个）<br />&nbsp;&nbsp; &nbsp;+ 取第1个 table.get_record("field1", val1, "field2", val2)<br />&nbsp;&nbsp; &nbsp;+ 取多个 table.get_records("field1", val1, "field2", val2)<br />* 按列名取字段<br />&nbsp;&nbsp; &nbsp;+ record.get_string("field3")<br />&nbsp;&nbsp; &nbsp;+ record.get_int("field3")<br />&nbsp;&nbsp; &nbsp;+ record.get_float("field3")<br />* 自动索引，首次查询建立索引<br />* 索引可以是多列组合<br /><br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/214681.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2017-02-15 16:18 <a href="http://www.cppblog.com/jinq0123/archive/2017/02/15/214681.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>hiredis异步接口封装并导出到Lua</title><link>http://www.cppblog.com/jinq0123/archive/2017/01/05/214574.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Thu, 05 Jan 2017 10:42:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2017/01/05/214574.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/214574.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2017/01/05/214574.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/214574.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/214574.html</trackback:ping><description><![CDATA[<div>hiredis异步接口封装并导出到Lua<br /><br />(金庆的专栏 2017.1)<br /><br />hiredis 不支持 Windows, Windows 下使用 wasppdotorg / hiredis-for-windows 。<br />Linux 下仍是 redis/hiredis。<br />hiredis-for-windows 是以 hiredis 0.13.3 为基础移植的。<br /><br />hiredis-for-windows 需要稍加修正：<br />&nbsp;&nbsp; &nbsp;* 去除 inline 宏<br />&nbsp;&nbsp; &nbsp;* TCP_NODELAY 改在连接之前设置。<br />详见其Issue.<br /><br />Cluster 支持采用 shinberg/cpp-hiredis-cluster。这是个CPP库，支持异步，<br />要求 hiredis &gt;= 0.12.0。<br />jinq0123/cpp-hiredis-cluster 在 develop 分支上更改了接口，让它更好用。<br /><br />因为网络库是boost asio, 所以需要asio适配器，采用 jinq0123/hiredis-boostasio-adapter。<br /><br />cpp-hiredis-cluster 提供的是统一的Command接口，接收字符串命令，返回 redisReply.<br />对于常用命令，需要更简单的接口。<br />在Lua手游服务器代码中新建CRedis类，封装 cpp-hiredis-cluster，<br />为常用redis命令封装更好用的接口。<br />CRedis类封装了asio, 接口是根据应用需要定义的，所以是专用接口，<br />不在 cpp-hiredis-cluster 中实现。<br /><br /><span style="color: #800000; font-family: Courier;">bool CRedis::Init(io_service&amp; rIos, const std::string&amp; sHost, uint16_t uPort)</span><br />创建 RedisCluster 对象。<br />io_service 用于创建一个 redis 事件适配器，<br />RedisCluster创建需要一个适配器。<br />sHost, uPort 用于初始化连接redis cluster, 获取集群信息。<br /><br /><br /><span style="color: #800000; font-family: Courier;">using Cmd = RedisCluster::AsyncHiredisCommand;</span><br /><br /><span style="color: #800000; font-family: Courier;">bool CRedis::Init(io_service&amp; rIos, const std::string&amp; sHost, uint16_t uPort)</span><br /><span style="color: #800000; font-family: Courier;">{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;m_pAdapter.reset(new Adapter(rIos));</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;try</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;m_pCluster.reset(Cmd::createCluster("127.0.0.1", 7000, *m_pAdapter));</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;}</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;catch (const RedisCluster::ClusterException &amp;e)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;LOG_ERROR("Cluster exception: " &lt;&lt; e.what());</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return false;</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;}</span><br /><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;return true;</span><br /><span style="color: #800000; font-family: Courier;">}</span><br /><br /><span style="color: #800000; font-family: Courier;">static Cmd::Action handleException(const RedisCluster::ClusterException &amp;exception,</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;RedisCluster::HiredisProcess::processState state)</span><br /><span style="color: #800000; font-family: Courier;">{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;// Check the exception type.</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;// Retry in case of non-critical exceptions.</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;if (!dynamic_cast&lt;const RedisCluster::CriticalException*&gt;(&amp;exception))</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;LOG_WARN("Exception in processing async redis callback: "</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&lt;&lt; exception.what() &lt;&lt; " Retry...");</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;// retry to send a command to redis node</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return Cmd::RETRY;</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;}</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;LOG_ERROR("Critical exception in processing async redis callback: "</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&lt;&lt; exception.what());</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;return Cmd::FINISH;</span><br /><span style="color: #800000; font-family: Courier;">}</span><br /><br /><span style="color: #800000; font-family: Courier;">static void handleSetReply(const redisReply &amp;reply, const CRedis::SetCb&amp; setCb)</span><br /><span style="color: #800000; font-family: Courier;">{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;if (!setCb) return;</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;if (reply.type == REDIS_REPLY_STATUS)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;const std::string OK("OK");</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (OK == reply.str)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;setCb(true);</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return;</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;}</span><br /><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;LOG_WARN("Set reply: " &lt;&lt; reply.str);</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;setCb(false);</span><br /><span style="color: #800000; font-family: Courier;">}</span><br /><br /><span style="color: #800000; font-family: Courier;">void CRedis::Set(const string&amp; sKey, const string&amp; sValue, const SetCb&amp; setCb)</span><br /><span style="color: #800000; font-family: Courier;">{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;assert(m_pCluster);</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;Cmd::commandf2(*m_pCluster, sKey,</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;[setCb](const redisReply&amp; reply) {</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;handleSetReply(reply, setCb);</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;},</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;handleException,</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;"set %s %s", sKey.c_str(), sValue.c_str());</span><br /><span style="color: #800000; font-family: Courier;">}</span><br /><br /><span style="color: #800000; font-family: Courier;">static void handleGetReply(const redisReply&amp; reply,</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;const CRedis::ReplyStringCb&amp; hdlStrReply)</span><br /><span style="color: #800000; font-family: Courier;">{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;if (!hdlStrReply) return;</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;using Rt = CRedis::ReplyType;</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;if (reply.type == REDIS_REPLY_NIL)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;hdlStrReply(Rt::NIL, "");</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return;</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;}</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;std::string sReply(reply.str, reply.len);</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;if (reply.type == REDIS_REPLY_STRING)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;hdlStrReply(Rt::OK, sReply);</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;else</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;hdlStrReply(Rt::ERR, sReply);</span><br /><span style="color: #800000; font-family: Courier;">}</span><br /><br /><span style="color: #800000; font-family: Courier;">void CRedis::Get(const std::string&amp; sKey, const ReplyStringCb&amp; hdlStrRepl)</span><br /><span style="color: #800000; font-family: Courier;">{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;assert(m_pCluster);</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;Cmd::commandf2(*m_pCluster, sKey,</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;[hdlStrRepl](const redisReply&amp; reply) {</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;handleGetReply(reply, hdlStrRepl);</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;},</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;handleException,</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;"get %s", sKey.c_str());</span><br /><span style="color: #800000; font-family: Courier;">}</span><br /><br />handleException 是Cmd::command() 接口中需要的异常处理，这里是尽量重试。<br /><br />Cmd::command() 中的第3个参数是 redis 应答的回调，读取 redisReply, 然后触发命令的回调。<br /><br />CRedis::Get() 执行 redis GET 命令，固定1个参数，返回是字符串，nil, 或错误。<br /><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp; &nbsp;enum class ReplyType</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp; &nbsp;{</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;OK = 0,&nbsp; // redis replys string/integer/array</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;NIL = 1,&nbsp; // redis replys nil</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;ERR = 2,&nbsp; // redis replys error status</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp; &nbsp;};</span><br /><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp; &nbsp;// 简单的常用命令会自动解析reply, 使用更易用的回调。</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp; &nbsp;using ReplyStringCb = function&lt;void (ReplyType, const string&amp; sReply)&gt;;</span><br /><br />CRedis::Get() 的回调就是 ReplyStringCb。<br /><br />void CRedis::Set() 的回调只需知道成功或失败，SetCb 定义为：<br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;using SetCb = function&lt;void (bool ok)&gt;;</span><br /><br />失败时会有错误信息，已统一打印日志，不再暴露出来。<br /><br />SET 命令完整的参数列表还有其他参数：<br />set key value [EX seconds] [PX milliseconds] [NX|XX]<br /><br />因为常用的就 "set key value", 其他扩展的命令需要使用通用的 command() 接口，<br />并需要读取 redisReply.<br /><br />使用 LuaIntf 导出 CRedis 到 Lua.<br />因为只有一个 CRedis, 所以导出为模块 c_redis:<br /><br /><span style="color: #800000; font-family: Courier;">#include &lt;LuaIntf/LuaIntf.h&gt;</span><br /><br /><span style="color: #800000; font-family: Courier;">namespace {</span><br /><br /><span style="color: #800000; font-family: Courier;">void Set(const string&amp; sKey, const string&amp; sValue, const LuaRef&amp; luaSetCb)</span><br /><span style="color: #800000; font-family: Courier;">{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;// Default is empty callback.</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;auto setCb = ToFunction&lt;CRedis::SetCb&gt;(luaSetCb);</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;GetRedis().Set(sKey, sValue, setCb);</span><br /><span style="color: #800000; font-family: Courier;">}</span><br /><br /><span style="color: #800000; font-family: Courier;">void Get(const string&amp; sKey, const LuaRef&amp; luaReplyStringCb)</span><br /><span style="color: #800000; font-family: Courier;">{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;auto replyStringCb = ToFunction&lt;CRedis::ReplyStringCb&gt;(</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;luaReplyStringCb);</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;GetRedis().Get(sKey, replyStringCb);</span><br /><span style="color: #800000; font-family: Courier;">}</span><br /><br /><span style="color: #800000; font-family: Courier;">}&nbsp; // namespace</span><br /><br /><span style="color: #800000; font-family: Courier;">void Bind(lua_State* L)</span><br /><span style="color: #800000; font-family: Courier;">{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;LuaBinding(L).beginModule("c_redis")</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;.addFunction("set", &amp;Set)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;.addFunction("get", &amp;Get)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;.endModule();</span><br /><span style="color: #800000; font-family: Courier;">}</span><br /><br />需要将 lua 的回调函数转成 cpp 的回调：<br /><br /><span style="color: #800000; font-family: Courier;">template &lt;class Function&gt;</span><br /><span style="color: #800000; font-family: Courier;">Function ToFunction(const LuaIntf::LuaRef&amp; luaFunction)</span><br /><span style="color: #800000; font-family: Courier;">{</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;// Default is empty.</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;if (!luaFunction)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return Function();&nbsp; // skip nil</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;if (luaFunction.isFunction())</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return luaFunction.toValue&lt;Function&gt;();&nbsp; // Todo: catch</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;LOG_WARN_TO("ToFunction", "Lua function expected, but got "</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&lt;&lt; luaFunction.typeName());</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;return Function();</span><br /><span style="color: #800000; font-family: Courier;">}</span><br /><br />Lua 这样调用：<br /><span style="color: #000080; font-family: Courier;">c_redis.set("FOO", "1234")</span><br /><span style="color: #000080; font-family: Courier;">c_redis.set("FOO", "1234", function(ok) print(ok) end)</span><br /><span style="color: #000080; font-family: Courier;">c_redis.get("FOO", function(reply_type, reply)</span><br /><span style="color: #000080; font-family: Courier;">&nbsp;&nbsp; &nbsp;assert("string" == type(reply))</span><br /><span style="color: #000080; font-family: Courier;">&nbsp;&nbsp; &nbsp;if 0 == reply_type or 1 == reply_type then</span><br /><span style="color: #000080; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print("FOO="..reply)</span><br /><span style="color: #000080; font-family: Courier;">&nbsp;&nbsp; &nbsp;else</span><br /><span style="color: #000080; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;print("Error: "..reply)</span><br /><span style="color: #000080; font-family: Courier;">&nbsp;&nbsp; &nbsp;end</span><br /><span style="color: #000080; font-family: Courier;">end)</span></div><img src ="http://www.cppblog.com/jinq0123/aggbug/214574.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2017-01-05 18:42 <a href="http://www.cppblog.com/jinq0123/archive/2017/01/05/214574.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title> ejabberd为游戏免除注册限制</title><link>http://www.cppblog.com/jinq0123/archive/2016/11/03/214377.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Thu, 03 Nov 2016 04:11:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2016/11/03/214377.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/214377.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2016/11/03/214377.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/214377.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/214377.html</trackback:ping><description><![CDATA[<div>20161103_ejabberd为游戏免除注册限制<br /><br />(金庆的专栏 2016.11)<br /><br />ejabberd聊天服务器默认会限制同一IP注册帐号须间隔600s。 <br />在游戏中需要为每个角色注册一个聊天帐号，不应该有此限制。 <br />可以更改服务器代码，为游戏服务器免除这一注册间隔时间。 <br /><br />假设游戏服用专用的帐号登录ejabberd, 然后为这种帐号免除注册限制。<br /><br />在ejabberd.yml配置访问控制列表(ACL)中添加 game_master：<br /><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; acl:</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; game_master:</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; user:</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - "game_master_1@localhost"</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - "game_master_2@localhost"</span><br /><br />game_master 帐号预先创建，供游戏服务器登录ejabberd.<br /><br />mod_register的配置中，将 "access_from: deny" 改为 "access_from: all". <br />"access_from: deny" 表示任何用户都不能注册帐号，只能是登录前注册。 <br />"access_from: all" 表示登录用户也能注册帐号。 <br />这样game_master帐号先登录，然后就可以注册新帐号了。 <br /><br />添加 access_from_without_time_limit，允许game_master无注册限制。<br /><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; mod_register:</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; access_from: all</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; access: register</span><br />&nbsp;&nbsp; &nbsp;<br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ## Allow some user register accounts without registration_timeout limit.</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; access_from_without_time_limit:</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - allow: game_master</span><br /><br />实际上也可以这样直接配置： &nbsp;<br /><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; access_from_without_time_limit:</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - allow:</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - user: game_master_1@localhost</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - user: game_master_2@localhost</span><br /><br />配置成 acl 的好处是，可以用<br /><br /><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp; ejabberdctl reload_config</span><br /><br />重新加载 acl, 而模块配置部分无法重新加载。<br /><br />acl 配置还可以在 http admin 界面更改。<br /><br />代码需稍加更改，修改mod_register.erl.<br /><br />try_register/5 添加 From 参数改为 try_register/6<br /><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;try_register(User, Server, Password, From, SourceRaw, Lang)</span><br /><br />并将其中<br /><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;case check_timeout(Source) of</span><br /><br />改成<br /><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;CheckTimeout = case check_from_without_time_limit(From, Server) of</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;allow -&gt; true;</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;_ -&gt; check_timeout(Source)</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;end,</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;case CheckTimeout of</span><br /><br />即如果From是gm帐号时，CheckTimeout直接通过，不再判断600s的间隔。<br /><br /><span>check_from_without_time_limit/2 仿照check_from/2，这样实现：</span><br /><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; check_from_without_time_limit(JID, Server) -&gt;</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Access = gen_mod:get_module_opt(Server, ?MODULE, access_from_without_time_limit,</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fun(A) -&gt; A end,</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; none),</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; acl:match_rule(Server, Access, JID).</span><br />&nbsp;&nbsp; &nbsp;<br />添加：<br /><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; mod_opt_type(access_from_without_time_limit) -&gt;</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fun acl:access_rules_validator/1;</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; ...</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; mod_opt_type(_) -&gt;</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [..., access_from_without_time_limit, ...].</span><br />&nbsp;&nbsp;&nbsp;</div><img src ="http://www.cppblog.com/jinq0123/aggbug/214377.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2016-11-03 12:11 <a href="http://www.cppblog.com/jinq0123/archive/2016/11/03/214377.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Unity3d导出Recast geomset.txt </title><link>http://www.cppblog.com/jinq0123/archive/2016/01/12/212645.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Tue, 12 Jan 2016 03:28:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2016/01/12/212645.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/212645.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2016/01/12/212645.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/212645.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/212645.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Unity3d导出Recast geomset.txt(金庆的专栏)Recast Demo 输入需要 geomset.txt 文件来指定区域类型。以ObjExporter.cs为基础，编写Unity3d地图导出工具，同时导出obj文件和geomset.txt.geomset.txt 示例# Obj file.f scene_level_0101.unity_56.obj# Convex volum...&nbsp;&nbsp;<a href='http://www.cppblog.com/jinq0123/archive/2016/01/12/212645.html'>阅读全文</a><img src ="http://www.cppblog.com/jinq0123/aggbug/212645.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2016-01-12 11:28 <a href="http://www.cppblog.com/jinq0123/archive/2016/01/12/212645.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Unity3d导出场景地图寻路</title><link>http://www.cppblog.com/jinq0123/archive/2015/12/04/212413.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Fri, 04 Dec 2015 10:39:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2015/12/04/212413.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/212413.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2015/12/04/212413.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/212413.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/212413.html</trackback:ping><description><![CDATA[<div>Unity3d导出场景地图寻路<br /><br />(金庆的专栏)<br /><br />Unity3d中用无渲染的透明盒子摆出地面和阻档区域。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.renderer.enabled = false;<br />所有这些盒子设为Navigation Static.<br />阻档盒子Navigation Layer设为 Not Walkable.<br />用EditorObjExporter.cs 导出这些盒子到obj文件。<br />http://wiki.unity3d.com/index.php?title=ObjExporter<br /><br /><img alt="" src="http://www.cppblog.com/images/cppblog_com/jinq0123/u3d_navigation.png" height="196" width="700" /><br /><br />用Recast Demo 打开obj文件，就可以测试地图寻路了。<br /><br /><img alt="" src="http://www.cppblog.com/images/cppblog_com/jinq0123/u3d_exported_map.jpg" height="1045" width="1858" /><br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/212413.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2015-12-04 18:39 <a href="http://www.cppblog.com/jinq0123/archive/2015/12/04/212413.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>