战魂小筑

讨论群:309800774 知乎关注:http://zhihu.com/people/sunicdavy 开源项目:https://github.com/davyxu

   :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  257 随笔 :: 0 文章 :: 506 评论 :: 0 Trackbacks

#

以下比较的基础都是基于一种编程语言+一定的第三方或者自己编写的网络库和底层进行的,Skynet稍微特殊,但总体比较合适放到比较中来

C#

开发效率:Windows下可以通过VisualStudio进行开发,其他平台可以使用MonoDevelop,非常方便

运行效率:JIT的性能优化比较到位,能适应90%性能环境

部署便捷性:可以通过交叉编译生成其他平台的可执行文件,通过mono运行可执行文件

调试便捷性:VisualStudio和MonoDevelop调试均很方便, 还可远程调试

上手度:对C系语言熟悉的几天就可上手

热更新:可以通过DLL方式进行

Web对接:可做,代码比较啰嗦

崩溃处理:可通过try catch捕获错误

网络库编写难度:一般,需注意gc问题

第三方网络库及框架数量:一般

 

Golang

开发效率:高

运行效率:并发上非常有优势,对CPU利用率比较高,原生运行无虚拟机

部署便捷性:一次编译到处运行,无任何运行库依赖

调试便捷性:实际操作中,单线程挂接调试器可行, 但变量显示不正确,开发期基本采用日志方式进行查错

上手度:语言简单,特性少, 新手1周能贡献代码

热更新:无法进行热更新,语言无法编译为DLL,也不支持DLL加载(linux平台的.so加载忽略不计)

Web对接:非常方便, 代码精简

崩溃处理:崩溃后以命令行方式打印出栈,程序内可以捕获任何崩溃错误并继续运行

网络库编写难度:简单,比C socket更简单

第三方网络库及框架数量:偏少

 

Skynet(lua+C)

开发效率:基于动态语言的开发初次写比较快,后期维护和重构会耗费一定的时间在查错上

运行效率:基于lua jit的运行效率还是能接受的

部署便捷性:方便, 只有底层修改需要重新编译, 大部分时间只用更新lua文件

调试便捷性:不是很方便,基于日志方式进行查错

上手度:lua语言特性有部分和C系语言有一定差异,基于Actor模型的思想学习,适应需要耗费一定的时间

热更新:类似于Erlang,可精确到函数级的热更新

Web对接:有一些http支持,通过社区慢慢进行完善

崩溃处理:lua天生可以捕获错误

网络库编写难度:自带,无需编写

第三方网络库及框架数量:通过社区慢慢完善

 

C++

开发效率:编译慢,文件多,通用库少

运行效率:native速度标杆

部署便捷性:编写各类的make门槛较高

调试便捷性:可通过VisualStudio进行Windows平台调试

上手度:2~3年经验的熟手仍然会写出崩溃和泄露代码

热更新:可通过DLL进行

Web对接:代码啰嗦,第三方库少

崩溃处理:Windows下可使用SEH捕获段异常,其他平台只能通过崩溃后进行coredump分析,容错非常差

网络库编写难度:基于asio编写较为简单,但总体看来难度不低

第三方网络库及框架数量:较多

 

以下是得分

image

 

从发文时的项目对这些语言使用率来说,Java,Erlang,C++编写的服务器较多,Golang,JavaScript,C#是第二梯队,Skynet由于上手不是很容易,所以仅有两位数的团队在使用,但总体表现还是比较出色的

对于老团队, C++的服务器工具链和框架已经相对成熟, 完全没必要更换新语言, 只是在对接sdk感觉困难时,可以尝试Golang这些对web有优势的语言进行混合语言开发

对于新团队,开发效率,上手度和部署效率是优先选择的,C#,Golang,JavaScript这些新兴语言会让你事半功倍

对于大规模无需选服的服务器, Skynet的actor模型对扩展会比较容易

对于大公司,好项目,上线后需要通过热更新进行bug修补的,C#,C++,Erlang会是首选

 

但总的一点, 还是根据团队熟悉度来选择语言,贸然的使用新语言的风险也是很大的

posted @ 2016-01-05 16:51 战魂小筑 阅读(18885) | 评论 (10)编辑 收藏

最近和团队讨论打包导致的发版本时间过长的问题: 平均1个小时

Unity3D现在官方不支持热更新, 虽然有一些lua的热更方案, 但鉴于项目开始时lua热更还不成熟, 所有没有采用

经过长时间业界和技术的摩擦, 渠道基本认同了Unity3D不能热更的现实

 

根据我们这边运营分析的热更新的需求: 临时关闭功能避免bug刷钱

在强力的QA支持(我们有)下, 可以保证版本的功能不会有明显问题, 即便有问题, 可以通过服务器关闭功能达到效果

 

这样来说, 代码上的热更新基本是不可能了, 只能通过服务器来配合, 所以我们只考虑资源热更新需求.

 

场景: 这部分属于PVE功能, 虽然包含有怪物摆放的逻辑, 但一般一旦做好基本没有更新需要

角色: 后期这块可能会考虑更新, 但明显系统上可以同时支持打包和非打包需求, 因此默认包内的角色可以直接打包, 未来的付费相关的角色热更新需求既可以通过

一阶段打包更新, 也可以根据增量更新进行. 

界面: 知识更新界面包只能修改界面布局和坐标, 逻辑依然需要打包更新, 所以这块完全有必要不做打包

特效: 特效与角色和界面关联紧密, 而且混用, 交叉的情况很多, 打包明显会造成一部分资源重复进入包体, 因此果断不打包

 

总结下来, 资源的更新一定是伴随着代码的更新来做的, 那么可以规划一个大版本, 把需要更新的资源和功能逻辑一块更新

虽然大包更新会损失一定的用户, 但未来在核心稳定的情况下, 可以考虑把新功能用脚本来更新

资源不打包带来明显的优势: 资源加载变的更快了, 制作流程变的更简单, 无需兼容打包和非打包情况. 包体大小有小幅度下降( 重复打包部分)

posted @ 2015-12-12 10:52 战魂小筑 阅读(4370) | 评论 (2)编辑 收藏

简单,方便,高效的Go语言的游戏服务器框架

func server() {
 
    pipe := cellnet.NewEventPipe()
 
    evq := socket.NewAcceptor(pipe).Start("127.0.0.1:7234")
 
    socket.RegisterSessionMessage(evq, coredef.TestEchoACK{}, func(content interface{}, ses cellnet.Session) {
        msg := content.(*coredef.TestEchoACK)
 
        log.Println("server recv:", msg.String())
 
        ses.Send(&coredef.TestEchoACK{
            Content: proto.String(msg.String()),
        })
 
    })
 
    pipe.Start()
 
}
 
func client() {
 
    pipe := cellnet.NewEventPipe()
 
    evq := socket.NewConnector(pipe).Start("127.0.0.1:7234")
 
    socket.RegisterSessionMessage(evq, coredef.TestEchoACK{}, func(content interface{}, ses cellnet.Session) {
        msg := content.(*coredef.TestEchoACK)
 
        log.Println("client recv:", msg.String())
 
    })
 
    socket.RegisterSessionMessage(evq, coredef.SessionConnected{}, func(content interface{}, ses cellnet.Session) {
 
        ses.Send(&coredef.TestEchoACK{
            Content: proto.String("hello"),
        })
 
    })
 
    pipe.Start()
}

 

项目地址: https://github.com/davyxu/cellnet

posted @ 2015-10-16 11:44 战魂小筑 阅读(11314) | 评论 (6)编辑 收藏

与慕课网合作的视频教程系列, 不定期更新, 按时间倒序排列

欢迎各位参观, 学习

Cocos2d-x游戏开发入门-贪吃蛇

http://www.imooc.com/view/487

http://www.imooc.com/learn/508

Cocos2d-x游戏开发基础之Lua基础篇

http://www.imooc.com/view/485

Cocos2d-x初体验之Lua篇

http://www.imooc.com/learn/448

posted @ 2015-10-16 10:45 战魂小筑 阅读(1430) | 评论 (0)编辑 收藏

最近接入pp助手的服务器端支付, 按照PP官方提供的文档来看, 需要服务器做RSA的验证.

首先我们来看下

RSA的几个标准用法

非对称加密解密

假设A要把内容传输给B

1. B生成RSA的公钥和密钥, 这是成对出现的, 密钥由B保存, 把公钥告诉A

2. A用B的公钥加密内容, 并把密文内容传输给B

3. B用密钥解密

验证

证明某个内容是你发的, 而不是被别人冒名顶替, 例如git的push中就带有这个功能

假设A有内容,  B要验证内容确实由A发出

1. A生成公钥和密钥

2. A将内容做一个hash, 把hash码用自己的密钥加密并把这段密文发给B

3. B用A的公钥对密文进行验证, 即可确认密文是否由A发出

 

可以看出, 两种用法都是典型的非对称用法

但PP助手却干了件神奇的事情:

非对称当对称算法加解密

在PP SDK官方文档里, 我们找到了PHP语言的验证方法, 方法里使用了这样一个API

openssl_public_decrypt

从官方文档看得出这个使用openssl的算法库

 

类似的, 还有Java, C++, Python语言的处理方法

其中, C++也是用的openssl, Python则是需要预编译C库,在Ubuntu下需要手工patch M2Crypto的_ssl.c文件.

 

先不说这些非正规的编译,patch方法会造成多大的问题, 单就这个用公钥解密就很蛋疼

从之前的RSA算法中了解, 只有对公钥进行验证的方法, 也就是只能得到是还是不是的结果. 但PP的SDK则要求必须用公钥解密…

解出的数据为一段json, 以对比是否有订单篡改.

 

那么这种做法就等效于, 用最简单的异或+一个公钥进行订单加密, 然后同样用这个公钥进行解密

只不过用RSA感觉很高级…

这种做法一旦公钥在PP助手服务器或者玩家的开发服务器, 甚至源代码泄露, 那么马上就有很大的伪造订单的危险

 

我把这个做法发给朋友看, 他们说, 其实PP助手的开发者只管用了RSA, 跟传输不是明文就好了, 至于什么信息安全, 都是屁!

posted @ 2015-10-12 14:27 战魂小筑 阅读(3272) | 评论 (3)编辑 收藏

看C#例子

            Action[] a = new Action[3];
 
            for (int i = 0; i < 3; i++)
            {
                a[i] = ( ) => { Console.WriteLine(i); };
            }
 
            for (int i = 0; i < 3; i++){
                a[i]();
            }

C#打印结果为3 3 3

 

Golang的例子

    a := make([]func(), 3 )
    
    for i := 0; i < 3; i++ {
        
        a[i]= func( ){
            
            fmt.Println(i)
            
        }    
    
    }
    
    for _, s := range a {
        s()
    }

Golang打印结果为3 3 3

 

最后是Lua的例子

a = {}
 
for i = 1, 3 do
 
    table.insert( a, function()
        print(i)
    end
    )
 
end
 
 
for _, v in ipairs(a) do
    v()
end

Lua打印结果为1 2 3

 

差异在于, C#和Golang将变量捕获到闭包内时, 均使用引用方式, 即当最后开始调用使用变量时, 由于变量已经结束循环, 所以是最终值

但是Lua捕获方式是值捕获, 因此比较容易理解, 是多少就是多少

posted @ 2015-09-23 18:31 战魂小筑 阅读(3802) | 评论 (2)编辑 收藏

多线程+同步阻塞模型

在我们的游戏项目中使用的golang服务器开发方式如下

1.多线程逻辑

2.同步阻塞. 也就是说, 每个人一个线程(goroutine), io线程=逻辑线程

这种方式的优点:

1. 同步阻塞方式与人的思维方式类同

2. 逻辑处理性能有一定提升

在大规模使用这种模式编写逻辑后, 我们发现了这种模式只有1个缺点: 编写者需要处理多线程关系

但这本身确实直接致命的, 回想C++时代, 多线程处理时, 调试重现的困难… 脑补景象太惨不敢直视

单线程+异步多进程模型

在C++时代, 我曾经编写过一套asio的C++服务器框架. 采用io多线程, 逻辑单线程, 依赖着C++高性能的优势, 让开发便捷简单且无需关心线程问题.

那么到了golang时代, 为什么不能试下单线程异步多进程方式来编写逻辑?

与多线程同步阻塞对比后, 我们发现, 两者优缺点互补. 那这就回到了领域选型问题了. 对于游戏服务器需要的上手简单, 开发便捷, 压力降低(非MMO)这些特点来说, 单线程异步多进程再合适不过了

那么, 我们在用golang编写单线程异步多进程服务器应该注意哪些点呢?

1. socket处理完全封装, 只通过channel抛出到逻辑线程排队处理

2. 数据库, rpc及其他io处理, 一律改为异步回调模式, 不使用同步接口

3. 玩家存盘提交数据可以考虑复制并提交到存盘线程方式, 提高性能.

4. 采用多进程架构, 比如设网关进程, 把io压力分散到进程中

5. 逻辑编写中, 不允许使用go开线程及channel, 有需要提高性能部分需要单独编写

 

Actor模型的痛

cellnet在开发时本来考虑使用actor模型来进一步简化多线程逻辑的麻烦, 经历了一段时间的原型开发后, 发现了一些问题, 列举如下:

1. golang的强类型不适合actor模型这种经常需要动态生成各类消息的模型, 但skynet(C+lua)/erlang就有天生优势

2. actor模型本身不是万能的, 不能解决所有需求, 特别是游戏

3. actor模型理解到应用有一定的难度. 本身还需要搭建框架, 上手复杂

总之, 看过一些erlang及skynet的用例, 没有应用的很纯正且成熟的成功actor模型案例, 从传统socket服务器框架跨越到actor模型会扯到蛋, 因此, 后期cellnet会考虑回归到成熟的socket服务器框架. 把架构做到简单上手, 高扩展上.

posted @ 2015-09-09 19:06 战魂小筑 阅读(6670) | 评论 (6)编辑 收藏

最近参加了一个大服务器架构讨论活动, 记录下心得

概述

游戏客户端采用Cocos2dx-Lua的纯Lua编写逻辑, 服务器采用Golang作为开发语言

游戏类型类似于COC,因此无需选服. 需要使用大服务器架构进行处理

数据库

采用Mongodb做持久存储, redis做跨服通信数据交换

使用UCloud的云技术, 省去了烦人的运维工作

通信及协议

客户端和服务器通讯使用HTTP短连接, 基于json的数据封包协议

服务器间大量使用Golang自带的json+rpc进行通信

服务器类型

服务器类型大致分为逻辑服务器,战斗服务器, 中心服务器

逻辑服务器

逻辑服务器负责日常逻辑及公共逻辑处理(好友, 公会)

1个逻辑服务器对应一个区, n个区均使用Ucloud云Mongodb进行数据存储

战斗服务器

战斗服务器是一个集群, 集群会返回一个负载最低的服务器返回使用

战斗服务器通过cgo技术与客户端C++/lua的战斗逻辑进行逻辑复用, 在此技术上进行

战斗逻辑的校验

中心服务器

客户端登陆前, 在中心服务器这里获得可登陆的逻辑服务器地址, 同时做一个负载均衡

短连接

评价

由于操作系统的技术趋于稳定, 同时, 手游的弱交互型导致的游戏架构趋于简单. 因此网络负载不再是游戏服务器技术的瓶颈. 从经验看来, 游戏服务器技术, 更重要的是还是看数据库的选型及处理方式. 

虽然Mongodb的性能上不如内存数据库. 但是从存储安全性上要比个人搭建的内存数据库简单, 安全

外加上云技术的引用, 性能的瓶颈和运维的技术复杂度迎刃而解

Redis用于跨服数据交互那是再好不过的数据中介了, 保证速度和稳定性, 绝对不是造轮子能比拟的

短连接在手游上处理起来比长连接简单一些, 无需做断线重连. 服务器的底层也是由Golang的框架库保证质量的. 因此负载毫无问题. 服务器对内及对外均使用json进行数据交换, 简化了协议处理. 也方便了调试

json rpc的性能损耗对于整个逻辑的处理来说均可以忽略不计

posted @ 2015-07-21 10:30 战魂小筑 阅读(5122) | 评论 (8)编辑 收藏

项目中, 我们使用Unity3D做客户端开发. 自己撸了一套C#网络库, 随着项目的推进, 问题来了:

问题

每次Unity3D编辑器打开时, 连接服务器都会有一定几率失败, 需要反复关闭再打开编辑器3~4次后, 才能正常接收到封包

转载请注明: 战魂小筑http://www.cppblog.com/sunicdavy

探索

我们的网络库基于C#的Begin/End系的异步Socket, 这种socket更接近C++的asio模型, 撸起来特爽.

1. 根据经验, 这个诡异问题多半跟多线程有关系. 复查代码, 无效.

2. 找友人更换网络库, 换阻塞Socket实现和SocketAsyncEventArgs这种实现都试过, 仍然无法解决问题.

3. 接下来还是对Begin/End系的网络库进行日志追踪. 发现, 发送会总是成功, 连接成功和接收封包有一定几率会断掉

我们并没有单独开线程来处理, 而是利用底层异步通知, 然后有线程安全队列切换到主线程进行投递. 因此底层的线程正常性是整个问题的焦点

由于一直无法找到原因, 这个问题搁置了

转载请注明: 战魂小筑http://www.cppblog.com/sunicdavy

解决方案

直到有一个偶然的机会, 取过同事代码后. 突然发现第一次打开Unity3D编辑器可以直接登录. 但之后又不行. 同事提醒, 会不会是优先度问题.

马上打开Edit->Project Settings->Script Execution Orders. 提高了网络组建优先度

image

测试, 通过, 问题解决

转载请注明: 战魂小筑http://www.cppblog.com/sunicdavy

总结

转载请注明: 战魂小筑http://www.cppblog.com/sunicdavy

一直怀疑这个问题跟Mono版本过老有关系, 但由于5.2版本到年底才更新, 之前只能自己啃bug.

在这个问题发生后解决前, 我们还有一个相关见闻: 我们将网络部分比较稳定的代码拆分放到dll中, 通过Unity3D的机制进行加载

结果, 网络无法初始化. 估计也是跟这个问题有关系

总之, 有类似问题时, 可以试用脚本执行顺序大法进行尝试

posted @ 2015-07-06 16:11 战魂小筑 阅读(3651) | 评论 (8)编辑 收藏

数据库选择历程

我们的项目一直使用MySQL作为数据库. 无论是从C++的服务器, 还是到Golang服务器. 当年搞服务器时, 看大部分人都是用SQL(MySQL/SQLServer), 而Mongo感觉像邪教一样, 再加上服务器还是Linux比较正统, 所以果断选了MySQL.

刚开始感觉,游戏服务器的数据存储其实应该是蛮神圣的过程. 那么多的数据, 需要按照MySQL一样分表, 分字段存储, 为了查询, 还要乖乖的学一下SQL的语法

就这么折腾了几年. 在云DB的蒙蔽下, 一直认为MySQL就是做游戏服务器存储的专业技术. 分布式和存储压力一定交给云DB来做. 直到真正试了下NoSQL在游戏服务器开发里的思路.

用了Golang, 才发现同步写逻辑是多么的优雅.

用了NoSQL系列的数据库, 才意识到: 游戏服务器的数据存储和游戏服务器的存盘两个概念差异其实蛮大的.

MySQL中, 背包其实跟角色完全没有关系, 只是通过1个角色id映射过去, 人为的割裂了数据的关联性. 还硬生生的整出个概念叫结构化查询让你学

NoSQL中, 只是把数据库当成是存储点, 每个角色的数据是完整的一块. 里面怎么存随你便. 每个角色通过id来查询, 其他都没有了

于是乎, 游戏开发变得异常简单. MySQL角色进门查询4~5次才能搞定要的数据.而NoSQL一口气全查出来, 存盘也无需增量, 直接存盘就可以了

所以现在觉得, NoSQL的思路对于游戏服务器存储来说简直是完美的!

转载请注明: 战魂小筑http://www.cppblog.com/sunicdavy

 

NoSQL数据库方案对比

NoSQL下实现方案很多, 游戏常用的就这么3家: mongo, redis, memcached

下面说下优缺点

mongo

磁盘映射内存数据库

value为document类型, 基于BSON的value序列化

应用场景:

适合多写少读, 例如日志和备份

转载请注明: 战魂小筑http://www.cppblog.com/sunicdavy

 

redis

内存数据库

单核

value限制512M

多种value类型, 游戏用途使用私有的序列化协议(例如protobuf)

支持落地(bgsave)

用户: 新浪, 淘宝, Flickr, Github

应用场景: 适合读写都很高, 数据处理复杂等

转载请注明: 战魂小筑http://www.cppblog.com/sunicdavy

 

memcached

内存数据库

多核

value限制1M

不支持落地(持久化)

用户: LiveJournal、hatena、Facebook、Vox

应用场景: 动态系统中的缓冲, 适合多读少写

转载请注明: 战魂小筑http://www.cppblog.com/sunicdavy

个人评价

memcached 适合网页缓冲, 游戏里很少有使用. 目前只有腾讯云支持云memcached

redis非常适合游戏的内存数据库, 但是落地策略会比较复杂, 需要具体分析, 可以参考后面的链接看下云风怎么处理这个问题

mongo数据库在早期还是非常不错的NoSQL的数据库. 工具比较方便, 可视化. 但是随着近年来游戏的并发度越来越高, 所以为了一次到位, 很多人还是选择了redis

下图参考自知乎问题. 链接在后面有提示, 若侵权请联系删除

转载请注明: 战魂小筑http://www.cppblog.com/sunicdavy

image

参考链接:

    谈谈陌陌争霸在数据库方面踩过的坑( Redis 篇)

http://blog.codingnow.com/2014/03/mmzb_redis.html

转载请注明: 战魂小筑http://www.cppblog.com/sunicdavy

Memcache,Redis,MongoDB(数据缓存系统)方案对比与分析

http://blog.csdn.net/suifeng3051/article/details/23739295

 

http://www.zhihu.com/question/31417262

posted @ 2015-06-19 16:23 战魂小筑 阅读(6577) | 评论 (5)编辑 收藏

仅列出标题
共26页: 1 2 3 4 5 6 7 8 9 Last