战魂小筑

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

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

2017年4月20日 #

使用Visual Studio Code调试Golang工程

关键字

  • 最简单的调试攻略
  • 多项目调试, 适用个人开发和项目开发
  • 无需修改系统环境变量

准备VSCode

在官网下载最新版的VSCode:

https://code.visualstudio.com/

安装Golang插件

  • 打开扩展面板

    VSCode->查看->扩展

  • 找到Go插件 在搜索框里输入Go, 找到第二行写有 Rich Go language support for Visual Studio Code的插件, 点击安装

    注意不是排名最高的

  • 重启编辑器

配置启动项

  • 打开调试面板

    VSCode->查看->调试

  • 添加调试目标

    在"没有调试"的下拉框中点击"添加配置.."

  • 添加目标调试配置

    例子:

    {
        "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
            }
        ]
    }

其中: "port", "host"都是go插件自动生成的

"env"为设置环境变量, 设置为你的工程目录就可以(包含bin, src的文件夹)

准备调试插件

此时找到main.go按F5, 会报错提示:

Failded to continue:"Cannot find Delve debugger. Install from https://github.com/derekparker/delve & ensure it is in your "GOPATH/bin" or "PATH"

我们使用go命令行编译调试器

go get github.com/derekparker/delve/cmd/dlv

将dlv调试器放在GOPATH(工程目录)的bin目录下

开始调试

选中要调试的main.go, 点击F5, 既可以开始调试

调试快捷键和Visual Studio系一致

  • F9 切换断点
  • F10 Step over
  • F11 Step in
  • Shift+F11 Step out

注意点

  • 某些结构体成员无法直接显示时, 可以直接选中变量名, 添加到监视, 或者右键点击: "调试:求值"

多项目调试

在launch.json中可以添加多组调试入口, 通过调试面板中选中对应的配置开启不同目标的调试

{
    "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}"是以当前选中文件作为启动点

更建议使用"program"的"${workspaceRoot}", 以包名作为启动点的方式进行配置

参考链接

https://code.visualstudio.com/Docs/editor/debugging

posted @ 2017-04-20 12:52 战魂小筑 阅读(1095) | 评论 (0)编辑 收藏

2017年4月8日 #

相位技术

相位技术大规模出现在魔兽世界WLK版本, 现在应用已经广泛应用在各种MMORPG游戏中. 下面对相位技术的做法进行简单归纳汇总

表现分类

副本相位

早期副本的出现, 避免抢怪问题. 所以, 副本其实本身就是一种相位技术. 只不过实现时, 我们一般会将小队和怪物直接预分配在独立的一个副本实例中(所以副本原文也是实例的意思)

分线相位

相位技术还没有正式命名时, 同一个场景, 玩家进到不同的分线看到的玩家不一样, 也是属于相位的一种. 当然, 如果是组队玩家, 服务器默认会分配所有队伍玩家在同一个线(位面)

真相位

副本相位和分线相位其实都是静态相位, 一旦进入, 中途不会有切换或者混合查看的过程. 真相位可以在一个场景中,动态切换相位, 相位内和相位外所以不同

我们常见的真相位表现为: 相位中的角色+玩家+队伍成员

在护送任务时, 还会在上面所见角色中叠加世界中的所有角色, 也就是说, 你和队伍成员可以看到的角色, 其他人看不到, 其他人也看不到你和你的队伍成员


为了清晰的简单的描述, 我为相位创建如下概念与名词

相位客体

表现为除玩家外的角色(怪物,交互物体与相位可见场景)

私有客体

这是最常见的一种相位内角色

  • 持有变量

    取PhasingID时,为PhasingTargetID

  • 生成规则

    当玩家开启相位后, 在玩家相位内生成的角色为私有客体.

    此时, 将 PhasingTargetID设置为相位生成者的实例ID

  • 删除规则

    如果玩家退出相位, 私有客体会存在一段时间或按照需求删除

公共客体

一般指提前放置在场景中, 世界内不可见, 但是能被同相位玩家可见,且同相位玩家都可以互相可见 比如: 只要接了同一个任务的玩家, 都可以看到的NPC

  • 持有变量

    取PhasingID时,为PublicPhasingID

  • 生成规则

    通过场景编辑器, 放置角色时, 设置其可被观察到的任务ID

    角色被加载后, 将任务ID设置到StaticPhasingID

  • 删除规则

    场景删除, 角色删除

相位主体

包含玩家与同队伍玩家

  • 开启相位后, 可见私有客体+公有客体

  • 队长视为相位主体, 单人时, 自己为队长

  • 队伍其他成员共享队长的私有相位客体

  • 队伍其他成员根据自己的PublicPhasingID匹配目标对象的PublicPhasingID时可互相可见

  • 持有变量

相位开启时, 取PhasingID时, 为角色实例ID

相位关闭时, 取PhasingID时, 为0

PublicPhasingID

可见规则

当两个角色的PhasingID相等时, 主体与私有客体互相可见

当两个角色的PublicPhasingID相等时, 主体与公有客体互相可见

可以通过开关设置, 是否在可见的相位客体基础上, 叠加世界角色(护送任务)

约束

  • 玩家同时只能看见1个相位
posted @ 2017-04-08 14:41 战魂小筑 阅读(979) | 评论 (0)编辑 收藏

2016年12月1日 #

Golang中没有设计构造函数. 取而代之的, 设计Golang的大师希望你用普通函数去实现构造的任务. 
一直只是觉得这只是体现Golang这门新语言的精简设计之道, 直到自己实现编译器后才发现构造函数的设计本身是值得商榷的

我们先看下构造函数的规则

构造函数调用规则

构造参数量: 0表示没有构造函数, 1表示有构造函数0个参数

本类构造父类构造处理方法
00不处理
10调本类ctor
01调父类ctor
11调本类ctor, 本类ctor调父类ctor
21调本类ctor, 本类ctor调父类ctor
12报错, 手动调父类ctor
22报错, 手动调父类ctor

普通函数重载规则

实际只用考虑最典型的一种行为: 实例化子类, 转为父类调用方法, 这个时候

如果方法是override, 调用的是子类

如果方法是virutal或者不指明, 调用的是父类

整个重载过程, 子类绝对不会隐式调用父类的行为

需要构造函数么?

构造函数的优点

  • 本身属于一种特殊的成员函数
  • 编译器帮你自动传导调用父级

构造函数的缺点

  • 隐式的调用规则
  • 虽然属于成员函数, 但是与其他成员函数调用规则完全不同, 需要特殊记忆
  • 带参数的构造函数, 在父类参数多于子类时, 需要引用复杂语法来实现父级构造调用

其实我们对初始化函数的需求只有1条: 自定义

所以, 可以理解Golang不加入构造函数的设计是正确的 
即: 简单, 清晰, 有规律

posted @ 2016-12-01 10:45 战魂小筑 阅读(1466) | 评论 (0)编辑 收藏

2016年11月2日 #

append, map, len不是关键字

他们其实还是类库功能, 都在buildin包里的, 系统默认给你做了个

  1. import(
  2. . "buildin"
  3. )

将buildin的包内容都映射到全局而已, 其实你也可以用自己的包这么做

打印的另一种写法

想跟脚本一样调试打印数据么?

  1. println("hello world")

无需包含任何包, 因为它在buildin包里

iota不是黑科技

这是在buildin包里的定义

  1. // iota is a predeclared identifier representing the untyped integer ordinal
  2. // number of the current const specification in a (usually parenthesized)
  3. // const declaration. It is zero-indexed.
  4. const iota = 0 // Untyped int.

其实go是有泛型概念的

想想map和数组的定义 
只是泛型没有开放给用户用而已(只许XX放火,不许XX点灯)

map是支持多个key的, 而且很方便

还在为多个key转id的复杂算法而头疼么?

  1. type myKey struct{
  2. number int
  3. str string
  4. }
  5. func main(){
  6. t := map[ myKey] int {
  7. myKey{ 2, "world"}: 1,
  8. }
  9. fmt.Println(t[myKey{2, "world"}])
  10. }
  11. 输出: 1

枚举是可以转成string的

默认定义一个枚举

  1. type MyConst int
  2. const (
  3. MyConst_A MyConst = iota
  4. MyConst_B MyConst = iota
  5. )
  6. func main(){
  7. fmt.Println(MyConst_A)
  8. }

输出: 0 
如果我们想自动化输出MyConst_A字符串时 
就需要使用golang的一个工具链:golang.org/x/tools/cmd/stringer 
将其下载, 编译成可执行工具后, 对代码进行生成 
生成的代码会多出一个xx_string.go 
里面就是枚举的String()string 函数

临时转换一个接口并调用的方法

  1. type love struct{
  2. }
  3. func (self*love)foo(){
  4. fmt.Println("love")
  5. }
  6. func main(){
  7. var chaos interface{} = new(love)
  8. chaos.(interface{
  9. foo()
  10. }).foo()
  11. }

Golang的receiver实际上就是this的变种实现

  1. func( self*MyStruct) foo( p int ){
  2. }

写不惯receiver的写法? 如果这样改下呢?

  1. func foo( self *MyStruct, p int ){
  2. }

所以为什么说Golang还是一个C语言嘛

关于内存分配…

  • new 传入Type类型, 返回*Type类型
  • make 可以在分配数组时设置预分配大小, new不可以
  • make 能分配数组,map, 但不能分配结构体和原始类型
  • new 能分配数组, map, 结构体和原始类型等的所有类型
  • &Type等效于new
  • 切片不需要分配内存(make,new), 直接声明就可以了…

Golang的反射无法通过一个类型名, 创建其实例

C#有Assembly概念, 可以在一个Assembly里搜索, 创建实例

Golang是静态类型语言, 如果需要, 只能注册你需要创建的结构体, 然后将注册好的map用于创建

Golang可以替换Python来进行复杂的工具流程处理

如果你需要跨平台的工具流程处理, 对Python不熟悉, 可以使用

  1. go run yourcode.go 参数1 参数2

方式来进行工具处理 
觉得慢, 可以编译成可执行文件

这样做的好处: 如果有些类库本身就是go写的, Python想使用是很麻烦的, 而Golang来写则轻而易举

例子: 通过go的protobuf库, 对一些文件进行处理

Golang可以自动持有方法的接收者实例

  1. type myType struct{
  2. }
  3. func (self*myType) foo( p int){
  4. fmt.Println("hello", p )
  5. }
  6. func main(){
  7. var callback func( int )
  8. ins := new(myType)
  9. callback = ins.foo
  10. callback( 100 )
  11. }

做过lua的C++代码绑定的童鞋都清楚: lua只支持外部静态或者全局函数调用 
如果要进行C++类成员函数调用时, 要自己处理this和成员函数 
这种技巧因为早起编译器的虚表不同平台实现细节不统一需要专门处理 
后面跨平台虚表统一后, 类成员函数的调用写法也是很恶心复杂的 
但是Golang的小白式用法, 直接吊打C++, 甚至C#复杂的delegate

LiteIDE篇: 多开秘籍

  • 找到 菜单->查看->选项->通用->存储->存储设置到本地ini文件

  • 关闭LiteIDE

  • 复制LiteIDE整个目录, 命名文件夹为你的工程名

  • 每个工程所在的LiteIDE的配置将是独立的, 不会互相干扰

LiteIDE篇: 测试程序也是可以调试的

别以为程序一定要是main开始的才可以调试

Golang的测试程序虽然都是一个个Test开头的函数,但执行go test时, 还是有main入口

在LiteIDE中, 可以在 调试->开始调试测试程序里进行测试程序调试

LiteIDE篇: 在Windows上可以输出linux可执行文件

go的工具链默认支持交叉编译 
在LiteIDE中, 可以通过切换输出平台, 输出不同平台的可执行文件

posted @ 2016-11-02 11:09 战魂小筑 阅读(2665) | 评论 (1)编辑 收藏

2016年9月3日 #

Google官方为Golang的调试例子默认使用了gdb

然而, 使用gdb调试go程序会遇到goroutine的各类问题, 因为gdb不懂go

因此, 这里使用delve黑科技来进行Golang的程序调试

纯命令行调试方法在网上很容易搜索到, 本文主要以LiteIDE来进行程序调试

关闭编译器优化

正常go build/install出的go程序是完全优化过的, 强行使用调试器挂接调试时, 某些local变量/lamda表达式捕获的变量会直接进入寄存器, 无法使用调试器查看

删掉所有的pkg, 为build或install参数加入关闭编译器优化的参数 -gcflags "-N -l"

例如:

  1. go install -gcflags "-N -l" svc\gamesvc

delve调试器安装方法

LiteIDE自带了gdb, 但是没有delve调试器, 需要自行安装, 命令如下

  1. go get github.com/derekparker/delve/cmd/dlv

delve调试器会被放到你的GOPATH/bin下

LiteIDE中的delve调试器配置

选择调试器

在LiteIDE菜单中选择 调试->debugger/delve

delve环境变量设置

这个时候, LiteIDE依然找不到delve, 因为它不在环境变量PATH中, 这里无需修改环境变量, 只需要LiteIDE的环境配置

在LiteIDE菜单中选择 查看->编辑当前环境, 在弹出的文档中修改

  1. PATH=c:\mingw32\bin;%GOROOT%\bin;%PATH%;c:\your\path\to\delve

去掉PATH前的注释#, 在%PATH%添加分号, 然后和你到delve调试器的路径

开始调试

选择你的工程, 点击F5, 进入调试模式

调试器显示变量值

LiteIDE使用delve调试时, 无法在 变量 监视等窗口中正确捕捉delve调试返回数据(因为格式太复杂了…)

没关系, 我们使用命令行配合显示即可

LiteIDE控制台或调试输出窗口在delve调试时, 实际上是一个标准命令行 
命令如下

  • p 变量名可以查看变量值

  • locals查看局部变量

  • ls可查看当前文件

  • stack查看栈

  • help可以查看各种帮助

调试外部程序

如果你的程序是外部程序, 或者使用go install安装到GOPATH/bin目录的程序, 那么使用delve调试器启动程序时, 可能会碰到启动路径错误的问题

使用LiteIDE菜单 调试->调试其他应用程序… 填入你要调试程序的路径以及工作目录, 可以解决这个问题

posted @ 2016-09-03 18:12 战魂小筑 阅读(2036) | 评论 (0)编辑 收藏

2016年8月12日 #

测试用例

我们对Golang的结构体变量赋值, 以及单参数函数调用进行反射和native操作的测试

 

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)

测试评测

  • 在结构体变量赋值测试用例中, 我们发现TestReflectFindFieldAndAssign赋值格外的耗时. 分析性能点在FieldByName这个函数上, 我们查了下底层如何实现的:

// 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来遍历获取数据, 但冷静下来分析. 这样做无可厚非.
试想如果reflect包在我们使用ValueOf时使用map缓冲好一个结构体所有字段的访问数据后, 肯定访问指定字段速度会很快
但是, 以空间换速度的需求其实最多满足了1%的需求.
同样的例子是图形API里访问Shader变量的方法, 总是默认使用字符串获取, 速度很慢. 当你想快速访问时, 请提前按需缓存字段
那么, Golang使用的也是这样的思路. 虽然暴力了一点, 但是能够让程序跑对, 性能优化的东西放在之后来做, 缓冲下就可以解决

  • 在调用测试用例中, 毫无悬念的, 调用速度很慢
    因此, 我们在平时使用反射时, 尽量偏向于反射变量缓冲存在下的变量赋值或者获取
    而调用的需求尽量减少, 如果有goroutine存在的情况下, 则不必太多担心.
posted @ 2016-08-12 15:26 战魂小筑 阅读(1800) | 评论 (0)编辑 收藏

2016年8月1日 #

这是一个Unity3D元老级bug, 表现就是:  角色在屏幕边缘放特效,  离开屏幕持续一段时间后再回到屏幕后, 发现特效重新播放. 显然这是错误的效果

解决方法:

在ParticleSystem组件上勾选SubEmitter, 不要问为什么, 做就好

相关官方链接

http://answers.unity3d.com/questions/218369/shuriken-particle-system-not-rendering-particles-w.html

 

天煞的, 4.X程序无法访问粒子系统的参数, 所以只能辛苦美术兄弟们了

posted @ 2016-08-01 15:23 战魂小筑 阅读(3395) | 评论 (0)编辑 收藏

2016年7月6日 #

热更新的内容可以是美术资源, 可以是代码, 但相对来说, 美术资源的更新不会受到约束, 代码实际上是重灾区, 本文介绍的主要是代码热更新

热更新对于开发者来说是一件麻烦事, 特别对于看重效率,便捷性和结构的程序员来说, 热更新就是运营人员的不懂技术的表现
然而, 对于上线才是刚刚开始的网络游戏, 特别是手游来说. 热更新是极为重要的基础功能

为什么要热更新

客户端

适应上线需求

对于手游客户端来说, 受到苹果审核的约束, 一次审核提交需要10~20天不等的等待时间, 而这段时间, 开发进度依然会推进很多.

一旦手游上线, 第一个版本在玩家疯狂行为下, 出点问题是必然的, 所以”上线更”就成了家常便饭. 如果你要说, 必须大包, 无法热更, 那么10~20

多天后, 游戏估计就没啥人了, 更别说渠道, 发行投入巨大资金进行推广之下让玩家迎来的一堆bug的版本以及所谓程序员的傲慢和清高.

热调试, 热开发, 热发布

除了线上问题之外, 由于Unity3D为了适应64位应用需求, 将C#编译出的IL代码利用il2cpp第三方库编译成为c++. 效率提升了倒是好,

但工程编译和发布时间变得相当感人, 没个1~2个小时完全搞不定. 即便加装ssd, 为了修改一个bug, 也不知道要等多少根烟的时间…

只要核心功能不变化的情况下, 完全可以让热更新成为开发期间的好工具, lua代码修改后, 马上可以在手机上看效果, 没有编译, 发布的时间损耗, 其实反而提升了开发效率

服务器

对于服务器来说, 常见游戏类型的玩家一般在半夜的在线人数会急速下降. 但是对于比较热门的MMO, 以沟通为基础的游戏, 半夜也会有很多人在线

因此传统的停服更新对于玩家的热情秒杀很大的. 想想看,屁股先锋公测停15天各位是什么感受? 所以为了玩家体验, 同时保证服务器稳定的前提下

修复一些轻微bug, 用热更新再合适不过了. 所以老服务器程序员, 千万不能以服务器稳定为借口而忽略了玩家体验.

技术是用来解决问题的, 不是用来装X的

怎么热更新

以下是Unity3D的几种热更新方式

基于C#, 使用动态加载Assembly反射更新代码

这种方式在安卓上完全可行, 对现有架构无需大的修改, 一样使用C#和Unity3D的方式进行开发

但在iOS上受到限制, 因此对于全平台首发的游戏, 或者双平台都要上的游戏, 已经慢慢的不使用这种方法进行热更新了

基于Lua, 将Lua代码视为资源, 动态加载并运行

云风团队早期研究出的UniLua是基于C#编写的Lua虚拟机来运行, 而且只支持字节码解释, 因此无法做动态功能, 效率奇低

后期, ulua的出现, 彻底将Lua作为比较正统的更新方式存在. ulua基于Tolua库进行封装, 添加了一些便捷封装, 代码打包和基本的框架

ToLua本身是一个基于C版Lua上扩充的库, 以静态链接库方式与Unity3D代码链接. 因此, 可以说ToLua是跑在C层上, 速度不亚于C++和Lua的组合

基于Lua的代码更新方式, 无论跨任何平台都可以以同一套代码和工作流进行, 因此避免很多麻烦, 成为现在主流的开发方式

游戏逻辑全都用Lua写么?

做过网页和手机App的童鞋都发现, js, 一个bug超多, 设计奇怪的语言居然成为主流界面开发语言, 为啥?

动态特性适合制作ui

另外一个反例就是: 使用C++开发界面, 例如Qt, MFC之类, 虽然设计严谨, 但是最终挡不住各种奇葩的修改需求

因此, 界面非常推荐使用动态语言来开发, 游戏界就是用Lua

而游戏核心, 根据各自游戏类型来定, 总的一点, 效率瓶颈点, Update之类的, 尽量使用C#或者C++来实现

写在最后

当前中国大环境下的玩家和各种氪金理由与纯的不能再纯的游戏人的基本愿望是冲突的

然而国外游戏的各种设计和机制, 暴雪战网更新不及时, 版本不对没提示, 这些基本错误在中国的网游都不会出现的

技术上无法赶英超美的我们, 在体验上已经输出了我们的价值观, 老外们都在学

对于程序员来说, 只是多贴近玩家, 多了解外面的世界而已

posted @ 2016-07-06 11:03 战魂小筑 阅读(4157) | 评论 (6)编辑 收藏

2016年6月16日 #

苹果要求在2016年6月1日后新的app必须支持ipv6网络, 技术发展靠苹果果然没错, 但开发者还是要开始忙起来了
这里介绍下Unity3D的适配的一些经验

基本注意点

  • ios ipv6适配无需修改服务器, 也就是说, 如果你的服务器依然是ipv4的也是可以使用的
  • 苹果的适配方案是将ipv4的地址转换为ipv6, 到了路由层再转回去继续利用ipv4网络传输

测试网络环境搭建

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

网上有很多翻译了苹果官方的搭建ipv6测试网络环境的文章, 例如:
http://www.cocoachina.com/ios/20160525/16431.html
注意以下几点

  • 无需路由器支持ipv6, 但猫(modem)必须要支持ipv6. 因为现在大多数都是光猫
    以下截图是光猫管理端
    3440e3f9-12de-435b-85ab-a7a3be8b384b[6]
    光猫里的ipv6支持默认是关闭的, 所以需要手动打开, 按默认值配置即可

  • 请确认mac os系统必须是osx 10.11以后的版本才可以打开NAT64

  • 正确连接mac的ios设备应是如下截图示意
    91f54476-4b5d-4585-a364-0da2139774c1[6]

  • 默认连接上wifi时看连接信息时, 一般只会有红色DNS地址或者根本不显示
  • 只有在第一次访问网络, 例如打开浏览器进入任意网站时, 才会显示上面的几条信息
  • 如果只有DNS没有IP地址和子网掩码, 一般是光猫没有打开ipv6的DHCP, 没有分配IP
  • 还有一种测试ipv6 DHCP是否正常工作的方法: 关闭NAT64时可以上网, 但打开NAT64无法上网

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

Unity3D的Socket适配

WWW类本身已经支持了IPV6, 无需处理, 这里讲解使用C#原生Socket的处理

  • 测试用的设备的iOS版本必须是9.3以上的
  • Socket构造时, AddressFamily 设置为InterNetworkV6时只支持ipv6网络, 传入InterNetwork时只支持ipv4网络
  • 4.7.2和5.4.3的当前版本在mono层并未支持ipv6代码适配的核心函数getaddrinfo, 因此需要通过oc层做转换, 以下是代码
    这段代码将getaddrinfo的地址转换成一个完整字符串, 格式是:
    ipv4|ipv4地址|ipv6|ipv6地址|

P.S. copyStr这种用法参考了http://www.codeinsect.net/blog/2016/05/26/unity-ipv6-socket-%E6%94%AF%E6%8C%81%EF%BC%8C%E5%B7%B2%E6%B5%8B%E8%AF%95%E9%80%9A%E8%BF%87/
会造成内存泄露, 如果有更好的方法欢迎反馈

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

iosaddrinfo.mm

   1:  #include <sys/socket.h>
   2:  #include <netdb.h>
   3:  #include <arpa/inet.h>
   4:  #include <err.h>
   5:  #define OUTSTR_SIZE 4096
   6:  extern "C"
   7:  {
   8:      const char* copyStr( const char* str )
   9:      {
  10:          char* s = (char*)malloc(strlen(str) + 1);
  11:          strcpy(s, str);
  12:          return s;
  13:      }
  14:      const char* IOSGetAddressInfo(const char *host )
  15:      {
  16:          if( NULL == host )
  17:              return copyStr("ERROR_HOSTNULL");
  18:          char outstr[OUTSTR_SIZE];
  19:          struct addrinfo hints, *res, *res0;
  20:          memset(&hints, 0, sizeof(hints));
  21:          hints.ai_family = PF_UNSPEC;
  22:          hints.ai_socktype = SOCK_STREAM;
  23:          hints.ai_flags = AI_DEFAULT;
  24:          printf("getaddrinfo: %s\n", host);
  25:          int error = getaddrinfo(host, "http", &hints, &res0);
  26:          if (error != 0 )
  27:          {
  28:              printf("getaddrinfo: %s\n", gai_strerror(error));
  29:              return copyStr("ERROR_GETADDR");
  30:          }
  31:          memset( outstr, 0, sizeof(char)*OUTSTR_SIZE );
  32:          struct sockaddr_in6* addr6;
  33:          struct sockaddr_in* addr;
  34:          const char* solvedaddr;
  35:          char ipbuf[32];
  36:          for (res = res0; res; res = res->ai_next)
  37:          {
  38:              if (res->ai_family == AF_INET6)
  39:              {
  40:                  addr6 =( struct sockaddr_in6*)res->ai_addr;
  41:                  solvedaddr = inet_ntop(AF_INET6, &addr6->sin6_addr, ipbuf, sizeof(ipbuf));
  42:                  strcat ( outstr, "ipv6|");
  43:                  strcat ( outstr, solvedaddr);
  44:              }
  45:              else
  46:              {
  47:                  addr =( struct sockaddr_in*)res->ai_addr;
  48:                  solvedaddr = inet_ntop(AF_INET, &addr->sin_addr, ipbuf, sizeof(ipbuf));
  49:                  strcat ( outstr, "ipv4|");
  50:                  strcat ( outstr, solvedaddr);
  51:              }
  52:              strcat ( outstr, "|");
  53:          }
  54:          return copyStr(outstr);
  55:      }
  56:  }
转载请注明:http://www.cppblog.com/sunicdavy战魂小筑

iosaddrinfo.h

   1:  #pragma once
   2:  extern "C"{
   3:      const char* IOSGetAddressInfo(const char *host );
   4:  }
  • C#层的处理假设多个地址中都是统一的地址类型,要么全是v4要么全是v6
    返回给定的host内多个IP地址, 可以供处理复杂的北网通,南电信问题

   1:  using System;
   2:  using System.Net;
   3:  using System.Net.Sockets;
   4:  using System.Runtime.InteropServices;
   5:  using UnityEngine;
   6:  using System.Collections;
   7:  using System.Collections.Generic;
   8:  public class IOSIPV6
   9:  {
  10:      [DllImport("__Internal")]
  11:      private static extern string IOSGetAddressInfo(string host );  
  12:      public static IPAddress[] ResolveIOSAddress(string host, out AddressFamily af)
  13:      {
  14:          af = AddressFamily.InterNetwork;
  15:          var outstr = IOSGetAddressInfo(host);
  16:          Debug.Log("IOSGetAddressInfo: " + outstr);
  17:          if (outstr.StartsWith ("ERROR")) 
  18:          {
  19:              return null;
  20:          }
  21:          var addressliststr = outstr.Split('|');
  22:          var addrlist = new List<IPAddress>();
  23:          foreach (string s in addressliststr)
  24:          {
  25:              if (String.IsNullOrEmpty(s.Trim()))
  26:                  continue;
  27:              switch( s )
  28:              {
  29:                  case "ipv6":
  30:                      {                        
  31:                          af = AddressFamily.InterNetworkV6;
  32:                      }
  33:                      break;
  34:                  case "ipv4":
  35:                      {
  36:                          af = AddressFamily.InterNetwork;
  37:                      }
  38:                      break;
  39:                  default:
  40:                      {
  41:                          addrlist.Add(IPAddress.Parse(s));
  42:                      }
  43:                      break;
  44:              }
  45:          }
  46:          return addrlist.ToArray();
  47:      }
  48:  }
转载请注明:http://www.cppblog.com/sunicdavy战魂小筑

参考链接

官方文档
https://developer.apple.com/library/ios/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/UnderstandingandPreparingfortheIPv6Transition/UnderstandingandPreparingfortheIPv6Transition.html#//apple_ref/doc/uid/TP40010220-CH213-SW1

某人的解决方案
http://www.codeinsect.net/blog/2016/05/26/unity-ipv6-socket-%E6%94%AF%E6%8C%81%EF%BC%8C%E5%B7%B2%E6%B5%8B%E8%AF%95%E9%80%9A%E8%BF%87/
注意, 此方案中的方法可用, 但是地址并不能解决南北互通的问题

posted @ 2016-06-16 14:18 战魂小筑 阅读(3009) | 评论 (0)编辑 收藏

2016年5月31日 #

最近整合ulua到项目里进行热更新, protoc-gen-lua这古老的东西重新让我继续发博客, 因为坑

生成好的协议报错找不到protobuf

在每个protoc-gen-lua生成的lua文件里, 都有一行

local protobuf = require "protobuf"

本身按照官方出的没问题, 但是在ulua的目录里, 总是报protobuf找不到的错误. 前后对比了下我生成的lua和ulua官方生成的代码里

发现居然他修改了地址改为了

local protobuf = require "protobuf/protobuf"

好吧, 只有修改生成器代码protoc-gen-lua\plugin\protoc-gen-lua中第412行改为

lua('local protobuf = require "protobuf/protobuf"\n')
 

生成消息无法找到Descriptor反射查信息

在LuaFramework\ToLua\Lua\protobuf\protobuf.lua的939行添加
message_meta._member.Descriptor = descriptor
在消息里就可以通过msg.Descriptor获得此消息的反射信息

由于proto文件定义的内容过多导致的lua local超过限制的警告

image

这个错误真是让我哭笑不得, protoc-gen-lua的可用性再一次被怀疑

我们的协议好歹分成了接近100个, 每个里面消息和数据是混合的, 更别说有些童鞋喜欢把一个项目的协议全写在一个文件里, 那生成的local数量简直是酸爽

 

 

 

后记

搜索protoc-gen-lua时, 无意间又搜到3年前自己的博文http://www.cppblog.com/sunicdavy/archive/2013/04/24/199693.html

记得那个时候准备在服务器使用lua, 还好没这么干, 转了go, 否则后果不堪设想

lua上使用pb其实并不容易, 云风的pbc写的不错, 但怕有坑, sproto直接不兼容现有项目, 风险大于易用性所以果断弃用

因此, 看来有必要自己写一个支持良好的lua pb库

posted @ 2016-05-31 11:26 战魂小筑 阅读(1504) | 评论 (0)编辑 收藏

仅列出标题  下一页