epoll 事件之 EPOLLRDHUP

在对系统问题进行排查时,我发现了一个奇怪的现象:明明是对方断开请求,系统却报告一个查询失败的错误,但从用户角度来看请求的结果正常返回,没有任何问题。

对这个现象深入分析后发现,这是一个基于 epoll 的连接池实现上的问题,或者说是特性 :)

首先解释一下导致这个现象的原因。

在使用 epoll 时,对端正常断开连接(调用 close()),在服务器端会触发一个 epoll 事件。在低于 2.6.17 版本的内核中,这个 epoll 事件一般是 EPOLLIN,即 0x1,代表连接可读。

连接池检测到某个连接发生 EPOLLIN 事件且没有错误后,会认为有请求到来,将连接交给上层进行处理。这样一来,上层尝试在对端已经 close() 的连接上读取请求,只能读到 EOF,会认为发生异常,报告一个错误。

因此在使用 2.6.17 之前版本内核的系统中,我们无法依赖封装 epoll 的底层连接库来实现对对端关闭连接事件的检测,只能通过上层读取数据时进行区分处理。

不过,2.6.17 版本内核中增加了 EPOLLRDHUP 事件,代表对端断开连接,关于添加这个事件的理由可以参见 “[Patch][RFC] epoll and half closed TCP connections”。

在使用 2.6.17 之后版本内核的服务器系统中,对端连接断开触发的 epoll 事件会包含 EPOLLIN | EPOLLRDHUP,即 0x2001。有了这个事件,对端断开连接的异常就可以在底层进行处理了,不用再移交到上层。

重现这个现象的方法很简单,首先 telnet 到 server,然后什么都不做直接退出,查看在不同系统中触发的事件码。

注意,在使用 2.6.17 之前版本内核的系统中,sys/epoll.h 的 EPOLL_EVENTS 枚举类型中是没有 EPOLLRDHUP 事件的,所以带 EPOLLRDHUP 的程序无法编译通过。

Shell Tips: Unix 时间到字面

我的工作需要天天跟报表数据打交道,在交换的文件中,一般时间的字段内容都是 Unix 时间。为了检查数据的正确性,不可避免地需要转换 Unix 时间到人类可读的字面时间。

下面想分享的是一个在 Shell 下转换 Unix 时间到字面的小方法。与前面几篇一样,这个小 shell 函数仍然可以放在 ~/.bashrc 中方便快捷使用。

# 转换 Unix 时间到本地时间字符串
function ctime()
{   
    date -d "UTC 1970-01-01 $1 secs"
}

使用方法很简单:

$ ctime 1234567890
Sat Feb 14 07:31:30 CST 2009

对 date 命令熟悉的同学会说,date 不是已经有直接转 Unix 时间的参数了吗?

$ date -d @1234567890
Sat Feb 14 07:31:30     2009

但是不好意思的是,小弟有时候用的 date 程序好老,不支持 @ 符号。

$ date --version
date (coreutils) 5.2.1
Written by David MacKenzie.

Copyright (C) 2004 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

PS: 写完这篇博文,我又想到了一个有趣的事情,既然很多 Linux 64 位版本的 time_t 已经是 long long 格式了,那么 date 命令有没有 year 2038 问题呢?

下面是 date (coreutils) 5.2.1 在 64 位服务器上的尝试结果:

$ date +%s -d "Tue Jan 19 11:14:07 CST 2038"
2147483647
$ date +%s -d "Tue Jan 19 11:14:08 CST 2038"
2147483648
$ date +%s -d "Tue Jan 19 11:14:09 CST 2999"
32473710849
$ ctime 2147483647
Tue Jan 19 11:14:07 CST 2038
$ ctime 2147483648
Sat Dec 14 04:51:44 LMT 1901
$ ctime 32473710849
Mon Mar 28 07:33:53 LMT 1910

看来字面时间和 Unix 时间之间互转存在着问题啊!但是用 Ubuntu 11.04 的 date (GNU coreutils) 8.5 尝试就不存在这个问题了。

淘宝OceanBase架构笔记

OceanBase架构图
OceanBase架构图(引自 rdc.taobao.com)

OceanBase 是淘宝研发的一套分布式 NoSQL 数据库系统。具体它是什么、怎样实现的,可以参考李震老师(花名楚材)的《OceanBase介绍》和杨传辉老师(花名日照)的《Oceanbase – 千亿级海量数据库》。这里我只是谈一下自己的感想,如有谬误,敬请指正。

OceanBase 相较于其它分布式存储系统,有一个特性是支持跨行跨表事务。这个特性太明星了,让几乎所有其它系统黯然失色。但实现这个是有代价和局限的,OceanBase 只能使用单机接受更新,也就是说它的 UpdateServer 只能有一个(或者准确地说,一组)。由于 UpdateServer 失去了扩展性,OceanBase 的应用必须建立在单机能够满足增量更新和查询性能需求(查询可以通过从机部分缓解)的前提下(或者硬件性能的增长快于性能需求的发展)。为满足这一点,需要对软件和硬件都进行很好的优化,幸运的是从淘宝核心系统团队成员的文章来看淘宝应该不缺这样的专家,也不缺买设备的钱。值得一提的是,每个公司看来都有自己的基因,看到 OceanBase 我脑子里就浮现出淘宝数据库架构中单机 Oracle 挂一堆 MySQL 的景象,何其相似啊!

阳振坤老师(花名正祥)在《淘宝海量数据库之二:一致性选择》这篇文章中说 OceanBase 是支持强一致性的。如果 UpdateServer 没有从库的话, 能够很容易理解。但考虑到 UpdateServer 从库也提供读服务,且 UpdateServer 之间使用 binlog 进行同步,那么还能否保证强一致性这一点我比较怀疑。也许会有其它的辅助机制来保证这一点,例如在 MergeServer 上做一定的策略。

在高可用性方面,对 RootServer 的说明较少,不清楚 OceanBase 有没有实现 UpdateServer 宕机后的 Master 选举。由于使用 binlog 同步,可能宕机恢复方面还是有一些风险的。

将 MergeServer 和 ChunkServer 部署在一起是个很好的选择,这样查询时能够利用一定的局部性。但除非根据业务需求非常精妙地部署,否则不可避免需要请求其它 ChunkServer 上的数据。我不知道它的查询是 MergeServer->(UpdateServer, ChunkServer0, ChunkServer1...),还是 MergeServer->(UpdateServer, MergeServer0, MergeServer1)。不同的模式有同的优缺点,如果 ChunkServer 只做存储的话,查询的过滤、合并应该是在 MergeServer 上做的。如果选用第一种方式请求,ChunkServer 间传输的数据没有过滤和合并,数据量较大;如果选用第二种方式请求,UpdateServer 的压力可能会被放大,视 MergeServer 封装的功能而定。

OceanBase 的介绍中没有提到 Chunk 或者 ChunkServer 是否分主从,也没有提到整体更新机制。考虑到更新是以批量方式合并到 Chunk 中,也许为了简化,Chunk 或者 ChunkServer 只是互备,没有主从。为了保证 ChunkServer 合并 UpdateServer 上冻结/转储数据时查询的正确性,可能用两阶段提交,不过我想仍然是一个很复杂的过程。

早上起来就想到这里,以后有问题再补充吧。

PS: 之前将楚材错当成了阳振坤老师的花名,已更正之。

纸上烤肉

大伙烤肉

听了凯哥对有孩子之前美好生活的描述,我觉得有必要趁着年轻清闲多腐败几把。于是当我家那个吃货缠着要吃好吃的时候,就去了离家不远但觊觎很久的大伙烤肉(南横西街店)。

在纸上烤肉这一点,我以前从来没有体验过,觉得很新鲜。电烤盘上面是过了油的纸,肉是放在纸上烤的。电烤盘下面是平底勺,鸡翅放在勺子里。这样的话,鸡肉是从上面开始熟的。用电烤的话,油烟是没那么大,但油滴溅的厉害,尤其是刚换上新纸的时候,会溅到桌边上。

烤肉店消费可真是贵,人均六十的话,不点主食我估计只能吃半饱。我跟吃货同学也就点了两个菜:调味牛排和鸡翅。调味牛排味道的确很赞!还有它配的酸甜的小料,非常合我的胃口。吃第一口的时候我简直有惊艳的感觉,我跟吃货同学说以后还要来。鸡翅倒一般了,贵且味道没特色。延吉冷面倒不贵,十块那么大一碗,我都没吃完。冷面也完全是酸甜味儿的,很好吃。

但是,酸甜味儿的最大缺陷,就是吃多了容易腻。我的实力还是不够,快吃完的时候已经有些腻了,看来这样的味道还是得适可而止。

亲历 Philips 电吹风召回事件

Philips 服务中心

我从没想到在中国能经历一次商品召回事件,而且是电吹风这种小家电的召回。6 月 1 日,飞利浦召回170余万台电吹风,很巧的是我买的两台电吹风都在这个召回列表中。感谢卓越亚马逊在第一时间发了两封电子邮件通知了我:

尊敬的客户:

您好!

您的订单(订单号 ***)订购的飞利浦电吹风商品正在全球召回和更换。详情请登录指定网站 (www.philips.com.cn/replace) 或拨打热线电话(800-820-0930)以便鉴别及更换。

受影响产品的型号和批次为:

飞利浦轻巧便携系列电吹风 HP4930 生产日期在080117到110130之间(含)
飞利浦轻巧便携系列电吹风 HP4931 生产日期在080117到110130之间(含)
飞利浦轻巧便携系列电吹风 HP4940 生产日期在080612到100613之间(含)

我网上申请了下更换,第二天就收到 Philips 服务中心的电话,约了一周内去领。今天中午骑车去了崇文区幸福大街路口的一个服务点,顺利地把货换回来了。更换的流程极其简单,提供一下预约人和电话,就从一个包装盒里拿出来新机器,把旧机器收走装起来。服务小姐说更换后的机器从今天开始算起享受两年的保修期。

值得一提的是,这两个电吹风都是在 09 年上半年买的,刚过两年的保修期。这次召回我算是捡了个便宜。感慨嘛,自然是有。这次召回事件抵消了 Philips 手机给我带来的坏印象。以后买小家电,Philips 会是首选了。我写这篇博客,也是觉得关心消费者的行为(Philips的召回和卓越亚马逊的及时通知)应该受到褒扬。

PS: 在骑车去幸福大街的一段路上,我忽然发现两边的建筑居然都不超过四五层高,但都挺气派。如果不看牌子的话,恍惚到了哪个小城市的新城区。又骑了一段我才反映过来,这是天安门正南啊,怪不得没有高楼!

网站被认证

经过各方证实,我确定了这个域名现在(2011年6月10日)已经被“认证”了。目前显示出来的现象是第一次访问大部分可以成功,点击页面链接就可能会被 reset。

不要问为什么会这样,我也不知道。不过我心理早就预期到可能有这一天,因而一切仍然会继续,除了要费点劲儿。不感慨了,引用菜头一句话:

鲜花总会长出来,不在墙这边相见,就在墙外面思念。

SHELL TIPS: rsync 和 crontab 变量

本周我遭遇了一个惨痛事件,远程开发机有两块硬盘同时损坏,整个分区数据完全丢失。这直接导致我在开发机上 home 目录中所有文件人间蒸发了,真是一觉回到解放前!

值得庆幸的是这件事发生在端午假期中间,放假前我把大部分源代码都提交到了库里,辛苦劳动的损失倒不大。但是之前种种努力搭建的开发环境全丢失了,这一点让人很郁闷。为了不再遭受这样的损失,我痛下决心学习了一下 rsync,用来同步和备份数据。

不得不说 rsync 也是一个神器啊,man rsync 发现有各种灵活用法,最有用的我觉得还是通过 ssh 来备份的方法。发现了这个秘密后,我觉得用来备份自己的网站数据也不错。下面是我使用的参数:

rsync -avzuptS --delete --max-delete=100 -e "ssh" username@solrex.org:~/ ~/backup

如果懒得输入 ssh 密码,可以使用 ssh 公钥认证

一旦配置了公钥认证,这个备份命令就可以定制为 cron 任务定时执行了。这时候我发现一个有趣的问题:如果镜像了整个 home 目录的话,无法记录命令执行日志。 因为无论日志写入到 home 目录下哪个地方,都会因为远端没有这个文件被 rsync 删除掉。无奈之下只好使用笨方法,让 cron 把同步日志发邮件给自己。

$ crontab -l
SHELL=/bin/bash
HOME=/home/work
MAILTO="someone@somesite.com"
PATH=/home/work/local/bin:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin
00 21 * * * mirror_site

把 SHELL 配成 /bin/bash 的原因是 /bin/sh 可能会导致一些不常见的诡异问题,例如这个。同样,在 crontab 中声明 PATH 环境变量可以避免某些命令找不到的问题,特别是自己写的放在 home 目录的脚本。

百花山黄草甸

上周六,组里一起去 bui 了一趟百花山。天气比较给力,阳光很灿烂,又有些山风,不热。行程是安排好的,没有特别赶,比较放松。凯哥把自家娃娃也带出来玩了,一路上逗小孩子玩,很欢乐。

从地图上来看,貌似去百花山走 108 国道更合适,没想到司机同志走的是 G109。因而我发现路程非常熟悉,直到看到了斋堂水库和爨柏景区。百花山比爨底下村大概只远几十公里的样子。

到景区附近之后,首先找了个农家院烧烤。设备和材料都是自带的,只有院子是租的。还有一些凉菜的开销,其实后来发现烧烤都没吃完,自带的水果也没吃完,就不提凉菜了。下面是同学们火热的烧烤场面。

百花山下农家院烧烤场面
农家院烧烤

吃饱喝足之后,坐车到山脚下,开始爬山。预计到百花草甸行程是 3 公里,可实际上爬山居然花了两个多小时。这是刚上山的一个植物园附近的风景。

小半山风景
小半山

爬了没多久,就有很多同学掉队退出了。我就还好,平时对这种类似自虐的行为就比较感兴趣,尤其是到了有风景的地方,肯定不愿意那么早就放弃。这是半山腰的风景,基本上到这里的也就剩一半儿人了。

半山腰的风景
半山腰

我虽然很兴奋,可 xixi 却不一样想法,早就喊累要下山了。不过我不愿意没看到所谓的百花草甸就半途而废,她还是被我拉着继续往上爬。越往上走,绿色就越少,可能是海拔的原因。

近山顶的景色
近山顶

最后,终于爬到了顶上的百花草甸。山风那是呼呼地吹啊,穿短袖快冻坏了。最重要的是,它的主色调是黄色的!这就是传说中的“百花草甸”吗?唉,哥又来早了啊!

百花草甸的风景
百花草甸

其实草甸不是最顶峰,但路上实在花费了不少时间,来不及再往上爬了,留些遗憾吧,以便以后再来。于是一路下山,又花了一个多小时,到底下小腿有点儿哆嗦,还是欠锻炼。

Poderosa 项目复活了

Poderosa截图,图片来自官网
Poderosa 截图

四年之前我就开始关注 Poderosa 这个项目,因为我认为它是我能找到的最好的 Cygwin 终端客户端——没有之一。当然,如果你把它当做 ssh 客户端去跟其它软件比,你就输了。

由于它在 2006 年 11 月以后再也没有更新过新版本,两年前,我曾经因为它有了一个“同人” 2009 特别版 而激动不已。

前两天,我偶尔去逛论坛,居然发现这个项目复活了!作者在开放讨论区里面声明发布了 4.3.0b,真是太让人兴奋了!

令人奇怪的是,xjzhang1979 君虽然提供了 N 个 Poderosa 特别版升级版本,但从来没有释出过源代码,看起来也没有打算这样做。作者 kzmi这里也提到:

5.7.x has more new features, but their source code has not been disclosed.

4.3.x are developed on the Poderosa project and you can get their source code.
Some features from 5.7.x were added to 4.3.x.
...
I believe that the terminal emulation of 4.3.x would be better than 5.7.x's.

由于 5.7.x 不开放的源代码,相比 4.3.x 而言用户肯定对其安全性有一定怀疑。现在有了更好选择,恐怕特别版的拥护者会越来越少了。这真的让人很遗憾!

关于话题的吸引力

上上个周末我参加了一个培训,一位来自 Intel 的培训师在介绍营销方式的时候,提到了一本叫做《粘住:为什么我们记住了这些,忘掉了那些?》的书。从这本书里,我读到了一些有趣的观点,因而迫不及待地想找个实例验证一下。

下面,就是我找到的实例,一个话题:

刚一哥儿们跟我说,当初他博客开在百度,百度擅自拿了他的文章去发布,被投诉之后先是关了他博客,然后被逼问之后又打开博客然后往上放了一堆黄色图片,说要他负责任,这公司真不一般…
一个话题

关于这个话题发生了什么事情呢?

Retweeted by cyclamen and 28 others
RT

被 retweet 了 28 次,不算多哈。但 Twitter 一般只标记官方 retweet 结果,那么 Google 搜索一下:

找到约 176 条结果 (用时 0.05 秒)
搜索结果

还不错哦,仅仅是三天时间,Google 就能搜索出 176 条结果。虽然比不上“私奔”、“抢盐”的话题,但作为一个没有幕后推手(至少我没看出来)的话题,也算是个不错的成绩了。至少,我不知道自己产出的哪些话题在这么短时间内达到过这样的效果。因此,这是一个不错的分析目标。

《粘住》中说,一个好的创意应该会包含 6 个基本要素:简约(Simple)、意外(Unexpected)、具体(Concrete)、可信(Credible)、情感(Emotional)、故事(Stories) 。来看看上面这个话题,是不是一个好的创意。

  • 简约:这一点应该没有人怀疑,在140个字以内(事实上作者只用了 86 个字),讲活了一个故事,既有转折也有评论点题,不可谓不简约。
  • 意外:这得看对谁来说了。对某些人来说,这可能是又一次验证了他一直以来持有的看法而已;对另外一些人,可能会认为一间大公司跟一个小博主过不去这件事情很意外。
  • 具体:在 86 个字中,作者描述了一个无奈又悲愤的博主,和一个强有力的罪恶大公司,动作和语言活灵活现。仿佛这件事情就发生在自己身边,那个朋友就是自己朋友一般,非常的具体。
  • 可信:单从话题内容来看,并不足够可信,因为细节太少,5W 和 1H 缺失了不少。但是由于 Twitter 的开放性,一些名人的 retweet 倒是可能让读者增加不少可信度。
  • 情感:这个话题描述了一个鸡蛋撞向石墙的画面,你我都知道,不难引起共鸣。对于大众来说,毫无威胁地站在鸡蛋这面是很容易的!伤及切身利益时,才会剩下真的勇士。
  • 故事:这是一个故事吗?

可以看到,除了在“可信”上有一些问题,这个话题还算是一个不错的创意,那么有前面那样的吸引力成绩也不算奇怪了。在《粘住》的开头,还为分析创意是否有黏性提出了一个简单的标准:当你看完故事一小时之后,是否能对别人复述出来?要不你来试试?

僵尸对象或 RAII

我最近在想这个问题,到底要不要在程序中使用异常?

以前写的 C 代码比较多,即使写 C++,基本上也是把它当成 C with object 来用。对异常的了解偏少,使用更是极少。最近评审别人代码的时候遇到一个问题:如果构造函数中 new 失败了,会发生什么事情?

工程的代码一般提倡哪里出错在哪里处理,不能恢复的要返回错误码给调用者。在一般情况下,使用 new(std::no_throw) 保证 new 不抛出异常(否则结果是灾难性的),并且检查分配是否成功是可以实现这一点的。

遗憾的是构造函数没有返回值,我们不能返回构造失败。那么只有用迂回的办法,为类定义一个成员变量 bool inited。初始化为 false,只有在构造的工作都完成之后,才将它置为 true。如果一个对象的 inited 成员为 false,就意味着它构造过程中出了问题,不能被使用。这就是一个僵尸对象,“活死人”。

看,我们成功地规避了使用异常。但是慢着,不是只有 bad_alloc 这一个异常啊!还有 bad_cast、runtime_error、logic_error,还有:

$ grep class /usr/include/c++/4.5/stdexcept 
// Standard exception classes  -*- C++ -*-
// ISO C++ 19.1  Exception classes
   *  program runs (e.g., violations of class invariants).
   *  @brief One of two subclasses of exception.
  class logic_error : public exception 
  class domain_error : public logic_error 
  class invalid_argument : public logic_error 
  class length_error : public logic_error 
  class out_of_range : public logic_error 
   *  @brief One of two subclasses of exception.
  class runtime_error : public exception 
  class range_error : public runtime_error 
  class overflow_error : public runtime_error 
  class underflow_error : public runtime_error 

天那,我未曾注意过标准库有那么多异常!那么如果在使用标准库时,不小心触发了什么异常,OMG!

这样看来,使用异常是很有必要的。但是,麻烦的问题又来了,一旦使用异常,函数的退出过程就变了。使用错误码有一个好处,就是你可以在函数返回前擦干净自己的屁股;但是使用异常呢?你既要保证对象能够自己擦屁股(RAII),还要保证函数能自己擦屁股(在正确的位置使用异常处理),这样才能在 stack unwinding 时不会导致内存泄露。哦,auto_ptr 可以帮上一些忙,但如果是分配的资源是数组呢?

还有一个麻烦是,你要遵从约定——特别是对于一个程序库作者来说。如果约定出错时抛出异常,那么可以抛;如果约定出错时返回错误码,或者这个库可能被 C 调用,那么抛出异常就可能是灾难。

现在看来,如果想实现更健壮的 C++ 程序,那么异常处理是不可或缺的。但在使用异常处理之前,必须得了解在哪里、怎样抛出和捕获异常,如果是团队合作,可能还需要有简单的操作指导手册,否则使用不当或者过量的异常也可能带来麻烦。

我还在路上!

无线Wi-Fi路由器的信道选择

早上起来看到一个朋友抱怨 Kindle 无法连接无线路由器,我有感而发,写了下面这篇微博

很多人从来没有注意过路由器的 wifi 频道,以为只要笔记本电脑能连上,无线路由就没问题。但有个问题是很多移动设备不能支持全频道,其中即有功耗考虑,也有销售目标国家考虑。因此有条件时最好检测一下信噪比,选择一个最适合自己的频道,而不是让路由器启动时自己去选择。

但接着又有朋友说:“求普及...”。参考 Avinash Kaushik 在他的网站统计博客“Occam's Razor”成立 5 周年时的感慨(这篇文章很值得一读,very inspiring),我想即使我不是专家,分享一下自己知道的这点儿事也不错。

切入正题,现在很多家庭都有了自己的无线 Wi-Fi 路由器,也有了各种接入互联网的移动设备:笔记本、上网本、手机、平板电脑、电纸书、MID(已没落)。很多移动设备联网时都会出现诡异问题,这篇文章仅仅关注其中一种诡异问题:错误的信道(Channel,也译为频道、频段)配置导致无法联网或者信号较差。

精确的技术知识我就不解释了,感兴趣的同学可以去读 Wikipedia Wi-Fi 或者 IEEE 802.11 等词条。下面主要说明为什么需要配置无线信道以及如何选择无线信道。

为什么需要配置无线信道?

相信大家都使用过收音机。使用收音机时,都有一个选台的问题,无论你是用旋钮、按键或者触摸,你总要选定某个台,才能收听该台的节目。无线路由也一样。你家里的无线路由器会广播一个 SSID (就是你看到的无线连接名),点击该连接,就会使你的电脑调制到该连接所在信道进行通信。

但是使用收音机时,可能会有这样的问题:1. 有些台根本收不到,比如你的收音机不是全波段的;2. 有些台杂音太大听不清,比如某些唢呐电台。同理到你的无线环境上,问题 1 转化为“笔记本电脑(全波段收音机)能连接无线路由器,但平板电脑(非全波段收音机)却无法连接”;问题 2 转化为“能连接到无线路由器,但干扰太多,达不到最大的网速(这个网速指 Wi-Fi 连接速度,与 ADSL 网速无关)”。

如果没有手动配置过,无线路由器会自动选择一个默认或者随机的信道进行广播和连接建立。在设备没有联网问题和周围没有别人使用时,这是 OK 的。

但如果你周围的人家都有无线设备,且大家用的都是同一款运营商赠送的无线路由器(或无线猫)时,那么极有可能所有人都选择了同一个信道进行通信。这就会造成很大的信号干扰,这一般影响不到网速——除非你家用的 ADSL 大于 4M,但会造成家里两台设备之间数据传输速度极慢,例如用豌豆荚往手机上无线发送视频文件时。

如果不巧的是你家无线路由器会随机选择信道,那么它极有可能选择到一个你设备不支持的信道。我就曾经遇到过爱国者某型号 MID 只支持信道 1-9 但无线路由器自己选择了信道 11 的情况,那是死活也连不上啊!移动设备出于降低功耗或其它考虑,不支持某些信道是很正常的;再加上不同国家对无线信道管制情况不同(参见 List of WLAN Channels),其它国家的水货设备到了国内可能也会水土不服。

幸运的是,你不能控制广播电台的波段,却可以控制自家无线路由器的无线信道。修改无线信道的方法请参考各路由器厂商的帮助文档,或者在网上寻求帮助。不过在修改之前,你还面临着,我该改到哪个信道呢?

如何选择无线信道?

首先,要选择自己通信设备都支持的信道。不过在此之前,首先确认所有设备都支持同一协议,比如你移动终端不支持 IEEE 802.11n,你非得在路由器上用 802.11n 协议,这就是找不自在了。这些知识要参考设备的手册,或者自己尝试。在 Linux 系统中,可以用 iwlist channel 列出移动终端支持的所有信道,比如 Kindle 支持的信道就是(看到这里我基本可以肯定那哥们 Kindle 问题不是因为信道了,除非他用的是日本产路由器):

[root@kindle root]# iwlist channel
lo        no frequency information.

wlan0     13 channels in total; available frequencies :
          Channel 01 : 2.412 GHz
          Channel 02 : 2.417 GHz
          Channel 03 : 2.422 GHz
          Channel 04 : 2.427 GHz
          Channel 05 : 2.432 GHz
          Channel 06 : 2.437 GHz
          Channel 07 : 2.442 GHz
          Channel 08 : 2.447 GHz
          Channel 09 : 2.452 GHz
          Channel 10 : 2.457 GHz
          Channel 11 : 2.462 GHz
          Channel 12 : 2.467 GHz
          Channel 13 : 2.472 GHz
          Current Frequency:2.412 GHz (Channel 1)

其次,在干扰不能容忍情况下,再去选择干扰较少的信道。这个可以通过扫描周围信号强度比较高的 SSID 都在使用哪些信道,通过和信道列表图谱比较,选择可能干扰较小的信道进行尝试。RSSI 和 SNR 测试需要专门的知识和工具(例如 Linux 下的 iwconfig),对普通人来说可能比较费力。

[root@kindle root]# iwconfig      
lo        no wireless extensions.

wlan0     AR6000 802.11g  ESSID:"mosaic" 
          Mode:Managed  Frequency:2.412 GHz  Access Point: 00:23:EB:B7:E6:94  
          Bit Rate=54 Mb/s   Tx-Power=16 dBm   Sensitivity=0/3 
          Retry:on  
          Encryption key:off
          Power Management:off
          Link Quality:49/94  Signal level:-46 dBm  Noise level:-96 dBm
          Rx invalid nwid:0  Rx invalid crypt:0  Rx invalid frag:0
          Tx excessive retries:0  Invalid misc:0   Missed beacon:0

最后总结一下,随着移动终端的发展,越来越多的移动终端支持更全的信道,但总有那么个别的终端厂商比较变态。因此本文谈到内容仅仅是作为一个提醒,在解决无线连接问题时作一个参考。

入手国产安卓机华为U8800感受

我总算理解了为什么别人说安卓用户三大爱好是:刷机、重启、换电池!

作为一个打拼在互联网界的民工,我不喜欢花很多钱在电子设备上,但又不想太落后于时代。在使用黑莓 8700 的这一年半中,对它的感觉经历了 “哦,这是一个很便宜的智能机” 到 “唉,这还能算是智能手机吗?” 的变化。用到目前,黑莓 8700 让我满意的地方就唯一剩下它可以独立为来电和短信铃声分配不同的提醒模式(振动、响铃、音量或LED闪),设置成为自定义的提醒profile。这对一天要24小时开机接收服务器告警短信的西二旗孩纸很有用!

于是乎,我就开始找性价比高的 Android 手机。其实之前拜托叶蒙买过一个内购的华为 U8220(坛子上一般称大内),女友一直在用;兼之电信送了一个华为 C5700,我也一直在用。不得不说使用后我对华为家终端的信任度大幅上升,因此第一考虑的国产品牌就是华为家了。

U8800 是从新蛋上买的,裸机价 1699(找代下单便宜20),移仓+快递用了 6 天——新蛋物流伤不起啊。

我在 U8800 坛子里逛了不少次,给我的第一印象是如果能忍受自带应用的话,不用刷机。回想起 U8220,貌似带的应用也不多,一直也没刷机提权啥的。谁知拿到 U8800 才发现,我靠,带了二三十个垃圾应用

于是我想到了在坛子里的第二印象,提权之后删掉自带的应用就干净了。于是我就 recovery,提权啥的,总算把垃圾应用删完了,这下该能用了吧。然后我发现,咦,Gtalk 呢?电子市场呢?同步 Gmail 联系人的地方呢?都去哪儿了?然后就开始搜啊搜,有人说得装 Google 服务包,结果下了N个装上都不管用。最后我搞明白了一个道理:行货手机自带系统是不能用 Google 服务的,除非有人专门弄出来一个针对它的 Google 服务包。坑爹啊!你们坛子里讨论这个 ROM、那个 ROM,肿么没人说联通定制 ROM 不能用 Google 服务呢?如果这样我还费劲提个屁权啊!

于是只好刷机了。其实华为家有一点比较好的地方,就是如果某款手机也在国外或者香港销售的话,找一个标准干净的官方 ROM 比较容易(垃圾的东西只敢用来欺负自己人),而且使用的是官方的比较安全的固件升级方法。比如 U8800 就有海外版 ROM,从华为的官网上能下到,下来之后直接看帮助文件就可以,不用从论坛上找什么安装方法。

于是折腾了将近 5 个小时,这款手机总算可用了。其实时间大部分都花在前面“不想刷机”的状态上了,到最后决定刷机倒反而很快了,除了下载固件比较慢。

我真的无法理解这些国内厂商,用别人开源的手机操作系统,却要把别人的服务全抹了去,这是怎样一种忘恩负义!还有一个关键是,你又无法提供更好的服务!拿我来说,为什么一定需要 Google 服务?因为我的联系人全在 Gmail 里,我的日程安排是通过 Google Calendar 双向同步到电脑和手机。光这两点,我就必需连接到 Google 服务。我敢把联系人全部存到开心网吗?我敢用手机直连我公司的 Exchange 服务器吗?

这货还是Kindle吗?

自从大家了解到 Kindle 的系统是 Linux 后,无数仁人志士前赴后继地尝试以各种方式 hack 这个系统,对于我等用户来说真是一大幸事!

好久没有跟进电纸书论坛,居然不知道神器 FBReader Kindle 版 —— fbKindle 的发布。mobileread 网站的原帖中,已经给出了安装和配置修改方法。不过我是遵从这篇帖子中的步骤,无它,摘要读起来更快。

没用过的朋友可能不太了解 FBReader,它是一个开源的跨平台电子书阅读器,支持很多种格式,包括我们常用的 GBK 编码 txt 文档以及 epub 格式。由于 epub 格式电子资源很多,以前我还纠结于为了这些资源要不要装多看系统。现在有了原生的 fbKindle,从 epub 支持这点来看,多看是没有什么必要装了。

此外,还有国人开发的Kindle 原生系统拼音输入法,可以支持在书中或者浏览器中输入中文。虽然我没有安装成功,但我对作者的努力是相当的佩服!

Kindle 原生系统拼音输入法
图片截取于输入法作者相册

在最近的使用中,我也发现了一个 Kindle3 离线读网上文章的小技巧。用 pdfcreator 将网页打印成 A6 幅面的 pdf 文件,并且在页面设置中设置四周页边距为 0(Chrome不支持,Firefox/IE可以),上下不打印网址、页数等信息。这样的 pdf 文件在 Kindle3 上会有比较好的展示效果(字体很小时顶多需要横屏),并且也有比较好的打印效果。由于 pdfcreator 还可以设置自动保存位置和自动文件命名规则,用起来还是挺方便的。

当然,如果你是可注册 K3-wifi 版用户,SendToKindle 浏览器插件可能更有用。

在越来越多有趣的事情发生在 Kindle 上之后,真期待这个生态圈将会变成什么样子。

令人失望的帝都西郊

继续说北京西郊。我以前沿着 108 国道,去过潭柘寺和戒台寺,但印象都不是很好。其主要原因是觉得环境较差,而且公路上货车多,扬尘厉害。

这次去爨底下,是沿着 109 国道走的。相对而言,较 108 国道好一些,路上小汽车比较多,都是出来郊游的。在上 109 国道之前,还堵了一大会儿。出京方向的行车位置主要是靠近山崖一侧,看不很明显。回去的时候从路边往外看,就会发现一路上只要稍微有点儿山水、生点儿花草的地方,就会有一堆堆的小汽车。北京人民游山玩水的劲头看来不小,即使这山水极其一般。

在如此鼎盛的形势下,让我惊奇的是,农家乐旅馆能经营得那么烂。晚上我们投宿了斋堂镇附近一家叫做什么“弘鑫源”的农家乐,条件那叫一个烂啊!洗手间没有热水就不说了,马桶座板坏掉,还不能冲水;房间就是平房,门是塑钢加玻璃的,把手是坏的,根本锁不上;电视机不能看,被子枕头不全,暖气空调都不能用。

就这样的条件,生意居然是出奇的好。吃完晚饭就客满了,还把之前给我们的房间换给了别人。经过交涉后又给了房间,但是被子居然能不够,还是老板开车回去再拿的。折腾到半夜总算住下,结果又冻了半夜。被子又短又薄,再加上山区温差大,差点没把人给冻死!

我原以为只是这一家的个别现象,但到其它地方玩的朋友也有类似的抱怨。这住宿条件无论是什么天气,都不会舒服。春秋天冷不方便,夏天热且蚊子多。这样的农家乐为什么到现在还没被淘汰?让我觉得很是离奇。

这事情给我的一个教训是,千万别迷信所谓的“随意游”,随意带来的八成是悲剧。出去玩还是做好计划才能让行程比较轻松、安全、舒适。

吐槽爨底下村

在大家都在抱怨沙尘暴时,我表示很淡定。第一,我没有出门;第二,作为一个北方人,真没什么大惊小怪的。因此淡定的我还是扯点儿其它淡吧,这其实是篇两周以前的游记。

清明节假期,我蹭朋友的车一起到爨底下村去玩,结果印证了那句话“盛名之下,其实难副”。究其原因,说到底咱是个俗人,俗人看民俗,更觉得到处烟火气,没个意境。罢了就成了摄影游,到个地方捏个影儿,走马观花一番。咱的摄影技巧和相机又不成,捏的照片回来看看都面目可憎,只能勉强挑几张晒晒,也不枉去了一遭。

村口有一个大字儿,这字儿念“cuan”,为了简化,很多人会把这里叫川底下村。这字儿的意思我也不解释了,我厂有款产品能专门帮人干这事儿。路上其实还有个更大的字儿,去的时候路过了没有停,回的时候找合影的地儿反而错过了。啊,多么像人生啊!

爨字
爨字,这个字真稠

到的时候已经快中午,我们在村里巷道随便逛了逛,就开始找吃饭的地儿了。吃的所谓农家菜,唉,不好吃吧,还不便宜。吃完就直接走去山上小庙了,随便逛了一番,然后就下来了。我跟 xixi 在小庙前面也捏了个影儿。

在某小庙前合影
在某小庙前合影

在山上小庙俯瞰整个小村,其实还是有些感觉的。只是看了同样也能知道,市场化是如何深入到这个小山村里。

爨底下村全景
爨底下村全景

从小庙下来,大伙儿也失去了在村子里闲逛的欲望,估计也都不太喜欢这扑面的烟火气。然后我们徒步走去了所谓的《投名状》的一线天取景地。您还别说,走路去还是挺远的。

所谓的一线天
所谓的一线天

这次出行,主要有一点运气不好:山还没有绿。黄秃秃的山跟绿油油的山,给人的心情是完全两样的。如果那山是绿的,这心情会洋溢起来也说不定!

爨底下前山
爨底下前山

其实回程也发生了不少事情,留待下次再吐吧。总而言之,我对北京西郊的失望又增加了一层。

更新 JabRef 2.7 中文版

JabRef 的中文版是我翻译的。但是好久没有关注邮件列表,偶然瞄到才发现又有新增的部分需要翻译。于是今天就花了些时间更新了一下中文翻译,将会集成到 JabRef 的 2.7 版本中。“抢先版”请看这里 ^_^

另外,在我 Windows 平台下的 JabRef 2.6 以后版本中,列表页中文文献名都显示为方框,由于下方细节中有正确显示,我也就没管它。但是 Linux 下却显示正常,这点让我比较好奇。今天才发现 Windows 平台下需要设置列表页字体为中文字体才能正确显示。这样看原来的显示正常到方框,大概与 Java 不同版本的默认字体设置有关系。

再另外,sourceforge 的 svn checkout 速度可是真慢啊!不知道是不是帝国的网络条件导致的。

Shell Tips: 用GNU Screen实现发送交互到所有会话

服务器冗余和分拆是互联网服务中经常用来缓解访问压力的手段,那么检查或者管理多台同构服务器也是互联网行业工程师们绕不开的操作。经常面临的问题是:如何高效地在多台服务器上执行相同的命令,进行批量系统操作或问题检查。

Windows 下的 ssh 客户端 XShellSecureCRT 都提供了类似的功能,当每个标签页都连接到一个服务器时,可以在命令窗口中发送交互到所有的标签页以实现同时操作多台服务器的目的。这招我还是从 OP 那里学来的,的确大大提高了生产力。

但这种方法也存在一些问题:

  1. 只适用于特定的 ssh 客户端。例如对 Linux 来说就有些不适用,不过据说 Konsole 也提供了类似功能,未验证。
  2. 每个标签页中,还是得一台一台地登陆上服务器,很难自动化。据说有的客户端支持编写脚本实现,但还要学习对应脚本语言,且灵活性有限。
  3. 无法一直保持持续的连接。特别是对有开发机的工程师,本来开发机是一直在线的,但由于客户端的限制,只能在本地电脑连接多服务器。当本地网络断开后,自然多服务器的连接也断开了。

为了解决这些问题,小弟想到了神器 GNU Screen。Screen 也是终端,难道无法做这件事吗?您还别说,在我费心劳力一上午之后,总算摸索出了用 Screen 解决上述问题的方法。下面两个可以放到 ~/.bashrc 中的函数,就是我心血的“结晶” :)

function screenssh ()
{
    local username=YOUR_USERNAME
    local password=YOUR_PASSWORD
    local server=''
    local timeout=3
    for server in $@; do
        screen -S $STY -X screen ssh $username@$server
    done
    sleep $timeout
    local cmd="screen -S $STY -X at ssh# stuff $'$password\n'"
    eval $cmd
}

function lets ()
{
    local cmd="screen -S $STY -X at ssh# stuff $'$1\n'"
    eval $cmd
}

Screen 的用法和技巧,在我之前的文章中也有提及,此处不再赘述。这里主要介绍一下上面两个函数的作用和用法:

screenssh 是在 screen 中自动登陆多台服务器的命令。这个 bash 函数接受服务器列表作为输入,执行后会在当前 screen 中为每个服务器打开一个 window,并使用提供的用户名和密码登陆这些服务器。这样当前 screen 中就会多出 N 个 window,分别对应登陆到 N 个服务器。在使用前,你要修改用户名、密码变量值为你需要的内容,而且该命令必须在 screen 中执行,在 screen 外执行是无效的。

执行完 screenssh 后,就可以祭出 lets 命令来在多个 window 中同时执行操作命令了。lets 接受一个字符串作为输入,执行后该字符串会作为命令发送到 N 个服务器对应的 N 个 windows 中执行。

看完以后令人困惑的地方可能是,我到底应该在哪里执行 screenssh 和 lets 这两个命令呢?下面用一个例子来更直白地阐述一下这两个命令的使用方法。

假设你需要在 3 台服务器:s1.solrex.org, s2.solrex.org, s3.solrex.org 上执行 grep FATAL ~/error_log 查看错误日志。那么你应当:

1. $ screen -S admin
# 首先创建一个 screen,这时候你有了 0 号 window;
2. $ screenssh s1.solrex.org s2.solrex.org s3.solrex.org
# 在 0 号 window 中执行 screenssh 命令,自动打开 3 个 window,连接到三个不同的服务器;
3. $ lets "grep FATAL ~/error_log"
# 在 0 号 window 中执行 lets,将命令自动分发到 3 台服务器上执行;
4. ctrl-a N 切换到不同的 window 查看命令的执行情况;
5. ctrl-a 0 切换到 0 号 window 执行下一条批量命令;

下面我们再回顾一下上文中提到的 3 个问题是否解决了:1. GNU Screen Linux 一般均自带,不存在专用客户端问题;2. screenssh 解决了自动化登陆多台服务器问题,且服务器列表作为参数,非常灵活且易定制;3. 开发机上运行的 screen 保证了客户端离线连接不断。

Shell Tips: cppath、scppath、mybackup

分享几个觉得有用的小 shell 函数。

1. scppath

在进行一些跨机器的操作时,每次 scp 总要手动去拼那个路径,首先从 PS1 拷贝粘贴用户名和主机名,然后再 pwd 拷贝粘贴当前目录,然后再 ls 拷贝粘贴要 scp 的文件名。好烦啊,所以就写了下面这个小函数来生成 scp 的文件路径,放到 ~/.bashrc 里。

function scppath()
{
    local _IFS=$IFS
    IFS=$(echo -en "\n\b")
    local _file
    for _file in $@; do
        echo "\"$USER@$HOSTNAME:$PWD/$_file\""
    done
    IFS=$_IFS
}

2. cppath

同样可以有 cppath。

function cppath()
{
    local _IFS=$IFS
    IFS=$(echo -en "\n\b")
    local _file
    for _file in $@; do
        echo "\"$PWD/$_file\""
    done
    IFS=$_IFS
}

3. mybackup

这个函数是偷懒备份用的。当写代码写到一半,不想或者不能 check in,但又想备份一下时,就用这个命令对文件或者目录进行自动的备份。

function mybackup()
{
    local _bak_dir=~/history
    local _path=''
    mkdir -p $_bak_dir
    local _IFS=$IFS
    IFS=$(echo -en "\n\b")
    for _path in $@; do
        if [ -f $_path ]; then
            cp $_path $_bak_dir/"$_path".`date +%Y-%m-%d.%H-%M-%S`
        elif [ -d $_path ]; then
            _path=`basename $_path`
            tar -cvf $_bak_dir/$_path.`date +%Y-%m-%d.%H-%M-%S`.tar $_path
        fi 
        echo "Backuped $_path to $_bak_dir."
    done
    IFS=$_IFS
}