Blogs

佩措尔德 G大调小步舞曲

《快乐的农夫》早就练好了,现在的目标是 Minuet in G major。

这首曲子听起来是相当的耳熟,以前一直传闻是巴赫的,后来考证出乃 Christian Petzold 的作品。现在右手已经很熟练了,左手的第一部分也很熟了。但在双手合练时碰到一个障碍——有一个小节是右手弹1/4音符,左手弹1/8音符——对右撇子来说,让左手弹得比右手快还保持节奏是挺不习惯的;而且紧跟这一小节后右手是一个波音左手有一个指位的变化,就更增加了偶这种菜鸟的心理畏惧感。

最近几个小时的练琴都是在攻克这个难关,争取本周能搞定,这个月把这首曲子练过去,08年结束之前再学一首新曲子

关于 "#include"

我想写C程序都通常是把系统头文件放在最上面,然后是第三方库的,最后是我自己的,比如下面这样

#include <stdio.h>
#include <stdlib.h>

#include <mysql.h>

#include <my_foo.h>
...

前天动手把我们的 apache module 移植到 nginx 上,里面自然是要 include 一堆 nginx 自己的头文件的。但它需要调 openssl 的库,那把 openssl 的头文件放哪里呢?我想了一下,觉得 openssl-devel 是系统自己 yum 安装上的,应该比 nginx 有更高的优先级,于是就是这么一个顺序

#include <openssl/rsa.h>
#include <openssl/pem.h>

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

(你看,我还是相当注重长幼尊卑的,正统思想啊~~)

这样程序似乎也没有问题,可就一个麻烦事情——调用 nginx 的 log 功能时就会 crash!用 gdb 跟踪,发现是使用 request->connection->log 的指针为空...

但就在 nginx 的代码里,向我的 handler 传入 request 之前,r->c->log 还是一个合法的指针,只要一进我的函数,这个就成了 NULL,而且一旦 handler 结束,指针值又非空了..

写到这里,聪明的读者就应该知道这是由于头文件预处理,导致两边结构体定义不一致。connection 这个结构体是如此定义的:

struct ngx_connection_s {
...
#if (NGX_SSL)
    ngx_ssl_connection_t  *ssl;
#endif
...
};

就这么一个小小的 bug,足足花了2个小时才找到原因。

获得什么教训呢?

  1. 下回再用 gdb 跟踪到这种情况,应该能第一时间意识到结构体错误
  2. 如果要提供开发接口给第三方,那第三方可能直接使用的结构体里千万不要包括预处理代码
  3. 只要小心,是可以避免预处理的。但人总有大意的时候,那就养成一个好习惯,把预处理部分都放在结构体的最后,这就考验第三方开发者的人品了

最后是一篇新鲜热辣刚出炉的Nginx模块开发备忘:http://www.dup2.org/files/nginx_module_development.html
nginx 的中文 wiki 上有人写了一篇很好的翻译,我的备忘和其比起来,多了一些数据结构的分析。

发现 PyCrypto 不如 ctypes 来得方便

至少对于一个C程序员来说是这样..

有一段RSA签名的程序,原本是用 C 调 libcrypto 包装成 jni 由 java 使用的,现在打算在 python 里面实现一个类似的功能,这段 C 代码大概是这样:

int rsa_sign(char *private_keyfile, char *message, int message_len, char *buf, int buf_size)
{
    RSA *key;
    int malloc_size, rsa_size;
    unsigned char *rsa;

    BIO *bio;

    bio = BIO_new_file(private_keyfile, "r");
    if (NULL == bio) {
        return 0;
    }
    key = (RSA *)PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, NULL);
    BIO_free(bio);
    if (NULL == key) {
        return 0;
    }

    malloc_size = RSA_size(key);
    rsa = (unsigned char *)malloc(malloc_size);
    if (NULL == rsa) {
        RSA_free(key);
        return 0;
    }

    if (0 == RSA_sign(NID_md5, message, message_len, rsa, &rsa_size, key)) {
        free(rsa);
        RSA_free(key);
        return 0;
    }
    RSA_free(key);

    if (rsa_size/3 * 4 >= buf_size) { /* 结果的 Base64 编码后长度大于 buf_size, 等于也错误(后面需要放 '\0' 的位置) */
        free(rsa);
        return 0;
    }

    b64_encode(rsa, rsa_size, buf);

    free(rsa);
    return 1;
}

可是找遍 PyCrypto, 楞是没有看到怎么实现 PEM_read_bio_RSAPrivateKey 这样从一个 PEM 格式的私钥文件里加载的功能。然后去看哪些基于 PyCrypto 包装的其他的包,比如 ezPyCrypto, Keyczar(这还是 google 发布的),包括 mail-list 上提供的 patch.... 这些似乎倒是可以 load key 了,可缺省好像也并没有对 NID_MD5 的支持, 绝望~~~~

于是抱着试试的心态用 ctypes 直接去调 libcrypto——以前只是在 PyS60 上玩票似的用过 ctypes,以及在 Win32 下用过,而且那时候 ctypes 还不太成熟——结果发现 python2.5 的 ctypes 竟如此的好用:

import ctypes
import base64

crypto_handler = ctypes.cdll.LoadLibrary("/usr/lib/libcrypto.so")
BIO_new_file = crypto_handler.BIO_new_file
PEM_read_bio_RSAPrivateKey = crypto_handler.PEM_read_bio_RSAPrivateKey
RSA_size = crypto_handler.RSA_size
RSA_sign = crypto_handler.RSA_sign
BIO_free = crypto_handler.BIO_free
RSA_free = crypto_handler.RSA_free
NID_md5 = 4

bio = BIO_new_file("/yourpathfile.key", "r")
key = PEM_read_bio_RSAPrivateKey(bio, 0, 0, 0)
r = BIO_free(bio)
if r != 1:
    # break here
    print 'BIO_free error'

rsa_size = RSA_size(key)
rsa = ctypes.create_string_buffer(rsa_size)
sign_size = ctypes.create_string_buffer(4)

message = "foobar"
if 1 != RSA_sign(NID_md5, message, len(message), rsa, sign_size, key):
    print 'RSA_sign error'
RSA_free(key)

print base64.urlsafe_b64encode(rsa.raw)

Apache Module 之在各个 request 之间共享数据(资源)的方法

不论是1.3还是2.x,apache 的模式都包括一个进程服务好多次请求后再退出。现在有一个需求,在每个 request handler 里面希望保存一些数据,这些数据在以后该进程处理其他的 request 中可能还要用到。

最开始我从 request->server->process->pool 顺藤摸瓜看到一个貌似是和进程有关系的资源池。于是仿照 RUN_INIT_ONCE 的apr_pool_userdata_get/apr_pool_userdata_set 来访问资源。这样做的确可以在 request handler 之间共享数据,可是进程退出(apachectl stop)的时候不会去调 cleanup,非常之讨厌。

google 了半天,并参考 apache 自己的 LDAP 模块,发现官方解决方案好像是这样的:

  • 定义module_config结构体的时候,增加一个资源指针
  • 注册child_init的hook函数,从传入的第一个参数(pool指针)里面申请资源
  • 用apr_pool_cleanup_register注册cleanup函数在这个 pool 上(这个是cleanup的核心步骤,我感觉理论上完全可以自己重新创建一个pool,或哪怕是自己malloc,但关键是把cleanup函数注册到这个pool上。因为在进程退出的时候apache只会去销毁这个pool上的资源,而cleanup函数是"资源"之一)
  • ap_get_module_config, 把申请到的资源挂到自己定义的config结构体里

这样在进程退出的时候就自动 cleanup 了。这也是为什么到了 apache 2.0 后就不再提供 child_exit hook 的原因,因为开发者觉得由于有 cleanup 注册的机制,对应 child_init 的 child_exit 就不再有存在的必要。

唯一的疑问就是,request->server->process->pool 和在 child_init 时传入的 pool 是什么关系..... 不过懒得再去寻根究底了,先把需求实现了再说。

取本机所有的 IP

代码修改自 qmail 的 ipme.c , 要点是对于 struct ifreq 里定义了 ifr_addr.sa_len 的系统(比如 FreeBSD),是和 Linux 不太一样的。
在 CentOS 3 和 FreeBSD 4.7 上测试通过

/* ipme.c from qmail */
#include "sys/types.h"
#include "sys/ioctl.h"
#include "sys/socket.h"
#include "netinet/in.h"
#include "net/if.h"

int main(int argc, char *argv[])
{
    struct ifconf ifc;
    int sockfd;
    char buf[20000];
    char *ptr;

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        return -1;
    }

    ifc.ifc_buf = buf;
    ifc.ifc_len = sizeof(buf);
    if (ioctl(sockfd, SIOCGIFCONF, &ifc) == -1) {
        return -1;
    }

    ptr = buf;
    while (ptr < buf + ifc.ifc_len) {
        struct ifreq *ifr;
        struct sockaddr_in *sin;
        int len;

        ifr = (struct ifreq *)ptr;
#ifdef __FreeBSD__
        len = sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len;

        if (ifr->ifr_addr.sa_family == AF_INET) {
            printf("%s", ifr->ifr_name);
            sin = (struct sockaddr_in *)&ifr->ifr_addr;
            printf("\t%s", inet_ntoa(sin->sin_addr));
            if (ioctl(sockfd, SIOCGIFFLAGS, ifr) == 0) {
                if (ifr->ifr_flags & IFF_UP) {
                    printf("\tUP\n");
                } else {
                    printf("\tDOWN\n");
                }
            }
        }

        // 这段代码来自 qmail 的 ipme.c, 在 FreeBSD 上实测中没有出现过 len 小的情况
        if (len < sizeof(struct ifreq))
#else
        if (ioctl(sockfd, SIOCGIFFLAGS, ifr) == 0) {
            if (ifr->ifr_flags & IFF_UP) {
                if (ioctl(sockfd, SIOCGIFADDR, ifr) == 0) {
                    if (ifr->ifr_addr.sa_family == AF_INET) {
                        sin = (struct sockaddr_in *)&ifr->ifr_addr;
                        printf("%s\t%s\n", ifr->ifr_name, inet_ntoa(sin->sin_addr));
                    }
                }
            }
        }
#endif
        len = sizeof(struct ifreq);
        ptr += len;
    }
}

昨天终于把《快乐的农夫》的右手弹得像模像样了

"Joyous Farmer" Robert Schumann

左手好几个月前就能弹了,但右手怎么也不行... 后来挺忙的,就一直没有练这个曲子。

前天晚上练极简单的 Ode to Joy 以后,突然想着去把这首舒曼右手再练一下,没想到就很感觉很熟练了,:)

昨天反反复复弹到大拇指下某根筋隐隐作痛;今年的目标是把两手合起来...... 主要是缺练琴的时间,难啊~~~

自己弹没有感觉,看人家的演奏发现弹起来手的表演性还真强。

方刚的"三个容器"

见:http://fangzl.blog.sohu.com/102159550.html

昨天在一个小会上听方刚提到这三个容器,现在我觉得我可以帮着解读一下这三个代表的意思了....

第一个容器,是搜狐博客
第二个容器,搜狐空间... 搜狐空间只是起了一个 Space 的名
第三个容器,是....一张白纸. 用毛主席的话,就是好画最新最美的图画

核心是,这三个容器,在方刚看来,是...完全不兼容的。

但有总线把这三个容器串在一起...
但为什么方刚没有把privacy和 profile/relationship 归一处。而是和 APP 放在一个总线里面呢? 这个可得再问问他

dup2.org 恢复访问

由于原来的 vmmatrix 服务调整,现在把dup2.org切换到新的一个 VPS 上,感谢老韩友情赞助服务资源。

顺便把 drupal 也升级到了最新的 6.x

在从 5.x -> 6.x 的升级过程中,导出的 sql 文件有 1 个G!! 检查后发现都是 watchdog 这个表的内容。

watchdog 在 5.x 是核心模块还不能 disable,现在升级到 6.x,可以把"Database logging"这个模块禁用了,hoho

扩展模块现在只剩下 4 个: GeSHi Filter (满足时不时贴代码的需求)、XML Sitemap (搜索引擎优化)、CAPTCHA(对付垃圾留言)、Similar entries (提高网站的可阅读性). 别的东西我觉得都没有用,否则每次升级 drupal 都是一个麻烦事。

说实话,如果不是已经熟悉了 drupal 以及国外的 blog 服务容易被盾,真想切换到 blogspot 上. 要么使用 blog.sohu.com?? 好像很不严肃的样子...

文本聚类研究

最近一直在研究文本分类。到今天,终于搞出一个收敛的单词聚类算法。

最主要的时间是在磨刀上——前前后后总共差不多有 1 周的时间在看 sogou 分词库的文档,以及给它开发一个 python-binding

然后是差不多花了半天的时间写爬虫,获得最初的文档库

利用"[PDF] WRM :一种基于单词相关度的文档聚类新方法"里介绍的概念计算单词两两之间的相关度

最麻烦的就是利用相关度对单词聚类,论文里介绍的方法好像并不好用。从周二到周日,都在不停的找论文,试验新算法,运行测试,修正... 的循环里度过

失败无数次后,最后在 Fuzzy C-Means Clustering 的指导下,投机取巧的设计了一个方法以及判断收敛的机制,今天下午已经成功的跑完一次聚类到 250 个集合的过程,另一个聚类到 100 个集合的过程虽然比较慢,但在 2 个小时前也已经结束。Oh, my god, 看起来是可以正常工作了。

感谢分词库开发者,感谢搜狗新闻、关键字广告 TEAM 曾给的帮助
【尤其是 sogou 的开发包,能返回词的词性... 虽然还有瑕疵,但已经对我帮助很大了】

感谢python, 没有它不可能短时间内把算法体无完肤的改上10几次

感谢《鬼吹灯》,周末的晚上是它让我保持清醒,时不时的去看一眼后台跑的计算过程.

感谢家里人忍受我最近的加班

gsopcast 的 channel 列表

十一放假的某一天,突然想看看国际米兰的欧冠在线转播。发现一个网站http://www.myp2p.eu,里面有各种 P2P 软件的频道链接。鉴于我的 Ubuntu 系统,我看了半天,决定安装 SopCast,这个似乎还比较有名,网上的安装帮助应该比较多。

先安装了命令行版本,再加了图形界面 gsopcast。命令行的能用,不过比较麻烦。可图形界面里面,怎么也显示不出频道列表。于是我尝试先直接在浏览器里访问频道列表的网址,哦,原来已经被挡在外面了,难怪呢。

上周末把我的宽带换成 1M 的了,想起再试试 P2P 在线视频,顺便又搜了一下 gsopcast,发现有高人给了一个新的网址 http://channel.sopcast.com/gchlxml,好使。不过怎么我上次没有搜出来呢,这个网页还是隐藏得太深了,这个内容还是被人转得太少了,我也提一提,希望有同样问题的人能更容易地找到解决办法。

随便点了几个频道看,感觉速度不太好 :(