我准备用WebSocket协议来承载RPC

先说RPC的始祖,sun rpc。它的协议很简单。首先找rpcbind问下,这个用户服务在哪个端口监听。然后连上去,每个请求和答复都是 (length、type、序列化后的二进制)这样的形式。

首先,length是必须的,因为tcp是一个流,我们需要对它切片,切成一个个的message。

其次,type也是必须的,接收方要根据type去找,调用哪个反序列化函数。

rpcbind解决了一个问题,如何在一个服务器上启动多个网络服务,但是只占用一个固定端口。嗯,这个在今天看起来不是很必要。也许架个naming service会解决的更彻底一点。

而完美在北京开发的所有游戏,除了某个还未上线的外,全采用的是这样的消息格式。Good? Or More Better?

WebSocket,作为HTML5的一部分,主要为了解决一个问题:如何在http服务器和客户端之间进行双向的socket通信。它经过的很多变迁,现在的版本也是Length-prefixed。但是,和前面所说的相比,它做了两个改进:

  1. 对length做变长编码。这个完美也做了。但是我绝对不认为这是一个好的设计。为这个我专门去找过panpan。因为你的length字段是变长编码,所以我必须先序列化body,然后得到长度。然后往流里面写长度,然后把序列好的body copy过来。于是这就多了一次memcpy操作。如果长度是固定的,我可以先留好了空,最后再填啊。其实,除非大部分协议都很碎小,否则根本省不了多少流量,连1%都不到。何苦呢,搞这么麻烦,不如选一个好一点的协议层压缩算法。

  2. Framing。回忆一下,传统来说,HTTP的header都会有一个Content-Length字段。指明Body的长度。但是为什么要有chunked编码?因为有时候回复的body很大,写header的时候根本不知道body有多大,而body是一边处理一边往外发。所以在websocket协议的第一个字节的最高位,标识这个包是不是最后一个,FIN。如果不是最后一个,接收的人要负责组装。啊 !这下好了。我可以分配一个64K的buffer,拿一个object往里塞。如果buffer塞满了,但是object还没序列化完,没关系,我把header一填,扔出去。继续写。这是非常高效的做法。如果没有这个功能,那么我们就得先分配一个128K的buffer,然后把这64K复制进去,然后继续写。再或者,像google protocol buffers那样,提供一个computeSize函数。先计算一次需要多大空间,然后真的执行序列化。

WebSocket协议从某种程度上也解决了我前面提的 ,如何在同一个固定端口上,提供多个服务的问题。因为服务器收到的第一个包是一个HTTP包,第一行里面有URI,后面的headers可能有Host字段,所以rpcbind完全可以替换成一个支持connect方法的http proxy。

Websocket协议也有一些我不想要的地方,它的安全部分。比如,handshake阶段,客户端生一个随机数,然后服务器拿着这个随机数加上一个公开的固定值做sha1发回去。请问,这样的方式,是为了何种安全目的?能防范什么样的攻击?!

其次,最让我想不通的是,client发过来的协议,必须要加密。加密的方式很简单,客户端生一个32位随机数,然后拿明文跟这个随机数做异或。最后,把这个随机数(也就是密钥)和加密后的密文放在一个包里,发给服务器。

我真心的想问,为啥呢?!!!!

websocket协议的body部分(官方术语:payload),可以采用两种格式,text/binary。这个在包头有描述。我刚测试了一下,chrome16已经支持binary格式了,虽然这一点未在它的任何公告中看到。于是我就想了一个很妙的搭配:websocket+protocol buffers+javascript。用websocket+protocol buffers做通用的RPC规范,配合http proxy、SSL、HTTP Auth等手段,可以把安全性和负载均衡做的很好,利用Cookie可以很容易和web网站部分做SSO。而用html+js,可以快速的搭建很多监控、调试、管理页面。而核心业务部分,可以用C++/JAVA/PHP等任何一个你所熟悉的语言去写。websocket协议的实现代码最多也就300-500行,可以很方便的嵌入到任何一个现有的tcp server中。

此博客中的热门博文

少写代码,多读别人写的代码

在windows下使用llvm+clang

tensorflow distributed runtime初窥