最近公司要求针对上传下载提速,打算采用kcp协议进行底层改造,于是对kcp进行了一些调研。
KCP是一个快速可靠协议,能以比 TCP浪费10%-20%的带宽的代价,换取平均延迟降低 30%-40%,且最大延迟降低三倍的传输效果。纯算法实现,并不负责底层协议(如UDP)的收发,需要使用者自己定义下层数据包的发送方式,以 callback的方式提供给 KCP。 连时钟都需要外部传递进来,内部不会有任何一次系统调用。
以上片段来自skywind3000/kcp 对kcp的介绍。而这里对kcp也不再展开,kcp的具体介绍还是大家自行搜索理解比较好。
而目前比较成熟完备的解决方案是kcptun,目前在Github上有9.8K star。 kcptun在kcp协议的基础上,做了很多优化,包括向前就错(FEC),差异化服务或(DSCP),加解密,压缩。 接下来,我将会以Android开发者的角度讲述kcptun移植到Android端的过程,默认读者已经配置好Android开发环境,因此不再赘述。
第一步 Go环境准备
kcptun本身是个Go项目,而Go项目本身是支持移植到Android或iOS客户端作为第三方库使用的。像我一样的Go小白可以先看一下这篇文章:使用 Go 进行 iOS 和 Android 编程 已翻译 100%
1.Go语言环境安装
请参考:菜鸟教程,记得将go配置到环境变量中方便使用。
2.GoMobile工具安装
GoMobile工具用于编译和运行 Android 和 iOS 的应用。 注意⚠️ 该工具下载可能需要科学上网,包括编译kcptun过程一些依赖库也是需要科学上网的。如果没有梯子建议使用国内镜像。请参考:国内的go get问题的解决
第二步 Go项目编译
在真正编译kcptun之前,可以使用命令行先编译一下最简单的 hello
项目来确认环境已经全部安装好。 笔者go的安装目录为 /usr/local/go
,在安装好gomobile之后,在/usr/local/go/src/golang.org/x/mobile/example/
目录下会有几个例子工程,现在对example下的bind项目开始编译。进入到/usr/local/go/src/golang.org/x/mobile/example/bind/hello
目录下编译hello.go
文件。 使用gomobile bind -target=android
即可,编译成功会在hello.go所在目录生成两个文件。这两个文件我们就可以导入到Android工程中使用
下面开始真正编译kcptun。
我们客户端所需要的只是kcptun的client部分。现在kcptun,将kcptun下的client文件夹挪到/usr/local/go/src/golang.org/x/mobile/example/
目录下,我这里将其重命名为了kcp-client。在开始编译之前我们需要对go源码进行一点小修改才能顺利进行。
修改kcp-client下的三个文件,将代码中的package main
改成别的包名,我这里是改成了`package kcpclient,如图:
这是因为gomobile编译的go文件不允许使用package main
。这三个文件修改完成后还需对main.go
这最后一点修改:
- 将main方法改成名成:Run,main方法是启动kcp client的唯一入口,将其改名成大写字母开头的方法才会暴露出来给Android端使用,否则是无法在Android端启动kcp client的。
- 在main.go预定义代码中加入一个配置路径属性CONFIGPATH,修改Run方法中代码片段,将其改为如下:
if c.String("c") != "" {
err := parseJSONConfig(&config, c.String("c"))
checkError(err)
}else if CONFIGPATH != "" {
err := parseJSONConfig(&config, CONFIGPATH)
checkError(err)
}
复制代码
这里加入了CONFIGPATH属性,并且暴露给客户端,可以在启动kcp client前通过setCONFIGPATH
方法,传入配置的json文件路径,对kcp client进行自定义参数配置。具体完整的main.go见Github。
上面都准备好后开始真正编译kcp client。 进入到kcp-client 所在目录,执行gomobile bind -target=android
命令,如果一切顺利会在kcp-client 目录下生成两个编译产物,如下图,kcpclient.aar以及kcpclient-sources.jar。
ps.如果编译过程中报错缺少库,99%都是因为墙的原因,自行翻墙或者使用国内镜像下载。
移植Android端使用
加入kcp依赖
将kcpclient.aar和kcpclient-sources.jar一直到安卓工程中的libs目录下,修改模块下的build.gradle文件以保证kcpclient.aar和kcpclient-sources.jar两个库能真正引用。
android {
...
repositories {
flatDir {
dirs 'libs', '../libs'
}
}
}
dependencies {
implementation fileTree(include: '*.jar', dir: 'libs')
implementation(name: 'kcpclient', ext: 'aar')
}
复制代码
启动kcp client
启动kcp client是个网络操作,所以要放到子线程进行操作。最简单的做法是如下:
new Thread() {
@Override
public void run() {
super.run();
//这里设置自己的kcp client配置,也可不调用,使用默认配置
Kcpclient.setCONFIGPATH("xxxx");
Kcpclient.run();
}
}.start();
复制代码
设置应用层代理
从上面的kcptun原理可以看出,应用层数据先是使用TCP协议封装,经过kcp client则转换成了kcp协议。这里面的转换是通过本地代理完成的。这也是kcptun的优势之一,无需改动原有的应用层实现,只需通过本地代理即可实现tcp->kcp的转换。这里以OkhttpClient为例,设置socks代理。
final String hostName = "127.0.0.1";
//kcp client config中设置的localaddr
final int port = 12948;
OkHttpClient.Builder builder = new OkHttpClient.Builder();
Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(hostName, port));
builder.proxy(proxy);
mOkHttpClient = builder.build();
复制代码
通过该方式创建OkHttpClient之后,请求会使用kcp协议进行请求了。
测试
为了测试kcptun移植后的可用性,需要先搭建一个kcptun server。kcptun server可以直接下载Github 上面的release版,根据对应的系统下载对应的版本。 我这里使用的是mac,下载了kcptun-darwin-amd64。 下载完成后解压,直接将server_darwin_amd64拖动命令后即可启动kcp服务器。如果需要自定义配置,使用命令server_darwin_amd64 -c 配置路径
即可。
其次,不论kcp client是自定义配置还是默认配置,kcp client的remoteaddr需要改成你电脑本机的IP,方便测试。
启动服务器后,还需要给服务器设置socks 代理,保证请求能通。这里以Charles为例。
经过以上准备,kcptun就可以正常使用了。以前的请求方式不用做修改,只需保证三点即可:
- 启动kcp client&server成功,并且保证两端配置对应,有问题请参考默认配置以及kcptun github的参数说明,记得kcp client的remoteaddr需要修改成你server端所在的ip。
- 设置本地代理
- 电脑需要设置socks代理,保证测试成功。
通过kcptun代理,每次请求都会有如下日志显示,并且在chales可以看到请求的执行情况。
总结
在经过上传下载测试发现,在网络良好的条件下,使用kcp和不使用kcp,速度相差无几,但是在网络不稳定时,通过kcp协议请求比不使用kcp会有较大的速度提升。但是使用kcp协议耗费流量增加非常明显,流量增加可能会比不使用kcp增加30%~50%,这也是kcp协议本身决定的,这个无法避免,所以是否使用kcp这本身也是个平衡选择,想要更快且适应不稳定的网络环境,kcp就是个比较好的选择,例如游戏,直播。 说了这么多,对于kcp我自己的理解也十分有限,所以如有不足之处还请谅解指点。