??xml version="1.0" encoding="utf-8" standalone="yes"?> Golang的热更新采用什么机? 使用go1.8提供的plugin包机制实?/p> plugin包本w设计的目的是热更新? plugin包其实只是支持将代码分别~译为多个动态库,动态加载后q行 q不能完全支持类似C/C++的动态库方式处理代码 带状态的q程热更新的基本概念及范围是什? 数据部分(model)不更? 只更新逻辑部分(函数) 表格和配|更新算热更C? ? 但不是本文描q范?/p> 热更新能在windows上用么? 不支?/p> 我能原来一个exe的代码编译ؓso提供lplugin使用? 可以, 但是必须仍然保留main包作为插件入? q在main包内d提供lplugin调用入口. 如果动态库中没有main? ~译出的so能用? 不能, 包必d含main, 否则输出的是.a的文? plugin包加载会报错 动态库? 非main包的的代码修改能做热更新? 不能!(崩溃了吧, 我提了一个issue: https://github.com/golang/go/issues/20554) 如果实做了修改, 底层会报? plugin was built with a different version of package 解决Ҏ: 修改plugin包底层实现ƈ重新~译 打开runtime/plugin.go, 注释以下代码 for _, pkghash := range md.pkghashes { if pkghash.linktimehash != *pkghash.runtimehash { return "", nil, pkghash.modulename } } 执行/usr/local/go/run.bash 重编?试 代码中哪些可以被更新? Ҏ可以被更C? 闭包? 只能更新拥有静态地址的结?例如: 包别函?cM于静态函? 例如: svc_0.so里有一个Foo函数, svc_1.so修改了Foo函数实现, 热更新可实现 闭包=函数+捕获变量, 实际上是一个动态结? 没有静态地址, 无法被更?/p> 各种包别变? l构体变? l构体方? 局部变量均不能被热更新, 但是变量g会被影响 新增l构可以被运?/p> 使用l构体方法调用了包别函? 包别函数能被更C? 可以, 虽然Ҏ不能被更? 但方法被调用的包U别函数的地址是固定的, 所以可以被热更?/p> init包初始化函数能用? 能被热更C? 官方q样描述: 插gW一ơ被打开? 其关联的, 没有成ؓE序的一部分的所有的包的init函数被调用. 插g的main函数不会被调? 插g只会被初始化一? 不能被关?/p> 因此, 需要手动将init函数Ҏ自己的函? l一在so的main包里调用 如何~译获得plugin包支持的动态库 -buildmode=plugin是重要参?/p> --ldflags里的-pluginpath的作用是: 每次~译的内部识别\径都是不同的, 避免重复加蝲的警?/p> 在官|下载最新版的VSCode: https://code.visualstudio.com/ 打开扩展面板 VSCode->查看->扩展 扑ֈGo插g 在搜索框里输入Go, 扑ֈW二行写?Rich Go language support for Visual Studio Code的插? 点击安装 注意不是排名最高的 重启~辑?/p> 打开调试面板 VSCode->查看->调试 d调试目标 ?没有调试"的下拉框中点?d配置.." d目标调试配置 例子: 其中: "port", "host"都是go插g自动生成?/p>
"env"|环境变? 讄Z的工E目录就可以(包含bin, src的文件夹) 此时扑ֈmain.go按F5, 会报错提C? 我们使用go命o行编译调试器 dlv调试器放在GOPATH(工程目录)的bin目录?/p>
调试快捷键和Visual StudiopM?/p>
注意?/p>
]]>
]]>基本概念
代码及结?/h1>
When a plugin is first opened, the init functions of all packages not already part of the program are called. The main function is not run. A plugin is only initialized once, and cannot be closed
~译
SVCNAME=$1 SVCVER=$2 TIMESTAMP=`date '+%Y%m%d_%H%M%S'` go build -v -buildmode=plugin --ldflags="-pluginpath=${SVCNAME}_${TIMESTAMP}" -o ${SVCNAME}_${SVCVER}.so ${SVCNAME}
]]>准备VSCode
安装Golang插g
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"type": "go",
"request": "launch",
"mode": "debug",
"remotePath": "",
"port": 2345,
"host": "127.0.0.1",
"program": "${fileDirname}",
"env": {
"GOPATH":"D:/Develop/vscodegolang"
},
"args": [],
"showLog": true
}
]
}
准备调试插g
Failded to continue:"Cannot find Delve debugger. Install from https://github.com/derekparker/delve & ensure it is in your "GOPATH/bin" or "PATH"
go get github.com/derekparker/delve/cmd/dlv
选中要调试的main.go, 点击F5, 既可以开始调?/p>
在launch.json中可以添加多l调试入? 通过调试面板中选中对应的配|开启不同目标的调试
{
"version": "0.2.0",
"configurations": [
{
"name": "client",
"type": "go",
"request": "launch",
"mode": "debug",
"remotePath": "",
"port": 2345,
"host": "127.0.0.1",
"program": "${fileDirname}",
"env": {
"GOPATH":"D:/Develop/vscodegolang"
},
"args": [],
"showLog": true
},
{
"name": "server",
"type": "go",
"request": "launch",
"mode": "debug",
"remotePath": "",
"port": 2345,
"host": "127.0.0.1",
"program": "${workspaceRoot}/src/server",
"env": {
"GOPATH":"D:/Develop/vscodegolang"
},
"args": [],
"showLog": true
}
]
}
"program"中的"${fileDirname}"是以当前选中文g作ؓ启动?/p>
更徏议?program"?${workspaceRoot}", 以包名作为启动点的方式进行配|?/strong>
我们先看下构造函数的规则
构造参数量: 0表示没有构造函? 1表示有构造函?个参?/p>
本类构?/th> | 父类构?/th> | 处理Ҏ |
---|---|---|
0 | 0 | 不处?/td> |
1 | 0 | 调本cctor |
0 | 1 | 调父cctor |
1 | 1 | 调本cctor, 本类ctor调父cctor |
2 | 1 | 调本cctor, 本类ctor调父cctor |
1 | 2 | 报错, 手动调父cctor |
2 | 2 | 报错, 手动调父cctor |
实际只用考虑最典型的一U行? 实例化子c? 转ؓ父类调用Ҏ, q个时?/p>
如果Ҏ是override, 调用的是子类
如果Ҏ是virutal或者不指明, 调用的是父类
整个重蝲q程, 子类l对不会隐式调用父类的行?/p>
构造函数的优点
构造函数的~点
其实我们对初始化函数的需求只?? 自定?/p>
所? 可以理解Golang不加入构造函数的设计是正的
? ? 清晰, 有规?/p>
他们其实q是cd功能, 都在buildin包里? pȝ默认l你做了?/p>
import(
. "buildin"
)
buildin的包内容都映到全局而已, 其实你也可以用自q包这么做
惌脚本一栯试打印数据么?
println("hello world")
无需包含M? 因ؓ它在buildin包里
q是在buildin包里的定?/p>
// iota is a predeclared identifier representing the untyped integer ordinal
// number of the current const specification in a (usually parenthesized)
// const declaration. It is zero-indexed.
const iota = 0 // Untyped int.
xmap和数l的定义
只是泛型没有开攄用户用而已(只许XX攄,不许XX点灯)
q在为多个key转id的复杂算法而头g?
type myKey struct{
number int
str string
}
func main(){
t := map[ myKey] int {
myKey{ 2, "world"}: 1,
}
fmt.Println(t[myKey{2, "world"}])
}
输出: 1
默认定义一个枚?/p>
type MyConst int
const (
MyConst_A MyConst = iota
MyConst_B MyConst = iota
)
func main(){
fmt.Println(MyConst_A)
}
输出: 0
如果我们惌动化输出MyConst_A字符串时
需要用golang的一个工具链:golang.org/x/tools/cmd/stringer
其下蝲, ~译成可执行工具? 对代码进行生?nbsp;
生成的代码会多出一个xx_string.go
里面是枚D的String()string 函数
type love struct{
}
func (self*love)foo(){
fmt.Println("love")
}
func main(){
var chaos interface{} = new(love)
chaos.(interface{
foo()
}).foo()
}
func( self*MyStruct) foo( p int ){
}
写不惯receiver的写? 如果q样改下?
func foo( self *MyStruct, p int ){
}
所以ؓ什么说Golangq是一个C语言?/p>
C#有Assembly概念, 可以在一个Assembly里搜? 创徏实例
Golang是静态类型语a, 如果需? 只能注册你需要创建的l构? 然后注册好的map用于创徏
如果你需要跨q_的工hE处? 对Python不熟? 可以使用
go run yourcode.go 参数1 参数2
方式来进行工具处?nbsp;
觉得? 可以~译成可执行文g
q样做的好处: 如果有些cd本n是go写的, Python想用是很麻烦的, 而Golang来写则轻而易?/p>
例子: 通过go的protobuf? 对一些文件进行处?/p>
type myType struct{
}
func (self*myType) foo( p int){
fmt.Println("hello", p )
}
func main(){
var callback func( int )
ins := new(myType)
callback = ins.foo
callback( 100 )
}
做过lua的C++代码l定的童鞋都清楚: lua只支持外部静态或者全局函数调用
如果要进行C++cL员函数调用时, 要自己处理this和成员函?nbsp;
q种技巧因为早L译器的虚表不同^台实现细节不l一需要专门处?nbsp;
后面跨^台虚表统一? cL员函数的调用写法也是很恶心复杂的
但是Golang的小白式用法, 直接吊打C++, 甚至C#复杂的delegate
扑ֈ 菜单->查看->选项->通用->存储->存储讄到本地ini文g
关闭LiteIDE
复制LiteIDE整个目录, 命名文g夹ؓ你的工程?/p>
每个工程所在的LiteIDE的配|将是独立的, 不会互相q扰
别以为程序一定要是main开始的才可以调?/p>
Golang的测试程序虽焉是一个个Test开头的函数,但执行go test? q是有main入口
在LiteIDE? 可以?调试->开始调试测试程序里q行试E序调试
go的工具链默认支持交叉~译
在LiteIDE? 可以通过切换输出q_, 输出不同q_的可执行文g
Google官方为Golang的调试例子默认用了gdb
然? 使用gdb调试goE序会遇到goroutine的各c问? 因ؓgdb不懂go
因此, q里使用delve黑科技来进行Golang的程序调?/p>
U命令行调试Ҏ在网上很Ҏ搜烦? 本文主要以LiteIDE来进行程序调?/p>
正常go build/install出的goE序是完全优化过? 使用调试器挂接调试时, 某些local变量/lamda表达式捕L变量会直接进入寄存器, 无法使用调试器查?/p>
删掉所有的pkg, 为build或install参数加入关闭~译器优化的参数 -gcflags "-N -l"
例如:
go install -gcflags "-N -l" svc\gamesvc
LiteIDE自带了gdb, 但是没有delve调试? 需要自行安? 命o如下
go get github.com/derekparker/delve/cmd/dlv
delve调试器会被放C的GOPATH/bin?/p>
在LiteIDE菜单中选择 调试->debugger/delve
q个时? LiteIDE依然找不到delve, 因ؓ它不在环境变量PATH? q里无需修改环境变量, 只需要LiteIDE的环境配|?/p>
在LiteIDE菜单中选择 查看->~辑当前环境, 在弹出的文档中修?/p>
PATH=c:\mingw32\bin;%GOROOT%\bin;%PATH%;c:\your\path\to\delve
LPATH前的注释#, ?PATH%d分号, 然后和你到delve调试器的路径
选择你的工程, 点击F5, q入调试模式
LiteIDE使用delve调试? 无法?变量 监视{窗口中正确捕捉delve调试q回数据(因ؓ格式太复杂了…)
没关p? 我们使用命o行配合显C即?/p>
LiteIDE控制台或调试输出H口在delve调试? 实际上是一个标准命令行
命o如下
p 变量名可以查看变量?/p>
locals查看局部变?/p>
ls可查看当前文?/p>
stack查看?/p>
help可以查看各种帮助
如果你的E序是外部程? 或者用go install安装到GOPATH/bin目录的程? 那么使用delve调试器启动程序时, 可能会碰到启动\径错误的问题
使用LiteIDE菜单 调试->调试其他应用E序… 填入你要调试E序的\径以及工作目? 可以解决q个问题
我们对Golang的结构体变量赋? 以及单参数函数调用进行反和native操作的测?pre>
package main
import (
"reflect"
"testing"
)
type data struct {
Hp int
}
const AssignTimes = 100000000
func TestNativeAssign(t *testing.T) {
v := data{Hp: 2}
for i := 0; i < AssignTimes; i++ {
v.Hp = 3
}
}
func TestReflectAssign(t *testing.T) {
v := data{Hp: 2}
vv := reflect.ValueOf(&v).Elem()
f := vv.FieldByName("Hp")
for i := 0; i < AssignTimes; i++ {
f.SetInt(3)
}
}
func TestReflectFindFieldAndAssign(t *testing.T) {
v := data{Hp: 2}
vv := reflect.ValueOf(&v).Elem()
for i := 0; i < AssignTimes; i++ {
vv.FieldByName("Hp").SetInt(3)
}
}
func foo(v int) {
}
const CallTimes = 100000000
func TestNativeCall(t *testing.T) {
for i := 0; i < CallTimes; i++ {
foo(i)
}
}
func TestReflectCall(t *testing.T) {
v := reflect.ValueOf(foo)
for i := 0; i < CallTimes; i++ {
v.Call([]reflect.Value{reflect.ValueOf(2)})
}
}
=== RUN TestNativeAssign
?PASS: TestNativeAssign (0.03s)
=== RUN TestReflectAssign
?PASS: TestReflectAssign (0.41s)
=== RUN TestReflectFindFieldAndAssign
?PASS: TestReflectFindFieldAndAssign (9.86s)
=== RUN TestNativeCall
?PASS: TestNativeCall (0.03s)
=== RUN TestReflectCall
?PASS: TestReflectCall (21.46s)
// FieldByName returns the struct field with the given name
// and a boolean to indicate if the field was found.
func (t *structType) FieldByName(name string) (f StructField, present bool) {
// Quick check for top-level name, or struct without anonymous fields.
hasAnon := false
if name != "" {
for i := range t.fields {
tf := &t.fields[i]
if tf.name == nil {
hasAnon = true
continue
}
if *tf.name == name {
return t.Field(i), true
}
}
}
if !hasAnon {
return
}
return t.FieldByNameFunc(func(s string) bool { return s == name })
}
各位看官必须吐槽用for来遍历获取数? 但冷静下来分? q样做无可厚?
试想如果reflect包在我们使用ValueOf时用map~冲好一个结构体所有字D늚讉K数据? 肯定讉K指定字段速度会很?br>但是, 以空间换速度的需求其实最多满了1%的需?
同样的例子是囑ŞAPI里访问Shader变量的方? L默认使用字符串获? 速度很慢. 当你惛_速访问时, h前按需~存字段
那么, Golang使用的也是这L思\. 虽然暴力了一? 但是能够让程序跑? 性能优化的东西放在之后来? ~冲下就可以解决
下面介绍下proto3的一些细节变?/p>
q个版本的protoc的protobuf~译器已l可以支持proto2语法和proto3的语?/p>
如果你的proto文g没有dsyntax说明的话, 用这个版本的~译器会报错, 提示你默认proto2支持, h加语法标?/p>
syntax = "proto2";
只保留repeated标记数组cd, optional和required都被L?/p>
实际使用证明, required的设计确实是蛋疼, C++的调试版会弹出assert,release版和optional也没啥区?/p>
map~写格式?/p>
map<key_type, value_type> map_field = N;
例如:
map<string, Project> projects = 3;
代码生成认支持map, q对于很多语a来说又可以偷懒了
位于proto2语法的字Dnumber后的[default=XX]
q个东西不能用了, 理由?
对于同一D序列化后的数据, 如果序列化端的default和反序列化端的default描述不一样会D最l结果完全不一?/p>
? 同一个数据两个结? q是不可预测的结? 因此q掉q个Ҏ?/p>
不过本h觉得, 对于游戏来说, q个功能本n可以压羃很多数据,虽然会有隐患
proto2里的默认值是枚D的第一个value对应的? 不一定ؓ0
proto3在你定义value? 强制要求W一个值必Mؓ0
q个修改为避免隐患还是有帮助?/p>
anycd, 可以代表Mcd, 可以先读q来, 再进行解? 没具体用, 步子跨大了怕扯到蛋
q个极好, json再次被同化了
js, objc, ruby, C#{等
然? C#版本的基runtime库是用C# 6.0的语法写?q对于Unity mono传2.0来说, 实扯到蛋了,没法?/p>
~译hE微ȝ, q要下个被墙掉的cmake?/p>
Golang的官方protobuf支持: https://github.com/golang/protobuf
对于proto2的文? 生成的go代码中的l构体依然用类型指针作为默认存? 兼容老的pȝ
对于proto3的文? 生成的go代码中的l构体直接用字D作为默认存? 不再使用GetXXX来作为字DD? 赋值时也无需使用proto.cd() 函数q行指针cd字段值创?
q个调整很是方便, 但丢׃optional判断功能, 对应C++里就是hasXXX的功? 不过好歹q个逻辑现在用的不多?/p>
q个修改大概也是配合json序列化来做的, go默认的json序列化时, 无法使用proto2生成的结构体? 因ؓ都是指针,无法赋?.
新插件会l每ơ生成的文gdq样一D代?/p>
var fileDescriptor0 = []byte{
// 220 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x8f, 0xcd, 0x4e, 0xc5, 0x20,0x10, 0x85, 0x53, 0xbd, 0x35, 0x32, 0xb7, 0xdd, 0x4c, 0x5c, 0xb0, 0x70, 0x71, 0xd3, 0xb8, 0x70,0x75, 0x17, 0xfa, 0x04, 0xc6, 0xd8, 0xb8, 0x50, 0x63, 0xa8, 0x2f, 0x80, 0xed, 0x44, 0x89, 0x28,0x04, 0xc6, 0xbf, 0x47, 0xf1, 0x6d, 0x95, 0x49, 0x8d, 0x4d, 0x5c, 0x01, 0xdf, 0x39, 0x7c, 0x30,0x00, 0x1c, 0x82, 0xdf, 0xc6, 0x14, 0x38, 0xe0, 0xaa, 0xec, 0xbb, 0x37, 0x68, 0x2e, 0x3e, 0x62,0x48, 0x7c, 0x49, 0x76, 0xa2, 0x84, 0x47, 0xd0, 0xde, 0x96, 0xf8, 0xee, 0x33, 0xd2, 0x8d, 0x7d,0x26, 0x5d, 0x6d, 0xaa, 0x63, 0x65, 0xda, 0xb8, 0x84, 0xd8, 0x41, 0x63, 0xc2, 0x7b, 0xef, 0xc8,0x4f, 0x52, 0xda, 0x91, 0x52, 0x93, 0x16, 0x0c, 0x0f, 0x41, 0x89, 0xa9, 0x77, 0x9e, 0xf4, 0xae,0x14, 0x54, 0xfc, 0x05, 0xdd, 0x57, 0x05, 0x4a, 0xba, 0xd7, 0xc4, 0x16, 0xb7, 0x80, 0x03, 0x27,0xf7, 0xf2, 0x70, 0x72, 0xe5, 0x32, 0x0f, 0xd1, 0x3b, 0xa6, 0x34, 0x5b, 0x31, 0xff, 0x4b, 0x70,0x03, 0x6b, 0x43, 0x91, 0x2c, 0x9f, 0x3f, 0xd2, 0xf8, 0x24, 0xf6, 0x7d, 0xb3, 0x4e, 0x7f, 0x08,0x0f, 0xa0, 0x3e, 0xf3, 0xce, 0x66, 0xbd, 0x12, 0x49, 0x6d, 0xcb, 0xa1, 0x4c, 0x37, 0xbf, 0xf3,0xb3, 0xbc, 0x8e, 0xac, 0x6b, 0xb9, 0xd9, 0xe6, 0x25, 0xbc, 0xdf, 0x93, 0x6f, 0x9e, 0x7e, 0x07,0x00, 0x00, 0xff, 0xff, 0x0c, 0x9f, 0x10, 0xa8, 0x2e, 0x01, 0x00, 0x00,}
对于meta信息的提取还是很方便?/p>
然?/p>
对于多个文g的生? q样做非常的ȝ, 因ؓq个字段会重复导致编译错?/p>
很多人在论坛里吐? 官方l出的解x法是, 使用protoc一ơ性传入一个package下的所有的proto直接生成一个go
而不是现在的一个proto一个go
以前q个代码需要自己来? 现在官方提供了支? 很是方便
然? Z么不支持遍历?D念? 又要自己动手?/p>
https://github.com/davyxu/tabtoy
开发效率:Windows下可以通过VisualStudioq行开发,其他q_可以使用MonoDevelopQ非常方?/p>
q行效率QJIT的性能优化比较CQ能适应90%性能环境
部v便捷性:可以通过交叉~译生成其他q_的可执行文gQ通过monoq行可执行文?/p>
调试便捷性:VisualStudio和MonoDevelop调试均很方便Q?q可q程调试
上手度:对Cp语a熟悉的几天就可上?/p>
热更斎ͼ可以通过DLL方式q行
WebҎQ可做,代码比较啰嗦
崩溃处理Q可通过try catch捕获错误
|络库编写难度:一般,需注意gc问题
W三方网l库及框架数量:一?/p>
开发效率:?/p>
q行效率Qƈ发上非常有优势,对CPU利用率比较高Q原生运行无虚拟?/p>
部v便捷性:一ơ编译到处运行,无Q何运行库依赖
调试便捷性:实际操作中,单线E挂接调试器可行Q?但变量显CZ正确Q开发期基本采用日志方式q行查错
上手度:语言单,Ҏ少Q?新手1周能贡献代码
热更斎ͼ无法q行热更斎ͼ语言无法~译为DLLQ也不支持DLL加蝲Qlinuxq_?so加蝲忽略不计Q?/p>
WebҎQ非常方便, 代码_
崩溃处理Q崩溃后以命令行方式打印出栈Q程序内可以捕获M崩溃错误ql运?/p>
|络库编写难度:单,比C socket更简?/p>
W三方网l库及框架数量:偏少
开发效率:Z动态语a的开发初ơ写比较快,后期l护和重构会耗费一定的旉在查错上
q行效率Q基于lua jit的运行效率还是能接受?/p>
部v便捷性:方便Q?只有底层修改需要重新编译, 大部分时间只用更新lua文g
调试便捷性:不是很方便,Z日志方式q行查错
上手度:lua语言Ҏ有部分和Cp语a有一定差异,ZActor模型的思想学习Q适应需要耗费一定的旉
热更斎ͼcM于ErlangQ可_到函数的热更新
WebҎQ有一些http支持Q通过C慢慢q行完善
崩溃处理Qlua天生可以捕获错误
|络库编写难度:自带Q无需~写
W三方网l库及框架数量:通过C慢慢完善
开发效率:~译慢,文g多,通用库少
q行效率Qnative速度标杆
部v便捷性:~写各类的make门槛较高
调试便捷性:可通过VisualStudioq行Windowsq_调试
上手度:2~3q经验的熟手仍然会写出崩溃和泄露代码
热更斎ͼ可通过DLLq行
WebҎQ代码啰嗦,W三方库?/p>
崩溃处理QWindows下可使用SEH捕获D异常,其他q_只能通过崩溃后进行coredump分析Q容错非常差
|络库编写难度:Zasio~写较ؓ单,但M看来隑ֺ不低
W三方网l库及框架数量:较多
以下是得?/p>
从发文时的项目对q些语言使用率来_JavaQErlangQC++~写的服务器较多QGolangQJavaScriptQC#是第二梯队,Skynet׃上手不是很容易,所以仅有两位数的团队在使用Q但M表现q是比较?
对于老团队, C++的服务器工具铑֒框架已经相对成熟Q?完全没必要更换新语言Q?只是在对接sdk感觉困难Ӟ可以试Golangq些对web有优势的语言q行混合语言开?
对于新团队,开发效率,上手度和部v效率是优先选择的,C#QGolangQJavaScriptq些新兴语言会让你事半功?
对于大规模无需选服的服务器Q?Skynet的actor模型Ҏ展会比较Ҏ
对于大公司,好项目,上线后需要通过热更新进行bug修补的,C#QC++QErlang会是首?
但ȝ一点, q是Ҏ团队熟悉度来选择语言QN然的使用新语a的风险也是很大的
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
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#打印l果? 3 3
Golang的例?/p>
a := make([]func(), 3 )
for i := 0; i < 3; i++ {
a[i]= func( ){
fmt.Println(i)
}
}
for _, s := range a {
s()
}
Golang打印l果? 3 3
最后是Lua的例?/p>
a = {}
for i = 1, 3 do
table.insert( a, function()
print(i)
end
)
end
for _, v in ipairs(a) do
v()
end
Lua打印l果? 2 3
差异在于, C#和Golang变量捕获到闭包内时, 均用引用方? 卛_最后开始调用用变量时, ׃变量已经l束循环, 所以是最l?/p>
但是Lua捕获方式是值捕? 因此比较Ҏ理解, 是多就是多?/p>
在我们的游戏目中用的golang服务器开发方式如?/p>
1.多线E逻辑
2.同步d. 也就是说, 每个Z个线E?goroutine), ioU程=逻辑U程
q种方式的优?
1. 同步d方式与h的思维方式cd
2. 逻辑处理性能有一定提?/p>
在大规模使用q种模式~写逻辑? 我们发现了这U模式只?个缺? ~写者需要处理多U程关系
但这本n实直接致命? 回想C++时代, 多线E处理时, 调试重现的困䏀?脑补景象太惨不敢直视
在C++时代, 我曾l编写过一套asio的C++服务器框? 采用io多线E? 逻辑单线E? 依赖着C++高性能的优? 让开发便L单且无需兛_U程问题.
那么Cgolang时代, Z么不能试下单U程异步多进E方式来~写逻辑?
与多U程同步dҎ? 我们发现, 两者优~点互补. 那这回C领域选型问题? 对于游戏服务器需要的上手? 开发便? 压力降低(非MMO)q些特点来说, 单线E异步多q程再合适不q了
那么, 我们在用golang~写单线E异步多q程服务器应该注意哪些点?
1. socket处理完全装, 只通过channel抛出到逻辑U程排队处理
2. 数据? rpc及其他io处理, 一律改为异步回调模? 不用同步接?/p>
3. 玩家存盘提交数据可以考虑复制q提交到存盘U程方式, 提高性能.
4. 采用多进E架? 比如讄兌E? 把io压力分散到进E中
5. 逻辑~写? 不允怋用go开U程及channel, 有需要提高性能部分需要单独编?/p>
cellnet在开发时本来考虑使用actor模型来进一步简化多U程逻辑的麻? l历了一D|间的原型开发后, 发现了一些问? 列D如下:
1. golang的强cd不适合actor模型q种l常需要动态生成各cL息的模型, 但skynet(C+lua)/erlang有天生优势
2. actor模型本n不是万能? 不能解决所有需? 特别是游?/p>
3. actor模型理解到应用有一定的隑ֺ. 本nq需要搭建框? 上手复杂
M, 看过一些erlang及skynet的用? 没有应用的很U正且成熟的成功actor模型案例, 从传lsocket服务器框架跨到actor模型会扯到蛋, 因此, 后期cellnet会考虑回归到成熟的socket服务器框? 把架构做到简单上? 高扩展上.
游戏客户端采用Cocos2dx-Lua的纯Lua~写逻辑, 服务器采用Golang作ؓ开发语a
游戏cdcM于COC,因此无需选服. 需要用大服务器架构进行处?
采用Mongodb做持久存? redis做跨服通信数据交换
使用UCloud的云技? 省去了烦人的q维工作
客户端和服务器通讯使用HTTP短连? Zjson的数据封包协?
服务器间大量使用Golang自带的json+rpcq行通信
服务器类型大致分为逻辑服务?战斗服务? 中心服务?
逻辑服务器负责日帔R辑及公共逻辑处理(好友, 公会)
1个逻辑服务器对应一个区, n个区均用Ucloud云Mongodbq行数据存储
战斗服务器是一个集? 集群会返回一个负载最低的服务器返回?
战斗服务器通过cgo技术与客户端C++/lua的战斗逻辑q行逻辑复用, 在此技术上q行
战斗逻辑的校?
客户端登陆前, 在中心服务器q里获得可登陆的逻辑服务器地址, 同时做一个负载均?
短连?
׃操作pȝ的技术趋于稳? 同时, 手游的弱交互型导致的游戏架构于? 因此|络负蝲不再是游戏服务器技术的瓉. 从经验看? 游戏服务器技? 更重要的是还是看数据库的选型及处理方?
虽然Mongodb的性能上不如内存数据库. 但是从存储安全性上要比个h搭徏的内存数据库? 安全
外加上云技术的引用, 性能的瓶颈和q维的技术复杂度q刃而解
Redis用于跨服数据交互那是再好不过的数据中介了, 保证速度和稳定? l对不是造轮子能比拟?
短连接在手游上处理v来比长连接简单一? 无需做断UKq? 服务器的底层也是由Golang的框架库保证质量? 因此负蝲毫无问题. 服务器对内及对外均用jsonq行数据交换, 化了协议处理. 也方便了调试
json rpc的性能损耗对于整个逻辑的处理来说均可以忽略不计
但作为新生代语言的特点就是实? Golangq一q里, 已经为项目提供了E_的服务器和强大的扩展能力, 与客L的Unity3D里的C#一? 都是强大, 极致开发效率代表的优秀开发语a.
Golang到底拿来做啥? 我需要么?
高效(性能,开?的服务器语言. 包括Web, 游戏, App
~写桌面UUI暂不是很适合
我需要把现在的C++, Python, Erlang{服务器ҎGolang?
性能有瓶? 开发效率低, 有钱有时间的? 完全可以
听过太多的h对Golang的评? 大概分ؓq么几类:
此类党员对Q何事物都q求极致?性能. q好Golang是直接生成native code, 否则会被批的体无完肤. 但是׃Golang底层为ƈ发和开发效率而做出的一些系l? cM于GC, 调度器和分配器等, 会在语言层上损失很多性能. 因此C/C++党还是有理由批Golang性能低下
作ؓ电信U元? Erlang的模型和架构当之无愧, OTP扩展性超U强. 完美的Actor模型也让逻辑~写比OO更加直观
CSP与Actor区别仅仅只是在channel的归属范围而已, 但这点细微差别却对两U语a的开发变的E然不?/p>
Golang在ƈ发模型上选择了CSP, 是考虑把架构的设计留给使用? 像C#一样徏立一个类库的世界, 而不是MFC一L框架世界. 让开发更自由
Erlang的Actor也没? 让开发更直观, 让崩溃提前来? 快处理
Rust在发文时已经发布?.0. q让R_兴奋的I梭于各大技术论坛和讨论?/p>
但Rust的理念在我看来有点偏执了, 一定要把各U错误在~译期暴露出? 所以造出了很多不需要的cd和概? q语a都比C语言更符可
大白话说? 有点~程l验的h看到JavaScript完全看得? 但看Rust却像天书
Ҏ同时期的TypeScript, Dart, Swift. Rust是有点那么独辟y径
该党党员l常性的用各U特性对比Golang, q求单特性的优秀.
但其? Golang本n是一门完整哲? 很多语言Ҏ互怹间有兌. 有设计不当的地方, 当然更多的是完整体系. 不求和其他语a?/p>
只追求解决问题的速度
云风看过Golang? 因ؓ该语a本n是强化版的C, 因此颇受云风喜欢. 但在一堆评价后, 云风q是果断选择了C+lua的组合写出的Skynet
虽然不知道原? 但我猜的? 毕竟是对语言本n的可控性还不那么看?/p>
同时, 我们发现Skynet使用的是Actor模型, 也发现大的程序员是有先?
转蝲h? 战魂筑http://www.shnenglu.com/sunicdavy
设计
t入Golang, ׃要尝试设计模?/p>
传统的OO在这里是非法? 试模拟只是一U搞W?/p>
把OO在Golang里换成复?接口
对实现者来? 把各U结构都复合h, 对外暴露Z个或多个接口, 接口好像用者在实现模型上打出的很多z?/p>
别怕全局函数, ?Package)可以控制全局函数使用范围.
没必要什么都用interface对外装, struct也是一U良好的装Ҏ
Golang无? 因此无需cL生图. 没有zq种点对点的依赖, 因此不会在大量类关系到来? 形成J杂不可变化的树形结?/p>
容器
用了很长旉map, 才发现Golang把map内徏aҎ时, 已经L了外|型map的apiҎ? 一切的讉K和获取都是按照语aҎ来做的, 原子?/p>
数组可以理解为底层对? 你^时用的都是切? 不是数组, 切片是指针, 指向数组. 切片是轻量的, 即便值拷贝也是低损耗的
内存
Golang在实际运行中, 你会发现内存可能会疯? 但跑上一D|间后, ׃持稳? q和Golang的内存分? 垃圾回收有一定的关系
C的编E语a的内存管理不会很_暴的直接从OS那边分配很多内存. 而是按需的不断分配成块的内存.
对于非v量应用, Golang本n的内存模型完全可以撑得下? 无需像C++一? 每个工程必做内存池和U程?/p>
Channel
Channel和锁谁轻? 一句话告诉? Channel本n用锁实现? 因此在迫不得已时, q是量减少使用Channel, 但Channel属于语言层支? 适度使用, 可以改善代码可读?/p>
转蝲h? 战魂筑http://www.shnenglu.com/sunicdavy
错误
觉得Golang不停的处理err? 那是因ؓqx在其他语aҎ没处理过错误, 要不然就是根部一ơ性tryq所有的异常, q是一U危险的行ؓ
panic可以被捕? 因此~写服务器时, 可以做到不挂
危险的interface{}
q东西就跟C/C++里的void*一L危险, nil被interface{}包裹后不会等于nil相等, 但print出来实是nil
模板估计可以解决容器内带interface{}的问? 但新东西引入, 估计又会让现在的哲学一些凌?/p>
转蝲h? 战魂筑http://www.shnenglu.com/sunicdavy
语言学习按照官网的教学走, 跑完基本׃?/p>
下蝲一个LiteIDE, 配合Golang的Runtime,基本开环境有?/p>
Golang的类库设计方式和C#/C++都不? 如果有Pythonl验的会感觉毫无q和?/p>
有一万个理由造轮子都请住? cd里有你要的东?/p>
写大工程h? Golang目目录l构l织
Golang语言本n本h没有发现bug, 即便有也早就被大们捉住? 唯一的一个感觉貌似bug? l常是结构体成员首字母小? 但是json又无法序列化出来?/p>
慎用cgo. 官方已经声明未来对cgo不提供完整兼Ҏ? M一门语a在早期都需要对C做出支持, 但后期完善后的不兼容都是常?/a>
转蝲h? 战魂筑http://www.shnenglu.com/sunicdavy
// String returns the time formatted using the format string
// "2006-01-02 15:04:05.999999999 -0700 MST"
func (t Time) String() string {
return t.Format("2006-01-02 15:04:05.999999999 -0700 MST")
}
试?006-01-02 15:04:05写入到自q例子?pre>func nowTime() string {
return time.Now().Format("2006-01-02 15:04:05")
}
l果q回正确. 询问了下, 据说q个日期是golang诞生的日子?咋那么自恋呢?