一周一行

一周一行

深圳取消了“一签多行”,改成“一周一行”。这是打着反水客旗号赤裸裸地歧视深圳人!梁特首感谢深圳政府的理解,而身为深圳人,我对这个决策无法理解,更感到愤怒!

在来往香港的几百万深圳人中,有多少是水客?这种做法的直接意义就是视全体深圳人为水客!更何况,水客中有至少一半是港人自己吧?这完全是把水客的脏水全部泼到深圳人身上!

没有别的手段打击水客吗?两地海关都不能掌控吗?扯蛋!

港府这是对港灿姑息养奸,对深圳(大陆)忘恩负义、过河拆桥!而内地海关(或者决策部门)也是一味地迁就香港,完全漠视深圳居民的尊严和感受!

hg小技巧

hg小技巧

比起大名鼎鼎的git,mercurial/hg相对来说默默无闻一些,不过我们一直使用hg,而且感觉还不错,与git相比该有的都有,不该有的都没有。除了clearcase,hg是我非常喜欢的版本管理工具。

hg在branch管理上相比clearcase还是有很大差距,在日常工作中,需要采取一些变通方式。例如以下一些场景:

只显示active的分支

加上参数“-a”即可,完整命令如下:

hg branches -a

如果不想每次都带上参数,默认就只显示active的分支,可以修改hgrc文件,加入以下内容:

[alias]
branches = branches -a

关闭分支

实际上我们希望有“删除分支”的功能。在平时工作中,多半有这样的场景:为了查某个问题需要修改代码,为了不影响开发分支或者主线分支,通常都会创建一个临时分支,加入一些打印、调试、定位代码甚至变更处理逻辑等代码。问题定位后,这些代码不需要merge进主线,分支也没有存在意义,最好就删除了事。遗憾的是hg不提供删除分支的功能,因此我们采用“关闭分支”的处理方式:

hg commit --close-branch -m "finish debug, useless now"
并发数

并发数

QQ群中有几位朋友在聊呼叫系统性能的问题,默默地观察了一段时间,感觉大家对一些基本的技术术语其实都没有澄清,比如并发数。

“并发”一般理解为“同时呼叫数”,很多朋友往往将ta误解为“同时试呼数”。“并发呼叫”英文术语是Concurrent Calls(CC),而“同时试呼数”英文术语一般是Calls per second(CPS)。从英文的意思来看其实就更明白一些。

CC和CPS都是衡量呼叫系统性能的重要指标,两者也有一定的联系,这涉及到另一个术语:平均通话时长。通常情况下,根据统计结果,一般呼叫系统中的平均通话时长大约为100秒。当然某些通话时段(例如晚间)、某些特殊人群(比如爱煲电话粥人士)的统计结果有很大差异,但就总体统计而言(尤其是企业通信领域),“100秒”是个相当有代表性的统计结论。

假如“平均通话时长”是100秒,那么CC和CPS的关系就是:CC = 100 × CPS。

例如,有位朋友要求系统能支持100个并发呼叫(CC=100),那么CPS只要1(CPS=100/100)就可以了。也就是每秒只需要支持1个呼叫,这对大多数呼叫系统而言都能轻松支持。

而如果要求能支持到每秒100个呼叫(CPS=100),那么系统资源就必须按照10000(CC=100×100)并发呼叫的容量去设计和考虑。这实际已经是中型呼叫系统的指标了,绝大多数基于Asterisk或者FreeSwitch的小型呼叫系统如果不做特殊修改或者定制,不可能支持这个性能要求。

在没有弄清楚CC和CPS含义的情况下,胡乱提要求或者回答问题是会闹笑话的。比如QQ群里一位大侠吹嘘自己呼叫系统的性能指标,按照上述计算公式,居然可以支持到3亿并发呼叫,也就是说只要四套这个系统,就可以让全中国的人同时打电话!

差点被吓死了。

php坑:ftok

php坑:ftok

最近有个小需求,需要php程序和服务器程序之间进行一些简单通信。调研了几个进程间通信的技术,选择了消息队列方式。

消息队列技术本身不复杂,无非就是生成一个ID,然后使用该ID发送消息或者接受消息。在C程序中,使用ftok来生成ID,而php同样提供了相同名字的函数。因此,在php代码中,想当然地写下了类似语句:

$key_t = msg_get_queue(ftok("/home", 2));

让人惊讶的是,C程序始终接受不到php发送的消息。百思不解之下,使用命令查看系统的消息队列情况:

ipcs -q

结果表明php程序的确在发送消息,只是php的消息队列ID与C程序的消息队列ID居然是不一致的。重新翻看了php的手册,对ftok函数是这么描述的:

int ftok ( string $pathname , string $proj )

值得注意的是:第二个参数居然是字符串型。而在C函数中,该参数定义为int型:

key_t ftok(const char *pathname, int proj_id);

因此,在php代码中,当我们传递整数2给函数ftok时,php转换成了字符‘2’,也就是说,上述示例的php代码实际相当于以下语句:

$key_t = msg_get_queue(ftok("/home", '2'));

导致最终的计算结果与C函数不一致。修改方式也简单,在C程序中,将第二个参数修改为0x32,与php一致即可。

无法理解php为什么将第二个参数改成字符串型,实在是多此一举,而且毫无意义。

贫瘠的土壤

贫瘠的土壤

周末带儿子去南山书城逛,打算买点书在寒假看。

在书城居然看到有“羊年喜羊羊”的漫画书卖了!儿子非常喜欢喜羊羊动画,前两天我刚带着去电影院看了最新的“羊年喜羊羊”动画电影。不禁感叹喜羊羊算是我们大陆比较成功的本土动画形象,商业运作也很成功啊。儿子非常喜欢看这漫画书,尽管已经看过电影了,还是抱着书坐在地上津津有味地看。也非常感谢南山书城提供的环境,真是很不错。

很显然很多小孩都喜欢喜羊羊漫画,儿子身边坐着一位差不多年龄的小朋友拿着也是同一本书。儿子很高兴,和这位小朋友聊得不亦乐乎。小朋友兴奋地说,马上就可以看“羊年喜羊羊”动画电影了。我赶紧告诉他:已经上映了!小朋友白了我一眼说:“在网上看”。我郁闷了,问了句:“这么喜欢的话,为什么不去电影院支持一下呢?”。小朋友估计觉得我是个白痴:“那还不得花钱啊?!”

那还不得花钱啊。。。是啊,当然是要花钱啊,不然原创的人员吃什么?人家也是人,要吃饭,家里也有小朋友要养啊。我看了小朋友几眼,一身名牌,家底不薄,感觉实在很无语。

儿子决定买下喜羊羊漫画。一路上拿着书,跟我说个没完里面的情节。真心希望明年能继续看到喜羊羊动画电影。

golang

golang

在Ubuntu14.04系统中非常容易安装golang,系统库中就自带所有安装包。不过网上的一些文章往往是直接取官方的源码进行编译,不知道这样做的好处是什么?跟踪最新的进展?这是不是反映golang还不够稳定,还在不断进化过程中?

如果不是很想追新的话,直接使用以下命令就可以安装了:

sudo apt-get install golang

简单查了一下,默认是安装golang的1.2.1版本。

嵌入式HTTP服务器设计

嵌入式HTTP服务器设计

定义

所谓“嵌入式HTTP服务器”包含这么几个意思:

(1)TA首先是微型的,不能太庞大。往往成为其他应用程序的一个组成部分,提供HTTP接口(包括RESTful接口)或者HTTP服务(例如WWW服务)。

(2)功能有欠缺,不是全功能服务器。比如,肯定不能像Apache、Nginx这样提供全面的HTTP支持。只能提供一个最小化的、需要定制化的功能集。

现状

在miniSIPServer中,我们以前采用mongoose这个轻量级http服务器来提供web接口。mongoose基本符合要求,但也很难让人感觉满意。主要存在以下一些问题:

(1)代码凌乱。是的,这个问题比较严重。实际上我们花了不少时间来清理TA的代码,但是效果不太好。TA的实现方式有不少问题,各逻辑层次之间解耦不充分,因此在编写应用层代码(尤其是ajax接口代码)时,不得不大量重复使用一些要求的宏定义,造成代码繁杂紊乱。

(2)定位不清晰。如果单纯定位为嵌入式服务器,可能mongoose的发展会好很多。但是我们看到,ta的代码之所以凌乱,有一个重要原因就在于:试图成为一个全功能集的http服务器。大量功能集堆积在一起,又没有处理好模块关系,结果就和意大利面一样绞成一团。

(3)原维护者似乎已经放弃这个产品。从代码库看,更新不是很频繁,缺乏关注。后续一些问题看不到解决的希望。在具体实现时,不得不小心翼翼地规避一些问题。

(4)IPv6支持不足,尤其不能适应IPv4和IPv6混合组网的情况。

新版的miniSIPServer设计中彻底抛开mongoose代码,重新实现自己的嵌入式HTTP服务。从效果看很不错:不考虑服务器自身的代码,仅仅看应用层的代码,就从原来近7K行,直接砍到了3K行。

新设计

当然,设计一个Apache或者Nginx是很复杂而且艰难的事,但设计一个嵌入式HTTP服务器的确很容易。简单来讲只有以下几个步骤而已:(1)打开TCP端口;(2)接受客户端连接;(3)收客户端的HTTP请求;(4)返回HTTP响应消息;(5)关闭TCP连接。

TCP网络编程是通用的,windows平台可以用IOCP,linux平台可以用epoll等。MSS作为SIP服务器,早就封装好了底层的通信库,因此仅仅需要关注步骤(3)和(4),读一读HTTP协议,根据HTTP的要求进行相应处理即可。

HTTP协议

最新的HTTP协议已经定义到HTTP/2.0,但现网大部分都还是HTTP/1.1,另外考虑到2.0主要是在SPDY以及加密等方面的修改,而这些对嵌入式HTTP设计而言不是很必要的,因此支持HTTP/1.1就好。

原HTTP协议定义在RFC2616,目前已经修改为多个RFC文档共同定义:RFC7230~RFC7235。实际上我们只需要关注前三个RFC文档:

  • RFC7230 “Message Syntax and Routing”。这是最基础的协议。定义了HTTP的基本交互模式,基本消息结构等。
  • RFC7231 “Semantics and Content”,定义了HTTP消息语法,包括RequestLine以及各Header等定义。SIP的语法就是基于这类定义。
  • RFC7232 “Conditional Requests”。主要是考虑一些有条件的请求及响应。最普遍的就是重复请求网页时,可以判断是否需要每次都返回内容,也可以要求客户端直接使用cache内容,节省网络流量,节约处理能力。

HTTP请求

与SIP的startLine是完全一致的,由method、requestTarget以及HTTP协议号三部分组成。method有多种,例如GET、POST、PUT、DELETE等。我们仅仅支持GET即可。

在请求消息的各项Header(头域)中,我们仅关心以下两个Header:

  • “Cookie”头域。用来传递Cookie参数。在web管理系统中,我们需要设置cookie确保用户登录系统后能继续使用各项管理界面。而没有cookie或者设置不正确的用户,将被视为非法用户。
  • “If-Modified-Since”头域。这是个非常有必要的域。一旦服务器发现客户端请求的内容没有变更,可以直接返回304响应,要求客户端直接用以前的结果就好。

HTTP响应

响应消息和SIP一样,也是定义1xx~6xx。实际设计中我们仅需要关心1xx、2xx、3xx以及4xx即可。响应消息没什么好说的,无非就是各种ContentType等,读取文件,通过TCP连接发给客户端即可。

与SIP比较而言,HTTP最大的特点是无状态。一个“请求-响应”即构成了一个完整的HTTP动作过程。因此HTTP可以利用LVS、HAProxy等技术支持大规模并发。而SIP受限于对话状态,多个“请求-响应”之间是可以关联的(例如INVITE和reINVITE),只能通过ALG、Proxy等呼叫分发来支持大规模的应用。

HTTP路由

具体内部实现时,采用action模式,根据HTTP请求的URL进行索引,用URL对应action即可。服务器本身可以设置一些过滤接口,也可以向应用层提供action接口,由应用层决定各URL对应的处理过程。这点在处理AJAX请求或者提供RESTful接口时尤其有用。

其他

我们的设计目前只支持HTTP。对于HTTPS,理论上而言也不是很困难,无非就是加载CA文件,在TCP建立时进行TLS/SSL鉴权,后续处理与HTTP没有差别。

另外就是安全防护问题,例如防DDOS攻击等,我们没有考虑这方面。虽然应用层可以利用过滤接口或者action接口进行一定程度的防护,但主要的保护依赖于边界网关,毕竟嵌入式HTTP服务器只是用来方便设备管理和配置,不是提供通用HTTP服务的。

内地女大学生遭抵制

内地女大学生遭抵制

原链接请点击这里

这个新闻里有个香港学生的贴子,让人非常反感的是以下几点:

(1)称呼中国为“支那”。我理解香港学生对大陆不认同,但是称呼“支那”就太过分了。“支那”是当年日本对中国的侮辱性称呼,香港学生不应该连这点常识都没有。又或者是知道这个称呼的含义,仍然故意这么称呼内地生,这就更不能接受了。

(2)“与中国有关的,都是有害的”。这完全无法让人理解。

(3)“摸到候选人家里,追问亲友。。。”这已经是完完全全、赤裸地威胁和侵犯隐私,就差没直接一棍子敲死拖走了。很难相信这居然是出自号称文明、民主、法制的香港大学学生之口。这简直是以前大陆文革所用的语言。可以想象,如果这类学生掌握权力,他们干出来的事会比他们所反对的人更坏。

(4)“港大学生会是法人团体,也不等于校园电视要受香港法例约束。。”这太吓人了。法人团体可以跳出香港法例约束? 可不可以说这是目无法纪?!这真是港大的学生吗?

去掉google fonts

去掉google fonts

在大陆的网络打开这个blog的速度非常慢,基本上都是几十秒才能打开。简单搜索了一下,根本原因是大陆的网络屏蔽了google的大部分IP地址,而wordpress的默认主题使用了google fonts。这就导致整个加载过程极其缓慢。

解决方法也比较简单。显然很多人都被这个问题给困扰了,因此有位朋友就制作了一个插件,名字就是”disable google fonts”,在wordpress的plugins库中能搜索到。

安装上述插件并激活后,blog的访问速度立刻就提升了很多。

为什么没有选择sipml5

为什么没有选择sipml5

有多种技术和实现方式可以将SIP与webRTC两个世界连接起来,比如我们的miniWebPhone/miniSIPServer以及sipml5等。当然,最早出现的是sipml5以及与TA配套的webrtc网关。既然已经有了sipml5,为什么我们在设计和实现miniWebPhone(以下简称MWP)时,不采用现成的解决方案呢?

回答这个问题之前,请先粗略的看一下完成后的情况。sipml5的javascript文件大小超过2MB,而MWP的javascript文件是20KB。仅仅对比这两个数据,我就认为我们的决定非常正确,sipml5实在是太臃肿了!

造成sipml5如此庞大的根本原因在于:TA的目标是在浏览器端用javascript来实现一个完整的SIP协议栈及呼叫处理过程。理想很丰满,现实太骨感。

我想sipml5的设计者被HTTP与SIP之间的相似性给迷惑了。两者的确都是基于文本格式,SIP甚至都基本遵循HTTP的消息定义,但是两者却有最根本的区别:HTTP本质上是无状态、无层次的协议,而SIP是有严格的状态,不仅有transaction的状态,也有session和dialog状态。同时SIP又是多层次的,包括transaction、session、UA等不同的层次。当你用一个无状态、无差别的协议模式强行去套一个多状态、多层次的模式,工作量无疑是巨大的。

而对javascript语言而言,其实并不擅长去解析或者分析类HTTP协议格式的文本。而SIP协议虽然采用HTTP协议的文本格式,但是在会话过程中,不仅仅要解析到header层面,还要进一步解析内部各种参数。这种情况就更加不是javascript擅长的。因此可以看到sipml5不得不耗费大量的处理过程去解析SIP协议的细节。javascript擅长处理什么文本格式呢?JSON!因此在miniWebPhone的设计和实现过程中,我们理所当然地采用JSON来重新定义消息格式。

让我们再看看服务器端的设计。这又是另一个让人很纠结的地方。由于浏览器不支持开UDP和TCP连接,只支持websocket连接(本质上其实还是个TCP连接),sipml5的设计者们不得不引入SIP over websocket(这个定义到现在还处于draft状态)。而这要求客户端和服务器两端都必须修改才能支持。虽然websocket与TCP几乎没有区别,但是对SIP协议栈、SIP会话层面的处理来说,可不是仅仅重用TCP处理那么简单,服务器端的工作量同样巨大。

说到这里就稍微跑跑题,让我们先吐槽一下浏览器的实现者们。当浏览器支持websocket的时候,实际上就已经支持了TCP,为什么不向应用层开放TCP连接能力?websocket本质上就是个TCP连接,只有开始的两个握手消息是HTTP格式,后续跟HTTP一点关系都没有。同样,既然已经支持了webRTC,为什么不向应用层开放UDP连接能力?打开一个SRTP端口和打开一个UDP端口同样一点区别都没有。如果浏览器开放了TCP和UDP连接能力,哪怕仅仅开放UDP能力,sipml5的开发者也不用一边哭一边改设计,更不用搞出“SIP over websocket”这么个爷爷不疼、姥姥不爱的东西了。

让我们回到原点。分析了这些困难和不足,既然服务器(或者网关)死活都要修改,那我们为什么不把工作量集中到一端,从而解放另外一端?因此我们放弃sipml5,重新思考:

客户端无疑还是必须基于webRTC和javascript的。但是消息格式不再是HTTP或者SIP格式,而是JSON格式,这样javascript就可以轻松处理。客户端采用无状态方式,呼叫的状态由服务器端来维持。这就是MWP的javascript文件仅仅20KB就ok了的根本原因。

既然客户端采用了JSON格式的消息,因此服务器端也要相应作出设计。主要工作无非就是转码成SIP消息格式并维持websocket连接,其他处理仍然可以沿用目前已有的SIP流程。而我们要做的,仅仅是在客户端和SIP之间做个转换层而已。