# Introduction

## Table of contents

* [Archive](https://blog.log4think.com/archive)
* [2016-12-13 JavaScript 中几种不同的基于 prototype 继承方式的区别](https://blog.log4think.com/javascript-inheritance-by-prototype)
* [2014-09-02  Facebook的Dalvik运行期补丁](https://blog.log4think.com/facebook-dalvik-patch-for-android)
* [2014-08-19  理解AnguarJS中的模板编译](https://blog.log4think.com/understanding-angularjs-template-compiling)
* [2014-08-13  在Android中使用OSGi框架(Knopflerfish)](https://blog.log4think.com/use-knopflerfish-in-android)
* [2014-08-13  在Android中使用OSGi框架(Apache Felix)](https://blog.log4think.com/use-apache-felix-in-android)
* [2014-07-30  3rd-party apt-key list for Ubuntu](https://blog.log4think.com/3rd-party-apt-key-list-ubuntu)
* [2014-07-21  'Failed to clone a large git repository: The remote end hung up unexpectedly'](https://blog.log4think.com/failed-to-clone-a-large-git-repository)
* [2014-07-02  genymotion Qt error in Ubuntu](https://blog.log4think.com/genymotion-qt-error-ubuntu)
* [2014-04-30  可自动安装依赖的Ubuntu离线包安装工具 gdebi](https://blog.log4think.com/a-better-package-tool-gdebi)
* [2014-04-23  解决搜狗输入法Ubuntu 14.04下黑块状态条](https://blog.log4think.com/sogou-linux-input-black-statusbar)
* [2014-04-08  Setup Ghost blog system on Ubuntu](https://blog.log4think.com/setup-ghost-blog-system-on-ubuntu)
* [2014-03-28  Trace a process](https://blog.log4think.com/trace-a-process)
* [2014-03-25  Forecast::IO 599 Internal Exception](https://blog.log4think.com/forecast-io-599-internal-exception)
* [2014-03-23  Mac Tips](https://blog.log4think.com/mac-tips)
* [2014-01-17  LD\_LIBRARY\_PATH shouldn't contain the current directory](https://blog.log4think.com/ld-library-path)
* [2014-01-10  Python on vim](https://blog.log4think.com/python-on-vim)
* [2013-11-12  ibus-pinyin doesn't work in KUbuntu 13.10](https://blog.log4think.com/ibus-pinyin-doesnt-work-in-kubuntu-13-10)
* [2013-11-11  phpMyAdmin login error to remote server](https://blog.log4think.com/phpmyadmin-login-error)
* [2013-11-04  Get SNMP(v3) working on Ubuntu 12.04](https://blog.log4think.com/get-snmpv3-working-on-ubuntu-12-04)
* [2013-10-30  Accessing Facebook by LWP](https://blog.log4think.com/accessing-facebook-by-lwp)
* [2013-10-17  Log4perl多个Appender重复输出日志的问题解决办法](https://blog.log4think.com/log4perl-duplicate-message)
* [2013-04-19  How to enable ORMLite internal log on Android](https://blog.log4think.com/how-to-enable-ormlite-internal-log-on-android)
* [2013-02-20  Bash Shortcuts](https://blog.log4think.com/bash-shortcuts)
* [2013-02-20  '"adb shell dumpsys" parameter list'](https://blog.log4think.com/adb-shell-dumpsys-parameter-list)
* [2013-01-27  循环有序数组的二分查找](https://blog.log4think.com/binary-search-sorted-loop-array)
* [2013-01-23  为Java运行环境导入根证书解决Eclipse的TFS插件的"PKIX path building failed"错误](https://blog.log4think.com/import-cert-to-java)
* [2013-01-21  ant 中通过重新定义  project.all.jars.path 在 classpath 中引入外部 jar 文件](https://blog.log4think.com/extend-ant-path-varible)
* [2012-09-22  Android的滚动条实现细节](https://blog.log4think.com/android-scrollbar-thumb)
* [2012-02-05  hostname自动变成bogon的问题](https://blog.log4think.com/bogon)
* [2011-07-09  代码段速记 gist.github.com](https://blog.log4think.com/code-snippets)
* [2011-07-08  A perl Data.Dumper clone for Python](https://blog.log4think.com/data-dumper-for-python)
* [2011-06-22  魔兽世界私服Trinity，从源码开始](https://blog.log4think.com/setup-wow-private-server)
* [2011-06-13  魔兽世界3.3.5 13930登录数据包分析](https://blog.log4think.com/wow-3-3-513930-login-packets)
* [2011-06-13  魔兽世界 3.3.5 13930 Trinity 认证补丁](https://blog.log4think.com/wow-3-3-5-13930-auth-patch)
* [2011-05-20  统一业务模型(UBM) in ERP5](https://blog.log4think.com/ubm-in-erp5)
* [2011-03-08  制作ASCII字符动画](https://blog.log4think.com/ascii-movie)
* [2011-01-14  Ubuntu升级导致的udevd错误修复](https://blog.log4think.com/ubuntu-udevd-error-fix)
* [2011-01-09  行列有序矩阵求第K个数](https://blog.log4think.com/k-th-number-in-sorted-matrix)
* [2011-01-09  字节按位逆序](https://blog.log4think.com/reverse-byte-bit)
* [2011-01-01  编程之美 1.2 中国相帅问题的一个简洁解法](https://blog.log4think.com/bop-1-2)
* [2010-11-26  为Windows 7/Windows Server 2008添加IPX协议](https://blog.log4think.com/ipx-protocol-for-windows-7-2008)
* [2010-11-12  How to debug with Android Logging](https://blog.log4think.com/debug-android-logging)
* [2010-09-16  利用google-code-prettify做网页内源码的语法高亮](https://blog.log4think.com/syntax-highlight-by-prettify)
* [2010-09-05  10 Ways We Hurt Our Romantic Relationships](https://blog.log4think.com/10-ways-hurt-romantic-relationships)
* [2010-08-30  利用ipkall+xlite+iptel.org开通google voice](https://blog.log4think.com/apply-google-voice-by-ipkall-iptel-xlite)
* [2010-08-27  避免Android开发中的ANR](https://blog.log4think.com/avoid-anr-in-android)
* [2010-08-27  在Eclipse中查看Android SDK的源代码](https://blog.log4think.com/browsing-android-source-in-eclipse)
* [2010-08-18  Git中判断一个commit是否在某个branch中](https://blog.log4think.com/find-commit-in-branch)
* [2010-08-04  修正auto-excerpt产生带格式的摘要](https://blog.log4think.com/fix-auto-excerpt)
* [2010-03-31  Go 编程语言入门教程](https://blog.log4think.com/go-tutorial)
* [2010-03-13  利用外部VPS主机通过ssh隧道穿透防火墙连接内网](https://blog.log4think.com/corss-firewall-by-ssh-tunnel-on-vps)
* [2009-12-30  旋转矩阵](https://blog.log4think.com/whirl-matrix)
* [2009-02-10  为什么cpio要比tar好](https://blog.log4think.com/tar-v-cpio)
* [2008-12-11  ThoughtWorks 的一道笔试题](https://blog.log4think.com/thoughtworks-interview-problem-one-trains)
* [2008-11-18  长距离打车如何省钱？](https://blog.log4think.com/save-money-for-taxi)
* [2007-02-08  ftp后台自动上传下载](https://blog.log4think.com/ftp-background-task)
* [2006-06-12  vi cheatsheet](https://blog.log4think.com/vi-cheatsheet)
* [2006-04-19  修正mysqlcc在MySQL 5.0上常报的 Table 'xxx' doesn't exist 错误](https://blog.log4think.com/fix-mysqlcc-table-xxx-doesnt-exist-error)
* [2006-04-01  'Perl中不寻常的 ?: 运算符'](https://blog.log4think.com/perl-ternary-problem)
* [2005-09-13  关于IoC、低耦合、配置文件及其本质意义的思考](https://blog.log4think.com/ioc-and-configuration-file)
* [2005-09-13  Perl与数据库DBI快速入门](https://blog.log4think.com/perl-and-dbi)
* [2005-09-12  Perl无废话入门指南](https://blog.log4think.com/perl-fast-tutorial)
* [2005-07-16  Solaris 下安装Perl的DBD-mysql模块失败的原因以及解决办法](https://blog.log4think.com/install-perl-dbd-mysql-on-solaris)
* [2004-10-15  SharpDevelop的AddInTree View 插件](https://blog.log4think.com/sharpdevelop-addintreeview)
* [2004-10-14  SharpDevelop源码分析 (完整版)](https://blog.log4think.com/read-sharpdevelop-sourcecode)


# Archive

{% content-ref url="/pages/-M1huStphEBIl4EGTiRj" %}
[2016-12-13 JavaScript 中几种不同的基于 prototype 继承方式的区别](/javascript-inheritance-by-prototype)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2B7sR\_afzWRLU8" %}
[2014-08-13  在Android中使用OSGi框架(Knopflerfish)](/use-knopflerfish-in-android)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2CNp14NqE6yLOz" %}
[2014-08-13  在Android中使用OSGi框架(Apache Felix)](/use-apache-felix-in-android)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2DGgfVK43nj-Ik" %}
[2014-07-30  3rd-party apt-key list for Ubuntu](/3rd-party-apt-key-list-ubuntu)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2EVbOIEEc33h0a" %}
[2014-07-21  'Failed to clone a large git repository: The remote end hung up unexpectedly'](/failed-to-clone-a-large-git-repository)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2FmkTPQ7kU2rTt" %}
[2014-07-02  genymotion Qt error in Ubuntu](/genymotion-qt-error-ubuntu)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2GeRvcnBe8R1pO" %}
[2014-04-30  可自动安装依赖的Ubuntu离线包安装工具 gdebi](/a-better-package-tool-gdebi)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2HPvnEarno\_dYd" %}
[2014-04-23  解决搜狗输入法Ubuntu 14.04下黑块状态条](/sogou-linux-input-black-statusbar)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2I53o3cxYGIUcU" %}
[2014-04-08  Setup Ghost blog system on Ubuntu](/setup-ghost-blog-system-on-ubuntu)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2JGBisBni2BXP2" %}
[2014-03-28  Trace a process](/trace-a-process)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2KXBB6204JH\_um" %}
[2014-03-25  Forecast::IO 599 Internal Exception](/forecast-io-599-internal-exception)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2LC49\_rr4ELYmT" %}
[2014-03-23  Mac Tips](/mac-tips)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2NKJG6BszzLttX" %}
[2014-01-10  Python on vim](/python-on-vim)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2OhL7tltfqkNan" %}
[2013-11-12  ibus-pinyin doesn't work in KUbuntu 13.10](/ibus-pinyin-doesnt-work-in-kubuntu-13-10)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2PGEtki10glN8L" %}
[2013-11-11  phpMyAdmin login error to remote server](/phpmyadmin-login-error)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2QsXT9rAHrJ6KY" %}
[2013-11-04  Get SNMP(v3) working on Ubuntu 12.04](/get-snmpv3-working-on-ubuntu-12-04)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2SY4L9CgfVBOLX" %}
[2013-10-17  Log4perl多个Appender重复输出日志的问题解决办法](/log4perl-duplicate-message)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2TNZvY0myI-wxd" %}
[2013-04-19  How to enable ORMLite internal log on Android](/how-to-enable-ormlite-internal-log-on-android)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2UN5A6XUuuAqBu" %}
[2013-02-20  Bash Shortcuts](/bash-shortcuts)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2V4ExOGzGczOnA" %}
[2013-02-20  '"adb shell dumpsys" parameter list'](/adb-shell-dumpsys-parameter-list)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2\_FZSaIUHO-9t5" %}
[2012-02-05  hostname自动变成bogon的问题](/bogon)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2aPy8NqN5yFzfE" %}
[2011-07-09  代码段速记 gist.github.com](/code-snippets)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2mEAzzbX6r5Iiv" %}
[2010-11-12  How to debug with Android Logging](/debug-android-logging)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2nOMUOGqHGwc1X" %}
[2010-09-16  利用google-code-prettify做网页内源码的语法高亮](/syntax-highlight-by-prettify)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2oXlADf2DKjLcO" %}
[2010-09-05  10 Ways We Hurt Our Romantic Relationships](/10-ways-hurt-romantic-relationships)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2qhVJX1glRUzVB" %}
[2010-08-27  在Eclipse中查看Android SDK的源代码](/browsing-android-source-in-eclipse)
{% endcontent-ref %}

{% content-ref url="/pages/-M1fHN2teB7yurRzNuhj" %}
[2010-08-04  修正auto-excerpt产生带格式的摘要](/fix-auto-excerpt)
{% endcontent-ref %}


# 2016-12-13 JavaScript 中几种不同的基于 prototype 继承方式的区别

## JavaScript 中几种不同的基于 prototype 继承方式的区别

### 普通属性的继承

#### 第一种方式

来自于 [MDN 对象模型的细节](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model)

```
function Employee1 (name, dept) {
    console.log('Employee constructor')
    this.name = name || "";
    this.dept = dept || "general";
    this.work = function() {console.log("Employee.work")}
    this.workAsEmployee = function() { this.work() }
}

function WorkerBee1 (projs) {
    console.log('WorkerBee constructor')
    this.projects = projs || [];
    this.work = function() {console.log("WorkerBee.work")}
    this.workAsBee = function() { this.work() }
}
WorkerBee1.prototype = new Employee1;

function Engineer1 (mach) {
    console.log('Engineer constructor')
    this.dept = "engineering";
    this.machine = mach || "";
    this.work = function() {console.log("Engineer.work")}
    this.workAsEngineer = function() { this.work() }
}

Engineer1.prototype = new WorkerBee1;

e1 = new Engineer1()
e1.work()
console.log(Employee1.prototype.isPrototypeOf(e1))
console.log(WorkerBee1.prototype.isPrototypeOf(e1))
console.log(e1)
```

#### 第二种方式

来自于 YUI 的实现，利用中间对象传递 prototype

```
function extend(Child, Parent) {
    var F = function () { };
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
    Object.defineProperty(Child, "super", { "value": Parent.prototype })
}

function Employee2 (name, dept) {
    console.log('Employee constructor')
    this.name = name || "";
    this.dept = dept || "general";
    this.work = function() {console.log("Employee.work")}
    this.workAsEmployee = function() { this.work() }
}

function WorkerBee2 (projs) {
    console.log('WorkerBee constructor')
    this.projects = projs || [];
    this.work = function() {console.log("WorkerBee.work")}
    this.workAsBee = function() { this.work() }
}
extend(WorkerBee2, Employee2);

function Engineer2 (mach) {
    console.log('Engineer constructor')
    this.dept = "engineering";
    this.machine = mach || "";
    this.work = function() {console.log("Engineer.work")}
    this.workAsEngineer = function() { this.work() }
}
extend(Engineer2, WorkerBee2);

e2 = new Engineer2()
e2.work()

console.log(Employee2.prototype.isPrototypeOf(e2))
console.log(WorkerBee2.prototype.isPrototypeOf(e2))
console.log(e2)
```

可以看到，区别主要在于，直接 `Child.prototype = new Parent()` 会把定义在 Parent 里面的方法也带到 prototype 里面去。另外，这种方式并没有执行父类的构造函数。

### 对于定义在 prototype 里面的方法呢

下面对上面的方法定义进行一点改进，把方法定义在 prototype 里，类似正常的 OO 编程中在类里面定义方法。

#### 第一种方式改进

```
function Employee3 (name, dept) {
    console.log('Employee constructor')
    this.name = name || "";
    this.dept = dept || "general";
}
Employee3.prototype.work = function() {console.log("Employee.work")}
Employee3.prototype.workAsEmployee = function() { this.work() }

function WorkerBee3 (projs) {
    console.log('WorkerBee constructor')
    this.projects = projs || [];
}
WorkerBee3.prototype = new Employee3;
WorkerBee3.prototype.work = function() {console.log("WorkerBee.work")}
WorkerBee3.prototype.workAsBee = function() { this.work() }

function Engineer3 (mach) {
    console.log('Engineer constructor')
    this.dept = "engineering";
    this.machine = mach || "";
}
Engineer3.prototype = new WorkerBee3;
Engineer3.prototype.work = function() {console.log("Engineer.work")}
Engineer3.prototype.workAsEngineer = function() { this.work() }

e3 = new Engineer3()
e3.work()

console.log(Employee3.prototype.isPrototypeOf(e3))
console.log(WorkerBee3.prototype.isPrototypeOf(e3))
console.log(e3)
```

#### 第二种方式的改进

```
function extend(Child, Parent) {
    var F = function () { };
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
    Object.defineProperty(Child, "super", { "value": Parent.prototype })
}

function Employee4 (name, dept) {
    console.log('Employee constructor')
    this.name = name || "";
    this.dept = dept || "general";
}
Employee4.prototype.work = function() {console.log("Employee.work")}
Employee4.prototype.workAsEmployee = function() { this.work() }

function WorkerBee4 (projs) {
    console.log('WorkerBee constructor')
    this.projects = projs || [];

}
extend(WorkerBee4, Employee4);
WorkerBee4.prototype.work = function() {console.log("WorkerBee.work")}
WorkerBee4.prototype.workAsBee = function() { this.work() }

function Engineer4 (mach) {
    console.log('Engineer constructor')
    this.dept = "engineering";
    this.machine = mach || "";
}
extend(Engineer4, WorkerBee4);
Engineer4.prototype.work = function() {console.log("Engineer.work")}
Engineer4.prototype.workAsEngineer = function() { this.work() }

e4 = new Engineer4()
e4.work()

console.log(Employee4.prototype.isPrototypeOf(e4))
console.log(WorkerBee4.prototype.isPrototypeOf(e4))
console.log(e4)
```

注意观察 `constructor` 和 `__proto__` 属性。

## 要执行所有构造函数

上述第二种方法，都没有执行父类的构造函数，也就没有真正的继承父类的初始化数据。为了弥补这一点，如下两种写法都可以达到目的。

### 利用 `super` 变量

```
function extend(Child, Parent) {
    var F = function () { };
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
}

function Employee5 (name, dept) {
    console.log('Employee constructor')
    this.name = name || "";
    this.dept = dept || "general";
}
Employee5.prototype.work = function() {console.log("Employee.work")}
Employee5.prototype.workAsEmployee = function() { this.work() }

function WorkerBee5 (projs) {
    console.log('WorkerBee constructor')

    this.super = Employee5;
    this.super();

    this.projects = projs || [];

}
extend(WorkerBee5, Employee5);
WorkerBee5.prototype.work = function() {console.log("WorkerBee.work")}
WorkerBee5.prototype.workAsBee = function() { this.work() }

function Engineer5 (mach) {
    console.log('Engineer constructor')

    this.super = WorkerBee5;
    this.super();

    this.dept = "engineering";
    this.machine = mach || "";
}
extend(Engineer5, WorkerBee5);
Engineer5.prototype.work = function() {console.log("Engineer.work")}
Engineer5.prototype.workAsEngineer = function() { this.work() }

e5 = new Engineer5()
e5.work()

console.log(Employee5.prototype.isPrototypeOf(e5))
console.log(WorkerBee5.prototype.isPrototypeOf(e5))
console.log(e5)
```

### 类似，但是用 `Parent.apply` 方法

```
function extend(Child, Parent) {
    var F = function () { };
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
}

function Employee6 (name, dept) {
    console.log('Employee constructor')
    this.name = name || "";
    this.dept = dept || "general";
}
Employee6.prototype.work = function() {console.log("Employee.work")}
Employee6.prototype.workAsEmployee = function() { this.work() }

function WorkerBee6 (projs) {
    console.log('WorkerBee constructor')
    Employee6.apply(this)
    this.projects = projs || [];
}
extend(WorkerBee6, Employee6);
WorkerBee6.prototype.work = function() {console.log("WorkerBee.work")}
WorkerBee6.prototype.workAsBee = function() { this.work() }

function Engineer6 (mach) {
    console.log('Engineer constructor')
    WorkerBee6.apply(this)
    this.dept = "engineering";
    this.machine = mach || "";
}
extend(Engineer6, WorkerBee6);
Engineer6.prototype.work = function() {console.log("Engineer.work")}
Engineer6.prototype.workAsEngineer = function() { this.work() }

e6 = new Engineer6()
e6.work()

console.log(Employee6.prototype.isPrototypeOf(e6))
console.log(WorkerBee6.prototype.isPrototypeOf(e6))
console.log(e6)
```


# 2014-09-02  Facebook的Dalvik运行期补丁

对于一个功能丰富的Android应用来说，要面对的挑战很多，其中很多问题绝大多数开发者都可能意识不到。Android应用的方法数量限制可能就是一个。

在一些传统的企业应用中，当处理跟业务数据相关的时候，通常不会将数据变量设置为公开访问 `public`，而是会用getter/setter这样的存取方法来封装访问数据模型中的私有变量。在一般的编程实践中，这是一个被推荐和鼓励的做法。但是其副作用是类文件中会产生大量的方法（每一个私有变量都对应着2个方法）。

这个副作用带来的问题是，当应用安装在一些老机型上的时候，可能导致Android的一个Bug<https://code.google.com/p/android/issues/detail?id=22586>。原因在于，在应用的安装过程中，会运行一个程序（dexopt）去根据当前机型为应用做一些特定的准备和优化。dexopt中使用了一个固定大小的缓冲区（名为LinearAlloc）来存储应用中方法的信息。最新的几个Android版本中，该缓冲的大小是8MB或16MB。但是Froyo（2.2）和Gingerbread（2.3）只有5MB。因为老版Android的这个限制，当方法数量超过这个缓冲大小的时候，会导致应用崩溃。

解决办法之一是可以利用<https://android-developers.blogspot.hk/2011/07/custom-class-loading-in-dalvik.html>提到的技术，将dex分成多个dex文件分别加载，首先加载核心dex，其他的模块和扩展功能放在其他的dex文件中。

但是某些时候，如果第二个dex包中的类需要被Android Framework直接访问到的话，上述方案就是不可行的。此时必须要将第二个dex文件注入到Android的系统class loader中。这个一般情况下是无法做到的，但好在Android是开源的系统，可以利用Java的反射去修改Android内部的一些数据结构来解决这个问题。

这个方案在实际运行中，你会发现其实LinearAlloc不仅仅只是在dexopt中存在，而是在每个运行中的Android程序中都有它的身影。dexopt使用LinearAlloc储存dex文件的方法信息的时候，运行期的Android应用使用它访问实际用到的方法。如果在运行期将所有的dex文件都加载到一个进程中的话，实际的方法数量还是会超过限制。应用虽然可以启动但是很快就会崩溃。

看起来似乎要么只能精简应用的功能，要么就只能放弃支持Android部分版本而只支持最新的ICS以上的版本。

再次回头去看Android的源码，找到LinearAlloc缓冲的定义<https://github.com/android/platform_dalvik/blob/android-2.3.7_r1/vm/LinearAlloc.h#L33>，也许你能意识到：其实只要能将缓冲从5MB增加到8MB就可以安全运转了。

也许，可以使用JNI把当前的缓冲替换成一个更大的缓冲区。这个方案看起来太疯狂了。修改Java的Class Loader的内部是一回事儿，修改运行中的Davlik虚拟机的内部可是另外一回事儿--运行中的代码可能会很危险。但是仔细查看过代码、分析所有使用LinearAlloc的地方后发现，只要在应用启动的时候去做，应该没什么危险。所需要做的就是，找到LinearAlloc对象，锁定，然后替换缓冲。

事实上，只要找到它，后面的事情就很顺利了。在这里<https://github.com/android/platform_dalvik/blob/android-2.3.7_r1/vm/Globals.h#L519>，DvmGlobals对象中保存了LinearAlloc缓冲区。大概距离对象的开始地址700个字节的位置。从对象的开头开始扫描到这里风险很大，但是幸运的是，可以用距离一步之遥的vmList对象作为起点。这里包含了一个值，可以通过JNI和JavaVM的指针做比较。

最终方案是，找到vmList的值，扫描DvmGlobals对象找到匹配的位置，往后跳几个字节找到LinearAlloc的头，然后替换缓冲区。编写JNI扩展，嵌入到应用中，启动，然后应用应该可以正常运行在Gingerbread上了。

但是又有一个问题是，这个方案在Sumsung Galaxy S II上会失败，这可是最流行的运行Gingerbread的手机。

似乎三星对Android做了一些改动，导致这个方案的失败。其它的厂商可能会做同样的事情。所以这个方案的代码应该得更加健壮才行。

经过观察，在GS II上，LinearAlloc的缓冲区地址距离实际要找的地址只有4个字节。于是调整代码，如果在期望的地址没有找到LinearAlloc，那么就在附近几个字节的范围内查找。做这件事需要获取当前线程的内存映射表，确保没有在附近的查找过程中访问到无效地址（否则会立刻导致应用崩溃）。

（以上就是Facebook的故事，[Via](https://www.facebook.com/notes/facebook-engineering/under-the-hood-dalvik-patch-for-facebook-for-android/10151345597798920)


# 2014-08-19  理解AnguarJS中的模板编译

在一开始学习AngularJS的过程中，模板的工作原理可能是最令人难解的问题之一。在其他的框架中，一般来说模板就是一个字符串，在这个字符串中使用一个特殊的表达方式可以嵌入一个传入的外部对象的数据，其本质上就是一个字符串的操作：输入字符串，输出字符串，然后通过`.innerHTML()`把所有搞出来的东西塞到DOM里面去。

很明显AngularJS的模板不是这样做的。它有双向数据绑定，可以完美的在视图中与最新的数据模型同步。它还会自动在`ng-click`上注册事件监听器，甚至可以将视图中输入控件中的数据同步回数据模型。太神奇了！

这个当然不是什么魔术，事实上将一个模板转换为一个带动态绑定和事件监听功能的动态视图，这中间的过程其实很简单。一旦搞明白了这个过程，对于AngularJS应用的理解也会上升一个台阶。

这里有一个模板

&#x20;Name:  # Hello {{yourName}}!

如你所见，这是个最简单的AngularJS的应用页面。

### 反向控制（IoC）

第一个需要了解的概念是"反向控制"（IoC）。AngularJS不需要手工启动应用，而是假定你会遵循一定的规则在代码中放一些基本的设定。

例如，AngularJS会假定HTML代码中有一个带有`ng-app`属性的元素，表示你的应用的"根"节点。它表示AngularJS可以接管这个节点以及之下所有子节点。这一点可以保证AngularJS可以和其他的JavaScript框架共存。当AngularJS启动应用的时候，它会遍历DOM内的节点，并查找这个属性，这个在AngularJS术语中叫"指令"（Directive）。注意这个`ng-app`指令可以放在任何DOM节点上，包括`<html>`和`<body>`标签。

AngularJS还会假定其他的框架不会接触到它的"根"节点下面的所有内容，否则可能会导致未知行为，或者打破双向数据绑定和事件监听器。

### 深入模板

```
<label>Name:</label>
<input type="text" ng-model="yourName" placeholder="Enter a name here">
```

## Hello !

这一部分是模板。"根"节点下面的所有内容都会被视为模板，之后将会被编译。模板与数据模型（`$scope`）和控制器一起构成了动态视图，用户可以在浏览器中看到并与之交互。本例中没有控制器和明确的数据模型，但AngularJS会在后台创建对应的内容。

所有的AngularJS都会有一个`$rootScope`，它会持有所有子scope的引用，它自身保存的数据也可以被所有的`$scope`共享。本例中，由于没有明确指定控制器，所以模板会被绑定到这个`$rootScope`上，并设置一个子`$scope`。

在模板中，给输入控件绑定了一个模型（由ng-model指明的yourName），AngularJS会在这个数据控件上注册一个键盘事件监听器，并且将所有输入到这个控件中的内容自动保存到`$rootScope.yourName`，但我们并没有也并不需要声明这个变量。`scope`中的变量会被初始化为`undefined`，就像正常的JavaScript对象一样。

模板中还用到了`$interpolate`指令，表示为两对大花括号 `{{ yourName }}`。这个指令会创建一个`$watcher`，监听模型的变化，并在发生变化的时候将数据更新到视图上。

接下来看看如何将一个DOMElement转换成动态视图。

### 编译模板

AngularJS找到`ng-app`指令之后，它会启动并创建一个新的`$rootScope`，开始编译"根"节点（带`ng-app`的那个节点）下面所有的子节点。

接下来发生的事情分为两步，编译和链接（这里借用了编译原理中的术语，但本质上是一样的）。更深的介绍可以参考文档[AngularJS documentation on the HTML Compiler](http://docs.angularjs.org/guide/compiler)。

首先，Angular的`$compile`函数将传入的DOMElement作为输入。这一点与其它的框架很不一样，AngularJS会使用浏览器的API遍历整个DOM，而其它模板只是在做字符串替换。如果需要用字符串作为传入的模板，则先要用`$angular.element`函数将字符串转为DOMElement。这个函数实际上就是jQuery中的`$()`函数的。

`$compile`函数会遍历DOM，并查找"指令"（Directive），将找到的每个"指令"添加到一个列表中，整个DOM遍历完成后，再将列表中的"指令"按照"优先级"排序。之后，执行每个"指令"自己的`compile`函数，让"指令"有机会去修改DOM。每个指令的`compile`函数会返回一个"链接"函数，该函数会被拼接成一个完整的链接函数，并被返回。

接下来，AngularJS会执行返回的"链接"函数，对应的scope会被传入到这个执行过程中。这一步中，所有的子"链接"函数都会被执行，并绑定在同一个scope上，或依照"指令"的设定创建一个新的scope。所有的"链接"函数执行完毕后，每个"链接"函数都会返回一组DOMElement，这些DOMElement已经完成数据绑定和事件监听，AngularJS会将它们添加到父节点。

上述过程的伪代码可以表示如下

```
var $compile = ...; //注入到你的代码
var $rootScope = ...; //注入
var parent = ...; //编译过的模板内容会被添加到该DOMElement下
var template = ...; //我们的模板的DOMElement

var linkFn = $compile(template); //编译模板，返回"链接"函数

var element = linkFn(scope); //"链接"，返回处理好的DOMElement

parent.appendChild(element); //将处理好的DOMElement添加到父节点
```

### 总结

基本就是这些了。如你所见，AngularJS完全不同于其他的模板系统，它约定了一些规则，并基于这些规则做了一些假定，之后你就不必将精力耗费在启动代码上，而可以放在真正的应用上。这只是AngularJS的一部分，之后会继续讲讲`$watch`和`$digest`如何保证视图刷新。

via [src](http://daginge.com/technology/2014/03/04/understanding-template-compiling-in-angularjs/)


# 2014-08-13  在Android中使用OSGi框架(Knopflerfish)

OSGi是用Java实现的一个模块化服务平台。每个模块被称之为Bundle，可以提供服务，也可以在不重启OSGi框架的情况下被安装或卸载。Knopflerfish是一个完全开源的OSGi R4.2标准的实现。

Android能够无缝的集成现有的Java代码，尽管使用的是与现有java字节码格式不兼容的虚拟机Dalvik，但是它可以轻松的将现有的jar文件和类转换为Android使用的Dalvik字节码格式。由于OSGi框架自身和Bundle都只是普通的jar文件，所以他们都应该可以在Android上运行。事实上，大多数时候是没问题的。

> 注意：这里只是如何在Android中嵌入OSGi系列文章的第一部分

如果只是想让OSGi框架在Android上跑起来，那么只需要编译Knopflerfish的Android版本，复制到设备上，然后就可以通过命令行启动起来了（见[上一篇文章](http://log4think.com/use-apache-felix-in-android/)和[这里](http://log4think.com/use-apache-felix-in-android/)）。

现在来看看如何将Knopflerfish和一系列Bundle嵌入到Android应用中，并且从应用中启动和管理OSGi框架和Bundle。

通过代码启动OSGi大概需要下面这几个步骤：

1. 创建framework实例（通过framework factory）
2. 初始化framework
3. 设置initlevel，并启动/安装 bunldes
4. 为所有的initlevel重复前述步骤
5. 设置startlevel
6. 启动framework

## 嵌入Framework

现在创建一个Android应用，包含一个Actviity。然后在app中引入`framework.jar`，这样就可以通过一个`FrameworkFactory`创建OSGi的framework实例了。

```
import org.knopflerfish.framework.FrameworkFactoryImpl;
import org.osgi.framework.BundleException;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.launch.FrameworkFactory;
...
private Framework mFramework;
...
Dictionary fwprops = new Hashtable();
// add any framework properties to fwprops
FrameworkFactory ff = new FrameworkFactoryImpl();
mFramework = ff.newFramework(fwprops);
try {
    mFramework.init();
} catch (BundleException be) {
    // framework initialization failed
}
```

引入的jar文件不需要dex化，后面build的时候会自动完成这一步的。

## Bundle文件dex化

现在bundle的jar文件可以被添加到应用中了，可以作为raw资源放在`res/raw`下面，也可以放在`assets/bunldes`。后面这种方式有一个优势：不需要被重命名，而且res资源的名字数量是有限的。

Bundle的jar文件需要被转换成dex格式，下面这个简单的脚本可以完成这件事：

```
dexify() {
    for f in $*; do
        tmpdir="`mktemp -d`"
        tmpfile="${tmpdir}/classes.dex"
        dx --dex --output=${tmpfile} ${f}
        aapt add ${f} ${tmpfile}
        rm -f ${tmpfile}
        rmdir ${tmpdir}
    done
}
```

然后就可以通过命令`dexify assets/bundles/*`将bundles转换为dex文件。如果是按照Knopflerfish的[教程](http://www.knopflerfish.org/releases/3.2.0/docs/android_dalvik_tutorial.html)编译的Knopflerfish，那么不需要将这些bundle的jar文件dex化，但是必须从knopflerfish的framework.jar文件中去掉classes.dex。

## 安装、启动Bundles

下面这段代码可以帮助启动bundle，并设置initlevel/startlevel。

```
private void startBundle(String bundle) {
    Log.d(TAG, "starting bundle " + bundle);
    InputStream bs;
    try {
        bs = getAssets().open("bundles/" + bundle);
    } catch (IOException e) {
        Log.e(TAG, e.toString());
        return;
    }

    long bid = -1;
    Bundle[] bl = mFramework.getBundleContext().getBundles();
    for (int i = 0; bl != null && i < bl.length; i++) {
        if (bundle.equals(bl[i].getLocation())) {
            bid = bl[i].getBundleId();
        }
    }

    Bundle b = mFramework.getBundleContext().getBundle(bid);
    if (b == null) {
        Log.e(TAG, "can't start bundle " + bundle);
        return;
    }

    try {
        b.start(Bundle.START_ACTIVATION_POLICY);
        Log.d(TAG, "bundle " + b.getSymbolicName() + "/" + b.getBundleId() + "/"
                + b + " started");
    } catch (BundleException be) {
        Log.e(TAG, be.toString());
    }

    try {
        bs.close();
    } catch (IOException e) {
        Log.e(TAG, e.toString());
    }
}

private void installBundle(String bundle) {
    Log.d(TAG, "installing bundle " + bundle);
    InputStream bs;
    try {
        bs = getAssets().open("bundles/" + bundle);
    } catch (IOException e) {
        Log.e(TAG, e.toString());
        return;
    }

    try {
        mFramework.getBundleContext().installBundle(bundle, bs);
        Log.d(TAG, "bundle " + bundle + " installed");
    } catch (BundleException be) {
        Log.e(TAG, be.toString());
    }

    try {
        bs.close();
    } catch (IOException e) {
        Log.e(TAG, e.toString());
    }
}

private void setStartLevel(int startLevel) {
    ServiceReference sr = mFramework.getBundleContext()
        .getServiceReference(StartLevel.class.getName());
    if (sr != null) {
        StartLevel ss =
            (StartLevel)mFramework.getBundleContext().getService(sr);
        ss.setStartLevel(startLevel);
        mFramework.getBundleContext().ungetService(sr);
    } else {
        Log.e(TAG, "No start level service " + startLevel);
    }
}

private void setInitlevel(int level) {
    ServiceReference sr = mFramework.getBundleContext()
        .getServiceReference(StartLevel.class.getName());
    if (sr != null) {
        StartLevel ss =
            (StartLevel)mFramework.getBundleContext().getService(sr);
        ss.setInitialBundleStartLevel(level);
        mFramework.getBundleContext().ungetService(sr);
        Log.d(TAG, "initlevel " + level + " set");
    } else {
        Log.e(TAG, "No start level service " + level);
    }
}
```

现在可以安装并启动bundle了

```
setInitlevel(1);
installBundle("event_all-3.0.4.jar");
startBundle("event_all-3.0.4.jar");
// install/start other bundles...

setStartLevel(10);

try {
    mFramework.start();
} catch (BundleException be) {
    Log.e(TAG, be.toString());
    // framework start failed
}

Log.d(TAG, "OSGi framework running, state: " + mFramework.getState());
```

## 问题

如果你按照上文所述一步步做下来了，你可能会发现还是没法跑起来。由于framework的classloader是在运行期加载的bundle文件，Dalvik虚拟机会试图将优化过的dex类文件放到一个系统目录下面`/data/dalvik-cache`，但是没有root权限的普通应用程序是不能写入那儿的。

下回将如何解决这个问题。

via [source](http://nilvec.com/embedding-osgi-into-an-android-application-part-1.html)


# 2014-08-13  在Android中使用OSGi框架(Apache Felix)

本文描述了如何在Android中使用Apache Felix

## Dalvik VM

Android允许开发者使用Java开发应用，但出于某些原因，代码实际是运行在名为Dalvik的一个针对移动设备平台的虚拟机上，而不是标准的Java虚拟机。Dalvik并不使用标准的Java字节码格式，而是使用Android SDK中的一个工具`dx`将由Java编译出来的类文件转换为另外一种类文件格式(.dex格式)。这个转换是在编译期完成的。

## 准备Bundles

虽然Felix从1.0.3开始内置了Android的支持，但是想要成功的让它跑起来还是需要费点力气。我们仍然需要安装Android SDK，并且PATH环境变量中包含Android SDK的工具目录`<android_SDK_HOME>/tools`。

*第一步：* 每一个用到的Jar文件，无论是Felix库还是你自己写的Bundle，都需要包含对应的DEX。也就说，需要为jar文件创建对应的dex文件：

```
dx --dex --output=classes.dex JAR_file.jar
```

然后将这个dex文件加入到jar文件中：

```
aapt add JAR_file.jar classes.dex
```

*第二步：* 将处理过的jar文件传到模拟器（或真机）中：

```
adb push JAR_file.jar path_emulator/JAR_file.jar
```

*第三步：* 以演示代码为例，准备Felix的jar文件和Bundle的jar文件：

目录结构

```
osgi-android: /
\- bin
\- bundle
\- conf
\- felix.sh
```

准备Felix jar文件

```
export PATH=<path-to-android>/tools:$PATH
cd bin
dx --dex --output=classes.dex felix.jar
aapt add felix.jar classes.dex
```

准备bundle的jar文件

```
cd bundle
dx --dex --output=classes.dex     org.apache.felix.shell-1.0.0.jar
aapt add org.apache.felix.shell-1.0.0.jar classes.dex
dx --dex --output=classes.dex org.apache.felix.shell.tui-1.0.0.jar
aapt add org.apache.felix.shell.tui-1.0.0.jar classes.dex
dx --dex --output=classes.dex EnglishDictionary.jar
aapt add EnglishDictionary.jar classes.dex
dx --dex --output=classes.dex FrenchDictionary.jar
aapt add FrenchDictionary.jar classes.dex
dx --dex --output=classes.dex SpellChecker.jar
aapt add SpellChecker.jar classes.dex
```

复制到模拟器中

```
cd osgi-android
find * -type f -exec adb push {} /data/felix/{} \;
```

## 启动Felix

完成上面的步骤之后，现在可以准备在Android上启动Felix和Bundle了

```
adb shell
cd /data/felix
sh felix.sh
```

`felix.sh`是一个shel脚本，用于启动Felix main class。

```
/system/bin/dalvikvm -Xbootclasspath:/system/framework/core.jar -classpath bin/felix.jar org.apache.felix.main.Main
```

如果一切顺利，现在你应该能看到Felix的命令行shell了。输入`help`可以看到命令说明。

现在可以安装EnglishDictionary,FrenchDictionary和SpellChecker来试试看Felix是否工作正常。这里有几个Apache Felix的示例：[Apache Felix 教程例子2](http://felix.apache.org/site/apache-felix-tutorial-example-2.html)，[Apache Felix 教程例子2b](http://felix.apache.org/site/apache-felix-tutorial-example-2b.html)，[Apache Felix 教程例子5](http://felix.apache.org/site/apache-felix-tutorial-example-5.html)。

* *EnglishDictionary* - 提供一个字典服务，支持下面几个词"welcome", "to", "the", "osgi", "tutorial"
* *FrenchDictionary* -  提供一个字典服务，支持下面几个词"bienvenue", "au", "tutoriel", "osgi"
* *SpellChecker* - 提供一个拼写检查服务，可以检查第一个英文此单的几个单词

在Felix Shell中启动Bundle

```
start file:bundle/EnglishDictionary.jar
start file:bundle/FrenchDictionary.jar
start file:bundle/SpellChecker.jar
```

## 嵌入Felix

Apache Felix也可以被集成到Android的应用中。只需要在Activity的onCreate中嵌入Felix，然后用上面的办法启动bundle即可。

## 下载

上面的演示代码[在此下载](http://felix.apache.org/site/documentation.data/osgi-android%20-%20felix%201.4,%20android%20SDK%201.0.zip)

via [source](http://felix.apache.org/site/apache-felix-framework-and-google-android.html)


# 2014-07-30  3rd-party apt-key list for Ubuntu

## Google Chrome

```
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
sudo sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
sudo apt-get update
sudo apt-get install google-chrome-stable
```

## JDK 1.7

```
echo "deb http://ppa.launchpad.net/webupd8team/java/ubuntu precise main" | tee -a /etc/apt/sources.list
echo "deb-src http://ppa.launchpad.net/webupd8team/java/ubuntu precise main" | tee -a /etc/apt/sources.list
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys EEA14886
apt-get update
apt-get install oracle-java7-installer
```

## NodeJS

```
sudo add-apt-repository ppa:chris-lea/node.js 
```

## MongoDB

```
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list
```


# 2014-07-21  'Failed to clone a large git repository: The remote end hung up unexpectedly'

When git clone a large repository sometimes it give you the error message like this:

```
$ git clone ssh://xxx@xxx:29418/xxx
Cloning into 'xxx'...
remote: Counting objects: 356213, done
remote: Finding sources: 100% (356213/356213)
Corrupted MAC on input. (277847/356213), 123.59 MiB | 3.86 MiB/s
Finished discarding for xxx
fatal: The remote end hung up unexpectedly
fatal: early EOF
fatal: index-pack failed
```

If you got this error message, you might want to try run the following command to fix it

```
git config --global http.postBuffer 524288000
git config --global --add core.compression -1
```


# 2014-07-02  genymotion Qt error in Ubuntu

When I was trying to install Genymotion on Ubuntu 14.04, I got this error

```
Installing log handler
Logging activities to file: /home/****
Aborted (core dumped)
```

the log file /home/xx/.Genymotion/genymotion.log has the following message:

```
... [Genymotion] [Fatal] Cannot mix incompatible Qt library (version 0x40806) with this library (version 0x40804)
```

The reason of this error is that Genymotion has its own Qt library which is not compatable with the system Qt library.

To fix this error, we need to let genymotion use system's Qt library. The following commands can fix this issue

```
sudo apt-get install libxi-dev libxmu-dev
# go to your genymotion directory
mkdir QtLibs && mv *Qt*.so* QtLibs
```


# 2014-04-30  可自动安装依赖的Ubuntu离线包安装工具 gdebi

Ubuntu下，通用的在线包管理工具是`apt`，但是对于下载好的离线deb包，我们通常会用`dpkg -i xxx.dev`来安装，但是这样常常会遇到依赖包不存在而无法安装的错误。dpkg不会为我们自动解决包依赖的问题，也不会自动下载安装所依赖的包。 gdebi是一个类似的管理工具，但是会自动下载安装依赖。

对于一个离线包，只需要使用下面的命令即可自动下载安装，包括所有依赖包

```
gdebi package_name.deb
```

可以使用下面这条命令安装这个工具

```
sudo apt-get install gdebi
```

better-package-tool-gdebi


# 2014-04-23  解决搜狗输入法Ubuntu 14.04下黑块状态条

搜狗的Linux输入法正式版发布之时，恰好Ubuntu 14.04 LTS也刚刚发布正式版。尝试安装搜狗的Linux输入法之后，出现了一个小小的问题：输入法状态条是一个黑色块。

我的Linux桌面用的是i3平铺式桌面管理器，多显示器环境下非常好用，[强烈推荐](http://i3wm.org)。除此之外，GUI底层用的是KDE。

做了一番功课之后，结果发现因为搜狗要支持皮肤，所以那个状态条是半透明状态的。而我目前的默认环境下不支持compositor窗口管理器，无法处理半透明窗口的效果，于是就只能显示一个黑色块。

解决办法是安装[Compton](https://github.com/chjj/compton)，以支持窗口的半透明效果。Ubuntu默认情况下没有可用的APT源，有两种办法安装：一个是从源码编译（不赘述），另外一个是使用一个第三方的源。

## 安装第三方源并安装compton

```
sudo apt-add-repository ppa:richardgv/compton
sudo apt-get update && sudo apt-get install compton
```

## 配置compton

编辑文件 `~/.compton.conf`，输入如下内容

```
backend = "glx";
paint-on-overlay = true;
glx-no-stencil = true;
glx-no-rebind-pixmap = true;
vsync = "opengl-swc"; 

# These are important. The first one enables the opengl backend. The last one
# is the vsync method. Depending on the driver you might need to use a
# different method.
# The other options are smaller performance tweaks that work well in most
# cases.
# You can find the rest of the options here:
# https://github.com/chjj/compton/wiki/perf-guide, and here:
# https://github.com/chjj/compton/wiki/vsync-guide

# Shadow
shadow = false;          # Enabled client-side shadows on windows.
no-dock-shadow = true;      # Avoid drawing shadows on dock/panel windows.
no-dnd-shadow = true;       # Don't draw shadows on DND windows.
clear-shadow = true;        # Zero the part of the shadow's mask behind the window (experimental).
shadow-radius = 7;      # The blur radius for shadows. (default 12)
shadow-offset-x = -7;       # The left offset for shadows. (default -15)
shadow-offset-y = -7;       # The top offset for shadows. (default -15)
shadow-exclude = [
    "! name~=''",
    "n:e:Notification",
    "n:e:Plank",
    "n:e:Docky",
    "g:e:Synapse",
    "g:e:Kupfer",
    "g:e:Conky",
    "n:w:*Firefox*",
    "n:w:*Chrome*",
    "n:w:*Chromium*",
    "class_g ?= 'Notify-osd'",
    "class_g ?= 'Cairo-dock'",
    "class_g ?= 'Xfce4-notifyd'",
    "class_g ?= 'Xfce4-power-manager'"
];                                                                                                                                                                                                            

# The shadow exclude options are helpful if you have shadows enabled. Due to
# the way compton draws its shadows, certain applications will have visual
# glitches
# (most applications are fine, only apps that do weird things with xshapes or
# argb are affected).
# This list includes all the affected apps I found in my testing. The "!
# name~=''" part excludes shadows on any "Unknown" windows, this prevents a
# visual glitch with the XFWM alt tab switcher.                                                                                                                                                               

# Fading
fading = true; # Fade windows during opacity changes.
fade-delta = 4; # The time between steps in a fade in milliseconds. (default 10).
fade-in-step = 0.03; # Opacity change between steps while fading in. (default 0.028).
fade-out-step = 0.03; # Opacity change between steps while fading out. (default 0.03).
#no-fading-openclose = true; # Fade windows in/out when opening/closing

detect-client-opacity = true; # This prevents opacity being ignored for some apps. For example without this enabled my xfce4-notifyd is 100% opacity no matter what.

# Window type settings
wintypes:
{
  tooltip = { fade = true; shadow = false; };
};
```

重点是其中 shadow 一项的值设置为 false 。

## 自启动

最后，修改i3配置文件 `~/.i3/config`，在最后加入下面的代码，实现自动启动compton和搜狗输入法

```
# auto start commands
exec --no-startup-id fcitx -r
exec --no-startup-id fcitx-qimpanel
exec --no-startup-id compton -b
```

安装完成了，系统也顺便带了半透明和淡入淡出效果。


# 2014-04-08  Setup Ghost blog system on Ubuntu

## Installation

Make sure you have install all required package

```
sudo apt-get install npm nodejs
```

Goto <https://ghost.org/download/> , download the latest zip file

```
$ wget https://ghost.org/zip/ghost-0.4.2.zip
$ unzip ../ghost-0.4.2.zip -d ghost
$ cd ghost
$ npm install --production
...
$ npm start

> ghost@0.4.2 start /home/simon/downloads/ghost
> node index

Ghost is running in development...
Listening on 127.0.0.1:2368
Url configured as: http://my-ghost-blog.com
Ctrl+C to shut down
```

Now you can browse your own Ghost blog at <http://127.0.0.1:2368>

> It's very possible that you got the below error when you run
>
> ```
> npm install --production
> ```
>
> sqlite3\@2.2.0 install /home/simon/downloads/ghost/node\_modules/sqlite3 node-pre-gyp install --fallback-to-build

```
/usr/bin/env: node: No such file or directory
npm WARN This failure might be due to the use of legacy binary "node"
npm WARN For further explanations, please read
/usr/share/doc/nodejs/README.Debian                                                                              

npm ERR! sqlite3@2.2.0 install: `node-pre-gyp install --fallback-to-build`
npm ERR! `sh "-c" "node-pre-gyp install --fallback-to-build"` failed with 127
npm ERR!
npm ERR! Failed at the sqlite3@2.2.0 install script.
npm ERR! This is most likely a problem with the sqlite3 package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR!     node-pre-gyp install --fallback-to-build
npm ERR! You can get their info via:
npm ERR!     npm owner ls sqlite3
npm ERR! There is likely additional logging output above.

npm ERR! System Linux 3.11.0-13-generic
npm ERR! command "/usr/bin/nodejs" "/usr/bin/npm" "install" "--production"
npm ERR! cwd /home/simon/downloads/ghost
npm ERR! node -v v0.10.15
npm ERR! npm -v 1.2.18
npm ERR! code ELIFECYCLE
npm ERR!
npm ERR! Additional logging details can be found in:
npm ERR!     /home/simon/downloads/ghost/npm-debug.log
npm ERR! not ok code 0
```

This error is caused by the wrong

```
node
```

command symblic link in your system. To fix this, run the following command, then run

```
npm install --production
```

again.

```
sudo update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10
```

It should work now.

## Configuration

After start the Ghost successfully, you need to go to administration page to configure your Blog or write a new post.

First, go to

```
http://127.0.0.1:2368/ghost/
```

. It will redirect to sign up page.

Then, type your inforamtion and 'Sign Up', you will be redirect to the administration page.


# 2014-03-28  Trace a process

For Linux

```
sudo strace -p PID
```

For Mac

```
sudo dtruss -p PID
```


# 2014-03-25  Forecast::IO 599 Internal Exception

Forecast.io provide great public weather data service API. They also provide SDK for many languages which is very friendly to developers.

However if you are using their Perl module Forecast::IO, maybe you will have this error when you try to install it

```
Running make test
PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
t/forecast.t .. # Please enter your Forecast.io API key:
xxxxxxxxxxxxxxxxxxxx
Request to 'https://api.forecast.io/forecast/xxxxxxxxxxxxxxxxxxxx/43.6667,-79.4167,1373241600?units=auto' failed: 599 Internal Exception
# Looks like your test exited with 255 before it could output anything.
t/forecast.t .. Dubious, test returned 255 (wstat 65280, 0xff00)
Failed 6/6 subtests
```

The root cause of this error is Forecast::IO use HTTP::Tiny for HTTP request. And HTTP::Tiny need Net::SSLeay module to access HTTPS url. If the module was not installed HTTP::Tiny would only say 599 error and you can't know the real reason unless you look into the code and try it like below.

```
$ perl -e "use HTTP::Tiny; use Data::Dumper; print Dumper(HTTP::Tiny->new>get('https://api.forecast.io/forecast/xxxxxxxxxxxxxxxxxxxx/43.6667,-79.4167,1373241600?units=auto'));"
$VAR1 = {
          'success' => '',
          'headers' => {
                         'content-type' => 'text/plain',
                         'content-length' => 53
                   },
          'status' => 599,
          'content' => 'Net::SSLeay 1.49 must be installed for https support',
          'reason' => 'Internal Exception',
          'url' => 'https://api.forecast.io/forecast/xxxxxxxxxxxxxxxxxxxx/43.6667,-79.4167,1373241600?units=auto'
        };
```

This is really a confused way to report this error.

Anyway, to fix this issue you only need to install the module Net::SSLeay

```
sudo perl -MCPAN -e 'install Net::SSLeay'
```


# 2014-03-23  Mac Tips

Prevent Mac sleep via command `pmset noidle`


# 2014-01-17  LD\_LIBRARY\_PATH shouldn't contain the current directory

I am trying to build a self-contain GLIBC 2.7. It shows following error when I run `configure`

```
checking whether ranlib is necessary... no
checking LD_LIBRARY_PATH variable... contains current directory
configure: error:
*** LD_LIBRARY_PATH shouldn't contain the current directory when
*** building glibc. Please change the environment variable
*** and run configure again.
```

my LD\_LIBRARY\_PATH is

```
$ echo $LD_LIBRARY_PATH
/home/sliu/opt/lib:/home/sliu/opt/libexec/gcc/x86_64-unknown-linux-gnu/4.8.1:/home/sliu/opt/lib64:/home/sliu/opt/lib64/gcj-4.8.1-14:
```

The error message complains that LD\_LIBRARY\_PATH contain the current directory. However my LD\_LIBRARY\_PATH doesn't contain any "current directory". Actually, it's caused by the path seperate character ":" instead of "current directory". If you search LD\_LIBRARY\_PATH in `configure`, you will find this comments

```
# Test if LD_LIBRARY_PATH contains the notation for the current directory
# since this would lead to problems installing/building glibc.
# LD_LIBRARY_PATH contains the current directory if one of the following
# is true:
# - one of the terminals (":" and ";") is the first or last sign
# - two terminals occur directly after each other
# - the path contains an element with a dot in it
```

Which means, **The LD\_LIBRARY\_PATH can not start or end with character ":".**

So this issue can be fixed by removing the last ':' character in LD\_LIBRARY\_PATH.

解决办法就是，去掉 LD\_LIBRARY\_PATH 最后的那个路径分隔符':'.


# 2014-01-10  Python on vim

\[TOC]

## modeline #\#

You can use **modeline** in python source file to change Vim's setting. Vim will read top and bottom 3 lines comments and use the comment start with vim: as modeline setting. By do this, opener can use same setting to edit this file. This can avoid mixing of space and tab char in source code.

Add the following line to the top or bottom of your python source file.

```
#vim: ai ts=4 sts=4 et sw=4 ft=python
```

## .vimrc #\#

I am using following settings in my `~/.vimrc` for python

```
" Automatic reloading of .vimrc
autocmd! bufwritepost .vimrc source %

" Better copy & paste
" When you want to paste large blocks of code into vim, press F2 before you
" paste. At the bottom you should see ``-- INSERT (paste) --``.

"" set pastetoggle=<F2>
"" set clipboard=unnamed

" Mouse and backspace
"set mouse=a  " on OSX press ALT and click
set bs=2     " make backspace behave like normal again

" Rebind <Leader> key
" I like to have it here becuase it is easier to reach than the default and
" it is next to ``m`` and ``n`` which I use for navigating between tabs.
let mapleader = ","

" Bind nohl
" Removes highlight of your last search
" ``<C>`` stands for ``CTRL`` and therefore ``<C-n>`` stands for ``CTRL+n``
noremap <C-n> :nohl<CR>
vnoremap <C-n> :nohl<CR>
inoremap <C-n> :nohl<CR>

" Quicksave command
"" noremap <C-Z> :update<CR>
"" vnoremap <C-Z> <C-C>:update<CR>
"" inoremap <C-Z> <C-O>:update<CR>

" Quick quit command
"" noremap <Leader>e :quit<CR>  " Quit current window
"" noremap <Leader>E :qa!<CR>   " Quit all windows

" bind Ctrl+<movement> keys to move around the windows, instead of using Ctrl+w + <movement>
" Every unnecessary keystroke that can be saved is good for your health :)
map <c-j> <c-w>j
map <c-k> <c-w>k
map <c-l> <c-w>l
map <c-h> <c-w>h

" easier moving between tabs
map <Leader>n <esc>:tabprevious<CR>
map <Leader>m <esc>:tabnext<CR>

" map sort function to a key
vnoremap <Leader>s :sort<CR>

" easier moving of code blocks
" Try to go into visual mode (v), thenselect several lines of code here and
" then press ``>`` several times.
"" vnoremap < <gv  " better indentation
"" vnoremap > >gv  " better indentation

" Show whitespace
" MUST be inserted BEFORE the colorscheme command
"" autocmd ColorScheme * highlight ExtraWhitespace ctermbg=red guibg=red
"" au InsertLeave * match ExtraWhitespace /\s\+$/

" Color scheme
" mkdir -p ~/.vim/colors && cd ~/.vim/colors
" wget -O wombat256mod.vim http://www.vim.org/scripts/download_script.php?src_id=13400
set t_Co=256
color wombat256mod

" Enable syntax highlighting
" You need to reload this file for the change to apply
filetype off
filetype plugin indent on
syntax on

" Showing line numbers and length
set number  " show line numbers
set tw=79   " width of document (used by gd)
set nowrap  " don't automatically wrap on load
set fo-=t   " don't automatically wrap text when typing
if version > 703
    set colorcolumn=80
endif
highlight ColorColumn ctermbg=233

" easier formatting of paragraphs
vmap Q gq
nmap Q gqap

" Useful settings
set history=700
set undolevels=700

" Real programmers don't use TABs but spaces
set tabstop=4
set softtabstop=4
set shiftwidth=4
set shiftround
set expandtab

" Make search case insensitive
set hlsearch
set incsearch
set ignorecase
set smartcase

" Disable stupid backup and swap files - they trigger too many events
" for file system watchers
set nobackup
set nowritebackup
set noswapfile

" Setup Pathogen to manage your plugins
" mkdir -p ~/.vim/autoload ~/.vim/bundle
" curl -so ~/.vim/autoload/pathogen.vim https://raw.github.com/tpope/vim-pathogen/HEAD/autoload/pathogen.vim
" Now you can install any plugin into a .vim/bundle/plugin-name/ folder
call pathogen#infect()

" ============================================================================
" Python IDE Setup
" ============================================================================

" Settings for vim-powerline
" cd ~/.vim/bundle
" git clone git://github.com/Lokaltog/vim-powerline.git
set laststatus=2

" Settings for ctrlp
" cd ~/.vim/bundle
" git clone https://github.com/kien/ctrlp.vim.git
let g:ctrlp_max_height = 30
set wildignore+=*.pyc
set wildignore+=*_build/*
set wildignore+=*/coverage/*

" Settings for jedi-vim
" sudo easy_install jedi
" cd ~/.vim/bundle
" git clone git://github.com/davidhalter/jedi-vim.git
let g:jedi#related_names_command = "<leader>z"
let g:jedi#popup_on_dot = 0
let g:jedi#popup_select_first = 0
map <Leader>b Oimport ipdb; ipdb.set_trace() # BREAKPOINT<C-c>

" Better navigating through omnicomplete option list
" See http://stackoverflow.com/questions/2170023/how-to-map-keys-for-popup-menu-in-vim
set completeopt=longest,menuone
function! OmniPopup(action)
    if pumvisible()
        if a:action == 'j'
            return "\<C-N>"
        elseif a:action == 'k'
            return "\<C-P>"
        endif
    endif
    return a:action
endfunction

inoremap <silent><C-j> <C-R>=OmniPopup('j')<CR>
inoremap <silent><C-k> <C-R>=OmniPopup('k')<CR>

" Python folding
" mkdir -p ~/.vim/ftplugin
" wget -O ~/.vim/ftplugin/python_editing.vim http://www.vim.org/scripts/download_script.php?src_id=5492
set nofoldenable
```


# 2013-11-12  ibus-pinyin doesn't work in KUbuntu 13.10

ibus and ibus-pinyin doesn't work in KUbuntu 13.10. It looks like this

```
$ ibus-setup
ERROR:root:Could not find any typelib for Gtk
Traceback (most recent call last):
  File "/usr/share/ibus/setup/main.py", line 29, in <module>
    from gi.repository import Gtk
ImportError: cannot import name Gtk
```

The problem is a dependency package is missing. This problem can be solved by this command: `sudo apt-get install gir1.2-gtk-3.0`.


# 2013-11-11  phpMyAdmin login error to remote server

I have a fresh install of Ubuntu 13.10 and phpMyAdmin version 4:4.0.6-1 . I add a remote server in config.inc.php for multiple server management. But phpMyAdmin failed to login to remote server.

there are following error logs in `/var/log/apache2/error.log`

```
PHP Fatal error:  Call to a member function getPresence() on a non-object in /usr/share/phpmyadmin/libraries/navigation/NavigationTree.class.php on line 1046
```

The code of `NavigationTree.class.php` at 1046 are

```
1039         } else if (($node->type == Node::CONTAINER
1040             && (   $node->real_name == 'tables'
1041                 || $node->real_name == 'views'
1042                 || $node->real_name == 'functions'
1043                 || $node->real_name == 'procedures'
1044                 || $node->real_name == 'events')
1045             )
1046             && $node->realParent()->getPresence($node->real_name) >= (int)$GLOBALS['cfg']    ['NavigationTreeDisplayItemFilterMinimum']
1047         ) {
```

I am not sure the root cause of this issue. But I can fix this error by adding a check for if statment

```
1039         } else if (($node->type == Node::CONTAINER
1040             && (   $node->realParent )
1041             && (   $node->real_name == 'tables'
1042                 || $node->real_name == 'views'
1043                 || $node->real_name == 'functions'
1044                 || $node->real_name == 'procedures'
1045                 || $node->real_name == 'events')
1046             )
1047             && $node->realParent()->getPresence($node->real_name) >= (int)$GLOBALS['cfg']['NavigationTreeDisplayItemFilterMinimum']
1048         ) {
```

That's it.


# 2013-11-04  Get SNMP(v3) working on Ubuntu 12.04

I am running an Ubuntu 12.04 as working environment on my local machine. Ubuntu 12.04 is latest LTS (Long Term Support) version so that it's a good choice for a server. I need to install SNMP on my machine recently and I install it by following command

```
sudo apt-get install snmpd snmp
```

However it seems doesn't work. After checking the system log, I found the following errors in `/var/log/syslog`

```
 snmpd[8461]: /etc/snmp/snmpd.conf: line 90: Error: Already have an entry for this process.
 snmpd[8461]: /etc/snmp/snmpd.conf: line 92: Error: Already have an entry for this process.
 snmpd[8461]: /etc/snmp/snmpd.conf: line 94: Error: Already have an entry for this process.
 snmpd[8461]: /etc/snmp/snmpd.conf: line 106: Error: includeAllDisks already specified.
 snmpd[8461]: /etc/snmp/snmpd.conf: line 106: Error: #011ignoring: includeAllDisks 10%
 snmpd[8461]: error on subcontainer '' insert (-1)
 snmpd[8461]: error on subcontainer '' insert (-1)
 snmpd[8461]: /etc/snmp/snmpd.conf: line 146: Error: duplicate trigger name
 snmpd[8461]: error on subcontainer '' insert (-1)
 snmpd[8461]: error on subcontainer '' insert (-1)
 snmpd[8461]: /etc/snmp/snmpd.conf: line 146: Error: duplicate trigger name
 snmpd[8461]: error on subcontainer '' insert (-1)
 snmpd[8461]: error on subcontainer '' insert (-1)
 snmpd[8461]: /etc/snmp/snmpd.conf: line 146: Error: duplicate trigger name
 snmpd[8461]: error on subcontainer '' insert (-1)
 snmpd[8461]: error on subcontainer '' insert (-1)
 snmpd[8461]: /etc/snmp/snmpd.conf: line 146: Error: duplicate trigger name
 snmpd[8461]: error on subcontainer '' insert (-1)
 snmpd[8461]: error on subcontainer '' insert (-1)
 snmpd[8461]: /etc/snmp/snmpd.conf: line 146: Error: duplicate trigger name
 snmpd[8461]: error on subcontainer '' insert (-1)
 snmpd[8461]: error on subcontainer '' insert (-1)
 snmpd[8461]: /etc/snmp/snmpd.conf: line 146: Error: duplicate trigger name
 snmpd[8461]: error on subcontainer '' insert (-1)
 snmpd[8461]: /etc/snmp/snmpd.conf: line 146: Error: duplicate trigger name
 snmpd[8461]: /etc/snmp/snmpd.conf: line 148: Error: duplicate trigger name
 snmpd[8461]: /etc/snmp/snmpd.conf: line 148: Error: duplicate trigger name
 snmpd[8461]: duplicate table data attempted to be entered. row exists
 snmpd[8461]: Failed to register extend entry 'test1' - possibly duplicate name.
 snmpd[8461]: duplicate table data attempted to be entered. row exists
 snmpd[8461]: Failed to register extend entry 'test2' - possibly duplicate name.
 snmpd[8461]: Turning on AgentX master support.
 snmpd[8461]: Error opening specified endpoint "udp:127.0.0.1:161"
 snmpd[8461]: Server Exiting with code 1
```

The content of `/etc/snmp/snmpd.conf` is like

```
 89                                # At least one  'mountd' process
 90 proc  mountd
 91                                # No more than 4 'ntalkd' processes - 0 is OK
 92 proc  ntalkd    4
 93                                # At least one 'sendmail' process, but no more than 10
 94 proc  sendmail 10 1
 95
 96 #  Walk the UCD-SNMP-MIB::prTable to see the resulting output
 97 #  Note that this table will be empty if there are no "proc" entries in the snmpd.conf file
 98
 99
100 #
101 #  Disk Monitoring
102 #
103                                # 10MBs required on root disk, 5% free on /var, 10% free on all other disks
104 disk       /     10000
105 disk       /var  5%
106 includeAllDisks  10%

145                                    # generate traps on UCD error conditions
146 defaultMonitors          yes
147                                    # generate traps on linkUp/Down
148 linkUpDownNotifications  yes
149
150
```

So let's fix the problem. I comment the snmpd.conf configurations appeared in syslog, then use command `cat /etc/snmp/snmpd.conf | grep -v '#' | grep -v '^\s*$'` to get a clean version.

```
view   systemonly  included   .1.3.6.1.2.1.1
view   systemonly  included   .1.3.6.1.2.1.25.1
rocommunity public  default    -V systemonly
rouser   authOnlyUser
sysLocation    Sitting on the Dock of the Bay
sysContact     Me <me@example.org>
sysServices    72
disk       /     10000
disk       /var  5%
load   12 10 5
trapsink     localhost public
iquerySecName   internalUser
rouser          internalUser
master          agentx
createUser testuser MD5 "testpasswd"
rouser testuser auth
```

After updated `/etc/snmpd/snmpd.conf` and `/etc/init.d/snmpd restart`, now it's working.

```
$ snmpwalk -v 3 -l authNoPriv -a MD5 -u testuser -A testpasswd 127.0.0.1:161 sysDescr
SNMPv2-MIB::sysDescr.0 = STRING: Linux simon-desktop 3.9.3-generic #19-Ubuntu SMP Wed Oct 9 16:20:46 UTC 2013 x86_64
```

BTW, The [i3](http://i3wm.org) is the best tiling window manager I have ever used.


# 2013-10-30  Accessing Facebook by LWP

Usually, we can get the content of a url by the following code in Perl

```
perl -MLWP::Simple -e 'getprint "http://www.google.com"';
```

But if you try to use it to access Facebook, like this

```
perl -MLWP::Simple -e 'getprint "http://www.facebook.com"';
```

you will get a page "Upadte your browser" instead of the real one. That's because Facebook doesn't like the default UserAgent of LWP or WWW::Curl.

The simplest way to solve it is to change the default LWP user agent string to your own string.

```
perl -e 'use LWP::Simple qw($ua get); $ua->agent("My agent/1.0"); print get "http://www.facebook.com";'
```


# 2013-10-17  Log4perl多个Appender重复输出日志的问题解决办法

Perl开发中会经常用到Log4perl来输出日志，例如有类似如下配置：

```
log4perl.rootLogger = DEBUG, Screen
log4perl.logger.Utils = DEBUG, Screen

log4perl.appender.Logfile = Log::Log4perl::Appender::File
log4perl.appender.Logfile.filename = test.log
log4perl.appender.Logfile.layout = Log::Log4perl::Layout::PatternLayout
log4perl.appender.Logfile.layout.ConversionPattern = %r %F %L %m%n

log4perl.appender.Screen = Log::Log4perl::Appender::Screen
log4perl.appender.Screen.stderr = 1
log4perl.appender.Screen.layout = Log::Log4perl::Layout::PatternLayout
log4perl.appender.Screen.layout.ConversionPattern = %M | %m%n
```

结果发现Utils中的消息，一次debug调用会输出两条同样的消息。Google一查发现还很常见，老祖宗Log4j下面也有类似的问题。作者在[FAQ中](http://log4perl.sourceforge.net/releases/Log-Log4perl/docs/html/Log/Log4perl/FAQ.html#a6c81)提到了这个问题，解决办法除了与Log4j类似的解决方案之外，Log4perl还有一个简单的办法，只要在配置加入一行

```
log4perl.oneMessagePerAppender = 1 
```


# 2013-04-19  How to enable ORMLite internal log on Android

When using ORM system sometimes we want to what SQLs are executed at background to understand how it works. The ORMLite support several different log system, like SLF4J,COMMONS\_LOGGING, LOG4J and ANDROID native log android.util.Log. It will use Android log for internal log by default. In code `com.j256.ormlite.android.AndroidLog` method `isLevelEnabledInternal`, it will determinate if log is enabled by `Log.isLoggable`

```
private final static String ALL_LOGS_NAME = "ORMLite";
...
return Log.isLoggable(className, androidLevel) || Log.isLoggable(ALL_LOGS_NAME, androidLevel);
```

That means there are two ways to enable ORMLite internal log.

1. You can change the default level by setting a system property: 'setprop log.tag. ' Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will turn off all logging for your tag.
2. You can also create a local.prop file that with the following in it: 'log.tag.=' and place that in /data/local.prop.

The YOUR\_LOG\_TAG could be any ORMLite short class name or "ORMLite" for all of them. So that you can enable ORMLite internal log like this:

```
$ adb shell
# setprop log.tag.ORMLite DEBUG
```

Notice that setprop is temporary until you reboot your device, even on rooted phones. You can persist properties through reboots if you write them into local.prop which is only possible on rooted phones.


# 2013-02-20  Bash Shortcuts

CTRL Key Bound

```
Ctrl + a - Jump to the start of the line
Ctrl + b - Move back a char
Ctrl + c - Terminate the command
Ctrl + d - Delete from under the cursor
Ctrl + e - Jump to the end of the line
Ctrl + f - Move forward a char
Ctrl + k - Delete to EOL
Ctrl + l - Clear the screen
Ctrl + r - Search the history backwards
Ctrl + R - Search the history backwards with multi occurrence
Ctrl + u - Delete backward from cursor
Ctrl + xx - Move between EOL and current cursor position
Ctrl + x @ - Show possible hostname completions
Ctrl + z - Suspend/ Stop the command 
```

ALT Key Bound

```
Alt + < - Move to the first line in the history
Alt + > - Move to the last line in the history
Alt + ? - Show current completion list
Alt + * - Insert all possible completions
Alt + / - Attempt to complete filename
Alt + . - Yank last argument to previous command
Alt + b - Move backward
Alt + c - Capitalize the word
Alt + d - Delete word
Alt + f - Move forward
Alt + l - Make word lowercase
Alt + n - Search the history forwards non-incremental
Alt + p - Search the history backwards non-incremental
Alt + r - Recall command
Alt + t - Move words around
Alt + u - Make word uppercase
Alt + backspace - Delete backward from cursor 
```

More Special Key bindings

Here 2T means Press TAB twice. And $ is the bash prompt.

```
$ 2T - Display all available commands(common)
$ string 2T - Display all available commands starting with string.
$ /2T - Show entire directory structure including hidden ones.
$ 2T - Show only sub-directories inside including hidden ones.
$ *2T - Show only sub-directories inside excluding hidden ones.
$ ~2T - Show all present users on system from "/etc/passwd"
$ $2T - Show all sys variables
$ @2T - Show all entries from "/etc/hosts"
$ =2T - List output like ls or dir
```


# 2013-02-20  '"adb shell dumpsys" parameter list'

Android开发中，常常可以用`adb shell dumpsys`这条命令来dump出系统运行时的状态信息，例如可以这样来察看某个应用的内存使用信息

```
adb shell dumpsys meminfo com.google.android.apps.maps
```

察看TaskStack

```
adb shell dumpsys activity activities
```

察看Alarm列表

```
adb shell dumpsys alarm
```

如果在linux下配合 watch 命令更是可以自动刷新实时察看。这条命令的用法是 `adb shell dumpsys SERVICE [option] [process name]`，可用的SERVICE列表：

```
$ adb shell dumpsys | grep 'DUMP OF SERVICE' | awk '{print $4}' | tr -d ':'
CustomFrequencyManagerService
DirEncryptService
SYSSCOPE
SecTVOutService
SurfaceFlinger
TvoutService_C
accessibility
account
activity
alarm
apn_settings_policy
application_policy
apppermission_control_policy
appwidget
audio
backup
battery
batteryinfo
bluetooth
bluetooth_a2dp
bluetooth_avrcp
bluetooth_policy
browser_policy
clipboard
clipboardEx
com.orange.authentication.simcard
commontime_management
connectivity
content
country_detector
cpuinfo
date_time_policy
dbinfo
device_info
device_policy
devicestoragemonitor
diskstats
drm.drmManager
dropbox
eas_account_policy
edm_proxy
email_account_policy
email_policy
enterprise_policy
enterprise_vpn_policy
entropy
firewall_policy
gfxinfo
hardware
input
input_method
iphonesubinfo
isms
kioskmode
location
location_policy
lock_settings
mdm.remotedesktop
media.audio_flinger
media.audio_policy
media.camera
media.player
meminfo
mini_mode_app_manager
misc_policy
motion_recognition
mount
netpolicy
netstats
network_management
nfc
notification
package
password_policy
permission
phone
phone_restriction_policy
phoneext
power
remoteinjection
restriction_policy
roaming_policy
samplingprofiler
samsung.facedetection_service
scheduling_policy
search
security_policy
sensorservice
serial
servicediscovery
simphonebook
sip
statusbar
telephony.registry
textservices
throttle
tvoutservice
uimode
updatelock
usagestats
usb
vibrator
voip
vpn_policy
wallpaper
wfd
wifi
wifi_policy
wifip2p
window
```

此外，某些服务还支持如下参数

```
ACTIVITY MANAGER PENDING INTENTS (adb shell dumpsys activity intents)
ACTIVITY MANAGER BROADCAST STATE (adb shell dumpsys activity broadcasts)
ACTIVITY MANAGER CONTENT PROVIDERS (adb shell dumpsys activity providers)
ACTIVITY MANAGER SERVICES (adb shell dumpsys activity services)
ACTIVITY MANAGER ACTIVITIES (adb shell dumpsys activity activities)
ACTIVITY MANAGER RUNNING PROCESSES (adb shell dumpsys activity processes)
INPUT MANAGER (adb shell dumpsys input)
WINDOW MANAGER LAST ANR (adb shell dumpsys window lastanr)
WINDOW MANAGER POLICY STATE (adb shell dumpsys window policy)
WINDOW MANAGER SESSIONS (adb shell dumpsys window sessions)
WINDOW MANAGER TOKENS (adb shell dumpsys window tokens)
WINDOW MANAGER WINDOWS (adb shell dumpsys window windows)
```


# 2013-01-27  循环有序数组的二分查找

亚马逊面试题：给定一个循环有序数组，形如 `[9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0, 1, 2, 3, 4, 5, 6, 7, 8]` ，在其中查找某个元素。

最简单直接朴素的算法，从头至尾依次遍历，复杂度O(n)。如果只能做到这样的话，就不必再说了。 对于普通的有序数组，通常会采用二分法查找元素，这样复杂度可以降低至O(logN)。但是这个循环的有序数组的最大最小部分在数组中间而不是在头尾，是否还可以采用二分法查找呢？ 普通的二分法思路是：在中间取一元素，与要查找的目标元素对比，若中间元素较大，则在左半部继续二分法查找；反之，若中间元素较小，则在右半部继续二分法查找。如下：

```
/*
   p is the start index,
   q is the end index,
   x is the target data
*/
int binary_search(int x, int p, int q, int *a) {
    if (p >= q && a[p] != x)
        return -1;

    int m = (p + q) / 2;

    if (a[m] == x)
        return m;

    if (x > a[m])
        p = m + 1;
    else
        q = m - 1;

    return binary_search(x, p, q, a);
}
```

该算法应用到这个循环数组上的时候，以在数组`[9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0, 1, 2, 3, 4, 5, 6, 7, 8]`中查找元素 0 为例，第一步取中间第 9 个元素值为 18 :

```
search: 0        |   0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19
----------------------------------------------------------------------------------------------------
p= 0, m= 9, q=19 |   9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,  0,  1,  2,  3,  4,  5,  6,  7,  8,
```

此时目标元素 0 小于中间元素 19，该如何判断下一步是应该在左半边查找还是在右半边查找呢？仔细观察该数组，在下标9处被分为两个部分，左边为`[9, 10, ..., 18]`，右边为`[19, 0, ..., 8]`。以目测的结果来看，应该在右半边继续查找，与原始二分法的判断结果恰好相反。原始的算法，只能应用于一个单调递增的数组，而循环数组则将一个单调递增数组变成了两个单调递增数组。第一部取完中点之后，数组被分为两个部分。一般的情况下，一部分必然为单调递增，一部分为非单调递增（含有断点）。很容易想到，如果目标元素在单调递增那一部分，则可继续在此区间查找，此区间内的查找则是原始的二分查找；如果目标元素在非单调递增部分，则又还原成了原问题，只是查找范围缩小了一半。重复这个过程，则可以得到查找结果。

```
search : 0       |  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19
----------------------------------------------------------------------------------------------------
p= 0, m= 9, q=19 |  9, 10, 11, 12, 13, 14, 15, 16, 18, 18, 19,  0,  1,  2,  3,  4,  5,  6,  7,  8,
p=10, m=14, q=19 |                                         19,  0,  1,  2,  3,  4,  5,  6,  7,  8,
p=10, m=11, q=13 |                                         19,  0,  1,  2,
result: 11
```

在这个过程中，需要注意几个个问题：

1. 如何判断一段数组是单调递增呢？在该段数组的头、中间、尾三个位置p,m,q取三个值a\[p], a\[m], a\[q]，如果是单调递增则一定满足 a\[p] >= a\[m] >= a\[q]，否则则非单调递增。
2. 判断目标元素下一步所在区间，有几种情况：
   * 当 x > a\[m] 时，
     * 右半边是单调递增区间，并且x在此区间内，下一步则可在此右半边区间内查找
       * 右半边是单调递增区间，并且x不在此区间内，下一步在左半边查找
       * 右半边是非单调递增区间，则x必然在此区间内，下一步在右半边查找
   * 当 x < a\[m] 时， 同理类似
     * 左半边是单调递增区间，并且x在此区间内，下一步则可在此左半边区间内查找
     * 左半边是单调递增区间，并且x不在此区间内，下一步在右半边查找
     * 左半边是非单调递增区间，则x必然在此区间内，下一步在左半边查找
3. 判断是否在单调递增部分，只需与区间的另外一头的元素比较一下大小即可知道

样例代码：

```
#include <stdio.h>

#define N 20
int arr[N] = {9,10,11,12,13,14,15,16,17,18,19,0,1,2,3,4,5,6,7,8};

void print_header(int target) {
    printf("search :%2d %*c | ", target, 5, ' ');
    for (int i = 0; i < N; i++) {
        printf("%2d  ", i);
    }
    printf("\n");
    for (int i = 0; i< N; i++) {
        printf("-----");
    }
    printf("\n");
}

void print_array(int p, int m, int q, int *a) {
    printf("p=%2d, m=%2d, q=%2d |%*c", p, m, q, 4 * p + (p>0?1:0), ' ');
    for (int i = p; i <= q; i++) {
        printf("%2d, ", a[i]);
    }
    printf("\n");
}

/* 非递归方式 */
int search_loop_array(int x, int* a, int length) {
    int p = 0;
    int q = length - 1;

    while ( p <= q ) {
        int m = ( p + q ) / 2;

        print_array(p, m, q, a);

        if ( x == a[m] )
            return m;

        if ( x > a[m] ) {
            int mm = ( m + q ) / 2;
            int increase = (a[m] <= a[mm] && a[mm] <= a[q]);
            if ( ( increase && x <= a[q] ) || ! increase)
                p = m + 1;
            else
                q = m - 1;
        } else {
            int mm = ( p + m ) / 2;
            int increase = a[p] <= a[mm] && a[mm] <= a[m];
            if ( increase && x >= a[p] || !increase)
                q = m - 1;
            else
                p = m + 1;
        }
    }

    return -1;
}

/* 递归方式 */
int search_loop_array2(int x, int low, int high, int* a) {
    if (low >= high && a[low] != x)
        return -1;

    int m = (low + high) / 2;

    print_array(low, m, high, a);

    if ( x == a[m] )
        return m;

    if ( x > a[m] ) {
        int mm = (m + high) / 2;
        int increase = (a[m] <= a[mm] && a[mm] <= a[high]);

        if ( !increase || (increase && x <= a[high] ) )
            low = m + 1;
        else
            high = m - 1;

    } else {
        int mm = (low + m) / 2;
        int increase = (a[low] <= a[mm] && a[mm] <= a[m]);

        if ( !increase || (increase && x >= a[low] ) )
            high = m - 1;
        else
            low = m + 1;
    }

    return search_loop_array2(x, low, high, a);
}

int main() {

    for (int i = 0; i < N; i++) {
        print_header(i);

        // printf("result: %d\n\n", search_loop_array(i, arr, N));
        printf("result: %d\n\n", search_loop_array2(i, 0, N-1, arr));
    }
    return 0;
}
```

完。


# 2013-01-23  为Java运行环境导入根证书解决Eclipse的TFS插件的"PKIX path building failed"错误

最近在项目中必须要使用微软的TFS作为项目管理工具以及版本控制，而开发的IDE使用的是Eclipse。好在TFS有一个Eclipse的插件能够在跨平台的环境下工作。不过这个插件的11.0版本中，连结服务器的时候如果使用HTTPS连结，可能会有一个证书的认证错误问题，

```
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
...
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
...
```

这个问题的是由于Java自带的根证书库中不包含HTTPS服务器上的根证书，因此无法得到认证。解决办法是将服务器的根证书导入到Java运行环境中的根证书库中。假设我的证书文件是zero.cer，首先确保命令行下所引用的java是下文命令中所指向`$JAVA_HOME/jre/bin`的是一个JRE，如果不是则需要修改JAVA\_HOME的位置。

```
$ keytool -import -noprompt -trustcacerts -alias zero -file zero.cer -keystore ~/mykeystore
```

这个命令会新建一个keystore文件，命令运行后会要求输入密码，这个密码是新建的keystore文件的访问密码。

For Linux:

```
$ sudo keytool -importkeystore -srckeystore ~/mykeystore -destkeystore $JAVA_HOME/jre/lib/security/cacerts
```

For Mac:

```
$ sudo keytool -importkeystore -srckeystore ~/mykeystore -destkeystore /Library/Java/Home/lib/security/cacerts
```

第二个命令将之前建好的keystore中的证书导入jre自带的keystore文件中。过程中会要求输入目标keystore（也就是jre自带的keystore）文件的密码，这个密码默认是 changeit （linux和mac下），或者是 changeme 。

之后，重启Eclipse，再次连结TFS服务器，即可成功通过服务器验证。此外，可以下载一个小工具 [SSLPoke](https://confluence.atlassian.com/download/attachments/180292346/SSLPoke.class?version=1\&modificationDate=1236556489366\&api=v2) 验证证书是否导入正常，下载之后在文件所在目录下，命令行运行

```
$ java SSLPoke tfs.yourserver.com 4343
Successfully connected
```

如果显示成功则说明证书导入的没有问题。


# 2013-01-21  ant 中通过重新定义  project.all.jars.path 在 classpath 中引入外部 jar 文件

在 Android 开发中，除了通常在 Eclipse 中的编译方法之外，有的时候为了进行持续集成，可能还需要用 ant 进行自动化编译。Android SDK 本身已经提供了默认的ant编译脚本，就在每个工程下的 `build.xml` 中，其中引用了SDK的编译脚本`${sdk_dir}/tools/ant/build.xml` 。 通常情况下，在工程根目录下直接执行 ant debug 即可进行一次正常的 build。默认的 classpath 会包括 libs 目录下的所有 jar 文件。但是如果工程中使用了 USER LIBRARY，或者引用了外部的 jar 文件，那么在编译中就可能会遇到问题，因为这些 jar 文件不会被自动包含在 classpath 中，这时就要扩展 ant 的 path 变量，把自己的 jar 文件加入到 classpath 中。

通过察看 sdk 提供的`build.xml`编译脚本，可以发现 javac 使用的 classpath 定义如下：

```
<path id="project.javac.classpath">
<path refid="project.all.jars.path"></path>

<path refid="tested.project.classpath"></path>
</path>

<javac encoding="${java.encoding}"
        source="${java.source}" target="${java.target}"
        debug="true" extdirs="" includeantruntime="false"
        destdir="${out.classes.absolute.dir}"
        bootclasspathref="project.target.class.path"
        verbose="${verbose}"
        classpathref="project.javac.classpath"
        fork="${need.javac.fork}">
    <src path="${source.absolute.dir}"></src>
    <src path="${gen.absolute.dir}"></src>
    <compilerarg line="${java.compilerargs}"></compilerarg>
</javac>
```

其中 `project.all.jars.path` 包含了所有的jar文件，我们可以通过在工程目录下的 `build.xml` 中重新定义这个变量来引入其他的jar文件。例如在我的工程中，引用了ormlite这个ORM库，为了能够在开发中使用"attach source"察看源码，该jar文件不能放在libs目录中，因为Eclipse不允许对libs目录中的jar文件 `attach source`。因此我将此文件放到了libs/ormlite目录中，为了能够将这两个jar文件加入到classpath中，就要重新定义 `project.all.jars.path` 这个元素。

基本思路是，重新定义 `-pre-compile` 这个target，在其中重新定义 `project.all.jars.path` 的值。

```
<target name="-pre-compile">
    <echo message="JARPATH=${toString:project.all.jars.path}"></echo>

<property name="ormlite.dir" value="${jar.libs.dir}/ormlite"></property>

<path id="ormlite.lib">
<path path="${toString:project.all.jars.path}"></path>

<pathelement location="${ormlite.dir}/ormlite-android-4.41.jar"></pathelement>

<pathelement location="${ormlite.dir}/ormlite-core-4.41.jar"></pathelement>
    </path>

<path id="project.all.jars.path">
<path refid="ormlite.lib"></path>
    </path>

    <echo message="JARPATH=${toString:project.all.jars.path}"></echo>
</target>
```


# 2012-09-22  Android的滚动条实现细节

在Android的UI系统中，每个View都有一个ScrollabilityCache的单例对象。该对象定义在View\.java中，用于保存ScrollBar的相关实例和属性(ScrollBar实际上是一个ScrollBarDrawable对象)，并且实现了淡入淡出动画效果的线程代码。这些会在该View的所有ScrollBar中共用。

以垂直滚动条为例，画出滚动条的过程大致是:

```
1. View::Draw
2. View::onDrawScrollBars
scrollBar.setParameters(computeVerticalScrollRange(),
            computeVerticalScrollOffset(),
            computeVerticalScrollExtent(), true);

3. View::onDrawVerticalScrollBar
    scrollBar.draw

4. ScrollBarDrawable::draw

Rect r = getBounds();

if (drawTrack) {
    drawTrack(canvas, r, vertical);
}

if (drawThumb) {
    int size = vertical ? r.height() : r.width();
    int thickness = vertical ? r.width() : r.height();
    int length = Math.round((float) size * extent / range);
    int offset = Math.round((float) (size - length) * mOffset / (range - extent));

    // avoid the tiny thumb
    int minLength = thickness * 2;
    if (length < minLength) {
        length = minLength;
    }
    // avoid the too-big thumb
    if (offset + length > size) {
        offset = size - length;
    }

    drawThumb(canvas, r, offset, length, vertical);
}
```


# 2012-02-05  hostname自动变成bogon的问题

Mac下开iTerm2，发现hostname经常自己变来变去的，有的时候是本机的名字，有的时候自己就变成了bogon。一查，发现：

```
simon@bogon:~$ nslookup
> 192.168.1.1
Server: 124.89.1.129
Address: 124.89.1.129#531.1.168.192.in-addr.arpa name = bogon.
> 10.1.1.1
Server:     124.89.1.129
Address:    124.89.1.129#53

1.1.1.10.in-addr.arpa   name = bogon.
```

原来 192.168.*， 10.* 被DNS反向解析成了bogon，换个DNS（比如8.8.8.8）就没问题了


# 2011-07-09  代码段速记 gist.github.com

github最初的主要功能是类似sourceforge和google code的代码库存储发布，后来似乎开始往面向程序员的SNS方向发展。SourceForge这么多年了，没想到从git这样一个貌似很常见的版本版本管理工具的基础上，能够发展出这么多形形色色的功能。

最近又发现GitHub发布了gist.github.com，这是一个用来保存和发布代码片段的功能，每个代码片段都用一个git仓库管理，可以保存历史版本。也可以通过一个链接很方便的嵌入在Blog或网页中。这样一来，发布代码片段的时候，再也不用为了保持代码格式和语法高亮去费劲的调试CSS和插件了，而且可以很方便的进行代码段的更新和版本跟踪。如果能够加上tag或者分类的话，就更加完美了。

此外，如果使用Chrome浏览器，还有一个[GitHub Gist](https://chrome.google.com/webstore/detail/ppnkhbfageapcimnniepboocoaggcbbd?hl=zh-CN)插件可以用。


# 2011-07-08  A perl Data.Dumper clone for Python

Perl中的Data::Dumper很好用，可以非常方便的将任意数据类型dump成可读的形式，debug的时候用来查看运行期的某种复杂数据结构非常的方便。

下面的这段代码是一个用于Python的近似实现，包含部分格式化的选项。几年前写的这段代码，其实还有不少改进的空间。


# 2011-06-22  魔兽世界私服Trinity，从源码开始

## 缘起因由

在一个无所事事的周末下午，突然想起魔兽世界，官方的账号很久没有上了，里面的大小号现在连满级都不是。以前曾经搭过传奇和星际争霸战网的私服自娱自乐，也听说过魔兽世界有开源的服务端模拟，既然兴致来了就小小的研究一下。

目前魔兽世界的私服比较流行的是MaNGOS和Trinity，二者都是模拟魔兽世界服务端。MaNGOS"号称"是一个研究型项目，目的是为了学习大规模的C++项目开发，有道理，不过我不信。Trinity是基于MaNGOS的代码开发的，以前主要是为了合并用户提交的补丁代码而设立的一个项目，不过现在已经单独独立出来了，主要开发成员包括以前MaNGOS的一些老人，现在的主要工作包括清理代码、优化、提供一个更好的服务端内核。

二者现在的代码提交和更新都很频繁，但是具体是否会合并对方的代码就不清楚了。总而言之，也就是说，我感觉Trinity大概也许应该是目前最好的一个魔兽世界服务端模拟了。作为一个程序员来说，玩游戏还在其次，看看代码才是件有意思的事情。整个过程记录在案 <http://log4think.com/setup_wow_private_server>，以便事后查询。

现在尝试一下从源码开始搭个魔兽世界的服务器，从源码开始主要还是想顺便看看代码的情况，基于Trinity来做。至于客户端的情况，截止到2011年6月12日，中国国服魔兽世界最新的版本是3.3.5 13930-巫妖王之怒。

### 安装基本工具

安装之前，需要准备几个要使用到的工具软件，用来下载、编译等等。

**版本控制工具 Git** 由于 MaNGOS 和 Trinity 都是托管在 GitHub 上，所以得用 Git 才能下到源码：

* [Git for Windows](http://code.google.com/p/msysgit/downloads/detail?name=Git-1.7.4-preview20110204.exe\&can=2\&amp)
* [Git 的图形化工具 TortoiseGit](http://code.google.com/p/tortoisegit/downloads/detail?name=Tortoisegit-1.6.5.0-32bit.msi\&can=2\&q=)

**编译工具和库**：

* [OpenSSL](http://www.slproweb.com/download/Win32OpenSSL-1_0_0d.exe)
* [CMake](http://www.cmake.org/cmake/resources/software.html)
* [Visual Studio 2010 Express](http://www.microsoft.com/express/Downloads/#2010-Visual-CPP) ，这个是Visual Studio的免费版本。如果装了要钱的Visual Studio 2010，就不必装这个了。

**运行环境：**

* [MySQL](http://dev.mysql.com/downloads/mysql)，安装后也包含编译需要的头文件和库。
* [MySQL的图形化客户端](http://dev.mysql.com/downloads/gui-tools/5.0.html)

以下二者按需选择其一

* [Microsoft Visual C++ 2010 Redistributable Package x86版本](http://www.microsoft.com/downloads/details.aspx?familyid=A7B7A05E-6DE6-4D3A-A423-37BF0912DB84\&displaylang=en)
* [Microsoft Visual C++ 2010 Redistributable Package x64版本](http://www.microsoft.com/downloads/details.aspx?familyid=BD512D9E-43C8-4655-81BF-9350143D5867\&displaylang=en)

以下非必需

* [.Net Framework 3.5](http://www.microsoft.com/downloads/details.aspx?FamilyId=333325FD-AE52-4E35-B531-508D977D32A6\&displaylang=en)， Visual Studio 2010里面好像带，有了就不必装了

### 生成项目文件

各个工具都下载、安装完毕（具体细节可Google之，不赘述了），准备工作做完之后，开始下代码编译。

1. 建一个目录，譬如 D:\workspace\trinity
2. 进入这个目录，右键 Git Clone... ，Url那里填入

   ```
   https://github.com/TrinityCore/TrinityCore.git
   ```

   ， 点OK。不喜欢图形化工具的可以直接进到新建的目录里面，命令行上运行

   ```
   git clone https://github.com/TrinityCore/TrinityCore.git
   ```

   。 会自动建立一个名为 TrinityCore 的源码目录，然后就是等着代码下完。
3. 在 D:\workspace\trinity 下建一个目录是Build等下放编译结果文件
4. 运行CMake的图形化工具（开始菜单里 CMake 下的 CMake (cmake-gui)）
5. 点 Browse Source... ， 选 D:\workspace\trinity\TrinityCore
6. Browse Build... ，选 D:\workspace\trinity\Build
7. 点 Configure ，出一个对话框
8. 确保勾选了 Use default native compilers，下拉框里面选 Visual Studio 10，至于是32位还是64位版本的，根据自己的情况选
9. 点Finish，CMake 工具会搜索源码配置，分析出一些编译选项来。确保 cmake-gui 下面的信息提示里面没有 ERROR 字样，否则根据具体错误修正后重来。
   1. 在 cmake-gui 上面的编译选项里面选中 SCRIPTS、SERVERS、TOOLS、USE\_COREPCH、USE\_SCRIPTPCH ，不要选 USE\_MYSQL\_SOURCES 。
   2. 最后点 Generate ， CMake 会在 D:\workspace\trinity\Build 下面生成 Visual Stdio 2010 的项目文件。

配置完成后，我的是这样:

![](http://log4think.com/wp-content/uploads/2011/06/CMake.jpg)

> 可能出现的问题： 1. 第10步里面\_GIT\_EXEC应该是msysgit中git的绝对路径，如果没有则是msysgit安装的时候没有把自己加到系统路径里面去 2. 如果下面出现红色的ERROR提示，类似于
>
> > Could not copy from: D:/dev/cmake/share/cmake-2.8/Templates/CMakeVSMacros2.vsmacros to: C:/Documents and Settings/Administrator/ÎÒµÄÎÄµµ/Visual Studio 2010/Projects/VSMacros80/CMakeMacros/CMakeVSMacros2.vsmacros
>
> 这个好像是因为 CMake 无法识别中文路径，把"我的文档"的位置改一下吧，改成路径不带中文的。或者直接自己把提示中的 CMak>eVSMacros2.vsmacros 拷到"我的文档"下的 Visual Studio 2010/Projects/VSMacros80/CMakeMacros/ 。

### 编译源码

注意以下几点：

1. 如果你的魔兽世界客户端是3.3.5 13930，那么记得给代码打个认证补丁以支持13930，具体请参考另一篇文章 [wow\_3-3-5\_13930\_auth\_patch](http://log4think.com/wow_3-3-5_13930_auth_patch)
2. 在 Visual Studio 里面打开 D:\workspace\trinity\Build\TrinityCore.sln ，先Build -> Clean Solution，然后Build -> Build Solution
3. 默认是生成 Debug ，结果在 D:\Build\bin\Debug下面，从 MySQL 的安装目录下的Lib目录里面拷一个 libmySQL.dll 到这个目录下， libeay32.dll 和 ssleay32.dll 是 OpenSSL 的，应该默认加到系统路径下了，如果后面提示找不到就从 OpenSSL 的安装目录里面拷过来。



### 安装数据库

简短截说：

1. 从 <https://github.com/TrinityCore/TrinityCore/downloads> 下载最新的（或者符合你的客户端版本号的）魔兽世界数据文件，
2. 用MySQL图形化工具，导入 D:\workspace\trinity\TrinityCore\sql\create\create\_mysql.sql 执行建立三个数据库

   > auth 数据库中导入 D:\workspace\trinity\TrinityCore\sql\base\auth*database.sql 执行， characte 数据库中导入 D:\workspace\trinity\TrinityCore\sql\base\character\_database.sql 执行， world 数据库中导入第一步中下载的 TDB\_full*???.sql 文件执行， 最终建立 auth、character、world 三个数据库。

### 生成地图文件

1. 在D:\Build\bin\Debug下面建一个makevmaps3\_simple.bat文件，内容为

   ```
   vmap3extractor.exe
   md vmaps
   vmap3assembler.exe Buildings vmaps

   pause
   ```

   保存运行，会从魔兽世界的客户端目录里面解压缩服务端需要的地图出来。根据机器速度不同，大概需要20分钟到一个小时的时间。运行结束的时候会给个提示"Press any key..."，按任意键结束。生成 vmaps 和 buildings 目录， buildings 目录无用可以删除。
2. 假设魔兽世界的客户端目录在 D:\WOW 下面，在 D:\Build\bin\Debug 下运行

   ```
   mapextractor.exe -i "d:\WOW"
   ```

   这样会生成 maps 和 dbc 目录。这个工具会根据客户端的雨中在 dbc 目录下生成不同的 dbc 语言版本。如果是中文的客户端，会提取出中文的 dbc 数据。

### 配置服务器

1. 在 D:\Build\bin\Debug 下有 worldserver.conf.dist 和 authserver.conf.dist 两个文件，分别是游戏服务器和认证服务器的配置模板文件。
2. 复制一份 worldserver.conf.dist 并改名为 worldserver.conf ，配置游戏服务器

   ```
   LoginDatabaseInfo = "127.0.0.1;3306;root;trinity;auth"
   WorldDatabaseInfo = "127.0.0.1;3306;root;trinity;world"
   CharacterDatabaseInfo = "127.0.0.1;3306;root;trinity;characters"
   ```

   分别是认证数据库、世界数据库、玩家角色数据库的地址， root 后面的 trinity 是 MySQL 中 root 的密码，改成你在安装 MySQL 时设置 root 密码。

   之前生成了 vmap 文件，这个是地图的相关数据，服务器可以根据这个来判断怪物和玩家之间是否可见（是否有墙，是否在建筑物的同一层上）。没有这个的话，怪物会穿墙打你，或者从楼下直接漂上来打你... 服务器配置这里默认情况下是开启 vmap 数据检测的。如果不想开启 vmap 检测，则将下面这些配置的值改成0

   ```
   vmap.enableLOS = 1
   vmap.enableHeight = 1
   vmap.petLOS = 1
   vmap.enableIndoorCheck = 1
   DetectPosCollision = 1
   ```
3. 复制一份 authserver.conf.dist 并改名为 authserver.conf ，配置认证服务器

   ```
   LoginDatabaseInfo = "127.0.0.1;3306;root;trinity;auth"
   ```

   同样，把 trinity 改成 MySQL 的 root 密码。
4. 检查数据库 auth 里面 realmlist 表里面的记录，记录中 port 的值应该和 worldserver.conf 里面的

   ```
   WorldServerPort = 8085
   ```

   这一项的值一样（这里是

   ```
   8085
   ```

   ）。同时， gamebuild 的值应该和你客户端的版本号是一致的（登陆界面右下角，当前最新的是 13930）。如果realmlist里面没有记录，则应该加一条。

### 修改客户端配置

客户端默认是去登陆官方服务器，需要修改一下地址改成让客户端登陆我们自己架设的游戏服务器。假设魔兽世界客户端安装在 D:\WOW 下面，到 D:\WOW\Data\zhCN 下面，备份 realmlist.wtf 文件。该文件原来的内容是

```
    set realmList cn.logon.warcraftchina.com
    set patchlist cn.version.warcraftchina.com
    set realmlistbn ""
    set portal cn
```

将其内容改成

```
    SET realmlist "127.0.0.1"
    SET patchlist "127.0.0.1"
```

### 注册用户账号

客户端后登录会发现没有账号可用，注册账号的办法有两种：

1. 第一种方法是通过SQL语句直接在数据库里面加

   ```
    INSERT INTO account
    (username, sha_pass_hash, email)
    VALUES
    ('用户名',SHA1(CONCAT(UPPER('用户名'),':',UPPER('密码'))), '邮件地址')
   ```

   替换命令中的 用户名、密码、邮件地址 即可。
2. 第二种方法最简单，后面启动游戏服务器worldserver之后，在这个命令窗口可以输入GM命令：

   ```
    create account 用户名 密码
   ```

## 启动游戏

1. 运行D:\Build\bin\Debug\authserver.exe
2. 运行D:\Build\bin\Debug\worldserver.exe
3. 运行客户端wow\.exe

如果运气好的话... 反正我运气不错...

相关的可能还需要一些配置和改动，不过目前我的这个版本能够正常运行，其它的未来再写吧。在此之前，至少可以先研究下源代码...

### 如何和朋友一起玩

如果想配个私服和朋友一起玩，那么需要一个公网 IP 地址。机房里面没有服务器没有关系，可以搭在自己家里的服务器上，然后去 [ip138](http://ip138.com) 上查到自己的IP地址，把 127.0.0.1 相关的地址都改成自己的IP地址就可以了。如果是通过ADSL路由上的网，去ADSL路由配置里面把自己的内外IP地址设置为DMZ主机地址开放给外网即可。

此外，如果是跑服务端的服务器有花生壳的动态域名或者自己的域名（比如我的 <http://log4think.com），可以将> 127.0.0.1 改成自己的域名。前面在 wowserver 和 authserver 中的地址中配置的 127.0.0.1 都要改，因为服务端要提供这个地址给客户端。 MySQL 相关的 127.0.0.1 地址不用改。非要改也可以，不过就是还得去配MySQL的外网访问的相关安全设置。

同时，如果是要搭在公网上对外提供服务，建议单独找个机器做 auth 服务器（配置不用太好），不同的游戏区跑在不同的服务器上（这个配置要好一点），每个服务器上都要跑worldserver。把服务器列表加到 auth 数据库里面的 realmlist 表里就是。

> 友情提示：提供公网服务小心被告。

#### 相关的几个小问题：

1. 连接服务器断开的话，到[这里](http://www.mangoscn.com/viewthread.php?tid=107229\&extra=page%3D1)下个补丁覆盖安装。
2. 登入后没有服务器列表的话，看看客户端的版本号（登陆界面的右下角），然后把auth数据库realmlist表里面的记录最后一项gamebuild改为看到的版本号（比如13930） 。

### 开发调试

既然是从源码编译的，因此如果中间出了任何问题都是可以通过调试的方式去解决的。调试方法很简单，几个简单的手段 1. 抓包，分析数据记录 2. 启动编译好的程序，开 Visual Studio ，附加到该进程上，下断点跟踪 3. 进入游戏，进行操作，Visual Studio 中如果下了正确的断点，就会在相关的位置停下

## 退而求其次

如果你觉得以上太复杂搞不定，如果只是想自己随便玩一下而已，那么可以下个别人做好的包，比如 TCCN-3.3.5-Trinity8400.exe ，安装好启动 web 服务器和 wow 服务器，注册个账号就可以进去玩了，简单的无需解释了。不过这个有人数限制，具体限制多少人我也不清楚，总之做公网服务是没戏的。

## 参考资料

二者的一些相关网站列举如下：

* MaNGOS 官网: <http://getmangos.com>
* MaNGOS 代码库: <http://github.com/mangos>
* 更多MaNGOS的链接: <http://getmangos.com/wiki>
* Trinity 官网: <http://www.trinitycore.info>
* Trinity 项目首页: <http://trinitycore.github.com>
* Trinity 代码库: <https://github.com/TrinityCore/TrinityCore>

本站后续相关文章可以在 <http://log4think.com/category/practice/fun/wow-trinity/> 找到

本文全文参考Trinity官方文档 <http://www.trinitycore.info/How-to:Win>


# 2011-06-13  魔兽世界3.3.5 13930登录数据包分析

官方的Trinity认证服务器在对3.3.5 13930客户端登陆的时候有问题，下面这个是抓的一个能够正常登陆的私服认证服务器的数据包。 使用工具[WPE](http://wpepro.net/)抓的包，这个也是经常用来做游戏外挂的一个抓包工具。相关的项目是TrinityCore的authserver，之前的[一篇Blog](http://log4think.com/setup_wow_private_server/)有关于如何下载代码的说明。

服务器端口配置情况说明如下 3306 MySQL 3724 认证服务器 10775 客户端与认证服务器通信的端口 10740 认证服务器与MySQL通信的端口

客户端登陆使用用户名passos，认证服务器、MySQL、客户端都在本地运行。数据包记录中加粗的是客户端与认证服务器的相关通信数据包。

&#x20;**1 127.0.0.1:10775 127.0.0.1:3724 40 Recv**&#x20;

```
0000  00 08 24 00 57 6F 57 00 03 03 05 6A 36 36 38 78    ..$.WoW....j668x
0010  00 6E 69 57 00 4E 43 68 7A E0 01 00 00 7F 00 00    .niW.NChz.......
0020  01 06 50 41 53 53 4F 53                            ..PASSOS
```

认证服务器接收到客户端发来的来源ip地址和用户名，以及客户端的一些信息 报文结构在`AuthSocket.h的sAuthLogonChallenge_C`，处理过程位于`AuthSocket.cpp的AuthSocket::_HandleLogonChallenge()`

2 127.0.0.1:10740 127.0.0.1:3306 51 Send

```
0000  2F 00 00 00 03 53 45 4C 45 43 54 20 2A 20 46 52    /....SELECT * FR
0010  4F 4D 20 69 70 5F 62 61 6E 6E 65 64 20 57 48 45    OM ip_banned WHE
0020  52 45 20 69 70 20 3D 20 27 31 32 37 2E 30 2E 30    RE ip = '127.0.0
0030  2E 31 27                                           .1'
```

认证服务器从MySQL中查询客户端ip是否被封

3 127.0.0.1:3306 127.0.0.1:10740 343 Recv

```
0000  01 00 00 01 05 32 00 00 02 03 64 65 66 06 72 65    .....2....def.re
0010  61 6C 6D 64 09 69 70 5F 62 61 6E 6E 65 64 09 69    almd.ip_banned.i
0020  70 5F 62 61 6E 6E 65 64 02 69 70 02 69 70 0C 21    p_banned.ip.ip.!
0030  00 60 00 00 00 FD 03 40 00 00 00 3C 00 00 03 03    .`.....@...<....
0040  64 65 66 06 72 65 61 6C 6D 64 09 69 70 5F 62 61    def.realmd.ip_ba
0050  6E 6E 65 64 09 69 70 5F 62 61 6E 6E 65 64 07 62    nned.ip_banned.b
0060  61 6E 64 61 74 65 07 62 61 6E 64 61 74 65 0C 3F    andate.bandate.?
0070  00 28 00 00 00 08 03 50 00 00 00 40 00 00 04 03    .(.....P...@....
0080  64 65 66 06 72 65 61 6C 6D 64 09 69 70 5F 62 61    def.realmd.ip_ba
0090  6E 6E 65 64 09 69 70 5F 62 61 6E 6E 65 64 09 75    nned.ip_banned.u
00A0  6E 62 61 6E 64 61 74 65 09 75 6E 62 61 6E 64 61    nbandate.unbanda
00B0  74 65 0C 3F 00 28 00 00 00 08 01 10 00 00 00 3E    te.?.(.........>
00C0  00 00 05 03 64 65 66 06 72 65 61 6C 6D 64 09 69    ....def.realmd.i
00D0  70 5F 62 61 6E 6E 65 64 09 69 70 5F 62 61 6E 6E    p_banned.ip_bann
00E0  65 64 08 62 61 6E 6E 65 64 62 79 08 62 61 6E 6E    ed.bannedby.bann
00F0  65 64 62 79 0C 21 00 96 00 00 00 FD 01 00 00 00    edby.!..........
0100  00 40 00 00 06 03 64 65 66 06 72 65 61 6C 6D 64    .@....def.realmd
0110  09 69 70 5F 62 61 6E 6E 65 64 09 69 70 5F 62 61    .ip_banned.ip_ba
0120  6E 6E 65 64 09 62 61 6E 72 65 61 73 6F 6E 09 62    nned.banreason.b
0130  61 6E 72 65 61 73 6F 6E 0C 21 00 FD 02 00 00 FD    anreason.!......
0140  01 00 00 00 00 05 00 00 07 FE 00 00 02 00 05 00    ................
0150  00 08 FE 00 00 02 00                               .......
```

MySQL返回的数据

4 127.0.0.1:10740 127.0.0.1:3306 83 Send

```
0000  4F 00 00 00 03 44 45 4C 45 54 45 20 46 52 4F 4D    O....DELETE FROM
0010  20 69 70 5F 62 61 6E 6E 65 64 20 57 48 45 52 45     ip_banned WHERE
0020  20 75 6E 62 61 6E 64 61 74 65 3C 3D 55 4E 49 58     unbandate<=UNIX
0030  5F 54 49 4D 45 53 54 41 4D 50 28 29 20 41 4E 44    _TIMESTAMP() AND
0040  20 75 6E 62 61 6E 64 61 74 65 3C 3E 62 61 6E 64     unbandate<>band
0050  61 74 65                                           ate
```

如果ip封锁已过期，解除封锁。

5 127.0.0.1:3306 127.0.0.1:10740 11 Recv

```
0000  07 00 00 01 00 00 00 22 00 00 00                   ......."...
```

MySQL返回的数据

6 127.0.0.1:10740 127.0.0.1:3306 159 Send

```
0000  9B 00 00 00 03 53 45 4C 45 43 54 20 61 2E 73 68    .....SELECT a.sh
0010  61 5F 70 61 73 73 5F 68 61 73 68 2C 61 2E 69 64    a_pass_hash,a.id
0020  2C 61 2E 6C 6F 63 6B 65 64 2C 61 2E 6C 61 73 74    ,a.locked,a.last
0030  5F 69 70 2C 61 61 2E 67 6D 6C 65 76 65 6C 2C 61    _ip,aa.gmlevel,a
0040  2E 76 2C 61 2E 73 20 46 52 4F 4D 20 61 63 63 6F    .v,a.s FROM acco
0050  75 6E 74 20 61 20 4C 45 46 54 20 4A 4F 49 4E 20    unt a LEFT JOIN 
0060  61 63 63 6F 75 6E 74 5F 61 63 63 65 73 73 20 61    account_access a
0070  61 20 4F 4E 20 28 61 2E 69 64 20 3D 20 61 61 2E    a ON (a.id = aa.
0080  69 64 29 20 57 48 45 52 45 20 61 2E 75 73 65 72    id) WHERE a.user
0090  6E 61 6D 65 20 3D 20 27 50 41 53 53 4F 53 27       name = 'PASSOS'
```

认证服务器从MySQL数据库中取帐号相关信息

7 127.0.0.1:3306 127.0.0.1:10740 577 Recv

```
0000  01 00 00 01 07 3E 00 00 02 03 64 65 66 06 72 65    .....>....def.re
0010  61 6C 6D 64 01 61 07 61 63 63 6F 75 6E 74 0D 73    almd.a.account.s
0020  68 61 5F 70 61 73 73 5F 68 61 73 68 0D 73 68 61    ha_pass_hash.sha
0030  5F 70 61 73 73 5F 68 61 73 68 0C 21 00 78 00 00    _pass_hash.!.x..
0040  00 FD 01 00 00 00 00 28 00 00 03 03 64 65 66 06    .......(....def.
0050  72 65 61 6C 6D 64 01 61 07 61 63 63 6F 75 6E 74    realmd.a.account
0060  02 69 64 02 69 64 0C 3F 00 0B 00 00 00 03 23 42    .id.id.?......#B
0070  00 00 00 30 00 00 04 03 64 65 66 06 72 65 61 6C    ...0....def.real
0080  6D 64 01 61 07 61 63 63 6F 75 6E 74 06 6C 6F 63    md.a.account.loc
0090  6B 65 64 06 6C 6F 63 6B 65 64 0C 3F 00 03 00 00    ked.locked.?....
00A0  00 01 21 00 00 00 00 32 00 00 05 03 64 65 66 06    ..!....2....def.
00B0  72 65 61 6C 6D 64 01 61 07 61 63 63 6F 75 6E 74    realmd.a.account
00C0  07 6C 61 73 74 5F 69 70 07 6C 61 73 74 5F 69 70    .last_ip.last_ip
00D0  0C 21 00 5A 00 00 00 FD 01 00 00 00 00 3A 00 00    .!.Z.........:..
00E0  06 03 64 65 66 06 72 65 61 6C 6D 64 02 61 61 0E    ..def.realmd.aa.
00F0  61 63 63 6F 75 6E 74 5F 61 63 63 65 73 73 07 67    account_access.g
0100  6D 6C 65 76 65 6C 07 67 6D 6C 65 76 65 6C 0C 3F    mlevel.gmlevel.?
0110  00 03 00 00 00 01 21 10 00 00 00 26 00 00 07 03    ......!....&....
0120  64 65 66 06 72 65 61 6C 6D 64 01 61 07 61 63 63    def.realmd.a.acc
0130  6F 75 6E 74 01 76 01 76 0C 21 00 FD FF FF FF FC    ount.v.v.!......
0140  10 00 00 00 00 26 00 00 08 03 64 65 66 06 72 65    .....&....def.re
0150  61 6C 6D 64 01 61 07 61 63 63 6F 75 6E 74 01 73    almd.a.account.s
0160  01 73 0C 21 00 FD FF FF FF FC 10 00 00 00 00 05    .s.!............
0170  00 00 09 FE 00 00 02 00 BC 00 00 0A 28 65 63 38    ............(ec8
0180  37 31 63 61 62 61 65 66 37 65 36 39 36 38 35 65    71cabaef7e69685e
0190  65 39 34 35 38 39 30 36 39 37 62 66 35 63 33 64    e945890697bf5c3d
01A0  39 30 34 39 34 02 31 31 01 30 09 31 32 37 2E 30    90494.11.0.127.0
01B0  2E 30 2E 31 01 33 40 36 31 34 38 45 31 46 42 35    .0.1.3@6148E1FB5
01C0  33 43 38 38 34 46 37 43 37 34 37 38 39 39 43 35    3C884F7C747899C5
01D0  31 46 43 38 30 36 46 43 43 46 41 30 46 36 43 33    1FC806FCCFA0F6C3
01E0  34 36 33 46 35 42 41 45 33 37 43 46 32 33 35 39    463F5BAE37CF2359
01F0  44 38 33 30 46 32 38 40 42 37 45 35 37 33 32 31    D830F28@B7E57321
0200  33 43 44 34 30 44 34 31 32 43 44 46 44 35 42 41    3CD40D412CDFD5BA
0210  39 37 41 43 30 42 45 31 37 39 38 34 32 45 37 42    97AC0BE179842E7B
0220  43 43 35 45 32 43 45 44 30 30 43 44 46 31 31 35    CC5E2CED00CDF115
0230  35 41 31 32 35 39 43 46 05 00 00 0B FE 00 00 02    5A1259CF........
0240  00                                                 .
```

MYSQL返回的数据

8 127.0.0.1:10740 127.0.0.1:3306 78 Send

```
0000  4A 00 00 00 03 53 45 4C 45 43 54 20 62 61 6E 64    J....SELECT band
0010  61 74 65 2C 75 6E 62 61 6E 64 61 74 65 20 46 52    ate,unbandate FR
0020  4F 4D 20 61 63 63 6F 75 6E 74 5F 62 61 6E 6E 65    OM account_banne
0030  64 20 57 48 45 52 45 20 69 64 20 3D 20 31 31 20    d WHERE id = 11 
0040  41 4E 44 20 61 63 74 69 76 65 20 3D 20 31          AND active = 1
```

认证服务器从MySQL查找帐号是否被封

9 127.0.0.1:3306 127.0.0.1:10740 175 Recv

```
0000  01 00 00 01 02 46 00 00 02 03 64 65 66 06 72 65    .....F....def.re
0010  61 6C 6D 64 0E 61 63 63 6F 75 6E 74 5F 62 61 6E    almd.account_ban
0020  6E 65 64 0E 61 63 63 6F 75 6E 74 5F 62 61 6E 6E    ned.account_bann
0030  65 64 07 62 61 6E 64 61 74 65 07 62 61 6E 64 61    ed.bandate.banda
0040  74 65 0C 3F 00 28 00 00 00 08 03 40 00 00 00 4A    te.?.(.....@...J
0050  00 00 03 03 64 65 66 06 72 65 61 6C 6D 64 0E 61    ....def.realmd.a
0060  63 63 6F 75 6E 74 5F 62 61 6E 6E 65 64 0E 61 63    ccount_banned.ac
0070  63 6F 75 6E 74 5F 62 61 6E 6E 65 64 09 75 6E 62    count_banned.unb
0080  61 6E 64 61 74 65 09 75 6E 62 61 6E 64 61 74 65    andate.unbandate
0090  0C 3F 00 28 00 00 00 08 01 00 00 00 00 05 00 00    .?.(............
00A0  04 FE 00 00 02 00 05 00 00 05 FE 00 00 02 00       ...............
```

MYSQL返回的数据

10 127.0.0.1:10740 127.0.0.1:3306 98 Send

```
0000  5E 00 00 00 03 55 50 44 41 54 45 20 61 63 63 6F    ^....UPDATE acco
0010  75 6E 74 5F 62 61 6E 6E 65 64 20 53 45 54 20 61    unt_banned SET a
0020  63 74 69 76 65 20 3D 20 30 20 57 48 45 52 45 20    ctive = 0 WHERE 
0030  75 6E 62 61 6E 64 61 74 65 3C 3D 55 4E 49 58 5F    unbandate<=UNIX_
0040  54 49 4D 45 53 54 41 4D 50 28 29 20 41 4E 44 20    TIMESTAMP() AND 
0050  75 6E 62 61 6E 64 61 74 65 3C 3E 62 61 6E 64 61    unbandate<>banda
0060  74 65                                              te
```

如果封号过期，取消封号

11 127.0.0.1:3306 127.0.0.1:10740 52 Recv

```
0000  30 00 00 01 00 00 00 22 00 00 00 28 52 6F 77 73    0......"...(Rows
0010  20 6D 61 74 63 68 65 64 3A 20 30 20 20 43 68 61     matched: 0  Cha
0020  6E 67 65 64 3A 20 30 20 20 57 61 72 6E 69 6E 67    nged: 0  Warning
0030  73 3A 20 30                                        s: 0
```

MYSQL返回的数据

&#x20;**12 127.0.0.1:3724 127.0.0.1:10775 119 Send**&#x20;

```
0000  00 00 00 BB 24 92 69 D7 23 87 F0 28 B5 A3 26 14    ....$.i.#..(..&.
0010  F9 36 DA B9 DF 83 13 35 35 38 4F E3 78 DA 93 9C    .6.....558O.x...
0020  59 A8 17 01 07 20 B7 9B 3E 2A 87 82 3C AB 8F 5E    Y.... ..>*..<..^
0030  BF BF 8E B1 01 08 53 50 06 29 8B 5B AD BD 5B 53    ......SP.).[..[S
0040  E1 89 5E 64 4B 89 CF 59 12 5A 15 F1 CD 00 ED 2C    ..^dK..Y.Z.....,
0050  5E CC 7B 2E 84 79 E1 0B AC 97 BA D5 DF 2C 41 0D    ^.{..y.......,A.
0060  D4 3C 21 73 E5 B7 B9 8A B1 1A 97 B6 58 32 7F 7F    .<!s........X2..
0070  4F 9D 23 9C 36 F8 00                               O.#.6..
```

返回给客户端的数据，第0个字节是包类型AUTH\_LOGON\_CHALLENGE， 第1个字节为00，第2个字节为认证结果WOW\_SUCCESS为00，第3-6这四个字节是根据auth数据库account中v、s两个字段算出来的一个数值，之后是安全之类的信息。计算过程在`AuthSocket.cpp的AuthSocket::_HandleLogonChallenge()`中。

&#x20;**13 127.0.0.1:10775 127.0.0.1:3724 75 Recv**&#x20;

```
0000  01 2A 62 13 B7 8F 50 A3 69 78 C1 B1 CA 3B CE D8    .*b...P.ix...;..
0010  95 14 13 BA 21 A9 06 E5 60 B3 EC 1B 8E 51 48 20    ....!...`....QH 
0020  20 38 E0 9D 9B AC 08 19 CD 15 8B 54 87 ED 30 64     8.........T..0d
0030  11 59 3C 8D 8F 9D CB 6C 0D 8E 90 16 7C 6F BE 22    .Y<....l....|o."
0040  A8 4C 5F 02 0A BC 04 F0 5F 00 00                   .L_....._..
```

客户端发给认证服务器的AUTH\_LOGON\_PROOF数据，结构定义在`AuthSocket.cpp的sAuthLogonProof_C`中。有一堆安全方面的计算，不知道Trinity这帮人怎么琢磨出来的。

&#x20;**14 127.0.0.1:3724 127.0.0.1:10775 32 Send**&#x20;

```
0000  01 00 1B F7 BE 50 54 84 8B B2 44 89 97 FD AA C3    .....PT...D.....
0010  44 AE 99 C9 4D A0 00 00 80 00 00 00 00 00 00 00    D...M...........
```

认证服务器返回给客户端的sAuthLogonProof\_S结构数据

15 127.0.0.1:10740 127.0.0.1:3306 221 Send

```
0000  D9 00 00 00 03 55 50 44 41 54 45 20 61 63 63 6F    .....UPDATE acco
0010  75 6E 74 20 53 45 54 20 73 65 73 73 69 6F 6E 6B    unt SET sessionk
0020  65 79 20 3D 20 27 36 42 33 44 37 37 33 46 39 34    ey = '6B3D773F94
0030  39 30 43 42 41 42 38 36 31 30 41 34 45 37 34 37    90CBAB8610A4E747
0040  43 36 33 42 38 31 37 41 44 30 32 38 31 38 30 44    C63B817AD028180D
0050  32 37 46 41 42 42 41 41 46 43 35 32 42 30 37 37    27FABBAAFC52B077
0060  37 37 45 37 36 37 46 36 37 35 44 36 42 32 36 45    77E767F675D6B26E
0070  34 37 37 39 33 42 27 2C 20 6C 61 73 74 5F 69 70    47793B', last_ip
0080  20 3D 20 27 31 32 37 2E 30 2E 30 2E 31 27 2C 20     = '127.0.0.1', 
0090  6C 61 73 74 5F 6C 6F 67 69 6E 20 3D 20 4E 4F 57    last_login = NOW
00A0  28 29 2C 20 6C 6F 63 61 6C 65 20 3D 20 27 34 27    (), locale = '4'
00B0  2C 20 66 61 69 6C 65 64 5F 6C 6F 67 69 6E 73 20    , failed_logins 
00C0  3D 20 30 20 57 48 45 52 45 20 75 73 65 72 6E 61    = 0 WHERE userna
00D0  6D 65 20 3D 20 27 50 41 53 53 4F 53 27             me = 'PASSOS'
```

认证服务器向MySQL更新账户信息。

16 127.0.0.1:3306 127.0.0.1:10740 52 Recv

```
0000  30 00 00 01 00 01 00 02 00 00 00 28 52 6F 77 73    0..........(Rows
0010  20 6D 61 74 63 68 65 64 3A 20 31 20 20 43 68 61     matched: 1  Cha
0020  6E 67 65 64 3A 20 31 20 20 57 61 72 6E 69 6E 67    nged: 1  Warning
0030  73 3A 20 30                                        s: 0
```

MySQL返回的数据

&#x20;**17 127.0.0.1:10775 127.0.0.1:3724 5 Recv**&#x20;

```
0000  10 00 00 00 00                                     .....
```

客户端向认证服务器要求Realm登陆服务器列表，10为REALM\_LIST命令，`AuthSocket::_HandleRealmList()`

18 127.0.0.1:10740 127.0.0.1:3306 53 Send

```
0000  31 00 00 00 03 53 45 4C 45 43 54 20 69 64 20 46    1....SELECT id F
0010  52 4F 4D 20 61 63 63 6F 75 6E 74 20 57 48 45 52    ROM account WHER
0020  45 20 75 73 65 72 6E 61 6D 65 20 3D 20 27 50 41    E username = 'PA
0030  53 53 4F 53 27                                     SSOS'
```

认证服务器从MySQL中查账户id

19 127.0.0.1:3306 127.0.0.1:10740 80 Recv

```
0000  01 00 00 01 01 2E 00 00 02 03 64 65 66 06 72 65    ..........def.re
0010  61 6C 6D 64 07 61 63 63 6F 75 6E 74 07 61 63 63    almd.account.acc
0020  6F 75 6E 74 02 69 64 02 69 64 0C 3F 00 0B 00 00    ount.id.id.?....
0030  00 03 23 42 00 00 00 05 00 00 03 FE 00 00 02 00    ..#B............
0040  03 00 00 04 02 31 31 05 00 00 05 FE 00 00 02 00    .....11.........
```

MySQL返回的数据

20 127.0.0.1:10740 127.0.0.1:3306 149 Send

```
0000  91 00 00 00 03 53 45 4C 45 43 54 20 69 64 2C 20    .....SELECT id, 
0010  6E 61 6D 65 2C 20 61 64 64 72 65 73 73 2C 20 70    name, address, p
0020  6F 72 74 2C 20 69 63 6F 6E 2C 20 63 6F 6C 6F 72    ort, icon, color
0030  2C 20 74 69 6D 65 7A 6F 6E 65 2C 20 61 6C 6C 6F    , timezone, allo
0040  77 65 64 53 65 63 75 72 69 74 79 4C 65 76 65 6C    wedSecurityLevel
0050  2C 20 70 6F 70 75 6C 61 74 69 6F 6E 2C 20 67 61    , population, ga
0060  6D 65 62 75 69 6C 64 20 46 52 4F 4D 20 72 65 61    mebuild FROM rea
0070  6C 6D 6C 69 73 74 20 57 48 45 52 45 20 63 6F 6C    lmlist WHERE col
0080  6F 72 20 3C 3E 20 33 20 4F 52 44 45 52 20 42 59    or <> 3 ORDER BY
0090  20 6E 61 6D 65                                      name
```

认证服务器根据账户id从数据库中查找对应地区的登陆服务器信息

21 127.0.0.1:3306 127.0.0.1:10740 765 Recv

```
0000  01 00 00 01 0A 32 00 00 02 03 64 65 66 06 72 65    .....2....def.re
0010  61 6C 6D 64 09 72 65 61 6C 6D 6C 69 73 74 09 72    almd.realmlist.r
0020  65 61 6C 6D 6C 69 73 74 02 69 64 02 69 64 0C 3F    ealmlist.id.id.?
0030  00 0B 00 00 00 03 23 42 00 00 00 36 00 00 03 03    ......#B...6....
0040  64 65 66 06 72 65 61 6C 6D 64 09 72 65 61 6C 6D    def.realmd.realm
0050  6C 69 73 74 09 72 65 61 6C 6D 6C 69 73 74 04 6E    list.realmlist.n
0060  61 6D 65 04 6E 61 6D 65 0C 21 00 60 00 00 00 FD    ame.name.!.`....
0070  05 40 00 00 00 3C 00 00 04 03 64 65 66 06 72 65    .@...<....def.re
0080  61 6C 6D 64 09 72 65 61 6C 6D 6C 69 73 74 09 72    almd.realmlist.r
0090  65 61 6C 6D 6C 69 73 74 07 61 64 64 72 65 73 73    ealmlist.address
00A0  07 61 64 64 72 65 73 73 0C 21 00 60 00 00 00 FD    .address.!.`....
00B0  01 00 00 00 00 36 00 00 05 03 64 65 66 06 72 65    .....6....def.re
00C0  61 6C 6D 64 09 72 65 61 6C 6D 6C 69 73 74 09 72    almd.realmlist.r
00D0  65 61 6C 6D 6C 69 73 74 04 70 6F 72 74 04 70 6F    ealmlist.port.po
00E0  72 74 0C 3F 00 0B 00 00 00 03 01 00 00 00 00 36    rt.?...........6
00F0  00 00 06 03 64 65 66 06 72 65 61 6C 6D 64 09 72    ....def.realmd.r
0100  65 61 6C 6D 6C 69 73 74 09 72 65 61 6C 6D 6C 69    ealmlist.realmli
0110  73 74 04 69 63 6F 6E 04 69 63 6F 6E 0C 3F 00 03    st.icon.icon.?..
0120  00 00 00 01 21 00 00 00 00 38 00 00 07 03 64 65    ....!....8....de
0130  66 06 72 65 61 6C 6D 64 09 72 65 61 6C 6D 6C 69    f.realmd.realmli
0140  73 74 09 72 65 61 6C 6D 6C 69 73 74 05 63 6F 6C    st.realmlist.col
0150  6F 72 05 63 6F 6C 6F 72 0C 3F 00 03 00 00 00 01    or.color.?......
0160  21 00 00 00 00 3E 00 00 08 03 64 65 66 06 72 65    !....>....def.re
0170  61 6C 6D 64 09 72 65 61 6C 6D 6C 69 73 74 09 72    almd.realmlist.r
0180  65 61 6C 6D 6C 69 73 74 08 74 69 6D 65 7A 6F 6E    ealmlist.timezon
0190  65 08 74 69 6D 65 7A 6F 6E 65 0C 3F 00 03 00 00    e.timezone.?....
01A0  00 01 21 00 00 00 00 56 00 00 09 03 64 65 66 06    ..!....V....def.
01B0  72 65 61 6C 6D 64 09 72 65 61 6C 6D 6C 69 73 74    realmd.realmlist
01C0  09 72 65 61 6C 6D 6C 69 73 74 14 61 6C 6C 6F 77    .realmlist.allow
01D0  65 64 53 65 63 75 72 69 74 79 4C 65 76 65 6C 14    edSecurityLevel.
01E0  61 6C 6C 6F 77 65 64 53 65 63 75 72 69 74 79 4C    allowedSecurityL
01F0  65 76 65 6C 0C 3F 00 03 00 00 00 01 21 00 00 00    evel.?......!...
0200  00 42 00 00 0A 03 64 65 66 06 72 65 61 6C 6D 64    .B....def.realmd
0210  09 72 65 61 6C 6D 6C 69 73 74 09 72 65 61 6C 6D    .realmlist.realm
0220  6C 69 73 74 0A 70 6F 70 75 6C 61 74 69 6F 6E 0A    list.population.
0230  70 6F 70 75 6C 61 74 69 6F 6E 0C 3F 00 0C 00 00    population.?....
0240  00 04 21 00 1F 00 00 40 00 00 0B 03 64 65 66 06    ..!....@....def.
0250  72 65 61 6C 6D 64 09 72 65 61 6C 6D 6C 69 73 74    realmd.realmlist
0260  09 72 65 61 6C 6D 6C 69 73 74 09 67 61 6D 65 62    .realmlist.gameb
0270  75 69 6C 64 09 67 61 6D 65 62 75 69 6C 64 0C 3F    uild.gamebuild.?
0280  00 0B 00 00 00 03 21 00 00 00 00 05 00 00 0C FE    ......!.........
0290  00 00 22 00 2A 00 00 0D 01 31 07 54 72 69 6E 69    ..".*....1.Trini
02A0  74 79 09 31 32 37 2E 30 2E 30 2E 31 04 38 30 38    ty.127.0.0.1.808
02B0  39 01 31 01 30 02 31 36 01 30 01 30 05 31 33 39    9.1.0.16.0.0.139
02C0  33 30 2E 00 00 0E 01 32 0B 54 72 69 6E 69 74 79    30.....2.Trinity
02D0  63 6F 72 65 09 31 32 37 2E 30 2E 30 2E 31 04 38    core.127.0.0.1.8
02E0  30 38 39 01 31 01 30 02 31 36 01 30 01 30 05 31    089.1.0.16.0.0.1
02F0  33 39 33 30 05 00 00 0F FE 00 00 22 00             3930.......".
```

MySQL返回的数据

22 127.0.0.1:10740 127.0.0.1:3306 77 Send

```
0000  49 00 00 00 03 53 45 4C 45 43 54 20 6E 75 6D 63    I....SELECT numc
0010  68 61 72 73 20 46 52 4F 4D 20 72 65 61 6C 6D 63    hars FROM realmc
0020  68 61 72 61 63 74 65 72 73 20 57 48 45 52 45 20    haracters WHERE 
0030  72 65 61 6C 6D 69 64 20 3D 20 27 31 27 20 41 4E    realmid = '1' AN
0040  44 20 61 63 63 74 69 64 3D 27 31 31 27             D acctid='11'
```

从数据库中取该登陆服务器上该账号realmid为1的角色数量

23 127.0.0.1:3306 127.0.0.1:10740 107 Recv

```
0000  01 00 00 01 01 4A 00 00 02 03 64 65 66 06 72 65    .....J....def.re
0010  61 6C 6D 64 0F 72 65 61 6C 6D 63 68 61 72 61 63    almd.realmcharac
0020  74 65 72 73 0F 72 65 61 6C 6D 63 68 61 72 61 63    ters.realmcharac
0030  74 65 72 73 08 6E 75 6D 63 68 61 72 73 08 6E 75    ters.numchars.nu
0040  6D 63 68 61 72 73 0C 3F 00 03 00 00 00 01 21 00    mchars.?......!.
0050  00 00 00 05 00 00 03 FE 00 00 02 00 02 00 00 04    ................
0060  01 33 05 00 00 05 FE 00 00 02 00                   .3.........
```

MySQL返回的数据

24 127.0.0.1:10740 127.0.0.1:3306 77 Send

```
0000  49 00 00 00 03 53 45 4C 45 43 54 20 6E 75 6D 63    I....SELECT numc
0010  68 61 72 73 20 46 52 4F 4D 20 72 65 61 6C 6D 63    hars FROM realmc
0020  68 61 72 61 63 74 65 72 73 20 57 48 45 52 45 20    haracters WHERE 
0030  72 65 61 6C 6D 69 64 20 3D 20 27 32 27 20 41 4E    realmid = '2' AN
0040  44 20 61 63 63 74 69 64 3D 27 31 31 27             D acctid='11'
```

从数据库中查询该账户realmid为2的角色数量

25 127.0.0.1:3306 127.0.0.1:10740 101 Recv

```
0000  01 00 00 01 01 4A 00 00 02 03 64 65 66 06 72 65    .....J....def.re
0010  61 6C 6D 64 0F 72 65 61 6C 6D 63 68 61 72 61 63    almd.realmcharac
0020  74 65 72 73 0F 72 65 61 6C 6D 63 68 61 72 61 63    ters.realmcharac
0030  74 65 72 73 08 6E 75 6D 63 68 61 72 73 08 6E 75    ters.numchars.nu
0040  6D 63 68 61 72 73 0C 3F 00 03 00 00 00 01 21 00    mchars.?......!.
0050  00 00 00 05 00 00 03 FE 00 00 02 00 05 00 00 04    ................
0060  FE 00 00 02 00                                     .....
```

MySQL返回的数据

&#x20;**26 127.0.0.1:3724 127.0.0.1:10775 81 Send**&#x20;

```
0000  10 4E 00 00 00 00 00 02 00 01 00 00 54 72 69 6E    .N..........Trin
0010  69 74 79 00 31 32 37 2E 30 2E 30 2E 31 3A 38 30    ity.127.0.0.1:80
0020  38 39 00 00 00 00 00 03 10 2C 01 00 00 54 72 69    89.......,...Tri
0030  6E 69 74 79 63 6F 72 65 00 31 32 37 2E 30 2E 30    nitycore.127.0.0
0040  2E 31 3A 38 30 38 39 00 00 00 00 00 00 10 2C 10    .1:8089.......,.
0050  00                                                 .
```

认证服务器返回给客户端登陆服务器列表


# 2011-06-13  魔兽世界 3.3.5 13930 Trinity 认证补丁

2011年6月13日，Trinity官方代码针对WoW客户端3.3.5 13930版本登陆有问题。经过代码跟踪调试，发现需要在认证服务器中加入如下补丁： 位于文件 `TrinityCore\src\server\authserver\Authentication\AuthCodes.h`

```
#define POST_BC_ACCEPTED_CLIENT_BUILD   {13930, 12340, 11723, 11403, 11159, 10571, 10505, 10146, 9947, 8606, 0}
```

改动之处就是，在这个客户端支持版本号的列表中加入13930。改动之后需要重新编译authserver和worldserver。


# 2011-05-20  统一业务模型(UBM) in ERP5

from <http://www.erp5.org/UnifiedBusinessModel>

## 什么是统一业务模型

统一业务模型(Unified Busines Model - UBM)是ERP5系统采用的一个抽象业务模型，也是其精华所在。ERP5的作者Jean-Paul Smets在建立ERP5的一个版本的时候提出了这个创新，并且将这个模型应用到了基于ERP5开发的所有应用中。 由于这个模型是如此的高效和有价值，ERP5的名字也是由此而来，意思是仅通过UBM中的5个概念建立的ERP系统，因此称之为ERP5。

## 统一业务模型中的五个概念是什么

UBM包含5个概念，分别是节点（Node）、资源（Resource）、迁移（Movement）、物品（Item）和路径（Path）。

**节点（Node）**&#x8868;示一个业务流程涉及到的一个关键点，例如一个人、一个组织、一个仓库、一个银行账号等等。这个概念与在图论和数学语言中的"节点"的概念类似。

**资源（Resource）**&#x8868;示在业务流程中节点之间流通的一种有形或无形的东西。例如，资源可能是产品、原始材料、服务、现金等等。

**迁移（Movement）**&#x63CF;述了在节点之间的有关资源的变迁。类似图论中"有向边"的概念。它包含订购、发货、财务结算、付款、生产等等。

**物品（Item）**&#x7684;概念使得资源可跟踪。这个概念类似于在面向对象编程中类和实例的关系，如果资源（Resource）对应为"类"，那么物品（Item）对应于"实例"。物品可以表示一个条形码、RFID、某种订阅或者一张票据。

**路径（Path）**&#x8868;示一种可能的迁移（Movement），在定义交易条件、供应条件、付款模式等时很有用，并且可以为业务逻辑、供应链、定价等提供支持。

## 例子

提供几个例子可以帮助你更好的理解这个模型。

例一： 假设A公司希望卖给B公司一批照相机，一旦双方达成了交易，A公司会发给B公司10个照相机，每个单价$100. 在这里场景中，A公司和B公司是节点（Node）。商品（照相机）是一种资源。销售本身（订购10个照相机总价$1000）是一个迁移（Movement）。如果这个条件（例如单价）双方都认可，这个条件就是一个路径（Path）。如果A公司希望跟踪照相机，可以在每个照相机上贴一个条形码，这样就产生了10个物品（Item）。

例二： 假设A公司希望对来自B公司的电汇信息记账。B公司发给A公司$1000的应收账款。 在这个场景中，应收账款账户和银行账户是节点（Node）--物理意义上的，A公司和B公司也是节点（Node）--逻辑意义上的。转账是一个迁移（Movement），钱是资源（Resource）。物品和路径在这里没有用到。

例三： 假设A公司希望在工厂中使用零件制造出10个照相机。 在这个场景中，工厂是一个节点（Node），照相机和零件是资源（Resource）。生产由两个迁移（Movement）组成，一个是消耗零件，一个是制造出照相机产品。为了跟踪产品，A公司可以为每个照相机和零件都打上标签，它们就成为了物品（Item）。由零件到产品的转换由路径（Path）描述。

## ERP5中如何应用统一业务模型

UBM统一业务模型完全集成在ERP5的实现中。ERP5中的所有文档都基于该模型设计，ERP5中的所有工具也都采用该模型统一实现不同的业务活动，例如交易、生产、客户关系管理、财务和项目管理。 这使得我们可以基于ERP5快速开发新的应用，同时降低维护成本。因为所有的数据都采用统一的设计架构，因此代码量也大幅度的减少。


# 2011-03-08  制作ASCII字符动画

看过Matrix的同学应该还记得，在母舰上一直在计算的电脑屏幕在Neo觉醒的时候，不停的下落的杂乱无章的字符组成了Neo当时所处的场景。其实利用开源和免费的工具，我们可以将现有的视频转换为ASCII字符格式的视频。

需要用到的工具是QQ影音、Ascgen dotNET、MPlayer和Mencoder。QQ影音用于提取视频和音频；MPlayer用于自动截图，可以从www\.mplayerhq.hu下载，其中应该包含了mencoder.exe。如果没有的话，可以去下一个mediacoder，里面有mencoder这个工具。Ascgen dotNET用于将图片专为ASCII的，可以从<http://ascgendotnet.jmsoftware.co.uk/download下载；最后使用mencoder将图片重新编码为视频。>

第一步，找到需要转换的视频，利用QQ影音的"转码/截取/合并"功能，将想要转换的视频和音频分别截取出来。

第二步，用MPlayer打开要前面准备好的视频，播放的同时按"Shift-D"也就是启动"开始/停止自动截图"功能，截取的图片默认是保存在`C:\Documents and Settings\Administrator\.smplayer\screenshots`下面。每分钟的视频大概会有1500多张图片。

第三步，使用`Ascgen dotNET`的`Batch Conversion`功能，将截取的所有图片文件或目录加入到列表中，设置好输出目录，选择输出为jpg格式、75%大小，勾选Colour选项，然后点Convert开始转换。这一步会将原始图片转换为以ASCII字符构成的图片。

最后一步，在转换好的ASCII图片目录中，使用mencoder用命令行编码为视频：

```
-ovc lavc  -lavcopts vcodec=mpeg4:mbd=2:trell -oac copy -audiofile [第一步提取出来的mp3文件] -o output.avi
```

其中"生成图片的高度和宽度"可以从查看生成的图片的属性，在"摘要"中有图片的高度和宽度信息。原始视频的fps值一般情况下是30，可以用QQ影音打开视频文件，右键点视频窗口--文件信息--"视频帧率"中查到，也可以查看视频文件的"属性"--摘要--"帧速率"。如果不需要声音的话，可以去掉命令行中"-audiofile \[第一步提取出来的mp3文件]"这一部分。

最终在当前目录下生成的output.avi文件就是转换好的ASCII字符视频了。


# 2011-01-14  Ubuntu升级导致的udevd错误修复

Linode上的VPS服务器，从Ubuntu Lucid升级至Karmic后，重启后启动失败。用Linode的RemoteAccess连上去看到如下启动错误信息：

```
init: ureadahead main process (986) terminated wit  status 5
udevd[1012]: failed to create queue file: No such file or directory

udevd[1012]: error creating queue file                                                              

init: udev main process (1012) terminated with sta us 1
init: udev main process ended, respawning
init: udevmonitor main process (1014) terminated w th status 2
udevadm[1734]: error sending message: Connection r fused                                            

mountall: Disconnected from Plymouth
init: plymouth main process (989) killed by SEGV signal
init: plymouth-splash main process (1735) terminat d with status 2
init: hwclock-save main process (1740) terminated  ith status 1
```

经查，由于udevd升级后不支持自动mount自身到/dev节点导致。需要手工修改/etc/fstab文件修复。用Linode的Recure Boot方式启动，mount上原分区，在/etc/fstab最后加入如下一行

```
dev /dev tmpfs rw 0 0
```

再次重启，没问题了。


# 2011-01-09  行列有序矩阵求第K个数

&#x20;**n\*n的行列有序的矩阵M，求第K大的数。行列有序是指每行、每列都是有序的。**&#x20;

~~最直接的办法就是给整个矩阵排序，然后找第K大的数。复杂度是(O(n^2logk))。其实还有更快的办法。~~ ~~假设行列都是增序。注意到在矩阵的对角线上中任选一点x，右下角的矩阵里面的数都大于或等于x，而这里包含了(x^2)个数。因此，必然可以找到一个位置x，使得 ((x+1)^2\leq n-k\leq x^2) 。 这样K就在 M(x+1, x+1) .. M(x+1, n) 和 M(x+1, x+1) .. M(n, x+1) 以及 x 这三个范围内。~~ ~~对这个范围内的数字排序，找第K个数字，复杂度是 (O(nlogk))~~

事实证明，我还是把这个问题想简单了，之前的解法是错的，因为取(a, a)点后，并不能保证a是除左上角矩阵最大的值，左下和右上仍然有可能有比(a, a)小的数字。

后来又想了个办法，做一个队列，伪代码如下：

```
findSortedMatrix {
    队列置空
    取M(0, 0)，入队
    for (int i=0; i < K; i++) {
        队列首元素出队，置为X 坐标为(x, y)
        取元素X的左边和下边的元素"插入"队列，使队列保持有序
    }
}
```

为了提高插入的速度，可以用堆的结构实现二分查找插入。这样，空间复杂度是(O(k)), 算法复杂度为(O(klogk))，最坏情况是空间(O(n^2)), 时间(O(klogn))。

当然，这仍然不是最快的解法。查了下资料，发现这个问题确实蛮困难，[有篇论文](http://scitation.aip.org/getabs/servlet/GetabsServlet?prog=normal\&id=SMJCAT000013000001000014000001\&idtype=cvips\&gifs=yes) 研究了这个问题，对于一个(m\times n)的矩阵算法复杂度可以达到(O(n\log2m/n)) ，不过原始论文要砍25刀。

[这篇Blog](http://zhiqiang.org/blog/science/computer-science/median-algorithm-of-ordered-matrix.html) 也提到了这个问题，给了个简要的描述。


# 2011-01-09  字节按位逆序

问题：给一个8位的字节，按位逆序。第一反应给了个简单的做法：

```
unsigned short intreverse_bit(unsigned short int b)
{
    unsigned short int r = 0;
    for (int i=0; i<8; i++) {
        b >> = 1
        r = (r << 1) | (b & 1);
    }
    return r;
}
```

每次循环，r左移一位，b取最后一位填充到r的最后一位上，这个解答需要做8次循环，32次操作（左移一次，加法一次，右移一次，按位与一次）。很明显还有更好的做法。

```
unsigned short int reverse_bit(unsigned short int b)
{
    b = b << 4 + b >> 4;    // &11110000, 56781234
    b = (b & 0x33) << 2 + (b & 0xCC) >> 2;    // &11001100, 78563412
    b = (b & 0x55) << 1 + (b & 0xAA) >> 1;    // &10101010, 87654321

    return b;
}
```

只需13次操作。

2011-01-18 更新： 发现了更赞的做法，一个比一个奇技淫巧。

## 第一种解法，还是常用的一个一个的移位的做法。

把输入和输出当作两个队列，每次取一位，然后一个出队一个入队，代码更精简，支持多字节整数。

```
unsigned int v;     // 要反转的输入，这里的实现支持是多于一个字节的可实现位操作的无符号整数
unsigned int r = v; // r 将会是v的按位反转的结果，下面的for循环会先做右移操作，这里的赋值可以先取得v的最低有效位
int s = sizeof(v) * CHAR_BIT - 1; // 反转后需要做的额外的移位操作次数，CHAR_BIT为8，指一个字节包含的bit数

for (v >>= 1; v; v >>= 1)
{
  r <<= 1;
  r |= v & 1; //取v的最低位的值，并置于r的最低位
  s--;
}
r <<= s; // v为0，s为v中剩余的高位为0的位数，填充到r的末尾
```

## 第二种解法，利用64位乘法和取模运算，三步操作解决

```
unsigned char b; // 反转这个8位的字节

b = (b * 0x0202020202ULL & 0x010884422010ULL) % 1023;
```

整个中间过程如下：

```
           0000001000000010000000100000001000000010 -> 0x0202020202
*                                          abcdefgh -> hgfe dcba
---------------------------------------------------
           000000h0000000h0000000h0000000h0000000h0
          000000g0000000g0000000g0000000g0000000g0
         000000f0000000f0000000f0000000f0000000f0
        000000e0000000e0000000e0000000e0000000e0
       000000d0000000d0000000d0000000d0000000d0
      000000c0000000c0000000c0000000c0000000c0
     000000b0000000b0000000b0000000b0000000b0
    000000a0000000a0000000a0000000a0000000a0
---------------------------------------------------
    000000abcdefghabcdefghabcdefghabcdefghabcdefgh0
&   00000010000100010000100010000100010000000010000
---------------------------------------------------
    000000a0000f000b0000g000c0000h000d00000000e0000
%   00000000000000000000000000000000000001111111111
---------------------------------------------------
    000000000000000000000000000000000000000hgfedcba
```

第一次乘法将字节复制成5份保存在一个64位整数里，第二次AND操作取出在正确位置（反转后的）上的位，每10位一组。这两次操作的结果是将每位放置到组中的正确位置上，注意下面每一位都在组中正确的相对位置上。

```
000000a 0000f000b0 000g000c00 00h000d000 00000e0000
```

最后一步的取模操作，将之前的结果以每10位一组合并起来。其中利用到了取模的一个特点，当`a<b`时，`a % b = a`，而`(a + b ) % p = (a % p + b % p) %p`，当`a + b < p`的时候，就有`(a + b ) % p = a % p + b % p`。而这五组二进制的数的总和也是小于或等于`1111111111`的，因此最终取模的过程类似于如下操作

```
    000000000a 0000000000 0000000000 0000000000 0000000000 % 1111111111 = 000000000a
               0000f000b0 0000000000 0000000000 0000000000 % 1111111111 = 0000f000b0
                          000g000c00 0000000000 0000000000 % 1111111111 = 000g000c00
                                     00h000d000 0000000000 % 1111111111 = 00h000d000
+                                               00000e0000 % 1111111111 = 00000e0000
------------------------------------------------------------------------------------
    000000000a 0000f000b0 000g000c00 00h000d000 00000e0000 % 1111111111 = 00hgfedcba
```

**2011-01-20 补充：** 之前的说明是我自己根据朴素的理解想明白的，但是hook同学知道了之后自己拿笔推了个公式出来，对于多项式(P(x))来说，(P(x)\mod (x-1) \equiv)各项参数之和 ，也就是同余： \[ (a*nx^n+a*{n-1}x^{n-1}+&#x61;*{n-2}x^{n-2}+\cdots+a\_1)\mod (x-1)=a\_n+a*{n-1}+a\_{n-2}+\cdots+a\_1]

hook的证明如下： 设 y=x-1，代入P(x)有 \[(a*n(y+1)^n+a*{n-1}(y+1)^{n-1}+&#x61;*{n-2}(y+1)^{n-2}+\cdots+a\_1)\mod y]，根据二项式展开式\[ (a+b)^n=\sum*{i=0}^{n}&#x43;*{n}^{i}a^ib^{n-i} ]有 \[ (a+1)^n=\sum*{i=0}^{n}&#x43;*{n}^{i}a^i ]，展开得 \[ (a\_n\sum*{i=0}^{n}&#x43;*{n}^{i}y^i + \cdots + a\_1)\mod y = a\_n \*\sum*{i=0}^{n}&#x43;*{n}^{i}y^i\mod y+\cdots+a\_1 \mod y ]，当(i\ne0)时(a\_n\sum*{i=0}^{n}&#x43;*{n}^{i}y^i\mod y=0)，当(i=0)时(a\_n\sum*{i=0}^{n}C\_{n}^{i}y^i\mod y=a\_n)，因此得证。

我的朴素证法： 设 y = x - 1，代入P(x)有 \[ (a*n(y+1)^n + a*{n-1}(y+1)^{n-1} + \cdots + a*1)\mod y = a\_n(y+1)^n\mod y + a*{n-1}(y+1)^{n-1}\mod y + \cdots + a*1\mod y ]， 根据模运算法则 ( a^b\mod p = (a \mod p)^b\mod p) 以及 ( (a  b)\mod p = ( (a\mod p)  (b\mod p) )\mod p )，有 \[ a\_n(y+1)^n\mod y = (a\_n\mod y) ((y+1)\mod y )^n\mod p = a\_n 1^n = a\_n ]， 因此有 \[ (a\_n(y+1)^n + a*{n-1}(y+1)^{n-1} + \cdots + a*1)\mod y = a\_n + a*{n-1} + \cdots + a\_1 ]

## 第三种解法，仅用64乘法，4步解决

```
unsigned char b; // reverse this byte

b = ((b * 0x80200802ULL) & 0x0884422110ULL) * 0x0101010101ULL >> 32;
```

整个过程如下，乘法操作先将要反转的字节复制4份，然后AND操作将每2位分布到一个8位组中，最后乘法将结果叠加起来，中间的一个8位组正好包含完整的逆序结果，最后利用移位操作将这个完整的逆序8位组移至低8位。在将结果赋值回给b的时候，因为b是一个字节，因此近保留了有效的低8位，前面的无效数据全部被舍弃，正好是正确结果。

```
                                                                                        abcd efgh (-> hgfe dcba)
*                                                      1000 0000  0010 0000  0000 1000  0000 0010 (0x80200802)
-------------------------------------------------------------------------------------------------
                                            0abc defg  h00a bcde  fgh0 0abc  defg h00a  bcde fgh0
&                                           0000 1000  1000 0100  0100 0010  0010 0001  0001 0000 (0x0884422110)
-------------------------------------------------------------------------------------------------
                                            0000 d000  h000 0c00  0g00 00b0  00f0 000a  000e 0000
*                                           0000 0001  0000 0001  0000 0001  0000 0001  0000 0001 (0x0101010101)
-------------------------------------------------------------------------------------------------
                                            0000 d000  h000 0c00  0g00 00b0  00f0 000a  000e 0000
                                 0000 d000  h000 0c00  0g00 00b0  00f0 000a  000e 0000
                      0000 d000  h000 0c00  0g00 00b0  00f0 000a  000e 0000
           0000 d000  h000 0c00  0g00 00b0  00f0 000a  000e 0000
0000 d000  h000 0c00  0g00 00b0  00f0 000a  000e 0000
-------------------------------------------------------------------------------------------------
0000 d000  h000 dc00  hg00 dcb0  hgf0 dcba  hgfe dcba  hgfe 0cba  0gfe 00ba  00fe 000a  000e 0000
>> 32
-------------------------------------------------------------------------------------------------
                                            0000 d000  h000 dc00  hg00 dcb0  hgf0 dcba  hgfe dcba
&                                                                                       1111 1111
-------------------------------------------------------------------------------------------------
                                                                                        hgfe dcba
```

## 第四种解法，仅用32位操作，7步解决

```
b = ((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16;
```

中间过程如下：

```
           0000100000000010 -> x0802
*                  abcdefgh
---------------------------
           0000h000000000h0
          0000g000000000g0
         0000f000000000f0
        0000e000000000e0
       0000d000000000d0
      0000c000000000c0
     0000b000000000b0
    0000a000000000a0
---------------------------
    0000abcdefgh00abcdefgh0  -> b * 0x0802LU
&   00000100010000100010000  -> 0x22110
---------------------------
    00000b000f0000a000e0000  -> (b * 0x0802LU & 0x22110LU)
```

```
           1000000000100000 -> x8020
*                  abcdefgh
---------------------------
           h000000000h00000
          g000000000g00000
         f000000000f00000
        e000000000e00000
       d000000000d00000
      c000000000c00000
     b000000000b00000
    a000000000a00000
---------------------------
    abcdefgh00abcdefgh00000  -> b * 0x8020LU
&   00010001000010001000000  -> 0x0x88440
---------------------------
    000d000h0000c000g000000  -> (b * 0x8020LU & 0x88440LU)
```

```
                        0000 00b0 00f0 000a 000e 0000  -> (b * 0x0802LU & 0x22110LU)
|                       0000 d000 h000 0c00 0g00 0000  -> (b * 0x8020LU & 0x88440LU)
-----------------------------------------------------
                        0000 d0b0 h0f0 0c0a 0g0e 0000
*                       0000 0001 0000 0001 0000 0001  -> 0x10101LU
-----------------------------------------------------
                        0000 d0b0 h0f0 0c0a 0g0e 0000
              0000 d0b0 h0f0 0c0a 0g0e 0000
    0000 d0b0 h0f0 0c0a 0g0e 0000
-----------------------------------------------------
    0000 d0b0 h0f0 dcba hgfe dcba hgfe 0c0a 0g0e 0000
>> 16
-----------------------------------------------------
                        0000 d0b0 h0f0 dcba hgfe dcba
&                                           1111 1111
-----------------------------------------------------
                                            hgfe dcba
```

这几个解法取自[这里](http://graphics.stanford.edu/~seander/bithacks.html)，神一般的位操作，撰文留念。


# 2011-01-01  编程之美 1.2 中国相帅问题的一个简洁解法

题目意思大概是，象棋棋盘上只有一相一帅，只能在9宫格内移动，且不能对面。要求给出相帅所有的位置的可能性，只能用一个变量。

只用一个变量，第一感是用位操作。给九宫格编个号：

```
1 2 3
4 5 6
7 8 9
```

遍历所有的位置，如果 位置编号 mod 3相同，说明在同一列中。按此思路，书中位操作的解法略显繁杂。这里一个简洁的的解法是：

```
void main()
{
    short unsigned int x;
    for (x = 0; (x & 0xF0) < 0x90; x += 0x10 ) {
        for ( x &= 0xF0; (x & 0x0F) < 9; x++)
            if ((x >> 4) % 3 != (x & 0x0F) % 3)
                printf("A = %d, B = %d\n", (x >> 4) + 1, (x & 0x0F) + 1);
    }
}
```

如果不用位操作的话，另外一个解法比较赞：

```
BYTE i = 81;
while (i--)
{
    if (i / 9 % 3 != i % 9 % 3)
        printf("A = %d, B = %d\n", i / 9 + 1, i % 9 + 1);
}
```


# 2010-11-26  为Windows 7/Windows Server 2008添加IPX协议

Windows 7 或者 Windows Server 2008 默认情况下不支持IPX协议，而在很多需要局域网的游戏中还是经常需要用到IPX协议的。解决办法就是从XP系统中复制出IPX协议的驱动文件，然后在Windows 7的网卡的配置界面中就可以找到IPX协议的添加项了。 相关文件是：

X86版本

```
Windows\inf\netnwlnk.pnf
Windows\inf\netnwlnk.inf
Windows\System32\rtipxmib.dll
Windows\System32\nwprovau.dll
Windows\System32\wshisn.dll
Windows\System32\drivers\nwlnkflt.sys
Windows\System32\drivers\nwlnkfwd.sys
Windows\System32\drivers\nwlnkipx.sys
Windows\System32\drivers\nwlnknb.sys
Windows\System32\drivers\nwlnkspx.sys
```

x64版本

```
Windows\nwlnkipx.sys
Windows\nwlnknb.sys
Windows\nwlnkspx.sys
Windows\inf\netnwlnk.inf
Windows\inf\netnwlnk.PNF
Windows\system32\nwprovau.dll
Windows\system32\wshisn.dll
Windows\system32\drivers\nwlnkipx.sys
Windows\system32\drivers\nwlnknb.sys
Windows\system32\drivers\nwlnkspx.sys
```


# 2010-11-12  How to debug with Android Logging

!\[AP Logger Architecture]

The above image shows the architecture of Android logging system.It provides a java class for logging named android.util.Log. It also provides log macros for native C applications in liblog.

There are four log devices in the kernel. Three for user space log: `/dev/log/main`, `/dev/log/radio`, `/dev/log/events`. One for kernel space log: `/dev/log/kernel`.

For user space applications, those binary log messages will be written to `/dev/log/events`. Those log messages with the tag "HTC\_RIL" "RILJ" "RILC" "RILD" "RIL" "AT" "GSM" "STK" will be written to `/dev/log/radio`. Other log messages will be written to `/dev/log/main`.

The log devices named `/dev/log/kernel` is for kernel log message collection. A console is registered in `/dev/log/kernel` and collect all the `printk` output to the device. It will output kernel log messages with the same log format as other log devices.`Logcat` is a tool provided by Android. It could read log messages from log devices and output to the console or to a file. You can find the detail usage later.

An `aplog` daemon is added for offline log. A filter is added to do some security checking.

## Logging interface For Java applications

### Logging class Introduction

Class Name: `android.util.Log`

General method:

```
 Log.v()
 Log.d()
 Log.i()
 Log.w()
```

The order in terms of verbosity, from least to most is `ERROR, WARN, INFO, DEBUG, VERBOSE`. `VERBOSE` should never be compiled into an application except during development. `DEBUG` logs are compiled in but stripped at runtime. `ERROR`, `WARN` and `INFO` logs are always kept.

> TIP: A good convention is to declare a TAG constant in your class: `private static final String TAG = "TAG_MyActivity";`>and use that in subsequent calls to the log methods. `Log.v(TAG, "index=" + i);`When you're building the string to pass into Log.d, Java uses a StringBuilder and at least three allocations occur: the StringBuilder itself, the buffer, and the String object. Realistically, there is also another buffer allocation and copy, and even more pressure on the GC. That means that if your log message is filtered out, you might be doing significant work and incurring significant overhead.

For more details, please visit [Android log reference](http://developer.android.com/reference/android/util/Log.html)

#### Program Example

```
package com.android.hello;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
import android.util.Log; /*import log class*/

private static final String TAG = "MyActivity"; /* define log tag*/

public class !HelloAndroid extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreaipte(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        TextView tv = new TextView(this);
        tv.setText("Hello, Android");
        setContentView(tv);

        Log.i(TAG, "this is a log.i message");
        Log.v(TAG, "test is a log.v message");
        Log.d(TAG, "test is a log.d message");
        Log.w(TAG, "test is a log.w message");
        Log.e(TAG, "test is a log.e message");
    }
}
```

## Logging interface For Native applications

Header File `include <cutils/log.h>`###Logging Macros

Common Logging Macros

```
LOGV LOGD LOGI LOGW LOGE
```

Condition Logging Macros

```
LOGV_IF LOGD_IF LOGI_IF LOGW_IF LOGE_IF
```

The definition is as below:

```
#define CONDITION(cond) (__builtin_expect((cond)!=0,0))
#define LOGV_IF(cond, ...)   ( (CONDITION(cond)) ?((void)LOG(LOG_VERBOSE, LOG_TAG, VA_ARGS)) : (void)0 )
```

#### Logging Macros overview

API for sending log output.

> NOTE:
>
> 1. You should define LOG\_TAG in your C source code firstly.
> 2. To build out C/C++ applications outside android, you should add `LOCAL_SHARED_LIBRARIES := liblog libcutils` in your Android.mk file

#### Program Example

```
#include <stdio.h>
#include <cutils/log.h> /* log header file*/
#include <cutils/properties.h>

/* define log tag */
#ifdef LOG_TAG
#undef LOG_TAG
#define LOG_TAG "app"
#endif
int main()
{
    LOGV("Verbose: _app");
    LOGD("Debug: _app");
    LOGI("Info: _app");
    LOGW("Warn: _app");
    LOGE("Error: _app");
    printf("Hello Android.\n");
    return 0;
}
```

## Log command on Android

Command location

```
/system/bin/log
```

Command usage

```
log [-p priorityChar] [-t tag] message
```

priorityChar should be one of : `v, d, i, w, e`## Log format on Android The format of a log messages is

```
tv_sec     tv_nsec     priority      pid     tid      tag      messageLen       Message
```

* tag: log tag
* tv\_sec & tv\_nsec: the timestamp of the log messages
* pid: the process id of where log messages come from
* tid: the thread id
* Priority value is one of the following character values, ordered from lowest to highest priority:
  * V - Verbose (lowest priority)\*
  * D - Debug\*
  * I - Info\*
  * W - Warning\*
  * E - Error\*
  * F - Fatal\*
  * S - Silent (highest priority, on which nothing is ever printed)\*


# 2010-09-16  利用google-code-prettify做网页内源码的语法高亮

由于HTML本身并不认空格，因此网页中如果要展现程序代码通常会用\`\`\`\`\`标签来保留缩进、空格等格式。

作为程序员的Blog，对此的需求更是高很多。单单利用\`\`\`\`\`标签保留代码格式还不能让代码看起来很漂亮，通常在IDE中都会有语法高亮的特性，利用[google-code-prettify](http://code.google.com/p/google-code-prettify/)可以很方便的在网页中实现这一功能。

官方主页在此：<http://code.google.com/p/google-code-prettify/> 下载最新的源码之后，在网页的HEAD部分加入以下两行

```
<link href="prettify.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="prettify.js"></script>
```

然后在需要高亮代码的地方，将代码放在 pre 标签中。在class中lang-X的X可以是下面几种语言之一： bsh", "c", "cc", "cpp", "cs", "csh", "cyc", "cv", "htm", "html", "java", "js", "m", "mxml", "perl", "pl", "pm", "py", "rb", "sh", "xhtml", "xml", "xsl"

如果是在Wordpress中的话，可以使用Code Block Enabler这个插件。

在配置完成之后，还有一个小小的缺憾。通常程序的代码都是使用等宽字体，最常见的就是Courier New字体。而google-code-prettify并没有设置代码的字体。简单的对其css文件作一个小小的修改即可实现这一点，打开WordPress的wp-content/plugins/code-block-enabler/prettify.css文件，找到pre.prettyprint的定义后面加上字体的定义，改为：

```
<pre class="prettyprint lang-css">
pre.prettyprint { padding: 2px; border: 1px solid #888; font-family: Courier New; font-size: 9px;}
```


# 2010-09-05  10 Ways We Hurt Our Romantic Relationships

10 Ways We Hurt Our Romantic Relationshipsx 幸福爱情的十大忌讳【石榴刺猬全文译】

It's not easy to have a great relationship with your boy/girlfriend, partner, or spouse. But it's not impossible, either - it takes some work, of course, but it's work, work that's a joy when everything comes together.

A lot of times, though, the work isn't enough. We get in our own way with ideas and attitudes about relationships that are not only wrong, but often work to undermine our relationships no matter how hard we work at it.

I've watched a lot of breakups (some of them my own). I've seen dramatic flare-ups and drawn-out slow fades, and I've tried to pay attention to what seems to be going on. Here are a few of the things I've seen that cause people to destroy their own relationships.

能够和自己的男友（女友）或伴侣保持一段美好的感情不是一件那么容易的事情，但却并不是不可能。它需要付出一些努力，当然，也会有效。

尽管很多时候，光靠努力是不够的。我们带着自己的观点和态度去对待感情，这些观点和态度可能没错，但不管我们怎么努力，通常却会在无形中伤害到彼此的感情。

我看过太多的分手（包括我自己的）。我看到过矛盾激化而迅速分手，也看到过感情慢慢消磨掉了而分手，我试图去搞明白到底是怎么回事。这里是一些我所看到的，会导致我们毁掉彼此感情的一些东西。

1. You're playing to win

   　事事争胜

   One of the deadliest killers of relationships is the competitive urge. I don't mean competition in the sense that you can't stand to lose at tennis, I mean the attitude that the relationship itself is a kind of game that you're tying to win. People in competitive relationships are always looking for an advantage, the upper hand, some edge they can hold over their partner's head. If you feel that there are things you can't tell your partner because she or he will use it against you, you're in a competitive relationship - but not for long.

逼迫性的要求是两性关系中最致命的一点。我所说的逼迫性并不是指在俩人发生矛盾的时候不能输给对方，而是指处理感情时就好像要去赢一场比赛一样的那种态度。人们在对抗性的感情中，总是希望获得某种优势、占据上风，或是寻求某种能够牢牢吃定对方的优势。如果你觉得有些事情不能告诉你的伴侣--因为对方可能会以此来对付你，那么你就处于一个对抗性的关系中--但是不会长久这样下去的。

1. You don't trust

   　缺乏信任

   There are two aspects of trust that are important in relationships. One is trusting your partner enough to know that s/he won't cheat on you or otherwise hurt you - and to know that he or she trusts you that way, too. The other is trusting them enough to know they won't leave you or stop loving you no matter what you do or say. The second that level of trust is gone, whether because one of you takes advantage of that trust and does something horrible or because one of you thinks the other has, the relationship is over - even if it takes 10 more years for you to break up.

感情中的信任有很重要的两个方面。 一个是给对方足够的信任，相信对方不会欺骗或伤害你，同时也相信对方也会用同样的方式对待你；另外一个是充分的相信对方--无论你说了什么或做了什么，对方都不会离开你或者不再爱你。如果失去了这种程度的信任，无论是因为你充分利用了对方的这种信任做了一些很糟糕的事情，还是因为你们中的一个认为已经失去了对方的这种信任，你们的感情算是完蛋了--即使你们已经在一起了十几年。

1. You don't talk

   　欠缺沟通　

   Too many people hold their tongues about things that bother or upset them in their relationship, either because they don't want to hurt their partner, or because they're trying to win. (See #1 above; example: "If you don't know why I'm mad, I'm certainly not going to tell you!") While this might make things easier in the short term, in the long run it gradually erodes the foundation of the relationship away. Little issues grow into bigger and bigger problems - problems that don't get fixed because your partner is blissfully unaware, or worse, is totally aware of them but thinks they don't really bother you. Ultimately, keeping quiet reflects a lack of trust - and, as I said that's the death of a relationship.

有太多的人在面对感情中会让人心烦意乱或吵架的时候，都会选择回避、不去说。要么是因为不想去伤害对方，要么是因为想赢（参看第一条，比如："如果你不知道我为什么生气，我更不会告诉你"）。在短期内这样做可以让事情好办一些，但长期来看这样会慢慢的侵蚀掉两人关系的基础，最终导致破裂。小问题会变成越来越大的问题，因为对方总是处于一种幸福的无知状态（掩耳盗铃），问题总是无法得以解决。即使意识到了问题的存在，也总想当然的认为这些问题不会真的给你带来麻烦。最终，这种无言的背后是信任的缺失--或者用我的话来说就是，两人感情的终结。

1. You don't listen 不会倾听 Listening - really listening - is hard. It's normal to want to defend ourselves when we hear something that seems like criticism, so instead of really hearing someone out, we interrupt to explain or excuse ourselves, or we turn inward to prepare our defense. But your partner deserves your active listening. S/he even deserves you to hear the between-the-lines content of daily chit-chat, to suss out his/her dreams and desires when even s/he doesn't even know exactly what they are. If you can't listen that way, at least to the person you love, there's a problem.

   倾听--真正的倾听--是一件很难的事情。当我们听到对自己的批评或者责难的时候，通常我们都会选择防守来保护自己。因此当听到这些对自己的批评的时候，我们不会等对方说完，而是直接打断对方来为自己解释或者开脱。对方会"理所当然"的认为你"应该"仔细的听这些唠叨，然后明白对方想要的东西--尽管有的时候对方自己也不知道自己想要什么。如果你不能用这样的方式来倾听（至少对你爱的人这样），这就是个问题。
2. You spend like a single person 自己过自己的 This was a hard lesson for me to learn - until it broke up a 7-year relationship. When you're single, you can buy whatever you want, whenever you want, with little regard for the future. It's not necessarily wise, but you're the only one who has to pay the consequences. When you are with someone in a long-term relationship, that is no longer a possibility. Your partner - and your children, if there are or will be any - will have to bear the brunt of your spending, so you'd better get in the habit of taking care of household necessities first and then, if there's anything left over, of discussing with your partner the best way to use it.

This is an increasing problem these days, because more and more people are opting to keep their finances separate, even when they're married. There's nothing wrong with that kind of arrangement in and of itself, but it demands more communication and involvement between the partners, not less. If you're spending money as if it was your money and nobody else has a right to tell you what to do with it, your relationship is doomed.

对我来说这是一个很沉重的教训，它让我失去了一段7年的感情。当单身的时候，你可以想买什么就买什么，想什么时候买就什么时候买，基本不考虑将来。虽然并不明智，但毕竟只会影响到你自己，一人吃饱全家不饿。但当你和某人开始一段长期的 感情中的时候，就再也不能这样了。你的伴侣--和你的孩子（如果已经有了或将来会有），将不得不去承担你的花费所带来的压力。因此你最好养成一个习惯，先去关注那些日常家用所需要的东西。之后如果还有结余，再来跟对方讨论如何使用这些钱最好。

目前这是一个日益严重的问题，因为现在很多人认为双方的财务应该分开，即使是结婚了也应该如此。这样做本身并没有什么问题，但是这样一来需要和对方有更多的沟通和交流，而不是更少。如果你觉得自己的钱爱怎么花就怎么花，别人谁也管不着，那你的感情基本就算完了。

1. You're afraid of breaking up

   　害怕分手

   Nobody in a truly happy partnership is afraid of breaking up. If you are, that's a big warning sign that something's wrong. But often, what's wrong is the fear itself. Not only does it betray a lack of trust, but it shows a lack of self-confidence and self-esteem - you're afraid that there's no good reason for someone to want to be with you, and that sooner or later your partner will "wise up" and take off. So you pour more energy into keeping up the appearance of a happy relationships than you do into building yourself up as a person. Quite frankly, this isn't going to be very satisfying for you, and it also isn't going to be very satisfying for your partner.

在一段幸福的感情中，没有人会害怕分手。如果你怕了，那么这就是一个很大的危险信号，这表明出问题了。但是通常，出的问题本身就让人不愿面对。它不仅仅表明你背叛了脆弱的信任（参看第2条信任的定义），也表明缺乏自信和自尊--你怕对方没有什么理由要跟你在一起，或者怕对方早晚都会想明白然后放手。因此你付出更多的精力来保持和维系这种貌似快乐的关系，而不是让自己变的更好。坦白的说，这不会让你觉得舒服，而且也不会让对方觉得很舒服。

1. You're dependent

   　依赖性强

   There's a thin line between companionship and support and dependency. If you depend on your partner - that is, if you absolutely cannot live without her or him - you've crossed that line. The pressure is now on your partner to fill whatever's missing in you - a pressure s/he will learn to resent. If you expect your partner to bring everything while you bring nothing to your relationship - and I'm talking finances as well as emotional support, here - you're in trouble. (Note: I'm not saying that you need to contribute equally to household finances - what I'm saying is that if you're not contributing to the household budget, and you're not contributing anywhere else, things are out of whack and that's never good.)

在感情、支持和依赖之间，有一条很窄的线。 如果你很依赖对方，如果你无法离开对方自己一个人生活，你就过线了。此时压力在对方身上，他要满足你的要求，这种压力会让对方对你不满。如果你只想对方付出所有，而自己不付出（这里对于感情和财务都一样），你就麻烦了。（注意：我不是说你必须要为家庭财务做出贡献，我是说如果你对于家庭财务或者其它任何方面都没有贡献的话，这绝对不是好事）

1. You expect Happiness

   　期望得到快乐

   A sign of a bad relationship is that one or both partners expect either to make the other happy or for their partner to make them happy. This is not only an unrealistic expectation to lay on yourself or on them - nobody can "make" you happy, except you - but it's an unrealistic expectation to lay on your relationship. Relationships aren't only about being happy, and there's lots of times when you won't and even shouldn't be. Being able to rely on someone even when you're upset, miserable, depressed, or grieving is a lot more important than being happy all the time. If you expect your partner to make you happy - or worse, you're frustrated because you aren't able to make your partner happy - your relationship isn't going to fare well when it hits a rough spot.

如果你或对方或你们两个都觉得自己应该让对方快乐，或者对方应该让自己快乐，这是感情中的一个很糟糕的现象。 这不仅仅是对你或你们自己的一个不现实的期望--没有人能够"让"你快乐，只有你自己能--这也是对你们感情的一个不现实的期望。感情不仅仅是获得快乐，很多时候你不会、甚至不应该仅仅是获得快乐。能够在心烦、痛苦、绝望或伤心的时候去依赖某个人，比仅仅只是获得快乐要重要的多。如果你希望对方能够"让"你快乐，或者进一步的，你因为无法"让"对方快乐而沮丧，那么当你的感情在遇到坎坷的时候会出问题的。

1. You never fight

   　从不吵架

   A good argument is essential, every now and then. In part, arguing helps bring out the little stuff before it becomes major, but also, fighting expresses anger which is a perfectly normal part of a human's emotional make-up. Your relationship has to be strong enough to hold all of who you are, not just the sunny stuff.

One reason couples don't fight is that they fear conflict - which reflects a lack of trust and a foundation of fear. That's bad. Another reason couples avoid arguments is that they've learned that anger is unreasonable and unproductive. They've learned that arguing represents a breakdown rather than a natural part of a relationship's development. While an argument isn't pleasant, it can help both partners to articulate issues they may not have even known they had - and help keep them from simmering until you cross a line you can't come back from.

偶尔进行一些良性的争论是件好事。一方面，争论能够让小问题在变成大问题之前暴露出来；同时，争论能够把生气和不满表达出来，这是人类众多情绪中最正常的一种。你们的感情必须足够坚实，这样才能包容你们的所有好的和不好的方面，而不仅仅只是那些美好的事情。

很多人不吵架是因为害怕冲突--觉得这代表了不信任，也是害怕的根本原因。这样不好。还有很多人不吵架是因为他们觉得生气是无理取闹，而且没有什么效果。他们认为吵架意味着感情的倒退，而不是认为这是感情发展中很自然的一部分。虽然吵架并不会让人愉快，但它能让双方把问题说清楚，甚至在此之前双方可能都不知道自己到底有什么问题；同时，吵架也能避免事态在无形中逐步升级，直到某一方做了再也无法挽回的事情。

1. You expect it to be easy/you expect it to be hard

   　把爱情想的很轻松或者很难

   There are two deeply problematic attitudes about relationships I hear often. One is that a relationship should be easy, that if you really love each other and are meant to be together, it will work itself out. The other is that anything worth having is going to be hard - and that therefore if it's hard, it must be worth having.

The outcome of both views is that you don't work at your relationship. You don't work because it's supposed to be easy and therefore not need any work, or you don't work because it's supposed to be hard and it wouldn't be hard if you worked at it. In both cases, you quickly get burnt out - either because the problems you're ignoring really don't go away just because you think they should. or because the problems you're cultivating are a constant drag on your energy. A relationship that's too much work might be suffering from one of the attitudes above, but a relationship that doesn't seem to need any work isn't any better.

我经常听到对感情的两种很有问题的态度。一个是，感情应该是轻松的，如果你真的爱对方，想跟对方在一起，感情自然就有了。另外一个是，任何值得去拥有的东西都很难得到，因此如果获得感情很难，它一定值得去拥有。

这两种观点表明你并没有为感情付出努力。要么因为如果获得感情是件很容易的事情，那么就并不需要付出什么努力；要么因为获得感情应该是件很难的事情，如果努力了那么它就不难了。这两种情况下，你很快会吃到苦头--要么是你所忽视的问题并没有因为你认为它应该那样而不存在，要么是你所制造出来的问题会一直拖累你。付出过多努力的感情，可能会让人比上面那两种情况更苦恼，但没有付出努力的感情，永远也不会变得更好。

Your choices 你的选择： There isn't any one answer to any of the problems above. There are choices though: you can either seek out an answer, something that addresses why you are hurting your relationship, or you can resign yourself to the failure of your relationship (and maybe the next one, and the next one, and...). Failure doesn't always mean you break up - many people aren't that lucky. But people can live quite unhappily in failed relationships for years and even decades because they're afraid they won't find anything better, or worse, they're afraid they deserve it. Don't you be one of them - if you suffer from any of these problems, figure out how to fix it, whether that means therapy, a solo mountain retreat, or just talking to your partner and committing yourselves to change. 上述问题都没有一个唯一确定的答案，但是你有不同选择：你可以尝试去给自己找一个答案，明白你为什么在伤害你俩的感情，你也可以选择退出这段失败的感情(也许还包括下一段，再下一段......)。失败的感情不一定意味着分手--很多人没有那么幸运。很多人可以一直在失败的感情中不愉快的生活很多年，甚至一辈子。因为他们害怕自己找不到更好的，更糟糕的是一些人认为这是自己应得的。如果你也是如此，不要跟他们一样，从问题中吸取教训，尝试去真正解决问题，例如寻求心理治疗，独自去远足一下，或者直接跟伴侣商量，确确实实的做一些改变......


# 2010-08-30  利用ipkall+xlite+iptel.org开通google voice

Google Voice开通需要一个米国的电话号码用于确认，所以在去搞Google Voice之前，得先弄到一个免费的、能用的米国电话号码。以下经验是俺综合了各方资料，加上自己研究得出的可能是最顺利的开通方式了，一切顺利的话15分钟就搞定了。

## **利用iptel.org开一个免费的sip帐号**

1、到这里来注册一个iptel.org的帐号：<http://serweb.iptel.org/user/reg/>

email：写自己的邮件地址 phone： 这里填稍后在ipkall中申请到的电话号码 pick your user name: 选一个用户名，可以用数字、小写字母、点（.) pick password: 输入一个密码，这个是这个sip服务器的认证密码，对应上面的用户名 confirmation password: 重复密码

## 从www\.ipkall.com注册个米国的电话号码

&#x20;有人说要几天的人工验证时间，我是注册后马上邮箱就收到了电话号码。也许是因为google voice的原因申请号码的人太多--人工验证忙不过来了... ...

## 配置sip客户端x-lite

<http://www.counterpath.com/x-lite-download.html> 下载、安装，无需重启。 点左上角的小三角，选择SIP Account Setting开始配置sip账号。用户名和密码，都写刚刚从iptel.org申请到的账户信息。

确认，x-lite会尝试登陆到sip服务器：disconvering network、registering，然后是ready，屏幕上会显示sip的用户名。

## **开通Google Voice**

准备工作做完，现在有了一个米国的电话号码，并配好了客户端能接电话了。现在开始注册google voice。

<https://www.google.com/voice（我肉身在国内，可以正常访问。但有的人即使将gmail语言设置为英语，仍然会被提示所在国家未开通服务。难道是因为我曾经在米国用过google> maps被记录下来了？访问不了的只能翻墙了）。

选择电话号码、确认就不赘述了，确认用的电话填写刚刚从ipkall申请到的电话号码，到第四步的验证会给出两个数字。打开x-lite，然后点google voice那个页面的"Call Now"，几秒种后x-lite会有一个呼入的电话，接起来，在x-lite的键盘上输入那两个数字。google voice的页面会自动跳转到成功页面。现在你有了两个米国的电话号码，一个是ipkall申请到的，一个是google voice分配给你的电话号码。 因为ipkall申请到的电话号码如果30天内没有电话进来的话，会自动回收注销号码。所以现在就可以用gtalk给自己的ipkall的电话号码打个电话了（自娱自乐、左右互搏）。

**注意1**：iptel.org申请到的sip可以免费接电话，可以打sip电话，但是是不可以打普通电话的。但google voice的电话号码是可以打普通电话的（无需翻墙），目前给美国和加拿大打是免费的，给国内打是0.02美元一分钟（大概是1毛4人民币）。但是通过web页面的google voice打电话没有电话拨号盘，也就是说如果在语音期间需要输入数字选择菜单的时候，是没办法的。只能通过google chat里面的call phone图标才会有拨号盘(需翻墙获得一个米国的ip地址)。

**注意2**：用iptel.org申请到的电话号码可以利用sip客户端打sip电话，电话号码就用<username@gtalk2voip.com>这样的形式（前面是你的sip用户名，后面是sip服务器地址）。比如给自己的google voice打电话，我的sip电话号码就是<yuntao.liu@gtalk2voip.com> （如果是给gtalk2voip的用户打的话，被呼叫方的gtalk上会有一个添加好友的请求，同意了之后就能接电话了）。

**注意3：**&#x76EE;前还未找到能够使用google voice的电话号码打电话的sip客户端。

**注意4：**&#x5230;现在为止已经你已经有了一个ipkall的电话号码，一个google voice的电话号码，还有对对应的sip帐号。是不是有点乱？我来整理一下，以我自己的账号为例：

| \*\*电话号码类型\*\* | \*\*sip号码\*\* | \*\*电话号码\*\* |
| -------------- | ------------- | ------------ |

| ipkall | <yuntao.liu@iptel.org> | 从ipkall的邮件得到 |
| ------ | ---------------------- | ------------ |

| google voice | <yuntao.liu@gtalk2voip.com> | Google voice中选的号码 |
| ------------ | --------------------------- | ----------------- |

&#x20;sip号码，可以在任何一个支持输入字符串格式的电话号码的sip客户端上拨打和接听，拨打对应的数字电话号码会转到sip号码上。google voice的电话号码默认是拨给google chat的，如果不在线则会转给ipkall的电话号码（我们认证的时候就是关联的这个号码）。总结一下：

| \*\*拨号终端\*\* | \*\*拨号号码\*\* | \*\*可行性\*\* |
| ------------ | ------------ | ----------- |

| 实际电话 | 电话号码 | 可 |
| ---- | ---- | - |

| 实际电话 | sip号码 | 不可 |
| ---- | ----- | -- |

| sip客户端 | sip号码 | 可（看具体客户端支持情况） |
| ------ | ----- | ------------- |

| sip客户端 | 电话号码 | 可（一般都要充值收费） |
| ------ | ---- | ----------- |

| google voice | 电话号码 | 可（美加免费，国内收费） |
| ------------ | ---- | ------------ |

| google voice |
| ------------ |

| sip号码 |
| ----- |

不可

**此外**：还有一个问题是x-lite只能输入数字格式的电话号码，得找一个能输入这种格式的电话的sip客户端，下面推荐两个客户端nimbuzz和fring。

2011-01-18更新：最新版的x-lite 4 已经可以支持<username@gtalk2voip.com>这样的sip电话号码了。

**nimbuzz作为sip客户端接打电话**

x-lite不错，不过可惜只是一个sip客户端，也只能打数字形式的电话，不能打sip电话。通常我们要开很多im的客户端，nimbuzz是一个非常好的万金油客户端，除了是一个SIP客户端之外，还是GTalk、MSN、Yahoo、Facebook、twitter、ICQ、myspace等等im的客户端。除了PC版本之外，也有手机版本和Web版本...

这里下载：<http://www.nimbuzz.com/en/pc/>

下载客户端、注册账号、登陆，在Tools--Option--Call Service，右边sip provider选择Other，然后输入从iptel那儿的sip账号信息。然后就可以接打sip电话了。

## Fring作为手机sip 客户端

fring是一个手机上的支持twitter、gtalk、msn、yahoo等IM的客户端，同时也支持SIP。只是可惜的是没有PC上的客户端。

在这里 <http://www.fring.com/download/> 。 在手机上安装完成后，启动、选项--转至--addons--SIP，输入iptel.org对应的账户认证信息。

然后，有人给你的google voice的号码拨号，或者给你的ipkall的电话号码拨号，手机都会响了。

注意：没有wifi宽带接入的情况下，语音有延迟。


# 2010-08-27  避免Android开发中的ANR

via: [Writing Zippy Android Apps](https://code.google.com/events/io/2010/sessions/writing-zippy-android-apps.html)

## ANR是什么

ANRs ("Application Not Responding")，意思是"应用没有响应"。

在如下情况下，Android会报出ANR错误：

* 主线程 ("事件处理线程" / "UI线程") 在5秒内没有响应输入事件
* BroadcastReceiver 没有在10秒内完成返回

通常情况下，下面这些做法会导致ANR

1、在主线程内进行网络操作

2、在主线程内进行一些缓慢的磁盘操作（例如执行没有优化过的SQL查询）

应用应该在5秒或者10秒内响应，否则用户会觉得"这个应用很垃圾""烂""慢"...等等

## 一些数据(Nexus One为例)

* \~0.04 ms - 通过管道进程从 A->B 再从 B->A 写一个字节；或者（从dalvik）读一个简单的/proc文件
* \~0.12 ms - 由 A->B 再由 B->A 进行一次Binder的RPC调用
* \~5-25 ms - 从未缓冲的flash
* \~5-200+(!) ms - 向为缓冲的flash中写点东西（下面是具体数据）
* 16 ms - 60fps 的视频中的一帧
* 41 ms - 24fps 的视频中的一帧
* 100-200 ms - human perception of slow action
* 108/350/500/800 ms - 3G网络上ping（可变）
* \~1-6+ seconds - 通过HTTP在3G网络上获取6k的数据

## android.os.AsyncTask

AsyncTask 可以与UI线程很方便的配合，这个类可以在后台执行一些操作，并在执行结束的时候将结果发布到UI线程中去，并且无需使用线程或handler来控制。

例子:

```
private class DownloadFilesTask extends AsyncTask {
    protected Long doInBackground(URL... urls) {  // on some background thread
        int count = urls.length;
        long totalSize = 0;
        for (int i = 0; i < count; i++) {
            totalSize += Downloader.downloadFile(urls[i]);
            publishProgress((int) ((i / (float) count) * 100));
        }

        return totalSize;
     } 

    protected void onProgressUpdate(Integer... progress) {  // on UI thread!
        setProgressPercent(progress[0]);
    } 

    protected void onPostExecute(Long result) {  // on UI thread!
        showDialog("Downloaded " + result + " bytes");
    }
} 

new DownloadFilesTask().execute(url1, url2, url3);  // call from UI thread!

private boolean handleWebSearchRequest(final ContentResolver cr) {
    ...
    new AsyncTask() {
        protected Void doInBackground(Void... unused) {
            Browser.updateVisitedHistory(cr, newUrl, false);
            Browser.addSearchUrl(cr, newUrl);
            return null;
        } 
    }.execute()
    ...
    return true; 

}
```

## AsyncTask要点

1、必须从主线程调用，或者线程中有Handler或Looper。 2、不要在一个可能会被另外一个AsyncTask调用的库里面使用AsyncTask（AsyncTask是不可重入的） 3、如果从一个activity中调用，activity进程可能会在AsyncTask结束前退出，例如：

* 用户退出了activity
* 系统内存不足
* 系统暂存了activity的状态留待后用
* 系统干掉了你的线程

如果AsyncTask中的工作很重要，应该使用......

## android.app.IntentService

Eclair(2.0, 2.1)文档中说：

"An abstract Service that serializes the handling of the Intents passed upon service start and handles them on a handler thread. To use this class extend it and implement onHandleIntent(Intent). The Service will automatically be stopped when the last enqueued Intent is handled." 有点令人困惑，因此...几乎没人用

Froyo (2.2) 的文档, 又澄清了一下....

android.app.IntentService

"IntentService is a base class for Services that handle asynchronous requests (expressed as Intents) on demand. Clients send requests through startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.

This 'work queue processor' pattern is commonly used to offload tasks from an application's main thread. The IntentService class exists to simplify this pattern and take care of the mechanics. To use it, extend IntentService and implement onHandleIntent(Intent). IntentService will receive the Intents, launch a worker thread, and stop the service as appropriate.

All requests are handled on a single worker thread -- they may take as long as necessary (and will not block the application's main loop), but only one request will be processed at a time."

## IntentService 的好处

* Acitivity的进程，当处理Intent的时候，会产生一个对应的Service
* Android的进程处理器现在会尽可能的不kill掉你
* 非常容易使用

日历中IntentService的应用

```
public class DismissAllAlarmsService extends IntentService {
    @Override public void onHandleIntent(Intent unusedIntent) {
        ContentResolver resolver = getContentResolver();
        ...
        resolver.update(uri, values, selection, null);
    }
}
in AlertReceiver extends BroadcastReceiver, onReceive()：  (main thread)
    Intent intent = new Intent(context, DismissAllAlarmsService.class);
    context.startService(intent);
```

## 其它技巧

1、当启动AsyncTask的时候，立刻disable UI元素（按钮等等）。 2、显示一些动画，表示在处理中 3、使用进度条对话框 4、使用一个定时器作为耗时警告，在AsyncTask开始时启动定时器，在AsyncTask的onPostExecute方法中取消定时器。 5、当不确定要耗时多久的时候，组合使用上述所有方法

## 总结

* 离开主线程！
* 磁盘和网络操作不是马上就能完的
* 了解sqlite在干嘛
* 进度展示很好

PS，在视频讲座中，作者还提到，Chrome团队为了避免Jank（响应超时而死掉），几乎所有的功能和任务都会在子线程里面去做。这一点也值得在Android中借鉴。

刘金雨译: [避免Android开发中的ANR](https://log4think.com/avoid_anr_in_android) 全文via: [Writing Zippy Android Apps](https://code.google.com/events/io/2010/sessions/writing-zippy-android-apps.html)


# 2010-08-27  在Eclipse中查看Android SDK的源代码

via <https://stuffthathappens.com/blog/2008/11/01/browsing-android-source-in-eclipse/>

Google的Android SDK中包含一个android.jar文件，里面有Android所有的公开类的API接口。同时，Google还提供了一个Eclipse插件，可以很容易的开始进行开发。但是，这里并没有一个类似于androidSrc.jar的文件，因此当我们试图在Eclipse去查看Android SDK的源代码的时候，会得到"源码无法找到"这样一个页面

Google已经发布了Android所有的源代码，很大。要在Eclipse中查看Android的源代码，需要去<https://source.android.com/>（国内需翻墙），Get Source那个页面内按照指示一步步的将所有的东西都通过Git弄下来。很值得抽出一个晚上的时间来做这件事，因为如果能够随时查看源码，对于理解SDK如何工作的是非常有帮助的。

## 链接到Eclipse

现在我们有了源码，应该可以告诉Eclipse如何找到它了。右键点击android.jar--属性，可是却发现了这样的信息："Modifications Not Allowed"

嗯....那段话的大意是，当前的class path的设置属于'Android Library'，不允许用户修改。好吧，只能去看看ADT的源码了，看能否找到什么办法。

## 查看ADT源码

当下载完Android源码之后，我们已经得到了所有内容，包括：操作系统、Dalvik虚拟机、Eclipse插件、公开的SDK...等等。

在 com.android.ide.eclipse.adt.project.internal 包里，我找到一个名为 AndroidClassspathContainerInitializer.java 的类，包含如下代码：

```
IPath android_src = new Path(AdtPlugin.getOsAbsoluteAndroidSources());
```

好，再来看看 AdtPlugin.java:

```
/** Returns the absolute android sources path in the sdk */
public static String getOsAbsoluteAndroidSources() {
  return getOsSdkFolder() + getOsRelativeAndroidSources();
}

/** Returns the android sources path relative to the sdk folder */
public static String getOsRelativeAndroidSources() {
  return AndroidConstants.FD_ANDROID_SOURCES;
}
```

最后来看看 `AndroidConstants.java`: `public static final String FD_ANDROID_SOURCES = "sources";` 搞定！

## 解决方案 #1

根据上面的分析，我们可以在android SDK的安装目录内创建一个sources目录，与android.jar位于同一个目录内。之后，我们可以在之前下好的Android源码中找到所有我们需要的代码。SDK的代码在frameworks/base/core/java，在这个目录下有一个android目录，我们需要将这个目录拷贝（链接）到SDK安装目录中的sources目录。你可能需要想想办法，把所有分散在不同Component的源码都弄到一起去。最终我们的目录结构大致如下：

```
SDK_PATH
  |-- android.jar
  +--docs/...
  +--samples/...
  +--sources
       +--android
       |      ...accounts, annotation, app, bluetooth, etc...
       +--com/android/etc...
       +--dalvik/...
       +--java/...
       +--javax/...
```

我把所有这样的目录都弄进来了，但是没有详细记录。

译注： 上面的代码是适用于以前的老版本的ADT，目前最新版本的ADT已经不适用了。经过查看源代码发现，最新版本的ADT需要在SDK目录下的platforms\android-X对应的目录下建立sources目录，其中X是3、4、7、8之一的数字，对应不同的SDK版本。这也是一个比较合理的方案，毕竟不同版本的SDK的源码还是不一样的。

如果你在Linux或者Mac下工作，sources的源码目录结构可以用我写的如下的一个Shell脚本来完成这个事情，在Android的源码目录下运行这个脚本，然后会在 `~/workspace/src-tree` 创建"几乎"所有Java源码的soft symbol link。之后也可以用tar带-h参数打包到windows下使用。你可以根据自己的需求修改一下。

```
simon@simon-desktop:~/bin$ cat make-src-tree
#!/bin/bash
# Author: Simon Liu <simon@log4think.com>
# Date: 2010-08-27
# License: MIT

curr_dir=$PWD
dest_dir=~/workspace/src-tree
if [ ! -z "$1" ]; then
        dest_dir=$1
fi

rm -rf $dest_dir
mkdir -p $dest_dir

for d in $(find -path .repo -prune \
    -or -path .git -prune \
    -or -path "*/src/com/*" -type d -print \
    -or -path "*/src/org/*" -type d -print \
    -or -path "*/java/com/*" -type d -print \
    -or -path "*/java/org/*" -type d -print \
    -or -path "*/java/android/*" -type d -print \
    -or -path "*/java/javax/*" -type d -print)
do
        sd=$(echo $d | sed 's#.*/src/\(.*\)/#\1/#g' | sed 's#.*/java/\(.*\)/#\1/#g')
        echo ";; $sd"

        mkdir -p $dest_dir/$sd
        for f in $(cd $d; find . -mindepth 0 -maxdepth 1 -type f | sed 's#^./##g'); do
                echo "        $f"
                ln -s $curr_dir/$d/$f $dest_dir/$sd/$f
        done
done
```

现在，当我再去查看Android SDK类的时候，可以看到源码了

## 解决方案 #2

如果你实在不愿意把所有的Android源码拷贝到SDK目录里面去，也可以创建一个Eclispe User Library并把源码附加上去。

via <https://stuffthathappens.com/blog/2008/11/01/browsing-android-source-in-eclipse/>


# 2010-08-18  Git中判断一个commit是否在某个branch中

方法1：`git branch --contains commit`

方法2：查找reflog `git reflog show --all | grep a871742`

会有类似如下的结果：`a871742 refs/heads/completion@{0}: commit (amend): mpc-completion: total rewrite`，其中 completion 就是所在的 branch

注：`git reflog show`等价于`git log -g --abbrev-commit --pretty=oneline`


# 2010-08-04  修正auto-excerpt产生带格式的摘要

Wordpress的正文通常都是显示全文，这样一来如果文章非常长的话，blog的首页的用户体验会很不好。网上有很多提到通过将index.php中的the\_content改成the\_excerpt来讲首页的文章内容显示为摘要的方法，但是这样的摘要要自己去写。

Wordpress的plugin库里面有一个auto-excerpt的插件，可以自动将文章的前几百字截取下来作为文章的摘要。确实是方便，但是却有一个小小的问题，截取出来的文字格式全部都丢失了，包括段落、链接等等。

通过对这个插件的代码做一点小小的改动可以恢复文字的格式。在后台管理中找到这个插件--edit

```
function auto_excerpt($content) {
    $content = substr(strip_tags($content),0,418)." [...]";
    return $content;
}
```

代码中可以看到，这个插件是用strip\_tags来去除正文中的所有html标签，然后截取前418个字符。其实strip\_tags还有一个可选的参数，可以指定忽略掉某些标签。为了保留基本格式，可以选择在strip\_tags中忽略掉p、br、a等标签恢复文字的格式。

```
function auto_excerpt($content) {
    $content = substr(strip_tags($content, '
<br>'),0,418)."<br/> [...]";
    return $content;
}
```

如果想在缩略图中保留图片，也可以在忽略标签列表中加上标签。


# 2010-03-31  Go 编程语言入门教程

> 原文：<http://golang.org> 翻译：刘金雨 <http://log4think.com>

## 介绍

本文档是关于Go编程语言基础的一个介绍性的入门教程，偏向于熟悉C或C++的读者。本文并非一份语言的完整指南，如果需要的话可以参考[语言规范](http://golang.org/ref/spec)。读完本教程之后，你可以继续学习[Effective Go](http://golang.org/doc/effective_go.html)，这份文档会更深入的挖掘如何使用Go语言。

此外还有一份《三日入门》的教程可供参考:

* [第一日](http://www.yeeyan.com/doc/GoCourseDay1.pdf)
* [第二日](http://www.yeeyan.com/doc/GoCourseDay2.pdf)
* [第三日](http://www.yeeyan.com/doc/GoCourseDay3.pdf)

本文将会以一系列适当的程序来说明语言的一些关键特性。所有的示例程序都是可运行的（在撰写本文时），并且这些程序都会提交到版本库的

```
/doc/progs/
```

目录下。

程序片段都会标注上在源文件中的行号，为了清晰起见，空行前面的行号留空。

## Hello World

先从一个最常见的开始:

```
05    package main

07    import fmt "fmt"  // 本包实现了格式化输入输出

09    func main() {
10        fmt.Printf("Hello, world; or &Kappa;&alpha;&lambda;&eta;&mu;έ&rho;&alpha; &kappa;ό&sigma;&mu;&epsilon;; or こんにちは 世界n");
11    }
```

每份Go的源文件都会使用

```
package
```

语句声明它的包名。同时也可以通过导入其它包来使用其中定义的功能。这段代码导入了包

```
fmt
```

来调用我们的老朋友--现在它的开头字母是大写的，并且前面带有包名限定

```
fmt.Printf
```

。

函数的声明使用关键字func，整个程序将会从为

```
main
```

包中的

```
main
```

函数开始（经过初始化之后）。

字符串常量可以包含Unicode字符，采用UTF-8编码（事实上，所有Go程序的源文件都是使用UTF-8编码的）。

注释的方式同C++一样：

```
/* ... */
```

或

```
// ...
```

稍后我们会继续提到

```
print
```

。

## 编译

Go是一个编译型语言。目前有两个编译器，其中

```
gccgo
```

编译器采用了GCC作为后端，此外还有一系列根据其所适用的架构命名的编译器，例如

```
6g
```

适用于64位的x86结构，8g 适用于32位的x86结构，等等。这些编译器比gccgo运行的更快、生成的代码更加有效率。在撰写本文的时候（2009年底），他们还具有一个更加健壮的运行期系统，但是gccgo也正在迎头赶上。

下面来看看如何编译和运行程序。采用

```
6g
```

是这样的

```
$ 6g helloworld.go  编译; 中间代码位于 helloworld.6 中
$ 6l helloworld.6   链接; 输出至 6.out
$ 6.out
Hello, world; or &Kappa;&alpha;&lambda;&eta;&mu;έ&rho;&alpha; &kappa;ό&sigma;&mu;&epsilon;; or こんにちは 世界
$
```

```
gccgo
```

的方式看起来更加传统一些。

```
$ gccgo helloworld.go
$ a.out
Hello, world; or &Kappa;&alpha;&lambda;&eta;&mu;έ&rho;&alpha; &kappa;ό&sigma;&mu;&epsilon;; or こんにちは 世界
$
```

## Echo

下一步，来实现一个Unix的传统命令Echo：

```
05    package main

07    import (
08        "os";
09        "flag";  // command line option parser
10    )

12    var omitNewline = flag.Bool("n", false, "don't print final newline")

14    const (
15        Space = " ";
16        Newline = "n";
17    )

19    func main() {
20        flag.Parse();   // Scans the arg list and sets up flags
21        var s string = "";
22        for i := 0; i < flag.NArg(); i++ {
23            if i > 0 {
24                s += Space
25            }
26            s += flag.Arg(i);
27        }
28        if !*omitNewline {
29            s += Newline
30        }
31        os.Stdout.WriteString(s);
32    }
```

这段程序很小，但是却有几个新出现的概念。前面这个例子中，我们看到可以使用

```
func
```

来声明一个函数，同时关键字

```
var、const
```

和

```
type
```

目前还没有用到）也可以用于声明，就好像

```
import
```

一样。

注意，我们可以将同一类的声明放到括号中，以分号分隔。例如第7-10行和第14-17行。但也并非一定要如此，例如可以这样写

```
    const Space = " "
    const Newline = "n"

分号在此处并不是必须的。事实上，任何顶层声明后面都不需要分号。但如果要是在一个括号内进行一系列的声明，就需要用分号来分割了。

你可以像在C、C++或Java中那样去使用分号，但如果你愿意的话，在很多情况下都可以省略掉分号。分号是用于表示语句间的**分隔**，而非表示其**中止**。因此，对于一个代码块中的最后一条语句来说，有无分号皆可。大括号之后的分号也是可选的，就像C语言中的一样。

比对一下
```

echo

```
的源代码，只有第8、15和21行必须要加分号，当然第22行中的
```

for

```
语句中为了分隔三个表达式也需要加分号。第9、16、26和31行的分号都不是必须的，加上分号只是为了以后再增加语句的时候方便而已。

这个程序导入了os包以访问
```

Stdout

```
变量，
```

Stdout

```
的类型是
```

\*os.File

```
。
```

import

```
语句实际上是个声明：通常情况下（如hello world程序中那样），它声明了一个标识符
```

fmt

```
用于访问导入的包的成员变量，而包是从当前目录或标准库下的
```

fmt

```
文件中导入的。在这个程序中，我们为导入的包显式的指定了一个名字，默认情况下，包名是采用在导入的包里面已经定义好的名字，通常会与文件名一致。因此在这个"hello world"程序中，可以只写
```

import "fmt"

```
。你可以任意为包指定一个导入名，但通常只有在解决名字冲突的情况下才有必要这样做。

有了
```

os.Stdout

```
，我们就可以用它的
```

WriteString

```
方法打印字符串了。

导入了
```

实 际flag

```
包之后，第12行创建了一个全局变量来保存 echo 的
```

-n

```
选项标志。
```

omitNewline

```
变量的类型是 *bool --指向bool值的指针。

在
```

main.main

```
中进行了参数解析，并创建了一个本地字符串类型的变量用于构造输出的内容。声明语句如下

    var s string = "";

这里用到了关键字
```

var

```
，后面跟变量名和数据类型，之后可以继续接=来赋初值。

Go试图尽量保持简洁，这个声明也可以用更短的形式。因为初值是一个字符串类型的常量，没有必要再声明数据类型了，因此这个声明可以写成这样：

    var s = "";

或者也可以直接用更短的形式：

    s := "";

操作符
```

:=

```
在 Go 语言里经常会用在赋初值的声明中，比如下面这个
```

for

```
语句的声明：

    22    for i := 0; i < flag.NArg(); i++ {
```

flag

```
包会解析命令行参数，并将参数值保存在一个列表中。

Go语言中的
```

for

```
语句和C语言中的有几个不同之处。首先，for是唯一的循环语句，没有
```

while

```
语句或
```

do

```
语句。其次，for语句后面的三个子句不需要圆括号，但大括号是必须的。这一条对
```

if

```
和
```

switch

```
语句同样适用。稍后还会有几个例子演示
```

for

```
语句的其它用法。

循环体中通过追加（+=）标志和空格构造字符串
```

s

```
。循环之后，如果没有设置
```

-n

```
标志，程序追加一个空行，最后输出结果。

注意，函数
```

main.main

```
没有返回值。它就是这样定义的，如果到达了
```

main.main

```
的末尾就表示"成功"，如果想表明出错并返回，可以调用

    os.Exit(1)
```

os

```
包还包含一些其他的常用功能，例如
```

os.Args

```
会被
```

flag

```
包用于访问命令行参数。

## 插播：数据类型 Types

Go支持一些常见的数据类型，例如
```

int

```
和
```

float

```
，其值采用机器"适用"的大小来表示。也有定义了明确大小的数据类型，例如
```

int8

```
、
```

float64

```
等，以及无符号整数类型，例如
```

uint

```
、
```

uint32

```
等。这些都是完全不同的数据类型，即使
```

int

```
和
```

int32

```
都是32位整数，但它们是不同的类型。对于表示字符串元素的类型
```

byte

```
和
```

uint8

```
也是同样如此。

说到字符串（
```

string

```
），这也是一个内置的数据类型。字符串的值不仅仅是一个
```

byte

```
的数组，它的值是**不可改变**的。一旦确定了一个字符串的值，就不能再修改了。但一个字符串**变量**的值可以通过重新赋值来改变。下面这段来自
```

strings.go

```
的代码是合法的：

    11    s := "hello";
    12    if s[1] != 'e' { os.Exit(1) }
    13    s = "good bye";
    14    var p *string = &s;
    15    p = "ciao";

然而下面这段代码是非法的，因为它试图修改一个字符串的值：

    s[0] = 'x';
    (*p)[1] = 'y';

按照C++的说法，Go的字符串有点类似带了
```

const

```
修饰符，指向字符串的指针也类似于一个 const 字符串的引用（reference）.

没错，前面看到的那些是指针，然而Go语言中的指针在用法方面有所简化，后文会提到。

数组的声明如下所示：

    var arrayOfInt [10]int;

数组同字符串一样是"值"，但是却是可变的。与C不同的是，C语言中
```

arrayOfInt

```
可以当做一个指向int的指针来用。在Go中，因为数组是**值**，因此
```

arrayOfInt

```
被看做（也被用做）指向数组的指针。

数组的大小是其数据类型的一部分。但是，你可以声明一个**slice**变量，然后可以用一个指向具有相同元素类型的数组指针给它赋值，更常见的是用一个形式为
```

a\[low : high]

```
的**slice**表达式，该表达式表示下标从
```

low

```
到
```

high-1

```
的子数组。 Slice 类型类似数组，但没有显式指定大小(
```

\[]

```
之于
```

\[10]

```
)，用于表示一个隐性（通常是匿名的）数组。如果不同的 slice 都是表示同一个数组中的数据，它们可以共享该数组的内存，但不同的数组则永远不会共享内存数据。

Slice 在 Go 程序中比数组更常见。它更灵活，并且具有引用的语义，效率也更高。其不足之处在于无法像数组一样精确控制存储方式，如果想在一个数据结构中保存一个具有 100个元素的序列，应该采用数组。

当给函数传一个数组参数的时候，绝大多数情况下都会把参数声明为 slice 类型。当调用函数时，先取数组地址，然后Go会创建一个 slice 的引用，然后传这个引用过去。

可以用 slice 来写这个函数(来自
```

sum.go

```
)：

    09    func sum(a []int) int {   // 返回一个整数
    10        s := 0;
    11        for i := 0; i < len(a); i++ {
    12            s += a[i]
    13        }
    14        return s
    15    }

之后这样来调用：

    19    s := sum(&[3]int{1,2,3});  // a slice of the array is passed to sum

注意在
```

sum()

```
的参数列表后面加 int 定义了其返回值类型（int）。
```

\[3]int{1,2,3}

```
的形式是一个数据类型后面接一个大括号括起来的表达式，整个这个表达式构造出了一个值，这里是一个包含三个整数的数组。前面的
```

&

```
表示提取这个值的地址。这个地址会被隐性的转为一个 slice 传给
```

sum()

```
。

如果想创建一个数组，但希望编译器来帮你确定数组的大小，可以用
```

...

```
作为数组大小：

    s := sum(&[...]int{1,2,3});

实际使用中，除非非常在意数据结构的存储方式，否则 slice 本身 （用[]且不带
```

&

```
） 就足够了：

    s := sum([]int{1,2,3});

除此之外还有map，可以这样初始化：

    m := map[string]int{"one":1 , "two":2}
```

sum还第一次出现 了

```
内置函数
```

len()，用于返回元素数量。

```
可以用于字符串、数组、slice、map、map和channel.

此外，
```

for

```
循环中的
```

range

```
也可以用于字符串、数组、slice、map、map和channel。例如

    for i := 0; i < len(a); i++ { ... }

遍历一个序列的每个元素，可以写成

    for i, v := range a { ... }

其中， i 会赋值为下标， v 会赋值为 a 中对应的值，[Effective Go](http://golang.org/doc/effective_go.html)中包含了更多的用法演示。

## An Interlude about Allocation

Go中的大多数数据类型都是值类型。对
```

int

```
、
```

struct

```
或数组的赋值会拷贝其内容。
```

new()

```
可以分配一个新的变量，并返回其分配的存储空间的地址。例如

    type T struct { a, b int }
    var t *T = new(T);

或者更常见的写法：

    t := new(T);

Some types-maps, slices, and channels (see below)-have reference semantics. If you're holding a slice or a map and you modify its contents, other variables referencing the same underlying data will see the modification. For these three types you want to use the built-in function
```

make()

```
:

    m := make(map[string]int);

This statement initializes a new map ready to store entries. If you just declare the map, as in

    var m map[string]int;

it creates a
```

nil

```
reference that cannot hold anything. To use the map, you must first initialize the reference using
```

make()

```
or by assignment from an existing map.

Note that
```

new(T)

```
returns type
```

\*T

```
while
```

make(T)

```
returns type
```

T

```
. If you (mistakenly) allocate a reference object with
```

new()

```
, you receive a pointer to a nil reference, equivalent to declaring an uninitialized variable and taking its address.

## An Interlude about Constants

Although integers come in lots of sizes in Go, integer constants do not. There are no constants like
```

0LL

```
or
```

0x0UL

```
. Instead, integer constants are evaluated as large-precision values that can overflow only when they are assigned to an integer variable with too little precision to represent the value.

    const hardEight = (1 << 100) >> 97  // legal

There are nuances that deserve redirection to the legalese of the language specification but here are some illustrative examples:

    var a uint64 = 0  // a has type uint64, value 0
    a := uint64(0)    // equivalent; uses a "conversion"
    i := 0x1234       // i gets default type: int
    var j int = 1e6   // legal - 1000000 is representable in an int
    x := 1.5          // a float
    i3div2 := 3/2     // integer division - result is 1
    f3div2 := 3./2.   // floating point division - result is 1.5

Conversions  only work for simple cases such as converting
```

ints

```
of one sign or size to another, and between
```

ints

```
and
```

floats

```
, plus a few other simple cases. There are no automatic numeric  conversions of any kind in Go, other than that of making constants have concrete size and type when assigned to a variable.

## An  I/O Package

Next we'll look at a simple package for doing file I/O with the usual sort of open/close/read/write interface. Here's the  start of
```

file.go

```
:

    05    package file

    07    import  (
    08        "os";
    09        "syscall";
    10    )

    12    type File  struct {
    13        fd      int;    // file descriptor number
    14        name    string; // file name at Open time
    15    }

The first few lines declare the name of the package-
```

file

```
-and then import two packages. The
```

os

```
package hides the differences between various operating systems to give a consistent view of files and so on; here we're going to use its error handling utilities and reproduce the rudiments of its file I/O.

The other item is the low-level, external
```

syscall

```
package, which provides a primitive interface to the underlying operating system's calls.

Next is a type definition: the
```

type

```
keyword introduces a type declaration, in this case a data structure called
```

File

```
. To make things a little more interesting, our
```

File

```
includes the name of the file that the file descriptor refers to.

Because
```

File

```
starts with a capital letter, the type is available outside the package, that is, by users of the package. In Go the rule about visibility of information is simple: if a name (of a top-level type, function, method, constant or variable, or of a structure field or method) is capitalized, users of the package may see it. Otherwise, the name and hence the thing being named is visible only inside the package in which it is declared. This is more than a convention; the rule is enforced by the compiler. In Go, the term for publicly visible names is ''exported''.

In the case of
```

File

```
, all its fields are lower case and so invisible to users, but we will soon give it some exported, upper-case methods.

First, though, here is a factory to create a
```

File

```
:

    17    func newFile(fd int, name string) *File {
    18        if fd < 0 {
    19            return nil
    20        }
    21        return &File{fd, name}
    22    }

This returns a pointer to a new
```

File

```
structure with the file descriptor and name filled in. This code uses Go's notion of a ''composite literal'', analogous to the ones used to build maps and arrays, to construct a new heap-allocated object. We could write

    n := new(File);
    n.fd = fd;
    n.name = name;
    return n

but for simple structures like
```

File

```
it's easier to return the address of a nonce composite literal, as is done here on line 21.

We can use the factory to construct some familiar, exported variables of type
```

\*File

```
:

    24    var (
    25        Stdin  = newFile(0, "/dev/stdin");
    26        Stdout = newFile(1, "/dev/stdout");
    27        Stderr = newFile(2, "/dev/stderr");
    28    )

The
```

newFile

```
function was not exported because it's internal. The proper, exported factory to use is
```

Open

```
:

    30    func Open(name string, mode int, perm int) (file *File, err os.Error) {
    31        r, e := syscall.Open(name, mode, perm);
    32        if e != 0 {
    33            err = os.Errno(e);
    34        }
    35        return newFile(r, name), err
    36    }

There are a number of new things in these few lines. First,
```

Open

```
returns multiple values, a
```

File

```
and an error (more about errors in a moment). We declare the multi-value return as a parenthesized list of declarations; syntactically they look just like a second parameter list. The function
```

syscall.Open

```
also has a multi-value return, which we can grab with the multi-variable declaration on line 31; it declares
```

r

```
and
```

e

```
to hold the two values, both of type
```

int

```
(although you'd have to look at the
```

syscall

```
package to see that). Finally, line 35 returns two values: a pointer to the new
```

File

```
and the error. If
```

syscall.Open

```
fails, the file descriptor
```

r

```
will be negative and
```

NewFile

```
will return
```

nil

```
.

About those errors: The
```

os

```
library includes a general notion of an error. It's a good idea to use its facility in your own interfaces, as we do here, for consistent error handling throughout Go code. In
```

Open

```
we use a conversion to translate Unix's integer
```

errno

```
value into the integer type
```

os.Errno

```
, which implements
```

os.Error

```
.

Now that we can build
```

Files

```
, we can write methods for them. To declare a method of a type, we define a function to have an explicit receiver of that type, placed in parentheses before the function name. Here are some methods for
```

\*File

```
, each of which declares a receiver variable
```

file

```
.

    38    func (file *File) Close() os.Error {
    39        if file == nil {
    40            return os.EINVAL
    41        }
    42        e := syscall.Close(file.fd);
    43        file.fd = -1;  // so it can't be closed again
    44        if e != 0 {
    45            return os.Errno(e);
    46        }
    47        return nil
    48    }
    50    func  (file *File) Read(b []byte) (ret int, err os.Error) {
    51        if file  == nil {
    52            return -1, os.EINVAL 

    53        }
    54        r, e  := syscall.Read(file.fd, b);
    55        if e != 0 {
    56            err =  os.Errno(e);
    57        }
    58        return int(r), err
    59    }

    61    func  (file *File) Write(b []byte) (ret int, err os.Error) {
    62        if  file == nil {
    63            return -1, os.EINVAL 

    64        }
    65        r, e := syscall.Write(file.fd, b);
    66        if e != 0 {
    67            err = os.Errno(e);
    68        }
    69        return int(r),  err
    70    }

    72    func (file *File) String() string {
    73        return file.name
    74    }

There is no implicit
```

this

```
and the receiver variable must be used to access members of the structure. Methods are not declared within the
```

struct

```
declaration itself. The
```

struct

```
declaration defines only data members. In fact, methods can be created for almost any type you name, such as an integer or array, not just for
```

structs

```
. We'll see an example with arrays later.

The
```

String

```
method is so called because of a printing convention we'll describe later.

The methods use the public variable
```

os.EINVAL

```
to return the (
```

os.Error

```
version of the) Unix error code
```

EINVAL

```
. The
```

os

```
library defines a standard set of such error values.

We can now use our new package:

    05    package main
    07    import  (
    08        "./file";
    09        "fmt";
    10        "os";
    11    )

    13    func  main() {
    14        hello := []byte{'h', 'e', 'l', 'l', 'o', ',', ' ',  'w', 'o', 'r', 'l', 'd', 'n'};
    15        file.Stdout.Write(hello);
    16        file, err := file.Open("/does/not/exist",  0,  0);
    17        if file == nil {
    18            fmt.Printf("can't open file;  err=%sn",  err.String());
    19            os.Exit(1);
    20        }
    21    }

The ''
```

./

```
'' in the import of ''
```

./file

```
'' tells the compiler to use our own package rather than something from the directory of installed packages.

Finally we can run the program:

    % helloworld3
    hello, world
    can't open file; err=No such file or directory
    %

## Rotting cats

Building on the
```

file

```
package, here's a simple version of the Unix utility
```

cat(1)

```
,
```

progs/cat.go

```
:

    05    package main

    07    import (
    08        "./file"
    09        "flag"
    10        "fmt"
    11        "os"
    12    )

    14    func cat(f *file.File) {
    15        const NBUF = 512
    16        var buf [NBUF]byte
    17        for {
    18            switch nr, er := f.Read(&buf); true {
    19            case nr < 0:
    20                fmt.Fprintf(os.Stderr, "cat: error reading from %s: %s\n", f.String(), er.String())
    21                os.Exit(1)
    22            case nr == 0:  // EOF
    23                return
    24            case nr > 0:
    25                if nw, ew := file.Stdout.Write(buf[0:nr]); nw != nr {
    26                    fmt.Fprintf(os.Stderr, "cat: error writing from %s: %s\n", f.String(), ew.String())
    27                }
    28            }
    29        }
    30    }

    32    func main() {
    33        flag.Parse()   // Scans the arg list and sets up flags
    34        if flag.NArg() == 0 {
    35            cat(file.Stdin)
    36        }
    37        for i := 0; i < flag.NArg(); i++ {
    38            f, err := file.Open(flag.Arg(i), 0, 0)
    39            if f == nil {
    40                fmt.Fprintf(os.Stderr, "cat: can't open %s: error %s\n", flag.Arg(i), err)
    41                os.Exit(1)
    42            }
    43            cat(f)
    44            f.Close()
    45        }
    46    }

By now this should be easy to follow, but the
```

switch

```
statement introduces some new features. Like a
```

for

```
loop, an
```

if

```
or
```

switch

```
can include an initialization statement. The
```

switch

```
on line 18 uses one to create variables
```

nr

```
and
```

er

```
to hold the return values from
```

f.Read()

```
. (The
```

if

```
on line 25 has the same idea.) The
```

switch

```
statement is general: it evaluates the cases from top to bottom looking for the first case that matches the value; the case expressions don't need to be constants or even integers, as long as they all have the same type.

Since the
```

switch

```
value is just
```

true

```
, we could leave it off-as is also the situation in a
```

for

```
statement, a missing value means
```

true

```
. In fact, such a
```

switch

```
is a form of
```

if-else

```
chain. While we're here, it should be mentioned that in
```

switch

```
statements each
```

case

```
has an implicit
```

break

```
.

Line 25 calls
```

Write()

```
by slicing the incoming buffer, which is itself a slice. Slices provide the standard Go way to handle I/O buffers.

Now let's make a variant of
```

cat

```
that optionally does
```

rot13

```
on its input. It's easy to do by just processing the bytes, but instead we will exploit Go's notion of an**interface**.

The
```

cat()

```
subroutine uses only two methods of
```

f

```
:
```

Read()

```
and
```

String()

```
, so let's start by defining an interface that has exactly those two methods. Here is code from
```

progs/cat\_rot13.go

```
:

    26    type reader interface {
    27        Read(b []byte) (ret int, err os.Error);
    28        String() string;
    29    }

Any type that has the two methods of
```

reader

```
-regardless of whatever other methods the type may also have-is said to**implement**the interface. Since
```

file.File

```
implements these methods, it implements the
```

reader

```
interface. We could tweak the
```

cat

```
subroutine to accept a
```

reader

```
instead of a
```

\*file.File

```
and it would work just fine, but let's embellish a little first by writing a second type that implements
```

reader

```
, one that wraps an existing
```

reader

```
and does
```

rot13

```
on the data. To do this, we just define the type and implement the methods and  with no other bookkeeping, we have a second implementation of the
```

reader

```
interface.

    31    type rotate13 struct {
    32        source    reader
    33    }

    35    func newRotate13(source reader) *rotate13 {
    36        return &rotate13{source}
    37    }

    39    func (r13 *rotate13) Read(b []byte) (ret int, err os.Error) {
    40        r, e := r13.source.Read(b)
    41        for i := 0; i < r; i++ {
    42            b[i] = rot13(b[i])
    43        }
    44        return r, e
    45    }

    47    func (r13 *rotate13) String() string {
    48        return r13.source.String()
    49    }
    50    // end of rotate13 implementation

(The
```

rot13

```
function called on line 42 is trivial and not worth reproducing here.)

To use the new feature, we define a flag:

    14    var rot13Flag = flag.Bool("rot13", false, "rot13 the input")

and use it from within a mostly unchanged
```

cat()

```
function:

    52    func cat(r reader) {
    53        const NBUF = 512
    54        var buf [NBUF]byte

    56        if *rot13Flag {
    57            r = newRotate13(r)
    58        }
    59        for {
    60            switch nr, er := r.Read(&buf); {
    61            case nr < 0:
    62                fmt.Fprintf(os.Stderr, "cat: error reading from %s: %s\n", r.String(), er.String())
    63                os.Exit(1)
    64            case nr == 0:  // EOF
    65                return
    66            case nr > 0:
    67                nw, ew := file.Stdout.Write(buf[0:nr])
    68                if nw != nr {
    69                    fmt.Fprintf(os.Stderr, "cat: error writing from %s: %s\n", r.String(), ew.String())
    70                }
    71            }
    72        }
    73    }

(We could also do the wrapping in
```

main

```
and leave
```

cat()

```
mostly alone, except for changing the type of the argument; consider that an exercise.) Lines 56 through 58 set it all up: If the
```

rot13

```
flag is true, wrap the
```

reader

```
we received into a
```

rotate13

```
and proceed. Note that the interface variables are values, not pointers: the argument is of type
```

reader

```
, not
```

\*reader

```
, even though under the covers it holds a pointer to a
```

struct

```
.

Here it is in action:

    % echo abcdefghijklmnopqrstuvwxyz | ./cat
    abcdefghijklmnopqrstuvwxyz
    % echo abcdefghijklmnopqrstuvwxyz | ./cat --rot13
    nopqrstuvwxyzabcdefghijklm
    %

Fans of dependency injection may take cheer from how easily interfaces allow us to substitute the implementation of a file descriptor.

Interfaces are a distinctive feature of Go. An interface is implemented by a type if the type implements all the methods declared in the interface. This means that a type may implement an arbitrary number of different interfaces. There is no type hierarchy; things can be much more**ad hoc**, as we saw with
```

rot13

```
. The type
```

file.File

```
implements
```

reader

```
; it could also implement a
```

writer

```
, or any other interface built from its methods that fits the current situation. Consider the**empty interface**

    type Empty interface {}

**Every**type implements the empty interface, which makes it useful for things like containers.

## Sorting

Interfaces provide a simple form of polymorphism. They completely separate the definition of what an object does from how it does it, allowing distinct implementations to be represented at different times by the same interface variable.

As an example, consider this simple sort algorithm taken from
```

progs/sort.go

```
:

    13    func Sort(data Interface) {
    14        for i := 1; i < data.Len(); i++ {
    15            for j := i; j > 0 && data.Less(j, j-1); j-- {
    16                data.Swap(j, j-1)
    17            }
    18        }
    19    }

The code needs only three methods, which we wrap into sort's
```

Interface

```
:

    07    type Interface interface {
    08        Len() int;
    09        Less(i, j int) bool;
    10        Swap(i, j int);
    11    }

We can apply
```

Sort

```
to any type that implements
```

Len

```
,
```

Less

```
, and
```

Swap

```
. The
```

sort

```
package includes the necessary methods to allow sorting of arrays of integers, strings, etc.; here's the code for arrays of
```

int

```
    33    type IntArray []int

    35    func (p IntArray) Len() int            { return len(p) }
    36    func (p IntArray) Less(i, j int) bool  { return p[i] < p[j] }
    37    func (p IntArray) Swap(i, j int)       { p[i], p[j] = p[j], p[i] }

Here we see methods defined for non-
```

struct

```
types. You can define methods for any type you define and name in your package.

And now a routine to test it out, from
```

progs/sortmain.go

```
. This uses a function in the
```

sort

```
package, omitted here for brevity, to test that the result is sorted.

    12    func ints() {
    13        data := []int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586};
    14        a := sort.IntArray(data);
    15        sort.Sort(a);
    16        if !sort.IsSorted(a) {
    17            panic()
    18        }
    19    }

If we have a new type we want to be able to sort, all we need to do is to implement the three methods for that type, like this:

    30    type day struct {
    31        num        int
    32        shortName  string
    33        longName   string
    34    }

    36    type dayArray struct {
    37        data []*day
    38    }

    40    func (p *dayArray) Len() int            { return len(p.data) }
    41    func (p *dayArray) Less(i, j int) bool  { return p.data[i].num < p.data[j].num }
    42    func (p *dayArray) Swap(i, j int)       { p.data[i], p.data[j] = p.data[j], p.data[i] }

## Printing

The examples of formatted printing so far have been modest. In this section we'll talk about how formatted I/O can be done well in Go.

We've seen simple uses of the package
```

fmt

```
, which implements
```

Printf

```
,
```

Fprintf

```
, and so on. Within the
```

fmt

```
package,
```

Printf

```
is declared with this signature:

    Printf(format string, v ...) (n int, errno os.Error)

That
```

...

```
represents the variadic argument list that in C would be handled using the
```

stdarg.h

```
macros but in Go is passed using an empty interface variable (
```

interface {}

```
) and then unpacked using the reflection library. It's off topic here but the use of reflection helps explain some of the nice properties of Go's
```

Printf

```
, due to the ability of
```

Printf

```
to discover the type of its arguments dynamically.

For example, in C each format must correspond to the type of its argument. It's easier in many cases in Go. Instead of
```

%llud

```
you can just say
```

%d

```
;
```

Printf

```
knows the size and signedness of the integer and can do the right thing for you. The snippet

    10    var u64 uint64 = 1<<64-1;
    11    fmt.Printf("%d %dn", u64, int64(u64));

prints

     18446744073709551615 -1

In fact, if you're lazy the format
```

%v

```
will print, in a simple appropriate  style, any value, even an array or structure. The output of

    14    type T struct { a int; b string };
    15    t := T{77, "Sunset Strip"};
    16    a := []int{1, 2, 3, 4};
    17    fmt.Printf("%v %v %vn", u64, t, a);

is

    18446744073709551615 {77 Sunset Strip} [1 2 3 4]

You can drop the formatting altogether if you use
```

Print

```
or
```

Println

```
instead of
```

Printf

```
. Those routines do fully automatic formatting. The
```

Print

```
function just prints its elements out using the equivalent of
```

%v

```
while
```

Println

```
inserts spaces between arguments and adds a newline. The output of each of these two lines is identical to that of the
```

Printf

```
call above.

    18    fmt.Print(u64, " ", t, " ", a, "n");
    19    fmt.Println(u64, t, a);

If you have your own type you'd like
```

Printf

```
or
```

Print

```
to format, just give it a
```

String()

```
method that returns a string. The print routines will examine the value to inquire whether it implements the method and if so, use it rather than some other formatting. Here's a simple example.

    09    type testType struct {
    10        a int
    11        b string
    12    }

    14    func (t *testType) String() string {
    15        return fmt.Sprint(t.a) + " " + t.b
    16    }

    18    func main() {
    19        t := &testType{77, "Sunset Strip"}
    20        fmt.Println(t)
    21    }

Since
```

\*testType

```
has a
```

String()

```
method, the default formatter for that type will use it and produce the output

    77 Sunset Strip

Observe that the
```

String()

```
method calls
```

Sprint

```
(the obvious Go variant that returns a string) to do its formatting; special formatters can use the
```

fmt

```
library recursively.

Another feature of
```

Printf

```
is that the format
```

%T

```
will print a string representation of the type of a value, which can be handy when debugging polymorphic code.

It's possible to write full custom print formats with flags and precisions and such, but that's getting a little off the main thread so we'll leave it as an exploration exercise.

You might ask, though, how
```

Printf

```
can tell whether a type implements the
```

String()

```
method. Actually what it does is ask if the value can be converted to an interface variable that implements the method. Schematically, given a value
```

v

```
, it does this:

    type Stringer interface {
        String() string
    }

    s, ok := v.(Stringer);  // Test whether v implements "String()"
    if ok {
        result = s.String()
    } else {
        result = defaultOutput(v)
    }

The code uses a ``type assertion'' (
```

v.(Stringer)

```
) to test if the value stored in
```

v

```
satisfies the
```

Stringer

```
interface; if it does,
```

s

```
will become an interface variable implementing the method and
```

ok

```
will be
```

true

```
. We then use the interface variable to call the method. (The ''comma, ok'' pattern is a Go idiom used to test the success of operations such as type conversion, map update, communications, and so on, although this is the only appearance in this tutorial.) If the value does not satisfy the interface,
```

ok

```
will be false.

In this snippet the name
```

Stringer

```
follows the convention that we add ''[e]r'' to interfaces describing simple method sets like this.

One last wrinkle. To complete the suite, besides
```

Printf

```
etc. and
```

Sprintf

```
etc., there are also
```

Fprintf

```
etc. Unlike in C,
```

Fprintf

```
's first argument is not a file. Instead, it is a variable of type
```

io.Writer

```
, which is an interface type defined in the
```

io

```
library:

    type Writer interface {
        Write(p []byte) (n int, err os.Error);
    }

(This interface is another conventional name, this time for
```

Write

```
; there are also
```

io.Reader

```
,
```

io.ReadWriter

```
, and so on.) Thus you can call
```

Fprintf

```
on any type that implements a standard
```

Write()

```
method, not just files but also network channels, buffers, whatever you want.

## Prime numbers

Now we come to processes and communication-concurrent programming. It's a big subject so to be brief we assume some familiarity with the topic.

A classic program in the style is a prime sieve. (The sieve of Eratosthenes is computationally more efficient than the algorithm presented here, but we are more interested in concurrency than algorithmics at the moment.) It works by taking a stream of all the natural numbers and introducing a sequence of filters, one for each prime, to winnow the multiples of that prime. At each step we have a sequence of filters of the primes so far, and the next number to pop out is the next prime, which triggers the creation of the next filter in the chain.

Here's a flow diagram; each box represents a filter element whose creation is triggered by the first number that flowed from the elements before it.

To create a stream of integers, we use a Go**channel**, which, borrowing from CSP's descendants, represents a communications channel that can connect two concurrent computations. In Go, channel variables are references to a run-time object that coordinates the communication; as with maps and slices, use
```

make

```
to create a new channel.

Here is the first function in
```

progs/sieve.go

```
:

    09    // Send the sequence 2, 3, 4, ... to channel 'ch'.
    10    func generate(ch chan int) {
    11        for i := 2; ; i++ {
    12            ch <- i  // Send 'i' to channel 'ch'.
    13        }
    14    }

The
```

generate

```
function sends the sequence 2, 3, 4, 5, ... to its argument channel,
```

ch

```
, using the binary communications operator
```

<-

```
. Channel operations block, so if there's no recipient for the value on
```

ch

```
, the send operation will wait until one becomes available.

The
```

filter

```
function has three arguments: an input channel, an output channel, and a prime number. It copies values from the input to the output, discarding anything divisible by the prime. The unary communications operator
```

<-

```
(receive) retrieves the next value on the channel.

    16    // Copy the values from channel 'in' to channel 'out',
    17    // removing those divisible by 'prime'.
    18    func filter(in, out chan int, prime int) {
    19        for {
    20            i := <-in;  // Receive value of new variable 'i' from 'in'.
    21            if i % prime != 0 {
    22                out <- i  // Send 'i' to channel 'out'.
    23            }
    24        }
    25    }

The generator and filters execute concurrently. Go has its own model of process/threads/light-weight processes/coroutines, so to avoid notational confusion we call concurrently executing computations in Go**goroutines**. To start a goroutine, invoke the function, prefixing the call with the keyword
```

go

```
; this starts the function running in parallel with the current computation but in the same address space:

    go sum(hugeArray); // calculate sum in the background

If you want to know when the calculation is done, pass a channel on which it can report back:

    ch := make(chan int);
    go sum(hugeArray, ch);
    // ... do something else for a while
    result := <-ch;  // wait for, and retrieve, result

Back to our prime sieve. Here's how the sieve pipeline is stitched together:

    28    func main() {
    29        ch := make(chan int);  // Create a new channel.
    30        go generate(ch);  // Start generate() as a goroutine.
    31        for {
    32            prime := <-ch;
    33            fmt.Println(prime);
    34            ch1 := make(chan int);
    35            go filter(ch, ch1, prime);
    36            ch = ch1
    37        }
    38    }

Line 29 creates the initial channel to pass to
```

generate

```
, which it then starts up. As each prime pops out of the channel, a new
```

filter

```
is added to the pipeline and**its**output becomes the new value of
```

ch

```
.

The sieve program can be tweaked to use a pattern common in this style of programming. Here is a variant version of
```

generate

```
, from
```

progs/sieve1.go

```
:

    10    func generate() chan int {
    11        ch := make(chan int);
    12        go func(){
    13            for i := 2; ; i++ {
    14                ch <- i
    15            }
    16        }();
    17        return ch;
    18    }

This version does all the setup internally. It creates the output channel, launches a goroutine running a function literal, and returns the channel to the caller. It is a factory for concurrent execution, starting the goroutine and returning its connection.

The function literal notation (lines 12-16) allows us to construct an anonymous function and invoke it on the spot. Notice that the local variable
```

ch

```
is available to the function literal and lives on even after
```

generate

```
returns.

The same change can be made to
```

filter

```
:

    21    func filter(in chan int, prime int) chan int {
    22        out := make(chan int);
    23        go func() {
    24            for {
    25                if i := <-in; i % prime != 0 {
    26                    out <- i
    27                }
    28            }
    29        }();
    30        return out;
    31    }

The
```

sieve

```
function's main loop becomes simpler and clearer as a result, and while we're at it let's turn it into a factory too:

    33    func sieve() chan int {
    34        out := make(chan int);
    35        go func() {
    36            ch := generate();
    37            for {
    38                prime := <-ch;
    39                out <- prime;
    40                ch = filter(ch, prime);
    41            }
    42        }();
    43        return out;
    44    }

Now
```

main

```
's interface to the prime sieve is a channel of primes:

    46    func main() {
    47        primes := sieve();
    48        for {
    49            fmt.Println(<-primes);
    50        }
    51    }

## Multiplexing

With channels, it's possible to serve multiple independent client goroutines without writing an explicit multiplexer. The trick is to send the server a channel in the message, which it will then use to reply to the original sender. A realistic client-server program is a lot of code, so here is a very simple substitute to illustrate the idea. It starts by defining a
```

request

```
type, which embeds a channel that will be used for the reply.

    09    type request struct {
    10        a, b    int;
    11        replyc  chan int;
    12    }

The server will be trivial: it will do simple binary operations on integers. Here's the code that invokes the operation and responds to the request:

    14    type binOp func(a, b int) int

    16    func run(op binOp, req *request) {
    17        reply := op(req.a, req.b)
    18        req.replyc <- reply
    19    }

Line 18 defines the name
```

binOp

```
to be a function taking two integers and returning a third.

The
```

server

```
routine loops forever, receiving requests and, to avoid blocking due to a long-running operation, starting a goroutine to do the actual work.

    21    func server(op binOp, service chan *request) {
    22        for {
    23            req := <-service;
    24            go run(op, req);  // don't wait for it
    25        }
    26    }

We construct a server in a familiar way, starting it and returning a channel connected to it:

    28    func startServer(op binOp) chan *request {
    29        req := make(chan *request);
    30        go server(op, req);
    31        return req;
    32    }

Here's a simple test. It starts a server with an addition operator and sends out
```

N

```
requests without waiting for the replies. Only after all the requests are sent does it check the results.

    34    func main() {
    35        adder := startServer(func(a, b int) int { return a + b });
    36        const N = 100;
    37        var reqs [N]request;
    38        for i := 0; i < N; i++ {
    39            req := &reqs[i];
    40            req.a = i;
    41            req.b = i + N;
    42            req.replyc = make(chan int);
    43            adder <- req;
    44        }
    45        for i := N-1; i >= 0; i-- {   // doesn't matter what order
    46            if <-reqs[i].replyc != N + 2*i {
    47                fmt.Println("fail at", i);
    48            }
    49        }
    50        fmt.Println("done");
    51    }

One annoyance with this program is that it doesn't shut down the server cleanly; when
```

main

```
returns there are a number of lingering goroutines blocked on communication. To solve this, we can provide a second,
```

quit

```
channel to the server:

    32    func startServer(op binOp) (service chan *request, quit chan bool) {
    33        service = make(chan *request);
    34        quit = make(chan bool);
    35        go server(op, service, quit);
    36        return service, quit;
    37    }

It passes the quit channel to the
```

server

```
function, which uses it like this:

    21    func server(op binOp, service chan *request, quit chan bool) {
    22        for {
    23            select {
    24            case req := <-service:
    25                go run(op, req);  // don't wait for it
    26            case <-quit:
    27                return;
    28            }
    29        }
    30    }

Inside
```

server

```
, the
```

select

```
statement chooses which of the multiple communications listed by its cases can proceed. If all are blocked, it waits until one can proceed; if multiple can proceed, it chooses one at random. In this instance, the
```

select

```
allows the server to honor requests until it receives a quit message, at which point it returns, terminating its execution.

All that's left is to strobe the
```

quit

\`\`\` channel at the end of main:

```
42        adder, quit := startServer(func(a, b int) int { return a + b });
...
55        quit <- true;
```

关于Go编程和并发程序设计还有许多其它的内容，但这份快速入门教程应该已经给你提供了一点基础知识。


# 2010-03-13  利用外部VPS主机通过ssh隧道穿透防火墙连接内网

前几天一时冲动在国外买了个VPS主机，最大的好处是拥有全部的权限，操作系统自己想怎么搞就怎么搞。于是动了给公司内网通过这个VPS作为中转开个ssh隧道的念头，这样就不用很麻烦的每次都要去连vpn了。

公司内网访问外网目前不需要设置代理服务器，但是因为防火墙的原因，要访问公司内网地址还是需要通过vpn才能访问。通过ssh建立隧道的文章已经很多了，[这篇文章](https://www.ibm.com/developerworks/cn/linux/l-cn-sshforward/) 讲的很详细，不熟悉的同学可以去学习下。

我面临的情况，无非是由于防火墙的原因，内网可以出来，但是外部连不进去。因此需要通过ssh端口转发，从内网建立一个到VPS主机的ssh隧道。由于只能内部往外连接，因此使用的是ssh的远程端口映射（概念不懂的去看前面推荐的文章）：`ssh -qnfNT -g -R 3322:localhost:22 myname@vpshost`这样就在vpshost上开了一个端口3322，和内网的22端口建立起了一个ssh隧道。在vpshost上，通过 ssh -p 3322 localhost 就可以直接连到公司内网了。

按理来说，我从我本地的笔记本ssh到vpshost的3322端口应该是可以直接连接到公司内网的--这也是"-g"这个参数的作用，没有这个参数则只允许从vpshost本地发起ssh连接。但是很奇怪试了很多次总是不行，vpshost的3322端口不接受外部（非本地）的ssh连接，也许是我哪里理解有误，知道的同学请告诉我。

现在问题其实也很明显，我没法从外部直接通过vpshost的3322端口连接上去，只能从vpshost本地发起连接。那么如果想从外部发起连接，暂且想到两个办法：

1. 在vps上用ssh新开一个本地端口转发，转发到本机的3322端口，这个新开端口也要用ssh的-g参数。按理来所应该能够起作用，但是尝试之后几次仍然不行，于是便放弃继续受挫了。
2. 通过本地端口映射，开一个能够接受外部连接的端口，然后将这个端口接收到的数据转发到3322上。

方法2其实有很多工具可以实现，比如iptables、netcat之类的。我嫌iptables太麻烦，而nc又不支持多连接。于是经过一番google，发现了socat这个极其变态强大的工具--果然一不小心就知道的太多了。

socat本质上就是一个数据管道：从一个数据源接收数据流，然后转发到另外的数据目的地。支持socket,tcp,udp,unix domain,pipe,文件等等几乎各类你能想到和想不到的数据源。socat有海量的参数，我就不一一解释了，想了解的同学可以去看[文档](http://www.dest-unreach.org/socat/doc/socat.html)，有不少例子应该能够照猫画虎的满足需求。我从文档中的例子里抄了一个TCP方式的端口转发

```
vps# socat TCP4-LISTEN:2222,reuseaddr,fork TCP4:localhost:3322
```

第一个地址表示使用tcp监听本地的2222端口，reuseaddr表示复用本地地址，fork表示如果接受了一个连接之后马上开启一个新的进程准备接受下一个连接。 第二个地址表示，将前者2222这个端口的数据，使用tcp方式转发到本地（localhost）的3322端口去。

这下我从本机上ssh 到vps的2222端口，果然顺利连接到公司内网。


# 2009-12-30  旋转矩阵

前天在Google Groups的TopLanguge中看到的一个从JavaEye转过来的帖子。本来应该是给大学生做的课后习题，可是看到的坛友给出的C、C++、Python、Java等代码，都是一大堆的if/else要么就是 switch，何其冗长。和楼主给出的那个反例也差不了多少了。 when int i=5; output:

```
1  2  3  4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
```

when int i=6; output:

```
1  2  3  4  5  6
20 21 22 23 24  7
19 32 33 34 25  8
18 31 36 35 26  9
17 30 29 28 27 10
16 15 14 13 12 11
```

## This is a simple python program which hate if/else and switch.

```
MAX = 10
matrix = [[0 for col in range(MAX)] for row in range(MAX)]
(x, y, count, link) = (-1, 0, 1, {1:(1,0,2), 2:(0,1,3), 3:(-1,0,4), 4:(0,-1,1)})
(dx, dy, direct) = link[1]
while count <= MAX*MAX:
    (nx, ny) = (x + dx, y + dy)
    if (0 <= nx < MAX) and (0 <= ny < MAX) and (matrix[ny][nx] == 0):
        matrix[ny][nx] = count
        (x, y, count) = (nx, ny, count + 1)
    else:
        (dx, dy, direct) = link[direct]

for x in range(MAX):
    print matrix[x]
```


# 2009-02-10  为什么cpio要比tar好

为什么cpio比tar好？有这样几个原因。 1、cpio会保留硬连接（hard link），备份的时候这个很重要 2、cpio没有文件名长度的限制。确实，guntar在这一点上做过改进，允许使用长文件名（实际上是创建了一个临时文件用来保存实际的文件名），但是在非gnu的tar工具上仍然存在这个问题。 3、默认情况下，cpio保留时间戳 4、在编写脚本的时候，cpio可以更好的控制要操作哪些文件。因为cpio需要显式的制定要操作的文件列表，例如下面哪个更加容易理解？

```
find . -type f -name '*.sh' -print | cpio -o | gzip >sh.cpio.gz
```

或者在Solaris上：

```
find . -type f -name '*.sh' -print >/tmp/includeme
tar -cf - . -I /tmp/includeme | gzip >sh.tar.gz
```

或者用gnutar：

\`\` find . -type f -name '\*.sh' -print >/tmp/includeme tar -cf - . --files-from=/tmp/includeme | gzip >sh.tar.gz

```
这儿有一个需要特别注意的：对于包含大量文件的列表，不能将find放在反引号（`）内，因为命令行长度会超出长度限制，因此必须使用中间文件。 find和tar分开跑很明显会使得速度减慢。

下面这个例子更加复杂，将一部分文件打包到一个文件中，其它部分打包到另外一个文件中：
```

find . -depth -print >/tmp/files egrep '.sh$' /tmp/files | cpio -o | gzip >with.cpio.gz egrep -v '.sh$' /tmp/files | cpio -o | gzip >without.cpio.gz

```
在 Solaris 下:
```

find . -depth -print >/tmp/files egrep '.sh$' /tmp/files >/tmp/with tar -cf - . -I /tmp/with | gzip >with.tar.gz tar -cf - . /tmp/without | gzip >without.tar.gz

```
使用 gnutar:
```

find . -depth -print >/tmp/files egrep '.sh$' /tmp/files >/tmp/with tar -cf - . -I /tmp/with | gzip >with.tar.gz tar -cf - . -X /tmp/without | gzip >without.tar.gz

```
同样的，find和tar分开跑会使得速度变慢。创建多个中间文件也搞出了更多的混乱。gnutar稍好些，但是它的命令行参数却是不兼容的。

5、如果有很多文件需要通过网络在两台机器之间复制，则可以并行的跑几个cpio。例如：
```

find . -depth -print >/tmp/files split /tmp/files for F in /tmp/files?? ; do cat $F | cpio -o | ssh destination "cd /target && cpio -idum" & done

\`\`\`

注意，如果能够将输入平均分成几个部分来进行并行处理会更好。

[Simon Liu](http://log4think.com) translated from [Ramblings](http://rightsock.com/~kjw/Ramblings/tar_v_cpio.html)


# 2008-12-11  ThoughtWorks 的一道笔试题

PROBLEM ONE: TRAINS

Problem: The local commuter railroad services a number of towns in Kiwiland. Because of monetary concerns, all of the tracks are 'one-way.' That is, a route from Kaitaia to Invercargill does not imply the existence of a route from Invercargill to Kaitaia. In fact, even if both of these routes do happen to exist, they are distinct and are not necessarily the same distance!

The purpose of this problem is to help the railroad provide its customers with information about the routes. In particular, you will compute the distance along a certain route, the number of different routes between two towns, and the shortest route between two towns.

Input: A directed graph where a node represents a town and an edge represents a route between two towns. The weighting of the edge represents the distance between the two towns. A given route will never appear more than once, and for a given route, the starting and ending town will not be the same town.

Output: For test input 1 through 5, if no such route exists, output 'NO SUCH ROUTE'. Otherwise, follow the route as given; do not make any extra stops! For example, the first problem means to start at city A, then travel directly to city B (a distance of 5), then directly to city C (a distance of 4).

1. The distance of the route A-B-C.
2. The distance of the route A-D.
3. The distance of the route A-D-C.
4. The distance of the route A-E-B-C-D.
5. The distance of the route A-E-D.
6. The number of trips starting at C and ending at C with a maximum of 3 stops.  In the sample data below, there are two such trips: C-D-C (2 stops). and C-E-B-C (3 stops).
7. The number of trips starting at A and ending at C with exactly 4 stops.  In the sample data below, there are three such trips: A to C (via B,C,D); A to C (via D,C,D); and A to C (via D,E,B).
8. The length of the shortest route (in terms of distance to travel) from A to C.
9. The length of the shortest route (in terms of distance to travel) from B to B.
10. The number of different routes from C to C with a distance of less than 30.  In the sample data, the trips are: CDC, CEBC, CEBCDC, CDCEBC, CDEBC, CEBCEBC, CEBCEBCEBC.

Test Input:

For the test input, the towns are named using the first few letters of the alphabet from A to D. A route between two towns (A to B) with a distance of 5 is represented as AB5.

Graph: AB5, BC4, CD8, DC8, DE6, AD5, CE2, EB3, AE7

Expected Output:

Output #1: 9 Output #2: 5 Output #3: 13 Output #4: 22 Output #5: NO SUCH ROUTE Output #6: 2 Output #7: 3 Output #8: 9 Output #9: 9

## Output #10: 7

用一个带权重的有向图表示节点之间的关系，使用深度优先搜索。

1-5:

```
package com.log4think.code;

public class TrainMap {

    public int[][] map = {
        //    A   B   C   D   E
            {-1,  5, -1,  5,  7},
            {-1, -1,  4, -1, -1},
            {-1, -1, -1,  8,  2},
            {-1, -1,  8, -1,  6},
            {-1,  3, -1, -1, -1},
    };

    public int getDistance(String path)
    {
        int distance = 0;

        for (int i=0; i
<path.length()-1; i++) {
            // get the node index in map
            int fromNodeIndex = path.charAt(i) - 'A';
            int toNodeIndex = path.charAt(i+1) - 'A';

            // check the distance of two node in map
            if (map[fromNodeIndex][toNodeIndex] == -1) {
                return -1;
            } else {
                distance += map[fromNodeIndex][toNodeIndex];
            }
        }

        return distance;
    }

    public static void main(String[] args) {
        TrainMap g = new TrainMap();

        int distance = g.getDistance("ABC");
        if (distance > 0)
            System.out.println(distance);
        else
            System.out.println("NO SUCH ROUTE");

//        g.findDistance("AD");
//        g.findDistance("ADC");
//        g.findDistance("AEBCD");
//        g.findDistance("AED");
    }
}
```

6:

```
public class TrainMap {

    public int[][] map = {
            {-1, 5, -1, 5, 7},
            {-1, -1, 4, -1, -1},
            {-1, -1, -1, 8, 2},
            {-1, -1, 8, -1, 6},
            {-1, 3, -1, -1, -1},
    };

    // used to record the trip round count
    public int tripCount = 0;

    /***
     *
     * @param end , the end node. if we reach it we found a path
     * @param path , the current path we already have
     * @param maxLength, the maximum stops
     */
    public void dfs(String end, String path, int maxLength)
    {
        // this is for debug and trace
        // System.out.println(";; " + path);

        // if the path reach the maximum stops, then cancel search
        if (path.length() - 1 > maxLength) return;

        // check if we have reach the "end" node
        if ( path.length() > 1 && path.endsWith(end) ) {
            System.out.println(path + ", " + tripCount);
            tripCount ++;
        }

        // caculate the lastest node index in map
        char lastChar = path.charAt(path.length()-1);
        int lastNodeIndex = lastChar - 'A';

        // loop all nodes in map which connected to lastNode, and try it
        for ( int i=0; i
<map[lastNodeIndex].length; i++ )
        {
            // convert index to node name
            char newChar = (char)(i + 'A');
            // try and search
            if ( map[lastNodeIndex][i] > 0) {
                dfs(end, path + newChar, maxLength);
            }
        }
    }

    public static void main(String[] args) {
        TrainMap g = new TrainMap();

        g.dfs("C", "C", 3);
    }
}
```

7:

```
public class TrainMap {

    public int[][] map = {
            { -1, 5, -1, 5, 7 },
            { -1, -1, 4, -1, -1 },
            { -1, -1, -1, 8, 2 },
            { -1, -1, 8, -1, 6 },
            { -1, 3, -1, -1, -1 }
    };

    public void bfs(String start, String end, int hops) {
        String lastRoute = start;

        for (int hop = 0; hop < hops; hop++) {
            String route = "";
            for (int i = 0; i < lastRoute.length(); i++) {
                char c = lastRoute.charAt(i);
                int id = c - 'A';

                for (int j = 0; j < map[id].length; j++) {
                    if (map[id][j] > 0)
                        route = route + (char) (j + 'A');
                }
            }
//          System.out.println(lastRoute);
            lastRoute = route;
        }

//      System.out.println(lastRoute);
        System.out.println(lastRoute.split(end).length - 1);
    }

    public static void main(String[] args) {
        TrainMap g = new TrainMap();

        g.bfs("A", "C", 4);
    }
}
```

8/9:

```
public class TrainMap {

    public int[][] map = {
            {-1, 5, -1, 5, 7},
            {-1, -1, 4, -1, -1},
            {-1, -1, -1, 8, 2},
            {-1, -1, 8, -1, 6},
            {-1, 3, -1, -1, -1},
    };

    public void dfs(String end, String path, int cost) {
        if (path.endsWith(end) && cost < bestCost && cost > 0) {
            bestPath = path;
            bestCost = cost;
            return;
        }
        char lastChar = path.charAt(path.length() - 1);
        int lastNodeIndex = lastChar - 'A';

        for (int i = 0; i < map[lastNodeIndex].length; i++) {
            char newChar = (char) (i + 'A');
            int newCost = map[lastNodeIndex][i];
            if (newCost > 0) {
                if (path.indexOf(newChar) > 0)
                    continue;
                dfs(end, path + newChar, cost + newCost);
            }
        }
    }

    public String bestPath = "";
    public int bestCost = Integer.MAX_VALUE;

    public static void main(String[] args) {
        TrainMap g = new TrainMap();

        g.dfs("C", "A", 0); // 8
//      g.dfs("B", "B", 0); // 9

        System.out.println("Best Path: " + g.bestPath + "\nCost: " + g.bestCost);
    }
}
```

10:

```
public class TrainMap {

    public int[][] map = {
            {-1, 5, -1, 5, 7},
            {-1, -1, 4, -1, -1},
            {-1, -1, -1, 8, 2},
            {-1, -1, 8, -1, 6},
            {-1, 3, -1, -1, -1},
    };

    public void dfs(String end, String path, int cost) {
        if (cost >= 30)
            return;

        if (cost > 0 && path.endsWith(end)) {
            System.out.println(path + ", " + cost);
        }

        char lastChar = path.charAt(path.length() - 1);
        int lastNodeIndex = lastChar - 'A';

        for (int i = 0; i < map[lastNodeIndex].length; i++) {
            char newChar = (char) (i + 'A');
            int newCost = map[lastNodeIndex][i];
            if (newCost > 0) {
                dfs(end, path + newChar, cost + newCost);
            }
        }
    }

    public static void main(String[] args) {
        TrainMap g = new TrainMap();

        g.dfs("C", "C", 0);
    }
}
```


# 2008-11-18  长距离打车如何省钱？

学JavaScript的时候写的一个用于在长距离打车时，计算多远抬一次表最省钱的抬表方案的小程序，参数可以根据需要随意修改。

X轴是距离，Y轴是费用，不同的颜色表示不同的抬表方案

大致的简单结论是20公里以上的时候，每15公里抬一次表，能省出10%左右吧。

&#x20;下面代码存为taxi.html，然后打开即可在浏览器中看到结果。

```
<html>
<script type="text/javascript" src="http://www.walterzorn.com/scripts/wz_jsgraphics.js"></script>

<body>
<script type="text/javascript">
<!--
function getPrice(distance, night) {
    var unitPrice = 2.0,
    basicDistance = 3,
    basicPrice = 10,
    extDistance = 15,
    extPrice = 3.0;

    var price = 0.0;
    if ( distance == 0 ) return 0;
    if ( distance <= basicDistance ) return basicPrice;

    if ( distance <= extDistance ) {
        price = basicPrice + unitPrice * (distance - basicDistance);
        return price;
    }

    price += basicPrice + unitPrice*(extDistance-basicDistance) + extPrice*(distance-extDistance)
    return price;
}

function getPriceWithReset(distance, night, reset) {
    if ( reset == 0 ) {
        return getPrice(distance, night);
    } else {
        return getPrice(reset, night) * Math.floor(distance/reset) + getPrice(distance % reset, night);
    }
}

var coordXUnit = 8, coordYUnit = 3,
basicX = 100, basicY = 50,
coordMaxX = 101, coordMaxY = 251;

function drawString(str, x, y) {
    jg.drawString(str, basicX+x*coordXUnit, basicY+(coordMaxY-y)*coordYUnit);
}

function drawPoint(x, y) {
    drawLine(x, y, x, y);
}

function drawLine(x1, y1, x2, y2) {
    jg.drawLine(basicX + x1*coordXUnit, basicY + (coordMaxY-y1)*coordYUnit, basicX + x2*coordXUnit, basicY + (coordMaxY-y2)*coordYUnit);
}

function drawPolyline(ax, ay) {
    var axx = new Array(), ayy = new Array();

    for (var e in ax) {
        axx.push(basicX + (ax[e] * coordXUnit) );
    }

    for (var e in ay) {
        ayy.push(basicY + (coordMaxY - ay[e])*coordYUnit);
    }

    jg.drawPolyline(axx, ayy);
}

function drawCoordinate() {
    jg.setColor("#ee8800");
    drawLine(0, 0, coordMaxX, 0);
    drawLine(0, 0, 0, coordMaxY);

    drawString(0, -2, -2);
    for (var i = 1; i < coordMaxX; i++) {
        if (i%5==0) {
            // draw grid
            jg.setStroke(Stroke.DOTTED);
            drawLine(i, 0, i, coordMaxY);
            // draw mark
            jg.setStroke(0);
            drawLine(i, 0, i, -2);
            drawString(i, i-1, -3);
        } else {
            drawLine(i, 0, i, -1);
        }
    }

    for (var i = 1; i < coordMaxY ; i++) {
        if (i%5==0) {
            // draw grid
            jg.setStroke(Stroke.DOTTED);
            drawLine(0, i, coordMaxX, i);
            // draw mark
            jg.setStroke(0);
            drawLine(0, i, -2, i);
            drawString(i, -7, i+1);
        } else {
            drawLine(0, i, -1, i);
        }
    }
}

function drawPriceLine(night, reset) {
    var ax = new Array(), ay = new Array();

    for (var d=0; d<70; d++) {
        ax.push(d);
        ay.push(getPriceWithReset(d, 0, reset));
    }
    drawPolyline(ax, ay);
}

/////////////// main section ///////////////
var jg = new jsGraphics();
jg.setFont("verdana,geneva,sans-serif", "10px", Font.PLAIN);

drawCoordinate();
drawString("reset:", 0, -10);
jg.setColor("#000000");
drawPriceLine(0, 0);
drawString("0", 5, -10);
jg.setColor("#FF0000");
drawPriceLine(0, 10);
drawString("10", 10, -10);
jg.setColor("#00FF00");
drawPriceLine(0, 15);
drawString("15", 15, -10);
jg.setColor("#0000FF");
drawPriceLine(0, 20);
drawString("20", 20, -10);

///////////////// from wangjing to my home, 35KM /////////////////
jg.setColor("#880000");
var xp = new Array(), yp = new Array();
for ( var i=10; i<30; i++) {
    xp.push(i);
    yp.push(getPriceWithReset(35, 0, i));
}
drawPolyline(xp, yp);

drawString(getPriceWithReset(35, 0, 15), 0, -20);
jg.paint();
//-->
</script>
</body>
</html>
```


# 2007-02-08  ftp后台自动上传下载

## 方法一: 直接用ftp实现脚本upload.sh

```
#!/bin/sh

host=xxx.xxx.xxx.xxx
user=xxx
pass=xxx
localdir=/xxx/xxx
remotedir=/xxx/xxx
filename=xxx

ftp -i -n $host <
user $user $pass
cd $remotedir
lcd $localdir
append $filename
quit
EOF
```

运行命令 `nohup ./upload.sh > upload.log 2>&1 &`

## 方法二:用lftp

编写一个脚本文件 upload.cfg,里面直接写ftp命令

```
open xxx.xxx.xxx.xxx
user xxx xxx
cd xxx
lcd xxx
put xxx
quit
```

运行命令 `lftp -f upload.cfg`。缺点是,似乎不支持append断点续传

## 方法三:用工具,例如ncftpput, 编写一个配置文件 login.cfg

```
host=xxx.xxx.xxx.xxx
user=xxx
pass=xxx
```

然后运行命令 `ncftpput -f login.cfg -z remote_dir local_dir/file`。缺点是,似乎也不支持断点续传(本来有一个-z的参数,但是好像不好用)

## 方法四: 用scp

假设远程机器和本地用户都是jack 首先用ssh-keygen在远程机器上生成一个密钥,密码为空. 然后把远程机器上的/home/jack/.ssh/id\_rsa.pub文件内容复制到本地机器上的/home/jack/.ssh/authorized\_keys 里面 这样再用ssh登录的时候就不用密码了

做好这个之后,可以用 `scp filename jack@remote_host`上传。缺点是,不支持断点续传,不支持后台运行...似乎是个垃圾解决方案,不过倒是支持加密,嗯


# 2006-06-12  vi cheatsheet

编辑: 把下一行挪到当前行的末尾，在Windows下很简单--在当前行末尾按del或者在下一行的开头按bs就可以了， 而在vi里面是要在当前行任意位置按J（大写的J，也就是Shift+j）

快速保存退出： 很多人习惯用标准的 :wq！ 保存退出，不过我更喜欢按Shift+z+z，也就是ZZ，一样的。

查找: /sometext 从当前向后找 ?sometext 从当前向前找 (这两个命令执行后 按n继续向后找 按N继续向前找) :g/sometext/

前者直接定位到单词上，后者仅仅是定位到那一行上

ex命令扩展：

vi下有一个ex命令模式可以用ex命令来进行快速编辑操作，基本的模式是：

\[范围] g\[lobal] /模式/ \[ex命令] 在指定的"范围"内，对匹配"模式"的行执行"ex命令"

\[范围] v\[lobal] /模式/ \[ex命令] 在指定的"范围"内，对不匹配"模式"的行执行"ex命令"

如果没有指定"范围"，则默认为全部。 如果没有制定"ex命令"，则默认是显示结果。 "模式"即正则表达式。 "ex命令"在编辑方面，大致有 m（移动）s（替换）d（删除）

例子：

:g/^/m0 会从第一行开始把每一行移动到第一行之后，结果就是所有行逆序反转

:g/127.0.0.1/s/http/ftp/g 对包含127.0.0.1的行，将http替换为ftp

:g/127.0.0.1/s/^/\t/g 对包含127.0.0.1的行，每行之前加一个制表符（TAB）

:g/127.0.0.1/d 删除包含127.0.0.1的行

如果命令的内容中包含/字符，可以选择转义 \\/ 或者把ex的命令分隔符/换成别的字符（例如\~@#$%等）。例如要把包含/home的行中的/simon删除：

:g/\\/home/ s/\\/simon//g 用转义字符输入/

:g#/home#s#/simon##g 用#作为命令分隔符，则字符/可以直接使用了

查找-替换: 通常情况下查找替换的ex命令是 :%s/oldtext/newtext/g

下面是对这个命令的解释 : 表示进入ex的命令模式 % 是表示ex命令的source的范围. 后面的命令在该范围内执行. 该范围有多种表达方式: % 表示全文 $ 表示到行尾 . 表示当前行 x,y 表示从第x行到y行, 例如 10,20s/oldtext/newtext/ 此外还可以用其它的ex搜索命令表达范围 g/sometext/ 表示包含sometext的行

s 是ex的替换命令 /.../.../ 表示"搜索-替换"的内容. g 是表示替换所有. 默认s只执行一次

-= 例子1 =- :10,20s/^/ / 把第10到20行的前面加上4个空格 可以分解为 : 10,20 s /^/ /

-= 例子2 =- :g/small/s/^/#/ 把所有包含small的行都加上注释# g/small/ 查找包含small的行 /^/ 表示第一个非空白字符 可以分解为 g/small/ s /^/#/


# 2006-04-19  修正mysqlcc在MySQL 5.0上常报的 Table 'xxx' doesn't exist 错误

公司上了MySQL 5.0， 随之而来的不是用的有多爽的问题， 而是一直用的很顺手的mysqlcc...不行了， 其表现形式为经常会在状态栏中提示 "\[192.168.22.72] ERROR 1146: Table 'rimkpi.1' doesn't exist" 之类的。 选出数据来也不能在表格中直接修改了，到底mysqlcc出了啥问题呢？

经过观察，发现在MySQL 5.0上 EXPLAIN 语句的结果和4.1一下的版本不同了。4.1以前的返回的结果第一个字段是tablename， 而4.1以后和5.0的返回的是id号（一般情况下就是1了），第三个字段才是tablename。看来是这里出了问题，不过怎么会在4.1上可以，5.0反而不行了呢？ 很不爽，于是去MySQL的官方网站把mysqlcc的source拖了一份下来，用Source-Navigator跟了一把，发现原来是在CQueryWindow\.cpp的execQuery的方法(line 447)中的一段白痴代码：

```
default_table = explain_query->row( mysql()->mysql()->version().major >= 4 &&
    mysql()->mysql()->version().minor >= 1 ? 2 : 0);
```

如果版本号是4.1或者5.1的话，这个判断的结果是2；而如果版本号是5.0的话，结果就是0了。正确的代码至少也应该是

```
default_table = explain_query->row(
    ( mysql()->mysql()->version().major == 4 && mysql()->mysql()->version().minor >= 1 ) ||
    ( mysql()->mysql()->version().major >= 5 ) ? 2 : 0);
```

真是...大概开源就这点好处了。 mysqlcc现在已经不再继续维护了，看来只能自己找到mysql的开发包重新编译个自用版本的mysqlcc了，哈哈


# 2006-04-01  'Perl中不寻常的 ?: 运算符'

前几天写一个perl的脚本 在:?运算符上遇到了一个很诡异的问题

```
$data->{$id}->{'total'} ?
    $data->{$id}->{'ratio'} = sprintf("%.2f%%", 100 * $data->{$id}->{'succ'} / $data->{$id}->{'total'}) :
    $data->{$id}->{'ratio'} = 'N/A';
```

我的本意是 如果 `$data->{$id}->{'total'}` 未定义则不计算ratio,把ratio赋值为N/A. 这条语句等同于

```
if ( $data->{$id}->{'total'} ) {
    $data->{$id}->{'ratio'} = sprintf("%.2f%%", 100 * $data->{$id}->{'succ'} / $data->{$id}->{'total'});
} else {
    $data->{$id}->{'ratio'} = 'N/A';
```

可奇怪的是，当无论total是否有定义 ratio的结果居然都是N/A. 可后面`if else`的语句是没有问题的，真的是让我百思不得其解。跑去查Perl的文档，其中对于?:的运算符号的解释是

*Ternary `?:` is the conditional operator, just as in C. It works much like an if-then-else. If the argument before the ? is true, the argument before the : is returned, otherwise the argument after the : is returned.*

貌似是return the argument，于是乎脑子里突然闪过一个念头, 在前后都加上了括号...

```
$data->{$id}->{'total'} ?
    ( $data->{$id}->{'ratio'} = sprintf("%.2f%%", 100 * $data->{$id}->{'succ'} / $data->{$id}->{'total'}) ) :
    ( $data->{$id}->{'ratio'} = 'N/A' );
```

...居然就对了。既然是return the argument，我就又换了一种方式：

```
$data->{$id}->{'ratio'} = $data->{$id}->{'total'} ?
    sprintf("%.2f%%", 100 * $data->{$id}->{'succ'} / $data->{$id}->{'total'}) :
    'N/A';
```

虽然后面两种方式都可以理解，那确实是一种正确的做法。 但为什么第一种方式的结果不对呢？我又写了一个简单的小程序测试

```
#!/usr/bin/perl

use strict;

my $total=1;
my $rval;

############################`
$total ?
    $rval = $total :
    $rval = 'N/A';

print $rval, "\n";

############################
$total ?
    ( $rval = $total ) :
    ( $rval = 'N/A' );

print $rval, "\n";

############################
$rval = $total ? $total : 'N/A';

print $rval, "\n";

############################
if ($total) {
    $rval = $total;
} else {
    $rval = 'N/A';
}

print $rval;
```

运行的结果显示, 无论第5行给$total赋什么值...包括1, "abc", "true", undef 等,执行的结果第一个print打印出来的都是N/A。 难道 $total? 不等价于 if ($total) 吗?

后来偶然的一次机会在PerlChina上问过此问题，才发现原来C和Perl对于 `:?` 和 `=`的优先级定义是不同的。在C中，`=` 的优先级高于`:?`，而Perl中则正好相反。这直接导致了第一种情况对于语句的解释顺序与C截然不同。对于`x ? a = 1 : a = 2;`，你期望解释为`x ? (a = 1) : (a = 2);`，实际却解释成了另外一个形式。

因此教训就是，文档在解释 ?: 的时候说的很清楚 `If the argument before the ? is true, the argument before the : is returned`，重点在*return*。所以，:? 里面应该尽量写"表达式"，而非"语句"。非要用语句，那么请加括号避免优先级问题。


# 2005-09-13  关于IoC、低耦合、配置文件及其本质意义的思考

看了一篇关于IoC的Blog[《你真的了解Ioc与AOP吗？》](http://www.cnblogs.com/zhenyulu/archive/2005/09/10/234181.html)，很是有点感想。

我之前对IoC了解一些但实际大规模应用的机会比较少，感觉它可以通过两种方式来改善耦合度关系：

1，通过"注入"实现功能替换。简单的"注入"，可以通过seter或者Constructor来实现。之后可以在运行期的装配阶段把各个注入类和宿主类组合起来。简单"注入"的缺点是，还是要将具体的注入类写死在装配方法中。

2，为了解决简单注入的问题，有人利用反射+配置文件实现了注入，现在只要修改配置文件就可以实现注入类的替换了。

所谓的"注入"，可以理解为一个*回调*的过程：把宿主类中要在运行期决议/替换的功能单独作为一个类来实现，然后把这个类的抽象（抽象类或者接口）作为宿主类的一个接口（通过seter或者constructor来赋值），最后在运行期把一个实现具体功能的实例赋给宿主类。上述的实现，宿主类要显式的标明注入类的接口类型，并且在装配阶段要显式的new出注入类，然后赋给宿主类的对应接口。这种事情，好处自然是有的了，就好像AbstractFactory或者Builder模式：只公开接口，把具体的实现抽离出来，这样可以方便的实现具体实现的替换。可是仔细看一下，这样的实现，耦合度从代码中转移到了装配类中。

而配置文件的方式，把类的装配工作以配置文件的形式实现了，这样不用修改代码、编译就可以实现注入类的替换。这样做的好处更是有的，不管怎么说，能够不修改代码实现功能的替换至少在发布方面是能够带来不错的效果。至于在代码方面能够带来多大程度上的好处，我想可能还是要视环境和应用的场合来定吧。毕竟这样做的复杂程度要比简单实现高很多了。而且这样的实现，事实上是把耦合从装配方法中转移到了配置文件中。

其实在IoC实现中花了很多的力气来做类型的匹配工作......宿主类和注入类的接口对应，这个是强类型语言所不能避免的。既然如此的话，那么无类型的动态语言实现IoC应该是非常的容易并且效果应该是不错的--可是同时负面效果就是运行期才能进行类型的识别和匹配工作容易出错，但目前似乎配置文件的方式也无法避免这个问题。

进一步的，配置文件其实也是可以演变、进化的。首先就是配置文件中的内容是否可以以元数据的形式出现？又或者以一个程序集的形式出现？该程序集其实就是配置文件或者元数据功能上的替代者--虽然修改不方便了，但是同时带来了安全上的保证。可是这样一来，我们又要面对编译的问题。呵呵，两难吧。

当下形形色色的配置文件已经泛滥成灾了，怪不得RoR要火爆起来。其实就配置文件来说，本质上是为了把耦合的位置从不可修改的编译代码中转移到一个可修改的文本文件中，这样就可以在替换部分功能的时候免除掉"编译"这个环节，为的就是这个效果吧（不知道这样说是否正确）。 虽然可以不用修改代码了但还是要修改配置文件，我个人感觉这本人本质上其实还是一回事。话说回来，如果这个趋势发展到极致，我想可能大家可以只用配置文件来写程序了，宿主程序则退化为一个配置文件的解释器--事实上，这个也是可以实现的，Eclipse和SharpDevelop的宿主程序已经是这样的一种东西了。XML的配置文件加一个解释器不就是一个新的语言么？如果不用XML做配置文件，而是用类似脚本的方式来定义配置，那么这个配置文件其实就是一个解释型的语言了。然后，为了提高这种配置方式程序的效率...... 我们兜了一个很大的圈子之后又转回到编译语言上来了。从这个角度上来说，这是一个平衡度的问题。 从更高一点的角度来看，这是一个螺旋式进化、演变、上升的过程。从现实的角度来看，未来二十年应该是解释语言和动态语言的天下。

退后一步来看这一切，我们当初为什么要OO，是为了降低大型软件中面向过程的封装问题导致的复杂性。有了OO之后，发现一切并不是那么的美好，为了降低对象之间的耦合度、增加灵活性，于是有了设计模式。人们应用了设计模式，发现调试不方便了，复杂性又提高了。IoC可以理解为是模式的一种吧，但是换一个角度来看，它不就是一个回调函数的OO方式的实现吗？我们果然是又转回了起点，下一步又要转向哪里呢？

这其实，都是为了解决软件的一种复杂性而导致的另外一种复杂性，所不同的就是人们可以选择自己更能够接收哪种复杂性。


# 2005-09-13  Perl与数据库DBI快速入门

上次的文章[Perl无废话入门指南](https://log4think.com/perl_fast_tutorial/)中，简单的介绍了Perl的开发环境。为了完成提到的任务，还需要做几个知识点。无论是写脚本还是做CGI，如何使用Perl来访问数据库就是很有用的一个知识。

各种语言和开发环境访问数据库有各种不同的方式，比如可以用C和数据库提供的API接口来进行访问，也可以用JDBC、ODBC、ADO等封装好的统一接口来进行访问。Perl访问数据库最常用的包是DBI，可以在[www.cpan.org](https://www.cpan.org)找到。另外还需要安装对应数据库的驱动包，例如DBD::MySQL、DBD::Oracle、DBD::Sybase或者DBD::ODBC等。具体的安装方法请参考上期文章，在此就不赘述了。

下文以常见的MySQL为例，说说如何实现对数据库的操作。

## 1 基本流程 #\#

习惯在Windows下开发数据库、熟悉ADO、ADO.NET的朋友，一定对ADOConnection/ADODataSet/ADOTable等类耳熟能详。DBI的接口与之类似，但在操作方法上又有不同，对ADO熟悉的朋友不妨比较一下异同。一般来说，数据库操作由以下几个步骤组成一个常见的流程：

1． 建立一个数据库连接

2． 通过建立的数据库连接，执行SQL语句

3． 执行SQL后获取返回的数据集

4． 在数据集中对记录进行处理，一般是一个循环的过程

5． 处理完毕，关闭数据库连接，释放资源

下面是按照上述的流程，在Perl中访问MySQL的一段代码，以这段代码为例，详细说明DBI的使用方法。

```
#!/usr/bin/perl -w
use strict;
use DBI;

my $dbh = DBI->connect("DBI:mysql:test:192.168.1.2", 'root', 'password');
my $sth = $dbh->prepare("SELECT * FROM test1");
$sth->execute();

while ( my @row = $sth->fetchrow_array() )
{
       print join('\t', @row)."\n";
}

$sth->finish();
$dbh->disconnect();
```

注意代码中的灰色部分就是要特别关注的数据库访问接口，这里展现的只是一部分，下面将会依次说明每一个步骤，以及其它的操作在Perl中是如何实现的。

### 1.1 连接数据库 ##\#

```
my $dbh = DBI->connect("DBI:mysql:test:192.168.1.2", 'root', 'password');
```

调用DBI的方法

```
DBI->connect
```

来建立一个数据库的连接，如果连接成功则返回一个数据库连接句柄，之后执行SQL等操作都要把这个连接句柄作为一个操作参数。在connect调用中，首先要提供一个数据库连接串。这个连接串用冒号分为了几个部分，请看下表

| 小节          | 说明      |
| ----------- | ------- |
| DBI         | 接口类型    |
| mysql       | 数据库类型   |
| test        | 数据库名称   |
| 192.168.1.2 | 数据库主机地址 |

在前面例子中的连接串中，DBI表示这是DBI接口的一个连接串；mysql表示要连接的数据库是MySQL数据库（如果要连接Oracle数据库，这里则是oracle），不同的数据库有不同的连接串定义，可以参考DBI对应的访问驱动的说明；test指明了连接到数据库主机上的数据库名称；192.168.1.2就是MySQL服务器的IP地址。这里要注意的是，连接串中的数据库类型mysql必须小写。如果省略了主机名，则缺省为localhost。connect方法的后面两个参数是连接数据库主机的用户名和密码，这个可是不可缺少的 J

如果在连接过程中出现任何错误，则connect的返回值都会是undef（和C语言中的NULL是一回事）。这里为了简化而略去了错误检查，实际做项目的时候应当对这些错误和返回值的进行检查。

### 1.2 执行SQL语句 ##\#

```
my $sth = $dbh->prepare("SELECT * FROM test1");
$sth->execute();

$dbh->do("UPDATE test1 SET time=now()");
```

连接上了数据库，获得了数据库连接句柄，就可以利用这个句柄来对数据库进行操作了。要执行一条SQL语句，为了提高性能，DBI分两个步骤来做。先把SQL语句通过prepare方法提交到数据库，数据库为该语句分配执行资源，之后调用execute方法通知数据库执行该SQL语句。注意prepare方法是通过数据库连接句柄调用的，如果成功则返回一个该SQL的句柄，之后通过该SQL语句句柄调用execute执行SQL。 一般来说execute执行的都是返回数据的语句（例如SELECT语句）。反之如果执行INSERT、UPDATE、DELETE、CREATE TABLE等不需要返回数据的语句，则有一个更方便、快速的方法

```
$dbh->do(SQL语句)
```

，可以省去prepare的步骤。do方法返回的是受该SQL影响的记录数量。

#### 1.2.1 技巧：对SQL进行排版 ###\#

常写大段SQL的朋友可能会对于SQL中的引号很头痛，每每都因为引号的问题搞的SQL语句乱成一团分辨不清。还记得上篇文章讲过的qq吗？这里正是它的好用处。由于qq中的字符串同双引号" "内的字符串一样会对变量进行解释，同时qq还可以换行。因此使用它来对SQL进行排版是非常好的一个选择，例如像这样的一条SQL语句：

```
my $res_operator = $dbhandle->prepare( qq{
       SELECT o_customerid, COUNT(*) AS totalMsgNum FROM mm4fcdrs
       WHERE (m_date>'$begindate') AND (m_date<'enddate')
       GROUP BY o_customerid
});
```

根本无需考虑引号的问题，可以和正常情况一样的写SQL，是不是方便了很多？

#### 1.2.2 通过SQL语句中的参数优化查询执行效率 ###\#

在执行大量INSERT之类的语句的时候，反复向数据库服务器提交同样结构的一个SQL语句，在这种情况下可以利用prepare和SQL参数来优化执行效率：

1．先使用prepare提交一个SQL模板给数据库服务器，把其中值的部分用参数占位符代替。 2．使用prepare让服务器为该SQL准备了执行资源后，调用execute并在该方法中传入参数实际的值执行SQL。 3．之后可以反复调用execute，不需要服务器重新prepare

假设要执行这样的一个系列的SQL

```
INSERT INTO test1 VALUES (NULL, &lsquo;a&rsquo;, &lsquo;2005-04-01&rsquo;)
... ...
INSERT INTO test1 VALUES (NULL, &lsquo;z&rsquo;, &lsquo;2005-04-01&rsquo;)
```

其中第二个字段的值是从a到z的字母。那么可以这样来优化执行效率：

```
my $sth = $dbh->prepare( qq{
    INSERT INTO test1 VALUES (NULL, ?, &lsquo;2005-04-01&rsquo;)
} );

for my $value('a'..'z')  {
    $sth->execute($value);
}
```

其中的问号就是前面说的参数占位符了，它的意思就是告诉在准备执行资源的服务器：这个SQL的这个位置会有一个值，但是现在还不知道，等下执行的时候再告诉你。 prepare了之后，用一个循环产生a-z的字符给变量$value，然后将$value在execute方法中作为一个参数传入，服务器那里会自动用传入的值替换前面的"?"。需要提醒的是，传入的参数个数一定要和SQL中的占位符的数量一样。

### 1.3 读取记录 ##\#

熟悉ADO的朋友一定知道里面有一个DataReader对象，DBI中读取数据的方法和它非常的相似。简单来说，就是单向、流式的读取数据，也就是每次只能向后读一条数据直到没有数据可以读取。

文章开头的例子中，用了

```
$sth->fetchrow_array()
```

方法来读取数据。其实DBI读取数据还有几种常见的方法，这几个方法是类似的，所不同的是返回记录的形式。

#### 1.3.1 fetchrow\_array ###\#

返回一个由字段的值组成的数组。该数组的第1个元素就是当前记录第1个字段的值。

```
while ( my @row = $sth->fetchrow_array() )  {
    print "$row[0], $row[1], $row[2]\n";
}
```

或者这样，不过要注意字段对应的顺序

```
while ( my ($id, $name, $time) = $sth->fetchrow_array() )  {
    print "$id, $name, $time\n";
}
```

#### 1.3.2 fetchrow\_arrayref ###\#

返回由字段的值组成的数组的引用。同

```
fetchrow_array
```

的区别很明显，

```
fetchrow_arrayref
```

返回的数组的引用。

```
while ( my $row_ref = $sth->fetchrow_arrayref() ) {
    for (my $i = 0; $i < @{$row_ref}; $i++)       {
        print "$row_ref->[$i]\t";
    }
    print "\n";
}
```

这里要注意的是，如果要取字段的个数，需要把这个引用转成数组的形式获得

```
@{$row_ref}
```

。获取数组元素的值的时候，因为$row\_ref是引用，因此需要使用

```
->
```

操作符。

#### 1.3.3 fetchrow\_hashref ###\#

返回一个由"字段名－字段值"这样的"键－值"对组成的HASH表。关键的不同就是，只有这个方法可以通过一个字段名获得它的值，而不必关心这个字段是第几个字段。而前者只能依靠索引来访问值。不过缺点就是，效率要比前面两个差一些。

```
while ( my $record = $sth->fetchrow_hashref() ) {
    for my $field( keys %{$record} ) {
        print "$field: $record->{$field}\t";
    }
    print "\n";
}
```

这里需要复习一下HASH表的操作方法。keys操作符获取HASH的键(key)的数组，

```
$record->{$field}
```

获得HASH表中$field对应的值。注意这里同样是引用，因此要用

```
->
```

操作符。

使用上面三个方法可以基本解决问题了。此外，还有两个方法

```
fetchall_arrayref
```

和

```
selectall_arrayref
```

可以直接通过SQL一次性获取整个数据集，不过使用上稍微复杂一些，要涉及到 perl的scalar 操作符，这里就不赘述了。有兴趣的读者可以参考DBI的相关资料。

最后是收尾工作。

### 1.4 结束一个SQL会话 ##\#

```
$sth->finish();
```

### 1.5 断开数据库连接 ##\#

```
$dbh->disconnect();
```

很简单明了，就不赘述了。

Perl中利用DBI访问数据库的接口基本上就是这些了，还有一些高级的内容留给有兴趣的读者自己发掘研究了。可能有些读者会感觉没有ADO、ADO.NET操作起来方便，但是在脚本的环境下能够如此方便的操作数据库，比起用C接口来说已经方便很多了。也许在看完这片文章之后的不久，可以在cpan上发现你的Module和全世界的Perl程序员一起分享呢。

## 2 参考资源 #\#

* 《Programming the Perl DBI》 O’Reily
* [DBI官方网站 http://dbi.perl.org/](http://dbi.perl.org/)
* [一个DBI编程的简短介绍](http://www.perl.com/pub/a/1999/10/DBI.html)


# 2005-09-12  Perl无废话入门指南

最近接到一个任务是这样的，一台Solaris服务器上需要运行一个脚本，每天统计MySQL数据库中的数据并生成报表。本来这是一个可以就事论事的小项目，但是为了以后的灵活和可扩展性，我设计了一个使用XML做统计模版配置的方案。由于在Bash下不太好实现XML的访问，因此我考虑用Perl来实现这个脚本。

Perl是一个强大的脚本语言，本来是设计应用在文本处理方面的，但是后来发展的越来越强大，已经可以处理网络、图形、系统、文件等等各个方面的内容。Perl本身内置了丰富的操作符和函数，外部也有多年积累下来的大量模块。但是不知道什么原因在国内好像很少有人用。关于Perl的历史我就不多说了，有兴趣可以上网查一下。有一点要说的是，目前Perl最新的版本是5.8.6，而Perl6虽然已经设计很久了但是由于自举问题目前还没有一个可用的版本。为了解决这个问题，台湾的唐宗汉发起的Pugs项目正在快速的实施中，可能很快就能有结果了，有兴趣的朋友可以多多关注一下，也许还可以为开源世界做点贡献。

虽然很早以前就了解过Perl，但是从来就没有实际的用它做过项目，因此这次的实现是一个边学边做的过程。作为一个程序员，学习一种新的语言总会有一点惯性思维，加之Perl在语法上与C语言比较类似。因此我想在这片文章中主要以C为背景做一个比较。这种比较不是比较语言上的优劣，而是说明同样的功能如何在Perl中实现以及之间的区别。限于篇幅，具体的技术实现的细节我就不在这里多说了，你可以在末尾的资源一节中找到很多相关的文章。如果你没有接触过Perl，我想你可能更希望看到学习Perl的过程中可能会遇到的一些问题以及解决方法。

## 工欲善其事，必先利其器 #\#

要写代码，首先至少得有一个编辑器。Perl是跨平台的一种解释型语言，可以在Unix/Linux/Windows/Mac等平台上运行。具体对应平台上的编辑器，最简单的方案是Unix下用vi，Windows下用UltraEdit。当然也有商业化的IDE，不过我尝试了一下发现并不是那么的好用，因此我在Windows平台上以UltraEdit作为编辑环境，完成后迁移到Solaris平台上。

关于环境的搭建，有这样几个需要注意的地方：

1、 Windows平台下对应的是ActivePerl，可以免费下载。

2、 去UE的网站上下载Perl的AutoComp文件，可以实现自动完成功能。

3、 下载Perl对应的语法加亮的Tag文件并加入到UE中，可以更块的发现拼写错误。

4、 在UE设置一个快捷工具，命令行为

```
C:\Perl\bin\perl.exe "%F"（捕获输出）
```

，可以实现快速运行并显示结果。

5、 如果你不喜欢UE，那么我推荐Vim

## 巧妇难为无米之炊 #\#

起始从某种角度来说，程序员和厨子是一样的。要做出一桌大餐来，首先得看看手上有什么原料，然后才能琢磨一下用这些东西能做出什么好吃的来。或者说想做什么东西，得先备好料才行。

看看我们现在都有什么：一个编辑器，一个Perl的开发环境，还有一个聪明的脑袋和满肚子的智慧。这个任务中，要处理命令行参数、访问MySQL数据库(SQL)、读写XML的配置文件以及输出一个固定格式的报表文件。

好了，去查查资料，看看访问数据库和读写XML都需要什么东西。正如同C语言本身带了很多标准函数库一样，Perl本身也有函数库，并把这些函数库称为Module（模块）。查了一下资料，发现要访问MySQL数据库需要DBI和DBD::MySQL两个模块，那么去哪里找这些模块呢。这里给大家介绍一个Perl的Module集散地 [www.cpan.org，这里包含了八千多个Module，可以从这里下载到几乎各种各样的Module。可以手工下载后安装，也可以使用工具来自动安装。在Windows下是可以使用ppm进行自动安装，例如DBD的安装过程如下：](https://blog.log4think.com/http:/www.cpan.org，这里包含了八千多个Module，可以从这里下载到几乎各种各样的Module。可以手工下载后安装，也可以使用工具来自动安装。在Windows下是可以使用ppm进行自动安装，例如DBD的安装过程如下：)

```
C:\>ppm
...
ppm> search DBI
Searching in Active Repositories
... 一大堆与DBI相关的包的列表，其中就包括DBI这个包
ppm> install DBI
...
ppm> install DBD::mysql 如果知道模块的名字也可以直接安装
...
ppm> quit
```

如此就安装完成了。附带说一下，Linux下没有ppm，但是有类似的方式。输入命令行

```
perl -MCPAN -e shell
```

然后

```
install DBI; install DBD-mysql
```

，和上面的操作几乎是一样的。

提示：如果是在linux下安装DBD::Mysql模块，需要把mysql的bin目录包含在环境变量PATH中，否则会提示找不到mysql\_config文件。mysql一般是安装在

```
/usr/local/mysql
```

下，因此可以通过执行命令行

```
PATH=$PATH:/usr/local/bin/mysql/bin
```

来将此路径加入到环境变量中。

访问XML有几种包可以选择：使用DOM和Simple模块。Simple模块是把XML用Perl的数组方式表示，而DOM是W3C维护的一个基于树的XML文档标准。具体用哪种就看个人的需要了。我使用的是DOM，因此要安装XML-DOM包，方法同上。

## 芝麻开门 #\#

说起编程语言，简单的来说无非就是这样几个必不可少的基本元素：变量、数据、表达式、流程控制语句（包括条件、分支、循环)、函数、对象。具体到语言上，大部分的内容只是表达的形式不同而已。而Perl与C又有什么区别呢？

首先要知道，Perl是一种脚本语言。所谓的脚本，就是没有主函数，从最开始一行一行的按照顺序解释执行（老版Basic不也是如此吗）。因此，尽管把你的思路转化为流程用Perl表达出来吧。

其次，Perl的设计中参考了很多语言的长处，并避免了设计上的缺陷。因此Perl的很多语法你可能都会觉得似曾相识。我把Perl的语法总结了一下，和C语言做了一个简单的对比表格。表格左右两边的语句是C和Perl对应表达同一个功能各自的不同方式。如果读者有Ｃ语言的经验，相信看到这个对比可以很快的上手吧？

| 语法元素 | C                                                                                | Perl                                                                                                                                                                                                                                          | Perl语法说明                                                                                                                                                                                                                                                                                                               |
| ---- | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 注释   | \`\`\` /\* comments \*/ \`\`\`                                                   | \`\`\` # comments \`\`\`                                                                                                                                                                                                                      | 只支持单行注释                                                                                                                                                                                                                                                                                                                |
| 变量   | \`\`\` int a, b, c; char c = 'A'; int x\[10]; \`\`\`                             | \`\`\` my ($a, $b, $c); my $c='A'; my @x; my %h; \`\`\`                                                                                                                                                                                       | <p> 声明使用 `my` 标示<br> 表示值的变量以 `$` 开头，表示数组的变量以 `@` 开头，表示哈希表的变量以 `%` 开头<br> 声明可以省略(不建议)</p>                                                                                                                                                                                                                               |
| 字符串  | \`\`\` char\* h1 = "hello\n"; char\* h2 = "hello\\\n"; \`\`\`                    | \`\`\` $h1 = "hello\n"; $h2 = 'hello\n'; \`\`\`                                                                                                                                                                                               | 双引号解释内部的\n，而单引号则不解释                                                                                                                                                                                                                                                                                                    |
| 一维数组 | \`\`\` int arr\[10]; arr\[0] = 0; for(i = 0; i                                   |                                                                                                                                                                                                                                               |                                                                                                                                                                                                                                                                                                                        |
| 多维数组 | \`\`\` int arr\[10]\[10]; arr\[0]\[1] = 9; \`\`\`                                | \`\`\` my @arr = (1, 2, 3); $arr\[0] = \[7, 8, 9]; print $arr\[0]->\[1]; // 8 \`\`\`                                                                                                                                                          | Perl不直接支持多维数组，但可以以数组引用的方式间接支持。数组引用以\[]初始化，用->操作符访问引用实例。例如arr\[0]的内容就是一个数组的引用。                                                                                                                                                                                                                                          |
| 指针   | \`\`\` char c; int\* x = \&c; c = 'a'; printf( \*x ); \`\`\`                     | \`\`\` my $c; my $x = \\$c; $c = 'a'; print $x; \`\`\`                                                                                                                                                                                        | \`\`\` \ \`\`\` 和C中的 \`\`\` & \`\`\` 类似，意思是取引用                                                                                                                                                                                                                                                                         |
| 函数指针 | \`\`\` void hello() { printf("Hello\n"); } void (\*hi)()=hello; (\*p)(); \`\`\`  | \`\`\` sub hello{ print "Hello\n"; } my $hi = \*hello; &$hi; \`\`\`                                                                                                                                                                           | <p> `&#x26;` 表示调用函数<br> `*` 取函数地址<br> 不必用括号把参数括起来<br> 调用时的括号也是可选的<br></p>                                                                                                                                                                                                                                              |
| 条件语句 | \`\`\` if (x > 0) x = 0; else x = 1; x > 0 ? x = 0 : x = 1; \`\`\`               | \`\`\` if ($x > 0) { $x = 0; } else { $x = 1; } $x = 0 if $x > 0; $x = 0 unless $x 0 ? $x = 0 : $x = 1; \`\`\`                                                                                                                                | - 基本与C大同小异 - if 结构可以反转，意义不变，注意前句没有分号 - unless是if的反义词。顾名思义, unless是"除非"的意思。这里的四个表达方式是等价的 - 注意第一种方式中，条件部分的圆括号和语句部分的花括号是不可省略                                                                                                                                                                                              |
| 循环语句 | \`\`\` for (int i = 0; i                                                         |                                                                                                                                                                                                                                               |                                                                                                                                                                                                                                                                                                                        |
| 函数   | \`\`\` int max(int x, int y) { return x > y ? x : y; } int n = max(1, 2); \`\`\` | \`\`\` sub max { my ($x, $y) = @\_; return $x > $y ? $x : $y; } my $n = max(1, 2) \`\`\`                                                                                                                                                      | - 注意下划线 \`\`\` \_ \`\`\` 也是一个合法的变量名。而 \`\`\` @\_ \`\`\` 是Perl内置的数组变量，值为当前函数的参数列表 - \`\`\` my ($x, $y) \`\`\` \`　表示声明了一个有两个元素的数组，并将两个元素映射到 $x 和 $y 上 - \`\`\` ($x, $y) = @\_; \`\`\` 则表示两个数组之间的复制，@\_中对应的元素的值就赋值给了 $x 和 $y .这是一个简便的写法，也可以这样写 \`\`\` my $x = $\_\[0]; my $y = $\_\[1]; \`\`\` - return是可选的，默认返回最后一个表达式的值 |
| 语法约束 | \`\`\` - 编译时打开编译器所有的警告选项 - 使用lint工具 \`\`\`                                       | - \`\`\` perl -w myprogram.pl \`\`\` 打开运行警告开关，如果运行时Perl检查到了可能的错误，会显示警告信息，否则它默认是什么也不提示继续执行 - \`\`\` #!/usr/bin/perl -w \`\`\` 在代码文件第一行中加入-w选项开关 - \`\`\` use strict; \`\`\` 使用严格语法约束 - \`\`\` use warning; \`\`\` 启用警告                         |                                                                                                                                                                                                                                                                                                                        |
| 运行   | 编译后直接执行                                                                          | - \`\`\` perl myprogram.pl \`\`\` - Unix下在代码第一行加入 \`\`\` #!/usr/bin/perl \`\`\` ，然后给文件加上可执行属性 \`\`\` chmod +x myprogram.pl \`\`\` ，之后就可以用 \`\`\` ./myprogram.pl \`\`\` 命令来运行 - Windows下，安装ActivePerl后，将.pl后缀的文件和perl的解释程序关联起来，因此直接双击文件图标就可以运行 |                                                                                                                                                                                                                                                                                                                        |

需要说明的是，在Perl的世界中有一句名言"条条大路通罗马"， 这句话的意思是说同样一件事情Perl允许你用很多种不同的方式去做。因此上表的例子风格是按照C的习惯来写的，并且为了简化起见，只是挑选了与C相似的内容。事实上，Perl包含了很多C没有的东西，例如内置的Hash表、队列、正则表达式、格式定义等等。

## 从框架开始 #\#

Perl有很多表达方式，我们可以选择一种自己熟悉、容易理解的方式来写Perl的程序。例如，你是一个经验丰富的C程序员，那么你可以选择以C的风格来写Perl程序。下面是一个小小的样板框架

```
#!/usr/bin/perl -w
use strict;
```

### 程序开始的第一行语句，调用main函数

```
main();
```

### 定义main函数

```
sub main
{
       // some code
}
```

在这个框架下面，你几乎可以容易就开始你的Perl开发了。如果需要处理命令行参数，就可以稍微的扩展一下这个框架。

### 处理命令行参数

```
#!/usr/bin/perl -w
use strict;
use Getopt::Std;
main();

my $configfile;

sub ProcessOptions
{
       my $VERSION = '1.0.0';
       my $USAGE = "pp.pl [-v | -c configfile]\n";
       my $opts={};

       die $USAGE unless( getopts("c:v", $opts) );
       die $VERSION if ($opts->{'v'});
       $configfile=$opts->{'c'} ? $opts->{'c'} : 'config.xml' ;
}

sub main
{
       ProcessOptions();
       print $configfile;

       // the others code
}
```

实际上，剩余的工作和以往的工作差不多了，编写一个一个的函数，并实现你的业务逻辑。对于你这样一个聪明的程序员来说，学会Perl是一个很容易的事情。

## 常见问题 #\#

以我的学习经验来看，在开发的过程中可能有一些常用但是很分散的细节问题会让你感到困惑。

1、 程序的入口参数怎么取？

内置数组@ARGV包含了所有的运行参数。可以打印出来看看 print @ARGV;

2、 函数如何传参数、取参数？

每个函数内部都有一个内置的数组 @ *，这个数组的元素就是函数的参数。例如传入的第一个参数就是$*\[0]，第二个是$\_\[1]。唔，如你所见，Perl的函数参数就是C中的动态参数。

3、 默认变量是什么

这个可能会把你的头搞晕。有一个内置变量 $\_ ，

4、 显示消息、退出常见的简单写法

```
die &lsquo;Error on program&rsquo;;
```

也可以在条件不满足的情况下使用

```
die &lsquo;Configuration error&rsquo; unless($doc->getDocumentElement);
```

5、 格式化输出

可以用简单的print语句进行一般的输出操作，如果需要复杂的格式化输出，可以使用printf语句......跟C的用法几乎是一样的。

```
printf("pi=%.6f", 355/113);
```

6、 =>是什么东西?

在使用Hash表的时候，可以经常看到=>这个符号。例如这样的一个定义:

```
my $account={
        'Simon'=> 'simon.jinyu.liu@email.com',
        'Cissy'=> 'cissy@email.com'
};
```

其实，

```
=>
```

符号跟逗号

```
,
```

是等价的。Perl里面的Hash表事实上是一个数组，只是把奇数位元素看做是Key（键），而把偶数位的元素看做是Value（值）。

7、 关于引用的一点说明

Perl的引用类似C的指针，所谓的引用事实上就是地址。取一个变量的地址用反斜杠"\\"操作符，例如 $p=$x; 那么$p就是一个指向$x变量的指针。要引用指针的值，使用"$"操作符，例如 print $$p; 就是打印$x的值。

引用不单单可以引用变量，也可以引用数组、HASH表、函数，取函数的地址可以使用\*操作符。

## 还能做什么 #\#

Perl作为一个功能强大的脚本语言，可以应用在Web 编程、数据库、XML、系统管理、图形图像、自然语言、压缩、加密、邮件系统、软件测试等各个地方。在CPAN上，你可以找到各种各样你所需要的模块支持。例如，你可以：

* 编写系统管理的脚本
* 和Apache结合起来，编写CGI程序
* 编写动态网页
* 使用Net命名空间下的类编写网络应用程序
* 使用Authen::Captcha模块实现提交时的验证码的功能
* 使用Storable模块处理Perl的各种数据结构
* 使用GD/Image::MagicK模块处理图形
* 等等...

## 资源 #\#

* [www.perl.org           Perl的官方站点](http://www.perl.org)
* [www.perl.com           O’Reilly 维护的关于Perl的站点](http://www.perl.com)
* [www.cpan.org           Perl的Module资源大全](http://www.cpan.org)
* [www.perlchina.com      中国Perl协会](http://www.perlchina.com)
* [www.pm.org             世界各地的Perl用户组织](http://www.pm.org)
* [www.perlmonks.org      用Perl写诗](http://www.perlmonks.org)
* <http://www-128.ibm.com/developerworks/cn/linux/sdk/perl/> IBM社区的Perl系列文章


# 2005-07-16  Solaris 下安装Perl的DBD-mysql模块失败的原因以及解决办法

Solaris下安装Perl的`DBD::Mysql`模块，已经出了两次问题了。现总结如下：

问题一：无法找到mysql\_config 1. 下载DBD-mysql-3.0000 2. 解压 3. make Makefile.PL 4. 提示找不到mysql\_config

解决办法： 出现这个问题是因为没有将`mysql_config`加入环境变量PATH中，只要将`mysql/bin`目录加入到路径中就可以了。

```
PATH=$PATH:/usr/local/mysql/bin
export PATH
```

然后重新 `make Makefile.PL`

问题二：无法找到库 `libmysqlclient.so` Solaris 的mysql发行版本的库文件都是.a的静态库，`DBD::Mysql`模块需要.so的动态库编译。可以下载带源码的Mysql自行编译出.so的动态库。

问题三：编译不通过 那是因为mysql*config给Makefile.PL的cflags参数不正确。在我的Solaris上，给的参数是 \`-I/usr/local/mysql/include -Xa -xstrconst -mt -D\_FORTEC* -xarch=v9`，gcc 版本是 3.4.0，而`-Xa -xstrconst -mt -xarch=v9\`这几个参数，solaris上的gcc不认。

因此我手工指定参数生成 Makefile：`perl Makefile.PL --cflags="-I/usr/local/mysql/include -D_FORTEC_"`，之后`make ; make install` 一切顺利。


# 2004-10-15  SharpDevelop的AddInTree View 插件

自从SharpDevelop 源码分析的系列文章发出来之后，很多朋友给了不错的评价，在这里先感谢各位朋友的鼓励。另外，评论中有位朋友想看看我在文章中提到的AddInTreeView插件，其实这个是个很简单的小东西，因此单独发在这里了。(好像没有找到那里能上传文件，因此直接贴代码了)

## AddinTreeViewCommand.cs

```
/*
 * Created by SharpDevelop.
 * User: Administrator
 * Date: 2004-10-4
 * Time: 4:12
 *
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.Windows.Forms;
using System.CodeDom.Compiler; 

using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Gui.Pads;
using ICSharpCode.Core.AddIns;
using ICSharpCode.Core.AddIns.Codons;
using ICSharpCode.SharpDevelop.Services; 

namespace Addins.AddinTreeView
{
    /// <summary>
    /// Description of MyClass.
    /// </summary>
    public class AddinTreeViewCommand: AbstractMenuCommand
    {
        public override void Run()
        {
            using (AddinTreeViewContent viewContent = new AddinTreeViewContent() )
            {
                WorkbenchSingleton.Workbench.ShowView(viewContent);
            }
        }
    } 

    public class AddinTreeViewContent: AbstractViewContent
    {
        AddinTreeViewControl viewControl = new AddinTreeViewControl(); 

        public override Control Control
        {
            get
            {
                return viewControl;
            }
        } 

        public override bool IsDirty
        {
            get
            {
                return false;
            }
            set
            {
            }
        } 

        IWorkbenchWindow workbenchWindow;
        public override IWorkbenchWindow WorkbenchWindow
        {
            get
            {
                return workbenchWindow;
            }
            set
            {
                workbenchWindow = value;
                workbenchWindow.Title = "AddInTreeView";
            }
        } 

        public AddinTreeViewContent()
        {
            TitleName = "AddinTree View";
        } 

        public override bool IsViewOnly
        {
            get
            {
                return true;
            }
        }
        public void SaveFile(){}
        public void Undo(){}
        public void Redo(){}
        public override void Save(){}
        public override void Save(string filename){}
        public override void Load(string filename)
        {
        } 

        public override string TabPageText
        {
            get
            {
                return "AddInTree";
            }
        } 

    }
} 
```

## AddinTreeViewControl.cs \#

```
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms; 

using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.Core.AddIns;
using ICSharpCode.Core.AddIns.Codons; 

namespace Addins.AddinTreeView
{
    /// <summary>
    /// AddinTreeViewControl 的摘要说明。
    /// </summary>
    public class AddinTreeViewControl : System.Windows.Forms.UserControl
    {
        private System.Windows.Forms.ColumnHeader chName;
        private System.Windows.Forms.ListView lvAddin;
        private System.Windows.Forms.ColumnHeader chInfo;
        private System.Windows.Forms.CheckBox cbShowAddinInfo;
        private System.Windows.Forms.Splitter splitter2;
        private System.Windows.Forms.ListView lvDebug;
        private System.Windows.Forms.Splitter splitter1;
        private System.Windows.Forms.TreeView tvAddin;
        private System.Windows.Forms.ColumnHeader chValue;
        /// <summary>
        /// 必需的设计器变量。
        /// </summary>
        private System.ComponentModel.Container components = null; 

        public AddinTreeViewControl()
        {
            // 该调用是 Windows.Forms 窗体设计器所必需的。
            InitializeComponent(); 

            // TODO: 在 InitializeComponent 调用后添加任何初始化
            InitAddinTreeView();
        } 

        /// <summary>
        /// 清理所有正在使用的资源。
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if(components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        } 

        组件设计器生成的代码 

        void InitAddinTreeView()
        {
            TreeNode pathNode = tvAddin.Nodes.Add("AddinRoot"); 

            tvAddin.BeginUpdate();
            try
            {
                foreach ( AddIn addIn in AddInTreeSingleton.AddInTree.AddIns)
                {
                    foreach ( ICSharpCode.Core.AddIns.AddIn.Extension e in addIn.Extensions)
                    {
                        string [] paths = e.Path.Split('/');
                        pathNode = tvAddin.Nodes[0]; 

                        for ( int i=0; i<paths.Length; i++)
                        {
                            bool foundPath = false; 

                            if ( paths[i] == "" )
                            {
                                pathNode = tvAddin.Nodes[0];
                                continue;
                            }                         

                            for ( int j=0; j<pathNode.Nodes.Count; j++)
                            {
                                if ( pathNode.Nodes[j].Text == paths[i] )
                                {
                                    pathNode = pathNode.Nodes[j];
                                    foundPath = true;
                                    break;
                                }
                            } 

                            if ( !foundPath )
                            {
                                pathNode = pathNode.Nodes.Add( paths[i] );
                                pathNode.Tag = new ArrayList();
                                //lvDebug.Items.Add("Add " + e.Path + " ---- " + paths[i]);
                            }
                        } 

                        (pathNode.Tag as ArrayList).Add(e);
                    }
                }
            }
            finally
            {
                tvAddin.EndUpdate();
            }
        } 

        void AddInfo(string Name, string Value)
        {
            lvAddin.Items.Add(Name).SubItems.Add(Value);
        } 

        private void tvAddin_AfterSelect(object sender, System.Windows.Forms.TreeViewEventArgs e)
        {
            lvAddin.Items.Clear(); 

            if ( e.Node.Tag != null )
            {
                foreach (AddIn.Extension et in (e.Node.Tag as ArrayList))
                {
                    AddInfo("Extension", et.ToString()); 

                    foreach ( ICodon codon in et.CodonCollection)
                    {
                        AddInfo("  ┏ Codon ID", codon.ID);
                        AddInfo("  ┣ Codon Name", codon.Name);
                        AddInfo("  ┗ Codon Class", codon.Class); 

                        if ( cbShowAddinInfo.Checked )
                        {
                            AddInfo("      ┣ Addin Name", codon.AddIn.Name);
                            AddInfo("      ┗ Addin FileName", codon.AddIn.FileName); 

                            foreach ( ICSharpCode.Core.AddIns.AddIn.Extension ex in codon.AddIn.Extensions)
                            {
                                AddInfo("          ┣ Addin Extensions", ex.Path);
                            }
                            AddInfo("          ┗━━━━━━━━━", "");
                        }
                    }
                }
            }
        }
    }
} 
```


# 2004-10-14  SharpDevelop源码分析 (完整版)

{% hint style="info" %}
本文是我2004年写的一系列的心得，原文是发在[CSDN](http://blog.csdn.net/passos/archive/2004/10/04/124722.aspx)下的系列文章。本站Log4think的Blog建立之后，把文章也迁移过来了。原文为三篇，迁移以后经过润色、合并，整理为一篇完整的文章。
{% endhint %}

## 〇、序

最近开始学习.Net，遇到了一个比较不错的开源的IDE SharpDevelop。这个开发工具是使用C#开发的，比较吸引我的一点就是它是采用了和Eclipse类似的插件技术来实现整个系统的。而这个插件系统是我最感兴趣的地方，因此开始了一段代码的研究。在本篇之后，我会陆续把我研究的心得写下来。

## 一、基本概念

首先，先从代码的角度来对 SharpDevelop 来一个感性的认识。可以从[这里](http://www.icsharpcode.com)下载到可执行程序和代码包。安装的细节略过不提，先运行一下看看，界面、风格的感觉是不是跟VS如出一辙。不过目前的版本是1.0.0.1550，还有很多地方需要完善。关于代码和系统结构，SharpDevelop的三个作者写了一本书，各位看官可以参考一下，不过我看过之后还是有很多地方不太理解。

然后，来解释一下什么叫插件以及为什么要使用插件系统。以往的系统，开发人员编译发布之后，系统就不允许进行更改和扩充了，如果要进行某个功能的扩充，则必须要修改代码重新编译发布，这就带来了比较大的不方便。解决的方法有很多，例如提供配置等等方法。在解决方案之中，插件是一个比较好的解决方法。大家一定知道PhotoShop、WinAmp吧，他们都有"插件"的概念，允许其他开发人员根据系统预定的接口编写扩展功能（例如PhotoShop中各种各样的滤镜）。所谓的插件就是系统的扩展功能模块，这个模块是以一个独立文件的形式出现的，与系统是相对独立。在系统设计期间并不知道插件的具体功能，仅仅是在系统中为插件留下预定的接口，系统启动的时候根据插件的配置寻找插件，根据预定的接口把插件挂接到系统中。

这样的方式带来什么样的优点呢？首先是系统的扩展性大大的增强了，如果在系统发布后需要对系统进行扩充，不必重新编译，只需要修改插件就可以了。其次有利与团队开发，各个功能模块由于是以插件的形式表现在系统中，系统的Daily Build 也变得很简单了，不会因为某个模块的错误而导致整个系统BUILD失败，失败的仅仅是一个插件而已。

PhotoShop和Winamp的插件系统是比较典型的，首先实现了一个包含基本功能的系统，然后在这个系统的基础上挂接其他扩展的功能插件。而SharpDevelop在插件的形式方面做的更加彻底，整个核心就仅仅是一个插件管理系统而已，没有任何具体的功能。而用户看到的所有的界面、功能全部都是以插件的形式挂入的。在这样的一个插件系统下，可以不修改基本的核心系统，仅仅开发不同的插件就keyhi构造出具备不同功能的应用系统。

进入到SharpDevelop的安装目录中，在Bin目录下的SharpDevelop.exe 和 SharpDevelop.Core.dll是它核心的插件系统。在Addins目录下有两个后缀为addin的文件，其中一个 SharpDevelopCore.addin 就是它的核心插件的定义（配置）文件，里面定义的各个功能模块存在于Bin\Sharpdevelop.Base.dll 文件中，另外还有一些插件定义在Addins目录下的addin文件中。

分析SharpDevelop的代码，首先要弄清楚几个基本的概念，某些概念的真实含义和从字面上看起来的有一些区别，这曾经让我非常困惑，直到深入了代码之后才发现其本来的含义。

### 1、AddInTree  插件树

SharpDevelop 中的插件被组织成一棵插件树结构，树的结构是通过 Extension（扩展点）中定义的Path(路径)来定义的，类似一个文件系统的目录结构。系统中的每一个插件都在配置文件中指定了 Extension，通过Extension中指定的 Path 挂到这棵插件树上。在系统中可以通过 AddTreeSingleton对象来访问各个插件，以实现插件之间的互动。

### 2、 AddIn 插件

在 SharpDevelop 的概念中，插件是包含多个功能模块的集合（而不是我过去认为的一个功能模块）。在文件的表现形式上是一个addin配置文件，在系统中对应 AddIn 类。

### 3、Extension 扩展点

SharpDevelop中的每一个插件都会被挂到 AddInTree（插件树） 中，而具体挂接到这个插件树的哪个位置，则是由插件的 Extension 对象中的 Path 指定的。在addin 配置文件中，对应于`<Extension>`标签 。例如下面这个功能模块的配置

指定了扩展点路径为 `/SharpDevelop/Workbench/Ambiences` ，也就是在插件树中的位置。

### 4、Codon

这个是一个很不好理解的词儿，在 SharpDevelop 的三个作者写的书的中译版中被翻译为*密码子*，&^@#WTF%&^\* 真是叫人凌乱，完全搞不懂是啥意思啊。这个单词在词典中还有一个翻译叫"基码"，总之是各种词不达意。

顺便对此抱怨两句，当在做一个其实已经有类似实现的系统的时候，某些作者经常喜欢用一些不同的词汇来表达其实以及存在的一个概念。比如把大家都能理解的plugin叫做addin，这个也就罢了，但是如果偏偏要新创一个陌生的词儿来表达已有的概念，我就不知该如何形容是好了。

根据我对代码的理解，Codon 的功能是描述(包装)一个功能模块（一个功能模块对应一个实现了具体功能的 Command 类）。为了方便访问各个插件中的功能模块， Codon 给各种功能定义了基本的属性，分别是 ID (功能模块的标识)，Name (功能模块的类型。别误会，这个Name 是addin文件定义中Codon的XML结点的名称，ID才是真正的名称)，其中Name可能是Class(类)、MenuItem(菜单项)、Pad(面板)等等。根据具体的功能模块，可以继承Codon定义其他的一些属性，SharpDevelop中就定义了 ClassCodon、MenuItemCodon、PadCodon等等，你可以根据需要自己定义其他类型的Codon。

在addin定义文件中，Codon对应于 `<Extension>` 标签下的内容。例如下面这个定义

```markup
	<Extension path="/SharpDevelop/Workbench/Ambiences">
	    <Class id=".NET" class="ICSharpCode.SharpDevelop.Services.NetAmbience" />
	</Extension>
```

`<Extension path=...>` 标签内部定义了一个Codon，扩展点路径为 `/SharpDevelop/Workbench/Ambiences`。内部的`<Class>` 标签表示该Codon是一个 Class(类)，接着定义了该Codon的 ID和具体实现该Codon的类名`ICSharpCode.SharpDevelop.Services.NetAmbience`。运行期间将通过反射来找到对应的类并创建出来。

再例如这一个定义

&#x20;...

这个扩展点中定义了三个菜单项，以及各个菜单项的名字、标签和实现的类名。这里的Codon就对应于系统中的MenuCodon对象。

### 5、Command 命令

正如前文所述，Codon描述了一个功能模块，而每个功能模块都是一个 ICommand 的实现。最基本的 Command 是 AbstractCommand，根据Codon的不同对应了不同的 Command。例如 MenuItemCodon 对应 MenuItemCommand 等等。

### 6、Service 服务

插件系统中，有一些功能是整个系统都要使用的，例如文件访问、资源、消息等等。这些功能都作为插件系统的一个基本功能为整个系统提供服务，就叫"服务"好了。为了便于访问，这些服务都统一通过 ServiceManager 来管理。其实服务也是一种类型的插件，它们的扩展点路径在目录树中的 `/Workspace/Services` 中。

理解了这几个基本的概念之后，就可以看看 SharpDevelop 的代码了。起点在 `src\main\startup.cs` ，之后是 addin.cs 和 addinTree.cs 等几个文件。

## 二、主程序+隐藏的初始化

在大学里，模拟电路这门课永远是我心头的痛，直到现在也是这样。我总会不自觉的按照程序设计的模块化思路去尝试理解电路图，会问"这部分电路是做什么用的"、"为什么会有这样的效果"之类。在我的印象里，每部分的电路都应该有一定的用处，可惜总是看不明白。妈妈说，电路图不应该总是一个个模块的看，而是应该是从电源的一极顺着电路看，一直看到电源的另一极。我现在仍然不懂看电路图，可是以我看代码的经验来说，我觉得分析源代码按照这样的思路来看会比较容易把脉络理清楚。也就是从入口开始，顺着逻辑流一路看下去，同时把数据结构分析清楚，就很容易理解代码的内容了。

在SharpDevelop的代码中，由于很多的接口和插件的原因，很多代码在看到某个地方会突然失去函数/方法调用的线索。例如看某个函数的实现的时候会跳到一个接口里面去，那是因为这部分功能在运行期才会给一个实现了这个接口的对象来进行具体的执行。从这个角度来说，设计模式也给研究代码稍微带来了一点小小的难度。在看Linux下源代码的时候也经常遇到这种问题，在这个时候寻找代码线索比较好的方法是用一个文本搜索工具来搜索相关的关键字。在Linux下经常会用grep，Windows下面类似UltraEdit的"批量文件查找"功能会很好用（或者"Search And Replace"之类的工具）。

我会尽量避免大段的贴代码，而是在需要的地方只抽出主要的代码。因此最好能够对照原始代码一起看。

原始代码包解压缩，由于SharpDevelop本身对于察看代码不是很方便，没有"转到定义"之类的功能，因此我建议你把它的代码转成VS的工程来看。不过很可惜，SharpDevelop的工程导出功能现在有问题，如果导出\src\SharpDevelop.cmbx 这个总的复合工程的话会失败（我记得RC1版本是可以成功的，不知道为什么后来的版本反而会出问题），所以只能一个一个工程的导出。

好了，开始读SharpDevelop的代码吧

### 1、起点

在主程序的起点在\src\Main\StartUp\SharpDevelopMain.cs，找到Main函数这就是整个程序的起点了。开始的部分是显示封面窗体并加上命令行控制，其中SplashScreenForm 定义在\src\Main\Base\Gui\Dialogs\SplashScreen.cs文件中，这部分就不多说了。之后是

```csharp
Application.ThreadException += new ThreadExceptionEventHandler(ShowErrorBox);
```

SharpDevelop为了有效的进行错误报告，因此自己进行了异常的控制。系统出现异常的时候，SharpDevelop会拦截下来弹出它自己的异常提示报告对话框。这个代码就是在这一行实现的。其中 ShowErrorBox 这个方法就在类SharpDevelopMain中，ExceptionBox 定义在\src\Main\StartUp\Dialogs\ExceptionBox.cs中。如果需要进行自己的异常控制，可以学习一下这里的技巧。

### 2、充满玄机的初始化

```csharp
string [] addInDirs = ICSharpCode.SharpDevelop.AddInSettingsHandler.GetAddInDirectories( out ignoreDefaultPath );
AddInTreeSingleton.SetAddInDirectories(addInDirs, ignoreDefaultPath);
```

这段不起眼的代码是启动并初始化的真正的起点。

首先通过AddInSettingsHandler取得插件的目录，并告知AddInTreeSingleton。AddInSettingsHandler定义在\src\Main\StartUp\Dialogs\AddInTreeSettingsHandler.cs中，它通过读取系统配置（App.config）文件中的AddInDirectory节点的Path属性来确定插件的目录位置，或者你也可以通过自己定义的AddInDirectories节来指定插件目录。如果你没有做这些配置，默认的目录在SharpDevelop运行目录的..\Addins目录下。

```csharp
ServiceManager.Services.AddService(new MessageService());
ServiceManager.Services.AddService(new ResourceService());
ServiceManager.Services.AddService(new IconService());
```

通过ServiceManager(服务管理器)加入三个系统默认的服务，消息服务、资源服务、图标服务。这三个服务中，消息服务是显示各种信息提示，另外两个是属于系统的资源，SharpDevelop通过服务来进行统一调用和管理。

```csharp
ServiceManager.Services.InitializeServicesSubsystem("/Workspace/Services");
```

初始化其他的服务。SharpDevelop把服务定义在插件树的/Workspace/Services这个路径中，凡是在这个路径下的插件都被认为是服务，因此如果你自己定义了一个服务的话，也需要挂到这个路径下（这里就是系统服务的扩展点了）。

注意！这一步中，在我们的眼皮子底下悄悄的进行了一个重要的初始化工作。请看，ServiceManager 定义在\src\Main\Core\Services ServiceManager.cs文件中，察看它的InitializeServicesSubsystem方法，发现这样一行

```csharp
AddServices((IService[])AddInTreeSingleton.AddInTree.GetTreeNode(servicesPath).BuildChildItems(this).ToArray(typeof(IService)));
```

在这里，AddInTreeSingleton首次调用了AddInTree（插件树）的实例。按照Singleton模式，只有在首次调用的时候才会初始化实例，这里也是同样如此。整个系统的AddInTree是在这一步中进行了初始化工作，后面会详细讲AddInTree如何进行初始化工作，这里暂且先顺便看看服务的初始化。在ServiceManager的InitializeServicesSubsystem方法中，通过AddInTree检索服务插件路径下的所有配置，并通过它来读取、建立具体的对象，然后加入到服务列表中。之后通过一个循环，逐个的调用各个服务的InitializeService方法初始化服务。

AddInTree的初始化工作容稍候再看，先把主体的代码看完。

```csharp
commands = AddInTreeSingleton.AddInTree.GetTreeNode("/Workspace/Autostart").BuildChildItems(null);
for (int i = 0; i < commands.Count - 1; ++i)
{
    ((ICommand)commands[i]).Run();
}
```

`/Workspace/Autostart`是系统自动运行命令的扩展点路径，定义在这个路径下的插件会在系统启动的时候自动运行。在这里，通过插件树初始化建立处于这个路径下的Command（命令），并逐一执行。BuildChildItems方法的功能是建立这个扩展点下的Command列表，会在介绍AddTree的时候具体说明它的实现。

### 3、收尾

主程序代码的最后，初始化完毕、关闭封面窗体，然后执行命令列表中最后一个命令（也就是系统的主界面）。在主界面退出的时候，系统卸载所有的服务。

在这部分代码中出现了两个系统指定的扩展点路径 /Workspace/Services 和 /Workspace/Autostart ，实现服务和指定系统自动运行命令的时候就可以挂到这两个扩展点路径下了。 托反射的福，ServiceManager.Services可以通过类型（接口）来查找具体的实例，也就是GetServices方法。但是ServiceManager的具体实现可以容后再看，这里已经不是最紧要的部分了。 接下来，来看看整个插件系统的核心－AddinTree的代码，看看它是如何通过插件配置进行初始化并建立起整个系统的插件树骨干。

## 三、插件系统

上回说到SharpDevelop入口Main函数的结构，`ServiceManager.Service`在`InitializeServicesSubsystem`方法中首次调用了AddInTreeSingleton的AddInTree实例，AddInTree在这里进行了初始化。本回进入AddInTree着重讲述SharpDevelop的插件系统。在叙述的时候为了方便起见，对于"插件"和插件具体的"功能模块"这两个词不会特别的区分，各位看官可以从上下文分辨具体的含义（而事实上，SharpDevelop中的"插件"是指.addin配置文件，每一个"插件"都可能会包含多个"功能模块"）。

### 1、插件的配置

既然说到插件系统，那么先来看一看SharpDevelop插件系统的组织形式和内部的数据结构。 很多时候，同一个事物从不同的角度来看会得出不一样的结论，SharpDevelop的插件系统也是如此。在看SharpDevelop的代码以前，按照对插件的理解，我认为所谓的"插件"就是代表一个功能模块，插件的配置就是描述该插件并指定如何把这个插件挂到系统中。SharpDevelop中有插件树的思想，也就是每一个插件在系统中都有一个扩展点的路径。那么按照最初对插件的理解，编写插件需要做的就是： A、根据插件接口编写功能模块实现一个Command类 B、编写一个配置文件，指定Command类的扩展点(Extension)路径，挂到插件树中

之后按照这样的理解，我编写了一个察看插件树的插件AddinTreeView，打算挂到SharpDevelop中去。根据SharpDevelop对插件的定义，我把具体插件的AddinTreeViewCommand实现了之后，编写了一个配置文件AddinTreeView\.addin如下：

在配置文件中，Runtime节指定了插件功能模块所在的库文件Addins.dll的具体路径，在Extension节中指定了扩展点路径`/SharpDevelop/Workbench/MainMenu/Tools`（我是打算把它挂到主菜单的工具菜单下），然后在Extension内指定了它的Codon为 MenuItem以及具体的ID、标签、Command类名。这样做，SharpDevelop运行的很不错，我的插件出现在了Tools菜单下。之后，我又编写了一个SharpDevelop的资源管理器（ResourceEditor）的插件类ResourceEditor.dll并把它挂到Tool菜单下。同样的，我也写了一个ResourceEditor.addin文件来对应。系统工作的很正常。

如果我们对于每一个插件都编写这样的一个配置文件，那么插件的库文件(.dll)、插件配置文件(.addin)是一一对应的。不过这样就带来了一个小小的问题，在这样的一个以插件为基础的系统中，每一个菜单、工具栏按钮、窗体、面板都是一个插件，那么我们需要为每一个插件编写配置文件，这样就会有很多个配置文件（似乎有点太多了，不是很好管理）。SharpDevelop也想到了这个问题，于是它允许我们把多个插件的配置合并在一个插件的配置文件中。因此，我把我的两个插件库文件合并到一个Addins工程内生成了Addins.dll，又重新编写了我的插件配置文件MyAddins.addin如下：

这样，我把两个插件的功能模块使用一个插件配置文件来进行配置。同样的，也可以把几十个功能模块合并到一个插件配置文件中。SharpDevelop把这个插件配置文件称为"Addin(插件)"，而把具体的功能模块封装为Codon，使用Command类来包装具体的功能。SharpDevelop本身的核心配置SharpDevelopCore.addin里面就包含了所有的基本菜单、工具栏、PAD的插件配置。

回过头来看一下，现在有了两颗树。首先，插件树本身是一个树形的结构，这个树是根据系统所有插件的各个Codon的扩展点路径构造的，表示了各个Codon在插件树中的位置，各位看官可以通过我写的这个小小的AddinTreeView来看看SharpDevelop中实际的结构。其次，插件的配置文件本身也具有了一个树形的结构，这个树结构的根节点是系统的各个插件配置文件，其下是根据这个配置文件中的Extension节点的来构成的，描述了每个Extension节点下具有的Codon。我们可以通过SharpDevelop的Tools菜单下的AddinScout来看看这个树的结构。 我为了试验，把SharpDevelop的插件精简了很多，构成了一个简单的小插件系统。下面是这个精简系统的两个树的截图。各位看官可以通过这两副图理解一下插件树和插件配置文件的关系（只是看同样问题的两个角度，一个是Codon的ExtensionPath，一个是配置文件的内容）。

总结一下SharpDevelop插件的配置文件格式。首先是`<AddIn>`节点，指定AddIn的名称、作者之类的属性。其次，在AddIn节点下的`<Runtime>`节点内，使用`<Import ...>`来指定本插件配置中Codon所在的库文件。如果分布在多个库文件中，可以一一指明。然后，编写具体功能模块的配置。每个功能模块的配置都以扩展点`<Extension>`开始，指定了路径(Path)属性之后，在这个节点内配置在这个扩展点下具体的Codon。每个Codon根据具体不同的实现有不同的属性。各位看官可以研究一下SharpDevelop的核心配置文件SharpDevelopCore.addin的写法，相信很容易理解的。

### 2、插件系统的核心AddIn和AddInTree

前文讲到，在SharpDevelop的Main函数中，ServiceManager.Service在`InitializeServicesSubsystem`方法中首次调用了AddInTreeSingleton的AddInTree实例，AddinTree在这个时候进行了初始化。现在我们就来看看`AddInTreeSingleton.AddInTree`到底做了些什么事情，它定义在`\src\Main\Core\AddIns\AddInTreeSingleton.cs`文件中。

```csharp
public static IAddInTree AddInTree
{
    get
    {
        if (addInTree == null)
        {
            CreateAddInTree();
        }
        return addInTree;
    }
}
```

AddInTreeSingleton是插件树的一个Singleton（具体的可以去看《设计模式》了），AddInTreeSingleton.AddInTree是一个属性，返回一个IAddinTree接口。这里我注意到一点，AddInTreeSingleton是从DefaultAddInTree继承下来的。既然它是一个单件模式，包含的方法全部都是静态方法，没有实例化的必要，而且外部是通过AddInTree属性来访问插件树，为什么要从DefaultAddInTree继承呢？这好像没有什么必要。这也许是重构过程中被遗漏的一个小问题吧。

我们先来看看IAddinTree接口的内容，它定义了这样的几个内容：&#x20;

A、属性ConditionFactory ConditionFactory 返回一个构造条件的工厂类，这里的条件是指插件配置中的条件，我们以后再详细说明。&#x20;

B、属性`CodonFactory CodonFactory` 返回一个构造Codon的工厂类。&#x20;

C、属性`AddInCollection AddIns` 返回插件树的根节点Addin（插件）集合。&#x20;

D、方法`IAddInTreeNode GetTreeNode(string path)` 根据扩展点路径（path）返回对应的树节点&#x20;

E、方法`void InsertAddIn(AddIn addIn)` 根据AddIn中的扩展点路径添加一个插件到树中&#x20;

F、方法`void RemoveAddIn(AddIn addIn)` 删除一个插件&#x20;

G、方法`Assembly LoadAssembly(string assemblyFile)` 读入插件中Runtime节的Import指定的Assembly，并构造相应的CodonFactory和CodonFactory类。

AddInTreeSingleton在首次调用AddInTree的时候会调用CreateAddInTree方法来进行初始化。CreateAddInTree方法是这样实现的：

```csharp
addInTree = new DefaultAddInTree();
```

初始化插件树为DefaultAddInTree的实例，这里我感受到了一点重构的痕迹。首先，DefaultAddInTree从名称上看是默认的插件树（既然是默认，那么换句话说还可以有其他的插件树）。但是SharpDevelop并没有给外部提供使用自定义插件树的接口（除非修改这里的代码），也就是说这个名称并不像它本身所暗示的那样。其次，按照Singleton通常的写法以及前面提到AddInTreeSingleton是从DefaultAddInTree继承下来的疑问，我猜想DefaultAddinTree的内容本来是在AddinTreeSingleton里面实现的，后来也许为了代码的条理性，把实现IAddinTree内容的代码剥离了出去，形成了DefaultAddinTree类。至于继承DefaultAddInTree的问题，也许这里本来是一个AddInTree的基类。这是题外话，也未加证实，各位看官可以不必放在心上（有兴趣的可以去找找以前SharpDevelop的老版本的代码来看看）。

这里有两个察看代码的线路，一个是DefaultAddInTree的构造函数的代码，在这个构造函数中构造了Codon和Condtion的工厂类。另外一个是CreateAddInTree后面的代码，搜索插件文件，并根据插件文件进行AddIn的构造。各位看官可以选择走分支线路，也可以选择先看主线（不过这样你会漏掉不少内容）。

#### 2.1 支线 （DefaultAddInTree的构造函数）

此时把CreateAddInTree的代码中断一下压栈先，跳到DefaultAddInTree的构造函数中去看一看。DefaultAddInTree定义在\src\Main\Core\AddIns\DefaultAddInTree.cs文件中。在DefaultAddInTree的构造函数中，注意到它具有一个修饰符internal，也就是说这个类只允许Core这个程序集中的类对DefaultAddInTree进行实例化（真狠啊）。构造函数中的代码只有一句：

```csharp
LoadCodonsAndConditions(Assembly.GetExecutingAssembly());
```

虽然只有一行代码，不过这里所包含的内容却很精巧，是全局的关键，要讲清楚我可有得写了。首先，通过全局的Assembly对象取得入口程序的Assembly，传入LoadCodonsAndConditions方法中。在该方法中，枚举传入的Assembly中的所有数据类型。如果不是抽象的，并且是AbstractCodon的子类，并且具有对应的CodonNameAttribute属性信息，那么就根据这个类的名称建立一个对应的CodonBuilder并它加入CodonFactory中（之后对Condition也进行了同样的操作，专门来看Codon部分，Condition跟Codon基本上是一样的）。 这里的CodonFactory类和CodonBuilder类构成了SharpDevelop插件系统灵活的基础，这里可要看仔细了。

以实例来演示，以前文我编写的AddinTreeViewCommand为例。在入口的Assembly中会搜索到MenuItemCodon，它是AbstractCodon的一个子类、包装MenuItem(菜单项)Command（命令）的Codon。符合条件，执行

```csharp
codonFactory.AddCodonBuilder(new CodonBuilder(type.FullName, assembly));
```

首先根据类名MenuItemCodon和assembly 构造CodonBuilder。CodonBuilder定义在\src\Main\Core\AddIns\Codons\CodonBuilder.cs文件中。在CodonBuilder的构造函数中根据MenuItemCodon的CodonNameAttribute属性信息取得该Codon的名称MenuItem。CodonNameAttribute描述了Codon的名称，这个MenuItem也就是在.addin配置文件中对应的`<MenuItem>`标签，后文会看到它的重要用途。在CodonBuilder中除了包含了该Codon的ClassName（类名）和CodonName属性之外，就只有一个方法BuildCodon了。

```csharp
public ICodon BuildCodon(AddIn addIn)
{
    ICodon codon;
    try {
        // create instance (ignore case)
        codon = (ICodon)assembly.CreateInstance(ClassName, true);

        // set default values
        codon.AddIn = addIn;
    } catch (Exception) {
        codon = null;
    }
    return codon;
}
```

很明显，BuildCodon根据构造函数中传入的assembly和类型的ClassName，建立了具体的Codon的实例，并和具体的AddIn关联起来。 之后，codonFactory调用AddCodonBuilder方法把这个CodonBuilder加入它的Builder集合中。继续向上一层，看看codonFactory如何使用这个CodonBuilder。

在文件`\src\Main\Core\AddIns\Codons\CodonFactory.cs`中，codonFactory只有两个方法。AddCodonBuilder方法把CodonBuilder加入一个以CodonName为索引的Hashtable中。另外一个方法很重要：

```csharp
public ICodon CreateCodon(AddIn addIn, XmlNode codonNode)
{
    CodonBuilder builder = codonHashtable[codonNode.Name] as CodonBuilder;

    if (builder != null) {
        return builder.BuildCodon(addIn);
    }

    throw new CodonNotFoundException(String.Format("no codon builder found for <{0}>", codonNode.Name));
}
```

在这里，addin是这个配置文件的描述（也就是插件），而这个XmlNode类型的CodonNode是什么东西？ 还记得配置文件中在标签下的、、之类的标签吗？我曾经说过，这些就是Codon的描述，现在来看看到底是不是如此。以前文的AddinTreeView配置为例：

SharpDevelop在读入插件配置文件的`<Extension>`标签之后，就把它的ChildNodes（XmlElement的属性）依次传入CodonFactory的CreateCodon方法中。这里它的ChildNodes\[0]就是这里的节点，也就是codonNode参数了。这个XML节点的Name是MenuItem，因此CreateCodon的第一行

```
CodonBuilder builder = codonHashtable[codonNode.Name] as CodonBuilder;
```

根据节点的名称(MenuItem)查找对应的CodonBuilder。记得前面的CodonBuilder根据CodonNameAttribute取得了MenuItemCodon的CodonName吗？就是这个MenuItem了。CodonFactory找到了对应的MenuItemCodon的CodonBuilder（这个是在DefaultAddInTree的构造函数中调用LoadCodonsAndConditions方法建立并加入CodonFactory中的，还记得么？），之后使用这个CodonBuilder建立了对应的Codon，并把它返回给调用者。

就这样，通过CodonNameAttribute，SharpDevelop把addin配置文件的`<MenuItem>`节点、CodonBulder、MenuItemCodon三部分串起来形成了一个构造Codon的路线。

回过头来再整理一下思路，SharpDevelop进行了下面这样几步工作：

A、建立各个Codon，使用CodonNameAttribute指明它在配置节点中的名称 B、DefaultAddInTree的构造函数中调用LoadCodonsAndConditions方法，搜索所有的Codon，根据Codon的CodonNameAttribute建立对应的CodonBuilder加入CodonFactory中。 C、读取配置文件，在标签下遍历所有的节点，根据节点的Name使用CodonFactory建立对应的Codon。 其中，Codon的CodonNameAttribute、CodonBuilder的CodonName以及标签下XML节点的Name是一致的。对于Condition（条件）的处理也是一样。

到这里，可以再来看看SharpDevelop中插件的灵活性是如何体现的。首先，addin配置中的Extension节点下的Codon节点名称并没有在代码中和具体的Codon类联系起来，而是通过CodonNameAttribute跟Codon联系起来。这样做的好处是，SharpDevelop的Codon和XML的标签一样具有无限的扩展能力。假设要自己定义一个Codon类SplashFormCodon作用是指定某个窗体作为系统启动时的封面窗体。要做的工作很简单：首先，在SplashFormCodon中使用CodonNameAttribute指定CodonName为Splash，并且在SplashFormCodon中定义自己需要的属性。然后，在addin配置文件使用`<Splash>`标签这样写：

另外，对于Condition（条件）的处理也是一样，也就是说可以使用类似的方法灵活的加入自己定义的条件。

#### 2.2 主线 (AddInTreeSingleton. CreateAddInTree)

继续读 AddInTreeSingleton中CreateAddInTree 的代码。

在建立了DefaultAddInTree的实例后，AddInTreeSingleton在插件目录中搜索后缀为.addin的文件。还记得在Main函数中曾经调用过AddInTreeSingleton.SetAddInDirectories吗，就是搜索这个传入的目录。看来SharpDevelop把在插件目录中所有后缀为.addin的文件都看做是插件了。

```csharp
FileUtilityService fileUtilityService = (FileUtilityService)ServiceManager.Services.GetService(typeof(FileUtilityService));
```

先学习一下如何从ServiceManager取得所需要的服务，在SharpDevelop中要取得一个服务全部都是通过这种方式取得的。调用GetService传入要获取的服务类的类型作为参数，返回一个IService接口，之后转换成需要的服务。

搜索插件目录找到一个addin文件后，调用InsertAddIns把这个addin文件中的配置加入到目录树中。

```csharp
static StringCollection InsertAddIns(StringCollection addInFiles)
{
    StringCollection retryList  = new StringCollection();

    foreach (string addInFile in addInFiles) {
        AddIn addIn = new AddIn();
        try {
            addIn.Initialize(addInFile);
            addInTree.InsertAddIn(addIn);
        } catch (CodonNotFoundException) {
            retryList.Add(addInFile);
        } catch (ConditionNotFoundException) {
            retryList.Add(addInFile);
        } catch (Exception e) {
            throw new AddInInitializeException(addInFile, e);
        }
    }

    return retryList;
}
```

InsertAddIns建立一个对应的AddIn（插件），调用AddInTree的InsertAddIn方法把它挂到插件树中。在这里有一个小小的处理，由于是通过Assembly查找和插件配置中Codon的标签对应的类，而Codon类所在的Assembly是通过Import标签导入的。因此在查找配置中某个Codon标签对应的Codon类的时候，也许Codon类所在的文件是在其他的addin文件中Import的。这个时候在前面支线中讲到CodonFactory中查找CodonBuilder会失败，因此必须等到Codon类所在的addin处理之后才能正确的找到CodonBuilder。这是一个依赖关系的处理问题。

SharpDevelop在这里处理的比较简单，调用InsertAddIns方法的时候，凡是出现CodonNotFoundException的时候，都加入一个retryList列表中返回。在CreateAddinTree处理完所有的addin文件之后，再重新循环尝试处理retryList列表中的addin。如果某次循环中再也无法成功的加入retryList中的addin，那么才提示失败错误。

回头再来看看对AddIn的处理。

#### 2.2.1  addIn.Initialize

建立了AddIn的实例后，调用Initialize 方法进行初始化。AddIn是对一个.addin文件的封装，定义在\src\Main\Core\AddIns\AddIn.cs文件中。其中包含了.addin文件的根元素`<AddIn>`的描述，包括名称、作者、版权之类的属性。在`<AddIn>`节点下包括两种节点：一个是`<Runtime>`节点，包含了`<Import>`指定要导入的`Assembly`；另外一个是`<Extension>`节点，指定Codon的扩展点。在`AddIn.Initialize`方法中，使用XmlDocument对象来读取对应的addin文件。首先读取name、author 、copyright之类的基本属性，之后遍历所有的ChildNodes（子节点）。

如果子节点是Runtime节点，则调用AddRuntimeLibraries方法。

```csharp
foreach (object o in el.ChildNodes)
{
    XmlElement curEl = (XmlElement)o;

    string assemblyName = curEl.Attributes["assembly"].InnerText;
    string pathName     = Path.IsPathRooted(assemblyName) ? assemblyName : fileUtilityService.GetDirectoryNameWithSeparator(path) + assemblyName;
    Assembly asm = AddInTreeSingleton.AddInTree.LoadAssembly(pathName);
    RuntimeLibraries[assemblyName] = asm;
}
```

通过`AddInTreeSingleton.AddInTree.LoadAssembly`方法把Assembly中所有的Codon和Condition的子类加入对应Factory类中（调用了`LoadCodonsAndConditions`方法，在DefaultAddInTree的构造函数中见过了），并且把该文件和对应的Assembly保存到RuntimeLibraries列表中。

如果子节点是Extension节点，则调用AddExtensions方法。

```csharp
Extension e = new Extension(el.Attributes["path"].InnerText);
AddCodonsToExtension(e, el, new ConditionCollection());
extensions.Add(e);
```

根据这个扩展点的XML描述建立Extension对象加入到AddIn的Extensions列表中，并通过`AddCodonsToExtension`方法把其中包括的Codon加入到建立的Extension对象中。Extension对象是AddIn的一个内嵌类，其中一个重要的属性就是CodonCollection这个列表。`AddCodonsToExtension`就是把在配置中出现的Codon都加入到这个列表中保存。

来看看`AddCodonsToExtension`方法。在代码中我略过了对Condition（条件）的处理的分析和一些无关紧要的部分，把注意力集中在插件的处理。首先是一个 `foreach (object o in el.ChildNodes)` 遍历 `<Extension>` 下所有的子节点，对于每个子节点的处理如下：

```csharp
XmlElement curEl = (XmlElement)o;
switch (curEl.Name)
{
    （处理各种条件）
    default:
        ICodon codon = AddInTreeSingleton.AddInTree.CodonFactory.CreateCodon(this, curEl);
        AutoInitializeAttributes(codon, curEl);

        （对codon.InsertAfter 和codon.InsertBefore 的处理，主要是处理codon在列表中的顺序问题，这一点在对于MenuItemCodon的处理上比较重要）

        e.CodonCollection.Add(codon);
        if (curEl.ChildNodes.Count > 0)
        {
            Extension newExtension = new Extension(e.Path + '/' + codon.ID);
            AddCodonsToExtension(newExtension, curEl, conditions);
            extensions.Add(newExtension);
        }
        break;
}
```

我们看到了一个期待已久的调用

```
AddInTreeSingleton.AddInTree.CodonFactory.CreateCodon(this, curEl);
```

经过了上文支线2.1代码中的铺垫，SharpDevelop使用建立好的CodonFactory，调用CreateCodon方法根据`<Extension>`下的节点构造出实际的Codon对象，一切尽在不言中了吧。

```
e.CodonCollection.Add(codon);
```

把构造出来的Codon对象加入到Extension对象的CodonCollection列表中。

之后，在形如菜单的这种允许无限嵌套的结构中，SharpDevelop对此进行了处理。如果该节点有嵌套的子节点，那么构造一个新的Extension对象，递归调用`AddCodonsToExtension`添加到这个Extension对象中。注意一点，这个新构造的Extension对象并不是分开保存在Codon中，而是直接保存在AddIn的扩展点列表中。这样是为了方便查找，毕竟保存在具体的Codon中也没有什么用处，我们可以通过Extension对象的Path属性得知它在插件树中的具体位置。

#### 2.2.2 addInTree.InsertAddIn（把AddIn添加到AddInTree中）

对AddIn的构造完成之后，需要把AddIn的实例对象添加AddInTree中管理。

```csharp
addIns.Add(addIn);
foreach (AddIn.Extension extension in addIn.Extensions)
{
    AddExtensions(extension);
}
```

DefaultAddInTree中保存了两课树。一个是根据插件文件的结构形成的树，每个插件文件作为根节点，往下依次是Extension、Codon节点。

`addIns.Add(addIn);`这一行就是把插件加入到这个树结构中。另外一个树是根据Extension的Path＋Codon的ID作为路径构造出来的，每一个树节点是一个AddInTreeNode类，包含了在这个路径上的Codon对象。嵌套在这个节点中的Codon在通过它子节点来访问。在DefaultAddInTree中可以通过GetTreeNode来指定一个路径获得插件树上某一个节点的内容。

`AddExtensions`方法很简单，遍历Extension中所有的Codon，把Extension的Path＋Codon的ID作为路径，创建这个路径上的所有节点，并把Codon连接到这个AddInTreeNode上。由于Codon的ID是全局唯一的，因此每一个AddInTreeNode都具有一个唯一的Codon。

#### 3、最后一公里（Codon和Command的关联）

在插件树的讨论中，我们依次把AddIn－Extension－Codon的配置和他们对应的类关联了起来。不过我们一直没有涉及到Codon和它包含的Command是如何关联的。由于这个关联调用是在插件树外部的（记得在讲述SharpDevelop程序入口Main函数中，提到ServiceManager的方法`InitializeServicesSubsystem`吗？

```csharp
AddServices((IService[])AddInTreeSingleton.AddInTree.GetTreeNode(servicesPath).BuildChildItems(this).ToArray(typeof(IService)));
```

这里就调用了 `BuildChildItems`，因此单独在这里说明。实现这个关联的就是AddInTreeNode的BuildChildItems和BuildChildItem方法以及Codon的BuildItem方法。

`BuildChildItem`方法和`BuildChildItems`方法仅有一字之差，

`BuildChildItem`是根据指定的Codon的ID在所属AddInTreeNode的子节点下查找包含该Codon的节点并调用该Codon的 `BuildItem`方法；而`BuildChildItems`则是首先遍历所属AddInTreeNode的所有子节点，依次调用各个子节点的Codon的`BuildItem`方法，之后再调用所属AddInTreeNode的Codon的`BuildItem`方法（也就是一个树的后根遍历）。

重点在Codon的`BuildItem`方法。在AbstractCodon中，这个方法是一个抽象方法，SharpDevelop的代码注释中并没有明确说清楚这个方法是做什么用的。但是我们可以找一个Codon的实例来看看。例如ClassCodon的BuildItem：

```csharp
public override object BuildItem(object owner, ArrayList subItems, ConditionCollection conditions)
{
    System.Diagnostics.Debug.Assert(Class != null && Class.Length > 0);
    return AddIn.CreateObject(Class);
}
```

调用AddIn的`CreateObject`，传入Codon的Class（类名）作为参数，建立这个类的实例。例如这个配置

而Codon的中的Class（类名）属性就是`ICSharpCode.SharpDevelop.Commands.InitializeWorkbenchCommand`。也就是说，Codon的Class指的是实现具体功能模块的Command类的名称。在读取addin配置中的`<Runtime>`节的时候，AddInTree把Assembly保存到了RuntimeLibraries中，因此`CreateObject`方法可以通过它们来查找并建立类的实例。

类似的，可以再看看MenuItemCodon的实现，同样是建立了对应的SdMenuCommand。

这样，SharpDevelop本身的插件结构可以和具体的对象建立分离开来，实际的对象建立是在各个Codon的BuildItem中进行的。因此我们可以发现在SharpDevelop整个是基础插件系统部分没有任何GUI的操作，实现了很好的解耦效果。

#### 4、问题

好了，本文对插件树构造的分析到此告一段落。我提一个小小的问题给各位看官思考：在构造插件树的过程中，如果Codon的某一个节点路径不存在（也就是说它的依赖项不存在），那么SharpDevelop会提示失败并且终止程序运行。可是实际上可能因为部署的原因或者权限的原因，某些Codon的失败并不会影响整个系统的使用，例如试用版本仅仅提供部分插件给客户使用，而并不希望系统因此而终止运行。那么就存在一个Codon依赖项失败而允许继续运行的问题。另外，我希望各个插件不在系统启动的时候全部调入系统，而是在运行期实际调用的时候才调入系统，也就是一个缓存机制，这样就可以实现系统插件的热部署。如何修改SharpDevelop的插件系统来实现这两个功能呢？


