合并 Debian 补丁的 OpenBSD netcat Linux 源码

目录 应用软件, 开源

前几天在我的 CentOS 4.3 古董服务器上想使用 ProxyCommand 给 ssh 配置 socks 代理,ssh -o "ProxyCommand nc -X 5 -x 127.0.0.1:1080 %h %p" 选项在我的 OSX/Ubuntu 上挺好用的,但是在 CentOS 4.3 却发现 " invalid option -- x",没有这个参数。

本来我以为是 netcat 没有更新到最新,特地去下载了 GNU netcat 最新的源码包,结果源码编译后还是没有 “-x” 这个参数。后来仔细看 man page,看起来根本就不是一个版本。调研了下,才发现 netcat 居然有好多的版本:

你们城里人可真会玩儿啊!

因为 OSX/Ubuntu 都是用的 OpenBSD netcat(移植或修改版),所以 -x/X 参数是存在的,能够实现代理功能;但是 CentOS 4 因为版本太老,用的还是 GNU netcat。本来找个移植后的源码包,直接编译安装就好了呗。但可是,我 Google 了半天 (打脸,谁让你还用 CentOS 4),还是没找到能直接编译的 OpenBSD netcat Linux 源码包,最后还是在 Arch 的某个网站上找到使用 Debian 源码进行 Patch 然后再编译的脚本,才搞明白怎么能在 Linux 编出来 OpenBSD netcat 。

可能是出于易维护的考虑吧,Debian 把源码分成了两个包,一个是原始的 OpenBSD netcat,一个是 Debian 的 N 个 Patch 源码。编译时要先把 Patch 打到 OpenBSD 源码上,然后再编译。可这样的过程不是维护者很难理解,为什么不多发布个打完 Patch 的源码呢?而且这种补丁包形式也没个官方网站介绍下,真的好难懂。

为了避免其它古董 Linux 发行版用户再有我这样的苦恼,我把 patch 后的代码上传到了 Github: https://github.com/solrex/netcat ,有需要的朋友可以自取。

手机上的 AI - 在 Android/iOS 上运行 Caffe 深度学习框架

目录 开源

目前在云端基于各种深度学习框架的 AI 服务已经非常成熟,但最近的一些案例展示了在移动设备上直接运行深度神经网络模型能够带来很大的处理速度优势。比如 Facebook 在官方博客上发布的可在移动设备上进行实时视频风格转换的应用案例 “Delivering real-time AI in the palm of your hand”。其中提到 Caffe2go 框架加上优化后的网络模型,可以在 iPhone6S 上支持 20FPS 的视频风格转换。Google Tensorflow 也提供了 iOS 和 Android 的 example

Caffe 是一个知名的开源深度学习框架,在图像处理上有着非常广泛的应用。Caffe 本身基于 C++ 实现,有着非常简洁的代码结构,所以也有着很好的可移植性。早年也已经有了几个 github 项目实现了 Caffe 到 iOS/Android 平台的移植。但从我的角度来看,这些项目的编译依赖和编译过程都过于复杂,代码也不再更新,而且最终产出的产品包过大。caffe-compact 最接近我的思路,但是在两年前未完工就已经不更新了。

从我个人在 APP 产品上的经验来看,移植深度学习框架到 APP 中,不仅仅是能不能跑,跑不跑得快,还有个很重要的因素是包大小问题。因为一般用深度学习模型只是实现一个产品功能,不是整个产品。一个产品功能如果对 APP 包大小影响太大,很多 APP 产品都无法集成进去。我希望依赖库能尽量地精简,这样打包进 APP 的内容能尽量地少。所以我在春节期间在 github 上启动了一个 Caffe-Mobile 项目,将 Caffe 移植到 Android/iOS 上,并实现了以下目标:

NO_BACKWARD:手机的电量和计算能力都不允许进行模型训练,所以不如干脆移除所有的后向传播依赖代码,这样生成的库会更小,编译也更快。

最小的依赖。原始的 Caffe 依赖很多第三方库:protobuf, cblas, cuda, cudnn, gflags, glog, lmdb, leveldb, boost, opencv 等。但事实上很多依赖都是没必要的:cuda/cudnn 仅支持 GPU, gflags 仅为了支持命令行工具,lmdb/leveldb 是为了在训练时更高效地读写数据,opencv 是为了处理输入图片,很多 boost 库都可以用 c++0x 库来替换。经过精简和修改部分代码,Caffe-Mobile 的第三方库依赖缩减到两个:protobuf 和 cblas。其中在 iOS 平台上,cblas 用 Accelerate Framework 中的 vecLib 实现;在 Android 平台上, cblas 用交叉编译的 OpenBLAS 实现。

相同的代码基,相同的编译方式。两个平台都采取先用 cmake 编译 Caffe 库(.a or .so),然后再用对应平台的 IDE 集成到 app 中。编译脚本使用同一个 CMakeList.txt,无需将库的编译也放到复杂的 IDE 环境中去完成。

可随 Caffe 代码更新。为了保证开发者能追随最新 Caffe 代码更新,我在修改代码时使用了预编译宏进行分支控制。这样进行 diff/patch 时,如果 Caffe 源码改动较大,merge 时开发者可以清楚地看到哪些地方被修改,是如何改的,更方便 merge 最新更新。

除了 Caffe 库外,在 Caffe-Mobile 项目中还提供了 Android/iOS 两个平台上的最简单的 APP 实现示例 CaffeSimple,展示了在手机上使用 Caffe example 里的 MNIST 示例(深度学习领域的 Hello World)训练出来的 LeNet 模型预测一个手写字符 “8” 图片的过程和结果。 Caffe-Mobile 项目的地址在:https://github.com/solrex/caffe-mobile 欢迎体验,感兴趣的同学们也可以帮忙 Star 下 :)

Android HTTPUrlConnection EOFException 历史 BUG

目录 Linux, 开源, 无线和移动

这是一个影响 Android 4.1-4.3 版本的 HTTPUrlConnection 库 BUG,但只会在特定条件下触发。

我们有一个 Android App,通过多个并发 POST 连接上传数据到服务器,没有加入单个请求重试机制。在某些 Android 机型上发现一个诡异的 bug,在使用中频繁出现上传失败的情况,但是在其它机型上并不能复现。

经过较长时间的排查,我们找到了上传失败出现的规律,并认为它跟 HTTP Keepalive 持久化连接机制有关。具体的规律是:当 App 上传一轮数据后,等待超过服务端 Nginx keepalive_timeout 时间后,再次尝试上传数据,就会出现上传失败,抛出 EOFException 异常。

更准确的特征可以通过连上手机的 adb shell 观察 netstat:当 App 上传一轮数据后,可以观察到有 N 个到服务器的连接处于 ESTABLISHED 状态;当等待超过服务端 Nginx keepalive_timeout 时间后,可以观察到这 N 个到服务器的连接处于 CLOSE_WAIT 状态;当上传失败后,发现部分 CLOSE_WAIT 状态的连接消失。

Java 的 HTTP Keepalive 机制,一直是由底层实现的,理论上来讲,不需要应用层关心。但从上面的 BUG 来看,对于 stale connection 的复用,在部分 Android 机型上是有问题的。为此我稍微研究了一下 Android 的源代码,的确发现了一些问题。

在 2011年12月15日,Android 开发者提交了这样一个 Commit,Commit Message 这样写到:

Change the way we cope with stale pooled connections.

Previously we'd attempt to detect if a connection was stale by
probing it. This was expensive (it relied on catching a 1-millisecond
read timing out with a SocketTimeoutException), and racy. If the
recycled connection was stale, the application would have to catch
the failure and retry.

The new approach is to try the stale connection directly and to recover
if that connection fails. This is simpler and avoids the isStale
heuristics.

This fixes some flakiness in URLConnectionTest tests like
testServerShutdownOutput and testServerClosesOutput.

Bug: http://b/2974888
Change-Id: I1f1711c0a6855f99e6ff9c348790740117c7ffb9

简单来说,这次 commit 做了一件事:在修改前,是在 TCP 连接池获取连接时,做 connection isStale 的探测。Android 开发者认为这样会在获取每个 connection 时都有 1ms 的 overhead,所以改成了在应用层发生异常时,再重试请求。 但是这个重试有个前提,就是用户的请求不能是 ChunkedStreamingMode,不能是 FixedLengthStreamingMode,这两种模式下,底层无法重试。很不幸地是,我们正好使用到了 FixedLengthStreamingMode 带来的特性。

// Code snippet of: libcore.net.http.HttpURLConnectionImpl.java
while (true) {
  try {
    httpEngine.sendRequest();
    httpEngine.readResponse();
  } catch (IOException e) {
    /*
     * If the connection was recycled, its staleness may have caused
     * the failure. Silently retry with a different connection.
     */
    OutputStream requestBody = httpEngine.getRequestBody();
    // NOTE: FixedLengthOutputStream 和 ChunkedOutputStream
    // 不是 instance of RetryableOutputStream
    if (httpEngine.hasRecycledConnection()
        && (requestBody == null || requestBody instanceof RetryableOutputStream)) {
      httpEngine.release(false);
      httpEngine = newHttpEngine(method, rawRequestHeaders, null,
          (RetryableOutputStream) requestBody);
      continue;
    }
    httpEngineFailure = e;
    throw e;
}

由于 BUG 的根源在 Android 的核心库 libcore 中。这次改动影响了从 4.1 到 4.3 的所有 Android 版本, Android 4.4 网络库的 HTTP/HTTPS 从 libcore 切换到 okhttp,所以4.4以后的 Android 版本不受影响。

既然底层不重试,那么只有在应用层重试,所以我们在应用层增加了最多『http.maxConnections+1』次重试机制,以修复此问题。在重试的时候,尝试使用一个 stale connection 会导致 EOFException,底层也会自动关闭这个连接。『http.maxConnections+1』次重试保证即使连接池中全都是 stale connection,我们也能获得一个可用的连接。

网上应该也有人遇到过这个 BUG,所以我也在这个 StackOverflow 问题下做了回答