BASE64 编码规则

Base64 编码是一种可以把二进制文件编码成文本的编码规则。在很多地方地方都有用到,比如我们可以把图像转成 Base64 编码,然后内联到 HTML 或者 CSS 中。

Base64 编码用一些很常见的 ASCII 字符来表示 0-63 ,构成 6 个 bit 。用 A-Z 表示 0-25 ,a-z 表示 26-51 , 0-9 表示 52-61 , + 表示 62, / 表示 63 。那怎么把一个 byte(8bit) 用这种只能表示 6 bit 的字符表示呢?转换规则如下:

很简单吧,就是用4个 Base64 编码来表示 3 个 byte 的内容。

如果到末尾,凑不齐 3 byte 怎么办呢?如果只有一个 byte ,就在后面补4个 0 bit ,构成 2 个 Base64 编码,如果余下 2 byte ,就补 2 个 0bit ,构成 3 个 Base64 编码。理论上,这样做就足够判断最后还剩下几个 byte ,但是有些系统需要明确指明最后剩下的 byte ,需要用 = 号来把 Base64 编码的个数补全到 4 的倍数。也就是如果最后余下了一个 byte , 就在编码后的字符补上 == ,如果余下了 2 byte ,就补上 = .

有了这些知识就知道Base64的基本规则,想了解更多的请阅读:http://en.wikipedia.org/wiki/Base64

VPS上部署 nodejs 应用并和现有应用共存

最近,nodejs 很火,我也尝试用 nodejs 写了一个小应用,主要用到了 express framework 和 socket.io ,数据库使用的 mongodb.

我有一台Linode的VPS ,自然是想把应用部署在自己的VPS上面,为什么没有选择云服务呢?一方面是省钱,我已经为Linode付过钱了,一方面是更加自由,可以随心配置。这台Linode上已经部署了标准的 LAMP 环境,跑了几个 wordpress 。那这次部署自然是不能影响到现有应用。

我的部署方案主要要达到的目的有:

  1. nodejs 应用可以像一个守护进程(daemon)一样运行
  2. 提供一对简单的命令来 start / stop nodejs 应用
  3. 提供监控机制,当应用崩溃,可以及时重启它
  4. 不影响现有 PHP 应用,但是也要用 80 端口访问
  5. socket.io 提供的 websocket 功能正常使用

下面分享下我的部署经验,不一定好,我也是第一次部署,欢迎交流。

1,在 VPS 上安装 nodejs ,我 VPS 跑的是 Ubuntu 12.04 , 最简单的方式当然是通过 apt-get 来安装,但是直接 apt-get install 的话,安装的版本比较旧,建议采用 https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager这个页面提供的方法,先添加源,再来安装,就可以安装到最新的稳定版了。这里还有其他主要 Linux 发行版的安装方式可以参考。下面的步骤默认使用的 OS 是 Ubuntu 12.04。

2,安装 mongodb (如果使用的不是 mongodb ,请忽略此步) ,http://www.mongodb.org/downloads#packages 这里同样有包管理器的安装方式,照着做就好了。

3,把你的代码放到 VPS 上来,然后以开发模式运行一次试试,不出意外的话,应该是可以运行了,如果有其他依赖需要解决,请自行解决。

4,把 nodejs 应用变成一个 daemon 。 我使用 upstart 来完成此任务 ,高版本的 ubuntu 已经自带,如果没有,直接 apt-get install upstart 来安装。然后新建一个文件在 /etc/init/yourapp.conf, 文件内容如下

#!upstart
description "node.js server"
author      "joe"

start on startup
stop on shutdown

script
    # We found $HOME is needed. Without it, we ran into problems
    export HOME="/var/www"

    echo $$ > /var/run/yourapp.pid
    exec sudo -u username NODE_ENV=production /usr/bin/node /where/yourapp.js >> /var/log/yourprogram.sys.log 2>&1
end script

pre-start script
    # Date format same as (new Date()).toISOString() for consistency
    echo "[`date -u +%Y-%m-%dT%T.%3NZ`] (sys) Starting" >> /var/log/yourapp.sys.log
end script

pre-stop script
    rm /var/run/yourprogram.pid
    echo "[`date -u +%Y-%m-%dT%T.%3NZ`] (sys) Stopping" >> /var/log/yourapp.sys.log
end script

你需要修改上述文件的 username 为你想要运行应用的用户名,推荐 www-data ,不要使用 root ,比较危险。如果你不使用 www-data 来运行你的应用,记得把export HOME=""改成对应的 HOME 目录。/where/yourapp.js 是你应用的路径。/var/run/yourapp.pid是你应用的pid文件,/var/log/yourapp.sys.log是你应用的日志文件。这些内容都需要根据你的情况修改。NODE_ENV=production 是让你的 nodejs 应用运行在生产环境的环境变量,你可以在代码中通过 process.env.NODE_ENV来获取。

配置文件创建好后,你就可以使用下面命令来开启/关闭你的应用了。

#!sh
start yourapp
stop yourapp

5,监控你的应用。我们不能保证我们的代码总是运行的很好,总会有崩溃的可能,所以我们需要监控它的运行,当它挂掉后,重启它。这里使用 monit 来解决这个问题,安装方法自己去这个网站找吧。

安装好后,新建一个文件在/etc/init/monit/conf.d/yourapp.conf,文件内容如下:

#!monit
set logfile /var/log/monit.log

check process nodejs with pidfile "/var/run/yourapp.pid"
    start program = "/sbin/start yourapp"
    stop program  = "/sbin/stop yourapp"
    if failed port 3000 protocol HTTP
        request /
        with timeout 10 seconds
        then restart

需要替换 /var/run/yourapp.pid 为你刚才设置的 pid 文件,yourapp 替换成你刚才在 upstart 设置的。port 3000 设置成你的 nodejs 应用实际监听的端口号,timeout 时间,可以根据你的实际情况调整。这几行配置文件很容易看懂,就不解释了。

然后确保 /etc/monit/monitrc 文件存在 include /etc/monit/conf.d/* 这行, 同时,你也可以在这个文件里修改监控频率,根据你的实际情况调整。然后重启 monit: service restart monit让刚才的配置文件生效。

upstart 和 monit 的配置我是参考 http://howtonode.org/deploying-node-upstart-monit 这篇博客的,你也可以来这里看看。

6,配置 apache 。我们需要在 80 端口来访问 nodejs 应用,现在又存在 apache ,那最简单的方式就使用反向代理来解决这个问题了。首先,需要安装 mod_proxy 和 mod_proxy_http ,你可以现在 /etc/apache2/mods-enabled/ 这个目录查看是否已经安装了,如果没有安装,你可以通过 a2enmod 命令来快速安装。
然后,新建一个 virtualhost ,按照你的习惯,新建一个文件 include 进去,或者一起写在某个文件。virtualhost 配置如下:

    
<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    ServerName yourhost.com

    ProxyRequests off

    <Proxy *>
        Order deny,allow
        Allow from all
    </Proxy>

    <Location />
        ProxyPass http://localhost:3000/
        ProxyPassReverse http://localhost:3000/
    </Location>
</VirtualHost>

如果你的应用不是跑在 3000 端口上,记得修改配置, ServeName 记得改成你的域名,然后把你的域名解析到你 VPS 的 IP 上来。 ProxyPass 的作用是用 yourhost.com 来代理 localhost:3000 。ProxyPassReverse 的作用是处理 30X 跳转的情况,会把 Location:中的 http://localhost:3000 替换成 http://yourhost.com 。现在开启你的应用,重启 apache , 访问试试看。

如果都配置对了,到这里就应该能正常访问了。

使用反向代理时,要特别注意一些事情。页面中 a 标签的 url 尽量使用省略域名的形式,例如 href="/login",因为如果你处理不好,写上了 http://localhost:3000 这个后端的域名加端口,点击后就打不开了。对于 30* 跳转,Location 的值如果是 “//localhost:3000/login” 这种省略了协议的形式,apache 的反向代理也是无能为力的,我在 express 某个非正式版发现了这个问题。建议 Location 也使用省略域名的形式,这样虽然不符合 HTTP 1.1 的规范,但是各个浏览器都能兼容,可以省去这样的麻烦。

等等,如果你和我一样,也使用了 socket.io 来提供 websocket 服务,你会发现,现在长链接虽然可以用,但是却不是走 websocekt 模式了,这是因为 websocket 是无法通过 mod_proxy 来反向代理的,那就要另外想办法了。我使用的办法是:直接让 websocket 的请求去连接应用监听的后端真实端口。

7, 如果你使用了 websocket ,可以在页面中埋点,把带有你应用真实监听端口的 URI 埋入页面,然后前端 js 获取此 URI ,再来 io.connect( server ) 就可以让 websocket 正常运行了。需要注意的是,这个带端口的 URI 的域名需要和你访问页面的域名保持一致,否则取不到 cookie ,就没法获取 session 了。

如果你有更好的方式,请在下面留言交流,或者新浪微博 @allenm 来交流

textarea 中的计数在前端和后端之间的差别

计算 textarea 中用户输入字符的个数,给出用户提示,并在前端和后端同时校验长度是非常常见的需求。但是在前端和后端,对于带有换行的文本的长度计算却有一点出入。

简单的说来,在前端和后端如果都直接计算字符的长度,那么,一个换行,在前端,长度计算是1,而在后端计算出来是2。

我写了个简单的 DEMO ,你可以点开看看,注意查看源代码。截图如下:

我们可以看到第三个字符对应的 unicode 编码,然后我们到 http://www.unicode.org/charts/PDF/U0000.pdf 这里来查看编码对应的字符,可以看到 61 对应的是字符”a”, a 对应 “NL(new line)”,62 对应的是字符“b” ,也就是说在 a 和 b 之间只有一个“NL”,所以,我们计算的长度为 5。

接着,我们提交表单试试,打开调试工具,选择录制请求,可以看到如下的结果:

可以看到,提交表单的时候,换行符就变成了 %0D%0A , 我们可以查到这两个字符对应的是 “CR(carriage return)” 和 “NL(new line)” ,所有后端在计算这个长度的时候,就变成 6 了。

那知道这个问题的存在了,解决办法就有好几种了,你可以在前端计算 length 的时候,把 \n 替换成 \r\n 再计算,和后端保持一致,也可以在后端做替换和前端保持一致。也可以在前后端都忽略掉换行符。具体的方法取决于你的业务场景。

感谢 xj.ye 的评论,在不同浏览器下表现也是不一致的,主要是 IE 和其他浏览器的区别。

尽快抛弃 photonVPS 这家烂 VPS

用了 photonVPS 这家 VPS 两年了,以前还觉得还将就用用,毕竟相对比较便宜嘛。但是自从今年过完年,续费了半年后,一切都变的非常糟糕,现在终于忍无可忍,迁移到了 Linode , 下面我来讲讲这家 VPS 到底有多烂。

我专门去后台看了下,从今年2月份,到现在,4个月左右的时间,我总共发了 12 个 tickets:

最常遇到的问题是,不能 ping localhost 、网络访问不了。从截图中可以看到,这些问题是经常发生。这些还不包括我没注意到的。毕竟我就是放个博客而已,很多时候也没注意到博客打不开。遇到这些网络问题后,客服想到的并不是去检查哪儿配置错了,而是经常询问我,我可不可以帮你重装系统啊,你要备份好数据哦。备份你妹啊,重装你妹啊。重装对于你们来说当然简单啦,全自动化的操作吧。系统装完我要配置软件环境吧,我哪儿有那么多的时间陪你们玩啊。

到后来,我每次发 ticket 都要事先说明,千万不要询问我是否可以重装系统,答案是不可以。这样也只能保证客服的第一条回复不提重装的事情,但是事情如果解决不了,第二条回复立马就问我可以重装系统吗?,艹,和这种人交流真是急死个人啊。他们的客服貌似不会关注你这条 ticket 的其他内容,只会关注你的最后一条,这个很让人着急啊,你经常需要重复的讲同一个问题,帮客服了解完整的处理过程。

另外他们号称提供中文客服,有一次,我还真写了中文的 ticket 试试,他们竟然回复英文,那是他们用机器把中文翻译成英文,然后让客服回答的吧。这种提供中文客服的方式也太坑爹了吧。

今年出的最严重的一个问题是,他们的物理硬盘挂掉了。造成写文件全部是 disk error, 到最后整个机器全部崩溃了,启动不起来了。由于写文件全部 disk error ,在最开始可以登陆,网络通的情况下,我想备份数据也备份不了了,因为无法打包。为了这个问题,交流了几天,最后他们才承认是物理硬盘坏了,VPS 系统文件已经损坏了。然后让我通过母鸡进去打包我要备份的数据,到指定目录,然后他们帮我备份数据,最后 rebuild 系统。我登陆进去,打包了一小部分数据,又 disk error 。真让人崩溃啊。还好的是,最后来了一个牛 B 的工程师,直接把 VPS 给恢复了,才得以没有灾难性的丢失数据。这个问题足足花了一天多才解决。首页还写着 uptime 99.9% 呢。这太扯淡了,我提到了他们说的 uptime 99.9% ,他们什么都没有表示。

以后不管在哪儿,只要遇到有人让推荐 VPS , 我就要去说,千万不要选 photonVPS , 然后贴这个博客的链接给他们看我的遭遇。

遇到有人问 photonVPS 好不好?photonVPS 怎么样?我都会去回复下,千万不要选这个,然后给出这个链接。

前天提的 ticket 说不能 ping localhost,这个会造成 wordpress 的后台自动更新功能不能用,因为它要连接本地的 FTP ,这个今天终于给我解决了,当然中间又少不了那句经典的:shall we rebuild your vps? Do you have any important data on vps? 我最后回复了:谢谢,但是我不再需要了,因为我已经迁移到 linode 了,再也忍受不了你们的垃圾服务了,你们动不动就问用户是否可以重装系统,太垃圾了。我会写blog告诉大家我的经历,让大家千万不要买你们的服务。再见。

============== 2013-7-3 update ==================

这个事情过去一年多了,到现在还有不少人能搜索到这里来,然后在下面留言。有些是已经上了贼船的,有些是上贼船之前看到这个博客的。不管怎么样,能够告诉大家我的遭遇,让更多的人避免受骗,我就觉得这个博客写的太值了。

现在不只是我自己的遭遇了,还有下面这么多网友的回复,都是 PhotonVPS 是个垃圾 VPS 服务商的最佳证据。

另外,我从写这篇博客开始,迁移到了 Linode,在这一年的时间里,只出现过一次访问不了的情况,最终通过发 ticket 换 ip 解决,大家应该明白这个不能访问是怎么造成的吧,不怪 Linode … Linode 响应 ticket 非常快速,而且很专业。这一年以来,维护 VPS 的工作量大减,省了不少心。前段时间 Linode 还搞了免费的硬件升级,买的时候内存 512M ,现在都免费到 1G 了,真是业界良心。

如果你觉得这个文章对你有帮助,并且你也准备买 Linode,不妨点这个链接去购买:http://www.linode.com/?r=434bcb5fd06364a329c09a0db255e22e09def513 。通过这个链接购买的用户,对你没有任何损失,但当你使用 Linode 90 天后,我可以获得 $20 刀用来后续购买 Linode 的服务。先谢谢各位了。

python中函数参数的默认值和List, dict

Python 的函数定义中,有种带有默认值的参数的语法,例如:

def foo( p = [] ): 
    print p

如果我们调用此函数的时候,没有传入参数 p ,那 p 就用默认值。

接下来,我们看看下面这段代码会得到什么结果:

def foo( p = [] ):
    p.append('a')
    print p

foo()
foo()

最开始,直觉告诉我,会得到结果:

['a']
['a']

但是实际结果是:

['a']
['a','a']

原来对于这种形式定义的带默认值的参数,参数的默认值是在函数定义的时候初始化的,当我们使用了 mutable 的对象的时候,我们中途改变了这个对象,在后面的函数调用中,它就不再是写在代码里的那个默认值。显然这样是很混乱的,我们不应该这样做。
python 官方文档也解释了这个事情,并且给出了解决方案,当我们需要一个 mutable 的对象作为默认值的时候,我们可以这样做:

def foo( p = None ):
    if p is None:
        p = []
    p.append('a')
    print p

这样每次都是生成一个新的,就没问题了。为了证实,带默认值的参数确实是在函数定义的时候初始化的,我们可以使用 id(p) 这个函数来查看这个变量的 identity , 可以看到,如果不传参数,让函数使用默认值,每次的 id 都是一样的。

解放你的 ALT(CMD)+TAB 键吧

做前端开发的童鞋们,最常敲的一组键就是 alt(cmd)+tab 然后紧接着是 F5 或者 ctrl(cmd) + r 了吧。这是在干什么?写好一段样式看看效果啊。

那我们能不能边写样式,边让浏览器自动加载最新的 css 呢?当然是可以的了,业界已经有很多这样的工具了。比如国内前段时间曾经成功推广过的 F5 ,还有国外的 live.js xrefresh 等工具。

但是这些工具都没有适合我的。F5 需要用它提供的 HTTP 服务,但是对于一个大型站点来说,静态资源都是单独管理的,页面上一般都写的绝对 URL 使用 cookie free 的域名或者 CDN 的域名。为了开发方便,我们一般会用 apache 在本地搭一个 style 环境,像我们现在,因为经常需要跨应用访问 css, js 这些,本地环境不一定有这些文件,还需要用 url rewrite 把这些文件 rewrite 到一个公共环境去。这些复杂功能都是 F5 所不能提供的。live.js 只能对 css 和 page 同域才有效,而且还不支持 @import ,xrefresh 也需要使用它提供的 HTTP 服务。而且这些工具都有一个共同点,是都需要往页面插入一段它的 js 。

于是,我就自己造了一个,优先满足我的需求,那就是要做到服务器无关,编辑器无关。然后要支持 css 和 page 不同域,支持 @import 。而我平时又使用 chrome 开发,那就写一个 chrome 插件好了。而且由于使用了浏览器插件技术,所以我也可以做到不在页面插入任何 JS,不改变任何 DOM 结构,尽量保证安全,不影响页面功能。

现在这个项目的源码托管在 github :https://github.com/allenm/css-auto-reload , 欢迎各位提 BUG和建议,或者来写一个其他浏览器的插件。

如果你和我一样喜欢使用 chrome 开发,那就直接来这里 https://chrome.google.com/webstore/detail/fiikhcfekfejbleebdkkjjgalkcgjoip 安装,使用很简单,只要点击插件图标,就会点亮图标,表示正在对当前标签页进行监控,再次点击会关闭监控。

详细介绍和使用说明可以看上面 github 项目的介绍。另外我还录了一段操作视频,你可以先看看,再决定要不要试试这个工具。

 

如果使用后觉得好用,确实对你有所帮助,不要忘记向你身边的 web developer 们介绍下哦,有心情的话,可以去 chrome web store 上面给我个好评哦。

jQuery deferred 对象的 promise 方法

jQuery 从 1.5 版本引入了 deferred 对象,这是一个基于 CommonJS Promises/A 的设计,为了方便异步操作,大家都知道,在 js 中,异步代码是非常的多。

这篇博客不是来详细讲解 $.Dererred 的,阮一峰 的博客里有一篇博客详细介绍了这个。但是在这篇博客中,关于 promise() 这个方法的讲解却是错误的,我发现这个是因为我徒弟去学习这个的时候,看了这篇文章,然后我让他给我讲述的时候,发现了这个错误。所以我想写篇 blog 来说明下。

update(2012.1.2):  我针对此问题给一峰发了 email, 他已经针对下面的问题进行了更新,下面的引用来自他最初的版本。现在他博客中对 promise() 的讲解是正确的,如果想了解整个  Deferred 对象,建议直接移步他的博客中学习。同时感谢阮一峰发现的我这篇博客中的一个小错误,最后面的那段代码,现在已经修复。

下面是引用自阮一峰的博客:

这里有两个地方需要注意。

首先,最后一行不能直接返回dtd,必须返回dtd.promise()。原因是jQuery规定,任意一个deferred对象有三种执行状态—-未完成,已完成和已失败。如果直接返回dtd,$.when()的默认执行状态为”已完成”,立即触发后面的done()方法,这就失去回调函数的作用了。dtd.promise()的目的,就是保证目前的执行状态—-也就是”未完成”—-不变,从而确保只有操作完成后,才会触发回调函数。

下面是 jQuery 的官方文档

The deferred.promise() method allows an asynchronous function to prevent other code from interfering with the progress or status of its internal request. The Promise exposes only the Deferred methods needed to attach additional handlers or determine the state (then,donefailalways,pipeprogress, and state), but not ones that change the state (resolverejectprogressresolveWithrejectWith, and progressWith).

也就是说,deferred.promise() 只是阻止其他代码来改变这个 deferred 对象的状态。可以理解成,通过 deferred.promise() 方法返回的 deferred promise 对象,是没有 resolve ,reject, progress , resolveWith, rejectWith , progressWith 这些可以改变状态的方法,你只能使用 done, then ,fail 等方法添加 handler 或者判断状态。

deferred.promise() 改变不了 deferred 对象的状态,作用也不是保证目前的状态不变,它只是保证你不能通过 deferred.promise() 返回的 deferred promise 对象改变 deferred 对象的状态。如果我们这个地方直接返回 dtd,也是可以工作的,.done 的处理函数还是会等到 dtd.resolve() 之后才会执行.

具体在那篇博客的例子, 如果我们把代码改成如下的形式:

var dtd = $.Deferred(); // 新建一个deferred对象
var wait = function(dtd){
    var tasks = function(){
        alert("执行完毕!");
        dtd.resolve(); // 改变deferred对象的执行状态
    };
    setTimeout(tasks,5000);
    return dtd;
};
$.when(wait(dtd))
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });

这样的执行结果和先前返回 dtd.promise 的结果是一样的。

差别在什么地方呢?如果我们把 $.when 的这块的代码改成这样的:

var d = wait(dtd);
$.when(d)
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
d.resolve();

我们会发现 alert(“哈哈,成功了!”) 会立即执行,“执行完毕”却需要5秒后才弹出来。

但是如果我们 wait 函数最后是 return dtd.promise() 这里 d.resolve() 就会报错了,因为对象 d 不存在 resolve() 方法。

同样如果我们把代码改成:

var dtd = $.Deferred(); // 新建一个deferred对象
var wait = function(dtd){
    var tasks = function(){
       alert("执行完毕!");
       dtd.resolve(); // 改变deferred对象的执行状态
   };
   setTimeout(tasks,5000);
   return dtd.promise();
};
dtd.resolve();
$.when( wait(dtd))
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });

我们也可以发现 alert(“哈哈,成功了!”) 会立即执行,因为 dtd 这个 deferred 对象在被传入 wait 之前,已经被 resolve() 了,而 deferred 对象一旦被 resolve 或者 reject 之后,状态是不会改变的。

然后我们再把 $.wait 这块的代码改成:

$.when( wait(dtd))
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
dtd.resolve();

我们也会发现 alert(“哈哈,成功了!”); 被立即执行,虽然 wait(dtd) 执行的时候, dtd 还没有被 resolve,而且 wait 方法返回的是 dtd.promise(), 但是 dtd 这个原始的 deferred 对象是暴露在外面的,我们还是可以从外面改变它的状态。

于是,如果我们真的不想让其他代码能改变 wait 方法内部的 deferred 对象的状态,那我们应该写成这样:

var wait = function(){
    var dtd = $.Deferred(); // 新建一个deferred对象
    var tasks = function(){
        alert("执行完毕!");
       dtd.resolve(); // 改变deferred对象的执行状态
   };
   setTimeout(tasks,5000);
   return dtd.promise();
};
$.when( wait())
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });

也就是不要把 deferred 直接暴露出来,最后返回 deferred.promise() ,让其他地方的代码只能添加 handler 。

2011个人总结

在开始写这篇博客的时候,还有20分钟就要到2012了。我觉得我还是需要来总结一下的,仔细回顾一下这一年我做了什么,什么做好了,什么没做好。

首先,打开了一年前这个时间写下的日志, 先看了下希望能做到的事情。第一条,购入一台Mac,这个完成了,在3月份买入的,这个投资现在看来是值得的。第二条,做了,但是做的不够好,开始有意识的节流,但是积蓄还是很少,这个来年继续做吧。第三条,尝试读了心理学的,还尝试读了小说,第一次读完一个长篇小说《1Q84》。第四条,几乎没做,不过在这个夏天,还是通过游泳、瑜伽、普拉提成功减肥10斤左右。第五条,这一年我总共写了8篇博客,太少了。第六条,我还是单身。

这一年,我做了什么呢?现在想起来,好像还真是充满坎坷呢。是本命年的缘故吗?不,我从来不信这个,连红内裤都没穿。这也许就是成长路上必须经历的吧。先是信心满满的开始做一个大项目,和团队合作的很愉快,但是在项目一期快完成的时候,项目却被停了,所有的努力都白费了,和失去了自己心爱的东西是一样的感觉。不过不管怎么样,也是有收获的,比如在技术上的积累,以及认识了一些朋友。

这个半途而废的项目结束之后,就开始做了很长一段时间小需求,还遇到了几个非常不靠谱的人,然后发了几次脾气,被投诉了几次。那段时间还被老大拉去谈话,说我这个要改。哎,我也不想为了这些事情去和别人吵架,但是每次遇到这些不靠谱的人的时候,我就很难忍住,总觉得和他们合作太浪费我的生命了。

不过还好,这样的日子不算太长,接下来由于架构调整,就去做我的阿里这个产品线了。下半年的大部分日子都在做这个,状态不错,主要是这个产品线的其他同事都比较靠谱,和靠谱的人合作,心情和效率都会很不错。接下来还要继续在这个产品线,我也有一些关于这个产品线的前端方面的规划,手头项目忙完,会好好整理一下。

下半年,还有一个项目对我的锻炼也蛮大,是 web IM 项目,这个项目前期,我就想了很多,然后我想根据需求想了一整套设计方案,来适应当前的应用场景和未来的应用场景,然后去给项目组其他成员讲这些,得到了一致认同。不过悲剧的是,项目中途遇到了一些技术难题(主要不是我负责的这块),然后当时很难搞定,就暂停了,这个我也有一部分这人,在项目前期,没有认真考察这些技术的可靠性,就想当然的开始编码了。还好现在这些已经有了进展,不过是其他同事在跟进,我实在是抽不出时间了。

好了,工作上的事情就到此为止,再回顾下生活上有什么进展吧。

首先,到现在我还是单身,好像也从来没怎么去努力摆脱这个现状,这个要批评自己了,2012要好好努力,哪怕世界毁灭,也希望能有一个心爱的人和我一起欣赏最后的风景。

一个人在外,每次有朋友来杭州都特别高兴,今年在杭州还是见了不少朋友,也是你们,让我在杭州的生活增添了不少色彩。

今年去了上海,去了南京,去找老朋友们玩,每次都玩的很开心,和这些老朋友们相聚,总是会回忆起学校的美好时光。遗憾的是,一直说的要去苏州的,甚至有次酒店都定好了,结果由于一些原因没有去,就再也没去过了,现在苗苗都走了,更不会去了。

另外今年还和同事们一起去了徽杭古道,一起去了舟山溗泗,一起去了西塘,每次都玩的很开心,感谢有这么一群可爱的同事们。去徽杭古道让我体验了一下睡帐篷的感觉。去溗泗我第一次下海水游泳,第一次尝到了海水的味道。西塘是我去的第一个江南古镇,虽然我觉得最好玩的是晚上大家在酒店玩狼人。

2012, 我要见更多的老朋友,去更多的地方看看,也要带爸妈来杭州看看。

2012,我要继续减肥,为了自己的健康,希望不再有脂肪肝。

2012,我要阅读更多种类的书籍,来完善自己。

2012,我要学会更合理的消费,开源节流。

2012,我要继续写博客,继续分享我认为值得分享的东西。

2012,我要在技术上精益求精,大胆尝试新技术,让新技术能和产品完美结合起来。

2012,我要学会控制自己的情绪,但是对待事情要同样的认真。

2012,我不要再一直一个人过。

Mac 在无法启动的情况下备份数据

上周,我的 MBP 悲剧的突然出现了无法启动的情况,启动的时候,出现一个进度条,然后这个进度条走不完,就会被关机。阅读说明书上的方法,先进从 recovery 盘启动,用磁盘工具检查硬盘,可以发现磁盘错误,但是尝试紧急恢复总是失败,然后又尝试用系统盘进入磁盘工具检查硬盘,结果一样,可以看到错误,但是无法修复。这个时候,我决定格式化硬盘重装系统了,但是数据还没备份出来呢,这个时候也没办法进入系统,没办法挂载这块有问题的硬盘。后来经过其他人的提示,想到了 single-user mode ,试了下,还真成功备份出数据来了。不过有一些技巧,下面记录下在 single-user mode 环境下,如果挂载移动硬盘用来转移数据。

1,连接好移动硬盘,然后启动 Mac 到 single-user mode (启动的时候按住 command + s ),记住,一定要在启动前连接好移动硬盘,中途插上,我试了好几次都没成功。

2,按照提示,输入
/sbin/mount -uw /

/sbin/fsck -fy
两个命令用来用读写模式挂载硬盘和检查磁盘。这个时候,如果你的情况和我一样,这个时候 /sbin/fsck -fy 应该会报出错误,但是没关系,接着往下操作。

3,执行
ls /Volumes
这个命令将显示所有被挂载的磁盘,你的移动硬盘或者U盘很可能还没在这里显示,那就说明还没有被挂载。没关系,如果没被挂载,我们先在这里创建一个挂载点。例如:
mkdir /Volumes/usb

4,我们需要确定挂载的移动存储设备的磁盘编号:
ls /dev/disk*
一般情况下,移动存储设备的编号在最后面,例如 /dev/disk1s1

5,现在,挂载移动存储设备到刚才创建的挂载点,执行如下命令:
/sbin/mount_msdos /dev/disk1s1 /Volumes/usb
由于我的硬盘格式是 FAT,如果你使用的硬盘或者U盘格式不是这个,可以在 /sbin/ 这个目录里找到对应的 mount 工具,执行命令。

6,如果挂载成功,你现在可以通过运行:
ls /Volumes/usb
查看移动存储设备内的文件了。

7,可以尝试从机器内的硬盘复制文件到移动存储设备了:
cp /Users/your_user_name/Documents/somefile /Volumes/usb/
如果成功,那么就可以把你的重要数据备份出来了。

这个方式是参考自:http://www.macsage.com/mounting-usb-drive-in-single-user-mode/

另外,如果你有办法在 single-user mode 下搞定网络问题,那么也可以通过 SSH,FTP等方法来备份,single-user mode 下面还是有蛮多的 UNIX 标准工具。

如果你的 Mac 硬盘遇到和我一样的问题,希望这个文章能帮到你。

WebSocket Protocol 介绍与实现

今天在方凳会上分享了 websocket protocol以及简单实现,PPT 和代码都放出来,有兴趣的可以围观

然后用 python 简单的实现了 websocket server,代码如下。在网上找的很多代码,都不再兼容 websocket draft 10, 因为 websocket 的草案进化太快,下面的代码是按照 draft10 写的。 chrome 14+ , firefox 7/8 都没有问题。

关于 websocket 草案,请阅读 http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10

 

另外推荐一个 Python 的 websocket server http://www.tavendo.de/autobahn/home.html, 基于 twisted 的异步模型。