Author Archives: Xu Jiwei

高性价比 NAS+HTPC 折腾记

最近组装了一台 NAS+HTPC 合体的机器,自认为还有比较好的性价比,在折腾了两个星期之后,终于差不多搞定所有功能。这台 HTPC 使用 XBMC 作为媒体中心,使用 OpenMediaVault 作为 NAS 系统,这篇博客主要记录 OpenMediaVault 和 XBMC 的安装过程和问题。另外,因为折腾的时候有些地方来来回回搞了好多次,记录的顺序可能并不严谨。

通过 XBMC 可以直接索引所有电影,使用 themoviedb.org 来获取电影海报、介绍、演员等信息,使用 IMDb 来获取电影的评分。使用 OpenMediaVault 来管理存储,设置共享文件夹等。

在设置成功 VA-API 后,XBMC 就可以使用显卡的硬件加速了,在播放 1080P 时,CPU 占用基本在 20% 左右,还算比较不错了。

当然,在一开始我也可以选择使用 Windows 系统,XBMC 也有 Windows 版本,在 Windows 上基本也不用操心驱动程序的事情,而且 Windows 上设置 Samba 共享也是很方便的。

注意:本篇文章都是以 AMD E350 APU 来讲安装过程的,如果你用的主板并不是 E350,或者型号与我使用的不完全一致,可能安装过程会有不同。另外,因为 OpenMediaVault 是基于 Linux 的,所以这需要你懂一些 Linux 知识。

PS. 另外,本篇文章的操作都是直接使用 root 账户进行,如果你在安装的时候使用了非 root 账户,那么多数操作可能需要加 sudo 来运行。

硬件

  • 机箱:万由 N400 4盘位 NAS 机箱
  • 主板:梅捷 SY-E350-U3M
  • 内存:芝奇 G.Skill DDR3 1333 4G
  • 电源:肯亿 200W 小 1U 电源
  • 系统磁盘:SSK SFD201 USB3.0 16G U盘

因为都是在促销,或者优惠的时候购买的,不包邮费总价在 1400 元左右。

软件

  • 基础系统:Debian Squeeze
  • NAS 系统:OpenMediaVault
  • 媒体中心:XBMC 10.1 Dharma

安装系统

创建 OpenMediaVault 安装盘

因为是用的 NAS 机箱,上面是没有光驱位的,而且我也没有现成的光驱可以用,因为需要用 U 盘来引导安装系统。

首先需要准备一个 512M 以上的 U 盘,然后从 openmediavault.org 下载最新版本的 OpenMediaVault,再使用下面的命令将镜像恢复到 U 盘上:

sudo dd if=openmediavault_0.2.5_i386.iso of=/dev/disk1 bs=4096

注意:disk1 是在我的机器上 U 盘所在的位置,另外,在 dd 恢复镜像的时候,要使用 /dev/disk1,而不是 disk1s1,disk1s1 就会将镜像恢复到某个分区而不是整个 U 盘了。

启动盘注意事项

因为是用的 USB 3.0 的 U 盘,我在安装系统的时候,把 U 盘插在了 USB 3.0 的接口上,但是在系统安装程序启动之后,USB 3.0 的 U 盘没有自动识别出来,因此要将 USB 3.0 的 U 盘插在 USB 2.0 的口上。

在安装的过程中,可以将 Debian 源的地址修改为网易的源镜像,这样安装软件的速度更快一些,网易源的地址为:

http://mirrors.163.com/debian/

安装基本软件

先安装一些基本的软件,包括 X.org,Gnome,GDM3,Vim 和 less:

apt-get install xorg gnome-core gdm3 vim less

Gnome 和 GDM3 其实到后面都不会用到,但是在运行一些需要图形界面的工具时,例如 glxinfo、fglrxinfo,就需要有 X 支持了。

设置 debian-multimedia 源

为了让一些包默认使用 debian-multimedia 的源,先将 debian-multimedia 的源添加到 /etc/apt/sources.list

deb http://www.debian-multimedia.org squeeze main non-free 

并将所有其他源行首添加 # 注释掉,运行 apt-get update 更新源。

在更新完成之后,再将其他源行首的 # 删掉,再次运行 apt-get update 更新源。

安装 debian-multimedia keyring

apt-get install debian-multimedia-keyring

设置 XBMC 用户

添加一个用户 xbmc 用户来运行 XBMC:

useradd -m -s /bin/bash xbmc
passwd xbmc

设置用户 openmediavault 密码

在后面的设置过程中,可能需要管理员权限,而这个时候是用的 openmediavault 这个用户,我不清楚这个用户的默认密码是什么,所以需要先设置这个用户的密码:

passwd openmediavault

启用 SSH

在安装完 OpenMediaVault 之后,就可以直接访问它的 Web 界面了,建议启用 SSH 以便直接在本机进行操作,省去使用键盘看着电视操作的麻烦。

OpenMediaVault 默认的管理员用户名为 admin,密码为 openmediavault

Read more »

推开窗户看世界 — Objective-C之外

引子

App Store 如今正风靡世界,许多人都想尝试去自己开发一个 iOS App,不过苹果官方推荐使用的是 Objective-C 这个语言。而 Objective-C 与 C、Java 之类的语言风格差异有些大,短时间并不容易掌握,又或者许多人只是想尝试开发一个可以运行在 iOS 上的程序而已,不想再去额外去学一门新的语言,那这个时候就可以考虑另外的一些技术来开发 iOS App。

在移动开发领域,得益于现在手机的性能越来越好,浏览器的功能越来越强大,JavaScript 也成为一门很流行的程序开发语言,而对应到 iOS 平台上,也有多种使用 JavaScript 来开发 iOS App 的技术。这次要介绍的三种技术之中,就有两种使用了 JavaScript。

另外,许多开发跨平台移动应用开发的解决方案,首选要支持的就是 iOS,因此在 iOS 这个平台上可以看到很多种不使用 Objective-C 去开发 iOS App 的技术,而且其中技术所编写出来的程序,除了可以在 iOS 上运行之外,还可以运行在 Android 以及其他系统平台上。能少写一份代码,何乐而不为呢~

不使用 Objective-C

这次要介绍的是三个不使用 Objective-C 来开发 iOS App 的解决方案:

  • Titanium Mobile
  • PhoneGap(http://phonegap.com)
  • Mono Touch(http://xamarin.com/monotouch)

这三个解决方案各有特点,它们的运行机制也有所不同,至于在开发 iOS App 时选用何种方案,可以根据每种方案不同的特点去选择适合自己的。

Titanium Mobile

Titanium Mobile 是 AppCelerator 推出的一个基于 JavaScript 的跨平台移动应用开发技术。通过 Titanium Mobile,可以直接使用 JavaScript 开发能运行于 iOS 和 Android 平台的应用程序,而代码只需要写一次。

Titanium Mobile 是通过将 JavaScript 代码映射到对应平台的 Native Code,在 Titanium Mobile 中操作某一个 UI 对象,或者是其他对象时,实时上是在操作对应平台中实际的 UI 对象,例如使用 Ti.UI.createView() 创建一个视图,它在 iOS 中运行时会实际对应到 UIView,使用 Ti.UI.createTabGroup() 就会对应到 iOS 中的 UITabBarController。这样做的好处就是,可以直接使用 JavaScript 来创建出原生的 UI,而不需要使用额外的代码来让程序 UI 更像系统的 UI。

另外,AppCelerator 在收购了 Apatna 之后,推出了适用于 Titanium 的 IDE Titanium Studio,通过使用 Titanium Studio,可以很方便的创建项目、带智能提示的编辑器,以及很方便的调试项目。

在 Titanium Studio 中调试 iOS 项目时,可以做到单步调试,也就意味着,可以拥有不弱于使用 Xcode 开发项目的体验。断点、单步、变量查看功能一应具全。如果需要发布到 App Store,也可以很方便的通过图形化界面来打包。

当然,使用 Titanium Studio 也需要配合 Xcode 来使用,必须要在安装了 Xcode 的情况下,才可以使用 Titanium Studio 来调试和测试项目。

在 Titanium Studio 推出之前,Titanium Mobile 是靠一个 Developer Tool 来进行打包等操作的,相对比较繁琐,而有了 Titanium Studio 之后,让一切变得简单,我也是从那时开始关注 Titanium Mobile。

对于前端开发工程师来说,只需要理解了 iOS 开发中的一些概念,就可以使用 Titanium Mobile 来开发一个像模像样的 iOS App。并且 Titanium Mobile 实现了 CommonJS 规范,可以很方便来模块化程序代码。

PhoneGap

PhoneGap 是一个使用 HTML+CSS+JavaScript 来开发移动 App 的解决方案,使用它来开发 App 只需要有 Web 开发基础即可。它在今年10月份被 Adobe 收购了,然后加入到 Apache Software Foundation 进行孵化。

目前 PhoneGap 已经支持了市面上大多数的智能手机平台,其中就有 iOS。

在我看来,PhoneGap 其实只是提供了一个运行于各种智能手机平台的浏览器的壳,通过这个壳,PhoneGap 的 JavaScript 库可以和系统进行沟通,从而实现在 Web 页面中与系统交互的功能。

因为 PhoneGap 只提供了系统功能的 API 调用,而没有提供任何和界面相关的 API,那么界面就只能自己来折腾了。

幸好开源的世界是强大的,已经有了一堆这样的界面库来供我们选择,像 jQTouchjQuery MobileSencha Touch 之类,都提供了类似于 iOS 的界面组件,可以让我们省去许多界面上的编码工作。

使用浏览器来运行 App 的一个坏处就是:慢。因为整个 App 就是一个网页,如果编码不当,整个程序使用起来体验会比较差,和原生的应用会有很大区别。

在上面提到的几个 UI 框架之中,我比较喜欢的是 jQTouch,它只提供了基本 UI 框架和一些视图切换效果,做出来的程序是一个单页面 App,在整个程序的反应速度上会感觉比较好。不过 jQTouch 也缺少一些常用的组件像对话框,而这些在 jQuery Mobile 和 Sencha Touch 之中又有提供。选择使用哪个框架就要看个人喜好了,它们的网站都提供了在线预览功能

其实,如果用不到系统的 API,只是想把自己的程序包装成一个很像 App 的 App 的话,只要用一用 PhoneGap 提供的壳就行了,它的 API 几乎可以不用看,配合一些 UI 框架可以快速的产出一个 iOS App 来。

当然了,PhoneGap 最大的好处就是,几乎跨了几乎所有的主流智能手机平台,这对于初创团队来说,是迅速推出各个平台客户端的一个好方法。另外 PhoneGap 官方网站也提供了一系列配套服务,例如使用 PhoneGap Build 可以直接编译各个平台的程序安装包,而不需要开发者自己在本地配置每个平台的编译环境。

Mono Touch

Mono Touch 是一个使用 C# 来编写跨平台应用的框架,同样支持了 iOS 和 Android 两大平台。

Mono Touch 对于 .NET 程序员来说应该是一个好消息,除了调试,其他的都可以在 Windows 中搞定,调试的时候可以通过在虚拟机里运行 Mac OS X 来解决。Mono Touch 因为是基于 Mono 的,也有个配套的 IDE 可以用:Mono Develop。因为都是 .NET,当然也可以用 Vistual Studio 这个更强大的工具了。

与 Titanium Mobile 和 PhoneGap 不同的是,Mono Touch 如果要编译到设备,或者发布到 App Store 的话,是需要收费的。

另外,因为 Mono Touch 也是编译成可执行代码再部署到设备上的,因此运行速度相对于 PhoneGap 所制作的应用来说,应该会快上一些。

不过 Mono Touch 要收费,并且收费还不是很便宜,可能会影响到它的普及率。

小结

在上面讲的三个解决方案之外,还有其他好多大大小小的移动应用开发解决方案,但是用于 iOS 上的解决方案,主要也就是映射代码、或者是包装的形式了,对于应用而不是游戏类型来说,一般都是够用的,在具体选择的时候,也可以根据自己更为熟悉哪种语言,或者框架来挑选。

如果你发现了其他有意思的移动应用开发技术,也可以和我分享一下 :)

参考资料

  1. http://www.appcelerator.com/products/titanium-mobile-application-development/
  2. http://developer.appcelerator.com/apidoc/mobile/latest
  3. http://phonegap.com/about
  4. https://build.phonegap.com/
  5. http://www.mono-project.com/Main_Page
  6. http://xamarin.com/monotouch
  7. https://github.com/xamarin/monotouch-samples
  8. http://blog.zhaojie.me/2010/09/develop-ios-app-with-monotouch-in-visual-studio-1.html

–EOF–

使用 node.js + nginx 建设网站

昨天搞定了一个小网站的搭建,用了 node.js,另外为了能在一个 VPS 上搭建多个网站,用了 nginx 作为反向代理。

软件介绍

嗯,从维基上复制了一下~

node.js

Node.js是一个事件驱动I/O伺服端JavaScript环境,基于V8。目的是为了提供撰写可扩充网络程式,如web服务。第一个版本由Ryan Dahl于2009年释出,后来,Joyent雇用了Dahl,并协助发展Node.js。

nginx

nginx(发音同engine x)是一款由俄罗斯程序员Igor Sysoev所开发轻量级的网页服务器、反向代理服务器以及电子邮件(IMAP/POP3)代理服务器。

cluster

在 node.js 0.6.0 之前,有一个第三方的 node.js 模块 cluster,用来进行多核服务器上运行 node.js,以及提供扩展的支持。但是在 node.js 0.6.0 之后,node.js 本身就提供了 cluster 的支持,另外,第三方的 cluster 也与 node.js 0.6 有兼容性问题。目前 node.js 的稳定版本是 0.6.5,因此需要使用原生的 cluster 来代替第三方的 cluster。

幸好内置的 cluster 也足够简单,如果只是为了多核负载均衡,以及支持即时服务重启的话,只需要写一点的代码就可以完成这些功能了。

server.js

var path = require('path');
var http = require('http');
var cluster = require('cluster');

var NODE_ENV = process.env.NODE_ENV || 'production';
var appName = path.basename(__dirname);
var appPort = 9000;

var numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
    process.title = appName + ' master';
    console.log(process.title, 'started');

    // 根据 CPU 个数来启动相应数量的 worker
    for (var i = 0; i < numCPUs; i++) {
        cluster.fork();
    }

    process.on('SIGHUP', function() {
        // master 进程忽略 SIGHUP 信号
    });

    cluster.on('death', function(worker) {
        console.log(appName, 'worker', '#' + worker.pid, 'died');
        cluster.fork();
    });

} else {
    process.title = appName + ' worker ' + process.env.NODE_WORKER_ID;
    console.log(process.title, '#' + process.pid, 'started');

    process.on('SIGHUP', function() {
        // 接收到 SIGHUP 信号时,关闭 worker
        process.exit(0);
    });

    http.Server(function(req, res) {
        res.writeHead(200);
        res.end('Worker ' + process.env.NODE_WORKER_ID);
    }).listen(8000);
}

运行服务 node server.js

nodejs master started
nodejs worker 1 #38928 started
nodejs worker 3 #38930 started
nodejs worker 2 #38929 started
nodejs worker 4 #38931 started

如果直接 kill 掉某一个 worker,kill 38928

nodejs worker #38931 died
nodejs worker 5 #38934 started

可以看到一个新的 worker 会马上启动,这就保证了服务的不间断性。

Virtual Host 支持

通常情况下,我们不会在一个 IP 上只部署一个网站。在使用 node.js 时,可以使用 connect 提供的 vhost 支持 Virtual Host,但是,这也限制了服务器只能用 node.js,而不能同时使用其他的服务,例如再安装一个 PHP 服务之类。

这时就可以使用 nginx 的反向代理来解决了,用户在访问网站时,请求先到 nginx 进行处理,如果是 node.js 站点的话,将请求转发到 node.js 的服务,然后再将 node.js 服务的结果返回给用户。

在 nginx 中设置反向代理很简单,一句 proxy_pass 就可以搞定:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://localhost:9000;
    }
}

在添加了 Virtual Host 之后,就可以把一些静态资源,例如 CSS、JavaScript 之类的文件,直接交给 nginx 来处理,而不是什么请求都需要到 node.js 这一层去处理,也省去反向代理这一关的消耗。

Session 支持

使用 express 这个 node.js web framework 来创建网站时,可以配合 connect 这个中间件来实现 session 支持。

默认情况下,connect 的 session 是使用内置的内存存储来存放 session 信息,这时如果 node.js 服务一旦重启,所有的 session 信息都会丢失,这对于用户来说不是个好体验,那么我们可以用外部的存储来存放 session 信息,例如 redis。

要让 connect 使用 redis 作为 session 存储的话也是很方便的:

var express = require('express');
var RedisStore = require('connect-redis')(express);

var app = express.createServer(
    express.session({ secret: 'keyboard cat', store : new RedisStore() })
);

监测文件改动

在调试的时候,经常需要重新启动 node.js 以便修改过的文件生效,原来第三方的 cluster 有一个配置项,可以很方便的配置监测时间间隔,文件改动后自动重新启动 worker,但是原生的 cluster 就没有这个功能了,需要自己来实现。

fs 模块提供了 watch 函数,可以方便的监测文件修改,使用这个就可以来实现文件修改后自动重启 woker 功能了。

if (cluster.isMaster) {
    process.title = appName + ' master';
    console.log(process.title, 'started');

    var workers = [];

    // 根据 CPU 个数来启动相应数量的 worker
    for (var i = 0; i < numCPUs; i++) {
        var worker = cluster.fork();
        workers.push(worker.pid);
    }

    process.on('SIGHUP', function() {
        // master 进程忽略 SIGHUP 信号
    });

    // 监测文件改动,如果有修改,就将所有的 worker kill 掉
    fs.watch(__dirname, function(event, filename) {
        workers.forEach(function(pid) {
            process.kill(pid);
        });
    });

    cluster.on('death', function(worker) {
        var index = workers.indexOf(worker.pid);
        if (index != -1) {
            workers.splice(index, 1);
        }
        console.log(appName, 'worker', '#' + worker.pid, 'died');
        worker = cluster.fork();
        workers.push(worker.pid);
    });

}

这样,每次文件保存之后,node.js 都会自动重启,从而避免了每次保存文件要手动重启服务的麻烦。

当然,在使用监测文件自动重启的时候,最好加上 NODE_ENV 的判断,在 development 的时候才进行自动重启,而 production 的时候使用手动重启就够了。

小结

总的来说,使用 node.js 来构建网站还是很方便的,加上 nginx 反向代理之后,与使用 PHP 之前也没有很大的区别,又可以享受到 node.js 的高效。

嗯,就这样了,希望此文对你有所帮助。

参考资料

  1. http://zh.wikipedia.org/wiki/Node.js
  2. http://nodejs.org/docs/v0.6.5/api/cluster.html
  3. http://learnboost.github.com/cluster/
  4. http://expressjs.com/
  5. http://senchalabs.github.com/connect/
  6. http://redis.io/

–EOF–

iOS App 自定义 URL Scheme 设计

在 iOS 里,程序之间都是相互隔离,目前并没有一个有效的方式来做程序间通信,幸好 iOS 程序可以很方便的注册自己的 URL Scheme,这样就可以通过打开特定 URL 的方式来传递参数给另外一个程序。

例如在 iPad 上浏览网页,并且 iPad 已经安装了 淘宝 HD,那么就打开下面这个链接就会在淘宝 HD 中查看这个商品的详细信息,也可以方便的使用淘宝 HD 进行购买、收藏等操作。

在淘宝 HD 中查看商品“2012西藏卓明谷方舟登舰卡(船票)”

当然,如果你在 Mac OS X 中打开这个链接,或者在没有安装 淘宝 HD 的 iPad 中打开这个链接,会提示没有程序来打开这个链接。

配置

要为 iOS 程序添加自定义协议的支持是一件很方便的事,只需要在程序的 Info.plist 添加一个 URL types 节点就可以了。在这个节点里,可以设置这个程序所支持的自定义协议名称,像 http、ftp 这种,一般我们可以设置为程序英文名称,像淘宝客户端中就设置了 taobao,这样 taobao:// 这个形式的 URL 就会关联到淘宝客户端的 App。

Info.plist config

实现

在 Info.plist 里面设置完 URL types 之后,就可以在程序中处理这类 URL 的打开请求了。

在外部程序中,如果打开了指定自定义协议的 URL,程序中 application delegate 的 application:handleOpenURL: 方法就会被调用,在这个方法里,可以获取到触发这个方法的 URL,可以通过对这个 URL 进行判断,例如根据不同的 Host,不同的 Query String 来执行不同的动作。

- (void)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
    NSLog(@"%@", [url absoluteString]);

    // 在 host 等于 item.taobao.com 时,说明一个宝贝详情的 url,
    // 那么就使用本地的 TBItemDetailViewController 来显示
    if ([[url host] isEqualToString:@"item.taobao.com"]) {

        // 这里只是简单地假设 url 形式为 taobao://item.taobao.com/item.htm?id=12345678
        // 先获取要查看的宝贝详情的 itemId
        NSString *itemId = [[url query] substringFromIndex:[[url query] rangeOfString:@"id="].location+3];

        // 使用本地 ViewController 来显示淘宝商品详情
        TBItemDetailViewController *controller = [[TBItemDetailViewController alloc] initWithItemId:itemId];
        [self.navigationController pushViewController:controller animated:YES];
        [controller release];
    }
}

淘宝 for iOS

现在,淘宝 和 淘宝 HD 两个客户端都支持 taobao:// 协议,来打开特定的链接。目前已经支持的有:

例如,想要在自己的程序中,使用淘宝客户端来显示一个淘宝商品的详情,以支持用户可以直接在 iPhone 上购买,收藏等,就可以使用下面的代码:

- (void)showItemInTaobao4iOS:(NSString *)itemId {
    // 构建淘宝客户端协议的 URL
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"taobao://item.taobao.com/item.htm?id=%@", itemId]];

    // 判断当前系统是否有安装淘宝客户端
    if ([[UIApplication sharedApplication] canOpenURL:url]) {
        // 如果已经安装淘宝客户端,就使用客户端打开链接
        [[UIApplication sharedApplication] openURL:url];
    } else {
        // 否则使用 Mobile Safari 或者内嵌 WebView 来显示
        url = [NSURL URLWithString:[NSString stringWithFormat:@"http://item.taobao.com/item.htm?id=%@", itemId]];
        [[UIApplication sharedApplication] openURL:url];
    }
}

使用淘宝客户端来打开淘宝链接的好处就是可以让用户更加方便的去购买商品,而不需要再重新登录,或者把用户名密码给了第三方的网站而导致安全隐患。

扩展性

在淘宝客户端中,支持的 URL 往往是淘宝网站已经有的链接,这些链接的 QueryString 中所带的参数往往已经满足了使用本地代码显示内容的需要,但是为了扩展性考虑,就需要添加一些额外的参数,并且与原有 QueryString 中不冲突的参数名称。通过这些额外的参数,再实现客户端打开链接时更多的自定义行为。

例如,如果在打开特定 URL,进行一些操作后需要再返回原来的程序,就会需要在 URL 中添加类似于 callback 这样的参数,这样在客户端处理完用户的操作后,可以将用户操作的结果返回给原来的程序,从而实现程序间的通信。

示例:

- (void)buyItemInTaobao4iOS:(NSString *)itemId {
    // 构建淘宝客户端协议的 URL
    NSString *format = @"taobao://item.taobao.com/item.htm?id=%@&_action=buy&_callback=myapp://taobaobuysuccess";
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:format, itemId]];

    // 使用淘宝客户端打开链接
    [[UIApplication sharedApplication] openURL:url];
}

注意:当前淘宝客户端并不支持这样的调用方式,这里仅是一个示例。

当然,在使用这种方式实现程序间通信的时候,需要考虑检查一下来源 URL 的合法性,防止一些非法的调用造成用户的损失。

结语

通过自定义协议地支持,可以将 iOS 程序的一些功能和服务提供给外部程序,也可以实现 Web 和本地应用之间的互相调用。

如果你的 iOS 程序有这些需求的话,那么就可以考虑在 iOS 程序中添加自定义协议的支持了。

参考资料

  1. iOS Application Programming Guide: Implementing Custom URL Schemes

— EOF —

一个路由器,两种网络,VPN和下载两不误

最近在用 Titanium 写一个 Twitter 客户端,因为要用到 OAuth 认证,所以就在手机连接 VPN,但是速度比较慢。刚好想在家里的路由器上加上 OpenVPN,昨天就折腾了一下。

在家里路由器上加上 OpenVPN 的一个主要问题是,家里的网络中还有下载机,而下载的流量是不想通过 VPN 去传输的。虽然 chnroutes 项目的路由表可以让国内的 IP 走直连,国外的 IP 走 VPN,但是 eMule 或者 BT 下载时,难免会连接到国外的用户或者服务器,这个时候也不想去浪费 VPN 的流量。

因为这些,我的想法是在路由器上做判断,如果是从下载机过来流量,就通过直接连接,如果其他机器,例如笔记本,就根据目标 IP 来判断是通过直接连接还是 VPN 来传输。

下载机是通过 LAN 连接到路由器的,本来想按进入流量的设备来判断是否为下载机,后来发现实现比较麻烦,就决定按流量来源 IP 来判断是否为下载机的数据。

准备工作

  • 运行 dd-wrt 的路由器一个,要带有 OpenVPN
  • OpenVPN 服务器一个,认证方式选择证书认证
  • Linux 知识若干

当然 dd-wrt 并不是必须的,也可以是 openwrt 或者 tomato 之类,只要带有 OpenVPN 就行,如果不带 OpenVPN,就需要在启动过程中去外部下载相关的软件,那就是另外的内容了,暂且不提。

网络分段

因为要按 IP 来区分流量是否要走 VPN,因此要先划分一下局域网里要用到的 IP 段。

路由器的 IP 是 192.168.2.1,DHCP 分配范围为 192.168.2.100~149,按照需求将一个 /24 的网段分为三个部分:

  • 192.168.2.16~31,此 IP 段的设备流量均走直连
  • 192.168.2.32~63,此 IP 段的设备流量根据目标 IP 判断走直连还是走 VPN
  • 192.168.2.100~149,此 IP 段为 DHCP 分配的 IP 段,流量也根据目标 IP 来判断是否走 VPN

因为 DHCP 分配的 IP 并不可控,所以将流量走直接的设备,例如下载机,通过静态 IP 的方式,直接分配一个在 192.168.2.16~32 中固定 IP,可以保证不会连接到有 VPN 的网络。

路由策略表

先创建一个用来直连的路由策略表,用来将所有指定 IP 段的流量走直连。

# 添加一个路由策略表,此表针对 192.168.2.16/28 IP 段有效
ip rule show | grep "lookup 10" || ip rule add from 192.168.2.16/28 ta 10

# 设置策略表的默认路由
WAN_IP=`ifconfig ppp0 | grep "inet addr" | cut -d ":" -f 2 | cut -d " " -f1`
ip route replace 192.168.2.0/24 dev br0 proto kernel scope link src 192.168.2.1 ta 10
ip route replace 127.0.0.0/8 dev lo  scope link
ip route replace 169.254.0.0/16 dev br0  proto kernel  scope link  src 169.254.255.1
ip route replace default via $WAN_IP dev ppp0 ta 10

将以上代码保存在 dd-wrt 的 Filewall Script 中,这样在每次 WAN IP 改变的时候,都可以更新这个路由策略表了。

OpenVPN 配置

OpenVPN 按默认配置即可,需要注意的是,路由器上的 tun mtu、tun mtu extra 以及 mssfix 需要与服务器一致,或者服务器与路由器上的配置一致。

因为要用到 chnroutes,但是 dd-wrt 中的 OpenVPN 配置并不支持自定义配置,没办法添加 route 选项,因此要把这些选项放到 OpenVPN 服务端的配置文件中,使用 push 指令在连接时推送到客户端来。

例如:

push "route 1.0.0.0 255.255.0.0 net_gateway 5"

另外一有点需要注意,如果在 dd-wrt 中同时启用了 OpenVPN Daemon,建议将 OpenVPN Daemon 的启动方式设置为“System Startup”而不是“WAN Up”,在我的路由器上,使用“WAN Up”时 OpenVPN Daemon 和 OpenVPN Client 会冲突,导致 OpenVPN Client 启动失败。

解决 max-routes

OpenVPN 客户端,默认最多只能添加 100 条路由记录,但是 chnroutes 正常生成的路由表,可能会在 1000 条以上,因此 100 条是远远不够的。

这个可以通过 max-routes 配置项来解决,本来打算这个配置同样从服务端推送过来,但是 OpenVPN 现在并不支持 push “max-routes 1500″ 这样的指令。

在 dd-wrt 的 OpenVPN 配置中,也没有相应的选项,为了解决这个问题,只能采取一个比较取巧的办法来解决。dd-wrt 中的 OpenVPN 配置都是存在 nvram 中的,在 dd-wrt 启动后,会自动从 nvram 中取 OpenVPN 的相关配置,组合成一个 openvpn.conf,而这个配置除了可以在 dd-wrt 的 Web 界面中修改,还可以直接 SSH 到 dd-wrt 上,直接使用 nvram 命令修改。

在这里要 hack 的配置是 mssfix,当然其他的属性也可以,选择 mssfix 是因这个属性比较简单,改起来方便。

我这里设置了 mssfix 为 1400,另外服务器要推送的路由表为 1300 条左右,直接将 max-routes 设置为 1500,在路由器上运行下面这个指令:

nvram set openvpncl_mssfix="1400
max-routes"

需要注意的是,一定要分两行来输入,否则生成的 openvpn.conf 中,mssfix 1400 和 max-routes 1500 会在同一行而导致配置失效。

这样在生成的 OpenVPN 配置文件中,就有了 max-routes 选项,服务端也可以正常推送路由表了。

不过这样也有一点坏处,那就是如果再修改了 OpenVPN 配置并保存,会把 mssfix 中的那个回车给去掉,再次导致连接失败。不过 OpenVPN 一旦配置完成,也不会经常改动,倒也不是很大的问题。

配置 DNSMasq

一般手机上连接上 WiFi 的时候,设置 DNS 等内容会比较麻烦,而如果不设置 DNS,会导致在手机上解析域名时,使用了国内的 DNS 服务器,而这也会导致一些问题,可以按照 autoddvpn 中的说明,将 DNS 设置为 Google Public DNS 和 OpenDNS:

8.8.8.8
8.8.4.4
208.67.222.222

打完收工

配置完成之后,就可以方便的分配家庭局域网里设备的流量走向了,想要设备的流量走直连,只要分配到 192.168.2.16~31 这个 IP 段就可以了,至于其他的设备,可以使用静态 IP,也可以直接使用 DHCP 分配。

嗯,这样再在真机上调试 Twitter 客户端之类的程序就方便了。

PS. 非常感谢 @tjmao 在折腾过程中帮助。

参考资料

  1. autoddvpn: DNSMasq
  2. OpenVPN 2.1 Manual
  3. linux 高级路由及流量控制总结
  4. ip(8)
  5. route(8)
  6. ddwrt: Hardware

— EOF —

淘宝 for iOS 历程

这是在 淘宝2011技术嘉年华 上讲的关于淘宝 for iOS 开发过程的演示文稿。

View more presentations from ohdarling88.

— EOF —

My Book Live 评测

早上到手,开箱照见 My Book Live 开箱照

之前做过 DS211j 的性能评测,这次也主要做一些传输性能的评测,另外加一些功能介绍。

在很早之前,就关注过西数的 My Book 系列,My Book World Edition 也是带网络接口的,不过价格相对较高,而且性能也是很强,性价比不高。前段时间在 Hi!PDA 看到别人发的 My Book Live 介绍之后就看了一下,目前的价格降到合适的位置,刚好下载机的硬盘坏了,就入手了一个 My Book Live。

基本信息

型号:WDBACG0010HCH-SESN

内核:

Linux MyBookLive 2.6.32.11-svn22400 #2 Fri Apr 15 11:08:36 PDT 2011 ppc GNU/Linux

CPU 和 内存:

MyBookLive:~# cat /proc/cpuinfo 
processor   : 0
cpu     : APM82181
clock       : 800.000008MHz
revision    : 28.129 (pvr 12c4 1c81)
bogomips    : 1600.00
timebase    : 800000008
platform    : PowerPC 44x Platform
model       : amcc,apollo3g
Memory      : 256 MB

默认数据分区格式为 ext4:

/dev/sda4 on /DataVolume type ext4 (rw,noatime,nodelalloc)

实际可用容量只有 992G,还是用 1K 的进制算的⋯⋯

MyBookLive:~# df -H
Filesystem             Size   Used  Avail Use% Mounted on
tmpfs                   53M      0    53M   0% /lib/init/rw
udev                    11M   6.9M   3.7M  65% /dev
tmpfs                   53M      0    53M   0% /dev/shm
rootfs                 2.1G   1.1G   865M  55% /
tmpfs                   53M   3.4M    50M   7% /tmp
ramlog-tmpfs            21M   2.5M    19M  12% /var/log
/dev/sda4              992G   6.7G   986G   1% /DataVolume

配置相对来说还不错,像 DS211j 也只有 128M 的内存⋯⋯

传输速度测试

传输性能测试环境:

  • My Book Live 1TB
  • MacBook Pro with SSD
  • Buffalo WZR-HP-G300NH
  • 网络环境:有线千兆LAN,无线 802.11n @ 130Mbps
  • 测试文件:Mac OS X Lion GM 文件,大小 3.75GB

小文件传输基本上和文件个数有关,就不做测试了。

LAN 上传

速度基本上稳定在 42MB/s 左右,和

Read more »

WD My Book Live 开箱照

下载机的硬盘快挂了,MSS 是很老的东西,再买块硬盘挂上也不爽,刚好最近看到西数的 My Book Live,配置还不错,刚好入一个当下载机。

不过买了之后才想到,我已经有 DS211j,也可以当下载机⋯⋯不过 My Book Live 配置还不错,可以同时挂更多任务了。

— EOF —

给 iOS 项目添加自动更新属性

在 iOS 项目中,有些时候为了方便跟踪发布时的版本,方便调试,或者为了展示程序的编译日期,可以通过在 Info.plist 里面添加自定义属性的方式来实现,但是这个工作是一个纯粹机械的重复劳动,每次手工去更新是比较麻烦的一个事情。

好在 Xcode 项目支持自定义脚本的编译步骤,我们可以通过在编译过程中使用自定义脚本去更新 Info.plist。

在 /usr/libexec 目录下面,有一个工具 PlistBuddy,它可以很方便地修改 plist 文件,而不需要去用 sed 之类的编辑工具来修改。

为了在使用 PlistBuddy 的时候可以直接用 Set 命令设置属性,我们先在 iOS 项目的 Info.plist 里面加一个需要自动更新的属性,例如 BUILD_DATE 和 GIT_REVISION,类型选择为字符串,值填 AUTO_GENERATED。

然后在 iOS 项目的 Target 上添加一个新的编译步骤 Run Script Build Phase:

下页就是要编写脚本了

# 获取编译的日期
DATE=`date +%Y.%m.%d`
 
# 获取编译时 Git 库的短版本号
GIT_REVISION=`git rev-parse --short HEAD`
 
# 设置 Info.plist 中相关的属性
/usr/libexec/PlistBuddy "$TARGET_BUILD_DIR/$INFOPLIST_PATH" -c "Set BUILD_DATE $DATE"
/usr/libexec/PlistBuddy "$TARGET_BUILD_DIR/$INFOPLIST_PATH" -c "Set GIT_REVISION $GIT_REVISION"
 
# 将 Info.plist 转换为二进制格式
plutil -convert binary1 "$TARGET_BUILD_DIR/$INFOPLIST_PATH"

如果需要在程序里获取这些属性,那么就可以用下面的方法:

NSString *buildDate = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"BUILD_DATE"];
NSString *revision = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"GIT_REVISION"];

— EOF —

HostsManager for Mac OS X v0.4.2 Released

更新日志

HostsManager for Mac OS X v0.4.2 更新

  • 修正在 Mac OS X Lion 下启动失败的bug
  • 在 Mac 系统中,使用 \n 作为 hosts 文件的行分隔符

下载

HostsManager for Mac OS X v0.4.2

注意: 此次仅更新 Mac OS X 版本,HostsManager for Windows 版本没有更新

.