Websocket协议简介 websocket握手协议
webAdd comments
一302012
今天@julyclyde在微博上问我websocket的细节。但是这个用70个字是无法说清楚的,所以就整理在这里吧。恰好我最近要重构年前写的websocket的代码。
众所周知,HTTP是一种基于消息(message)的请求(request )/应答(response)协议。当我们在网页中点击一条链接(或者提交一个表单)的时候,浏览器给服务器发一个request message,然后服务器算啊算,答复一条response message。主动发起TCP连接的是client,接受TCP连接的是server。HTTP消息只有两种:request和response。client只能发送request message,server只能发送response message。一问一答,因此按HTTP协议本身的设计,服务器不能主动的把消息推给客户端。而像微博、网页聊天、网页游戏等都需要服务器主动给客户端推东西,现在只能用long polling等方式模拟,很不方便。
OK,来看看internet的另一边,网络游戏是怎么工作的?
我之前在一个游戏公司工作。我们做游戏的时候,普遍采用的模式是双向、异步消息模式。
首先通信的最基本单元是message。(这点和HTTP一样)
其次,是双向的。client和server都可以给对方发消息(这点和HTTP不一样)
最后,消息是异步的。我给服务器发一条消息出去,然后可能有一条答复,也可能有多条答复,也可能根本没有答复。无论如何,调用完send方法我就不管了,我不会傻乎乎的在这里等答复。服务器和客户端都会有一个线程专门负责read,以及一个大大的switch… case,根据message id做相应的action。
while( msg=myconnection.readMessage()){
switch(msg.id()){
case LOGIN: do_login(); break;
case TALK: do_talk(); break;
…
}
}
Websocket就是把这样一种模式,搬入到HTTP/WEB的框架内。它主要解决两个问题:
从服务器给客户端主动推东西。
HTTP协议传输效率低下的问题。这一点在web service中尤为突出。每个请求和应答都得带上很长的http header!
websocket协议在RFC 6455中定义,这个RFC在上个月(2011年12月)才终于定稿、提交。所以目前没有任何一个浏览器是能完全符合这个RFC的最终版的。Google是websocket协议的主力支持者,目前主流的浏览器中,对websocket支持最好的就是chrome。chrome目前的最新版本是16,支持的是RFC 6455的draft 13,http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-13。IE9则是完全不支持websocket。而server端,只有jetty和Node.js对websocket的支持比较好。
Websocket协议可以分为两个阶段,一个是握手阶段,一个是数据传输阶段。
在建立TCP连接之后,首先是websocket层的握手。这阶段很简单,client给server发一个http request,server给client一个http response。这个阶段,所有数据传输都是基于文本的,和现有的HTTP/1.1协议保持兼容。
这是一个请求的例子:
Connection:Upgrade
Host:echo.websocket.org
Origin:http://websocket.org
Sec-WebSocket-Key:ov0xgaSDKDbFH7uZ1o+nSw==
Sec-WebSocket-Version:13
Upgrade:websocket
(其中Host和Origin不是必须的)
Connection是HTTP/1.1中常用的一个header,以前经常填的是keepalive或close。这里填的是Upgrade。在设计HTTP/1.1的时候,委员们就想到一个问题,假如以后出HTTP 2.0了,那么现有的这套东西怎么办呢?所以HTTP协议中就预定义了一个header叫Upgrade。如果客户端发来的请求中有这个,那么意思就是说,我支持某某协议,并且我更偏向于用这个协议,你看你是不是也支持?你要是支持,咱们就换新协议吧!
然后就是websocket协议中所定义的两个特殊的header,Sec-WebSocket-Key和Sec-WebSocket-Version。
其中Sec-WebSocket-Key是客户端生的一串随机数,然后base64之后填进去的。Sec-WebSocket-Version是指协议的版本号。这里的13,代表draft 13。下面给出,我年前写的发送握手请求的JAVA代码:
// 生一个随机字符串,作为Sec-WebSocket-Key
?View CodeJAVA
byte[] nonce = new byte[16]; rand.nextBytes(nonce); BASE64Encoder encode = new BASE64Encoder(); String chan = encode.encode(nonce); HttpRequest request = new BasicHttpRequest("GET", "/someurl"); request.addHeader("Host", host); request.addHeader("Upgrade", "websocket"); request.addHeader("Connection", "Upgrade"); request.addHeader("Sec-WebSocket-Key", chan); // 生的随机串 request.addHeader("Sec-WebSocket-Version", "13"); HttpResponse response; try { conn.sendRequestHeader(request); conn.flush(); request.toString(); response = conn.receiveResponseHeader(); } catch (HttpException ex) { throw new RuntimeException("handshake fail", ex); }
服务器在收到握手请求之后需要做相应的答复。消息的例子如下:
HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Date:Sun, 29 Jan 2012 18:05:49 GMT
Sec-WebSocket-Accept:7vI97qQ5QRxq6lD6E5RRX36mOBc=
Server:jetty
Upgrade:websocket
(其中Date、Server都不是必须的)
第一行是HTTP的Status-Line。注意,这里的Status Code是101。很少见吧!Sec-WebSocket-Accept字段是一个根据client发来的Sec-WebSocket-Key得到的计算结果。
算法为:
把客户端发来的key作为字符串,与” 258EAFA5-E914-47DA-95CA-C5AB0DC85B11″这个字符串连接起来,然后做sha1 Hash,将计算结果base64编码。注意,用来和” 258EAFA5-E914-47DA-95CA-C5AB0DC85B11″做连接操作的字符串,是base64编码后的。也就是说,客户端发来什么样就是什么样,不要试图去做base64解码。
示例代码如下:
?View CodeCPP
std::string value=request.get("Sec-WebSocket-Key"); value+="258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; unsigned char hash[20]; sha1::calc(value.c_str(),value.length(),hash); std::string res=base64_encode(hash,sizeof(hash)); std::ostringstream oss; oss<<"HTTP/1.1 101 Switching Protocolsrn" "Upgrade: websocketrn" "Connection: Upgradern" "Sec-WebSocket-Accept: "<<res<<"rn"; connection.send(oss.str());
握手成功后,进入数据流阶段。这个阶段就和http协议没什么关系了。是在TCP流的基础上,把数据分成frame而已。首先,websocket的一个message,可以被分成多个frame。从逻辑上来看,frame的格式如下
isFinal
opcode
isMasked
Data length
Mask key
Data(可以是文本,也可以是二进制)
isFinal:
每个frame的第一个字节的最高位,代表这个frame是不是该message的最后一个frame。1代表是最后一个,0代表后面还有。
opcode:
指明这个frame的类型。目前定义了这么几类continuation、text 、binary 、connection close、ping、pong。对于应用而言,最关心的就是,这个message是binary的呢,还是text的?因为html5中,对于这两种message的接口有细微不一样。
isMasked:
客户端发给服务器的消息,要求加扰之后发送。加扰的方式是:客户端生一个32位整数(mask key),然后把data和这32位整数做异或。
mask key:
前面已经说过了,就是用来做异或的随机数。
Data:
这才是我们真正要传输的数据啊!!
发送frame时加扰的代码如下:
java.util.Random rand ;
ByteBuffer buffer;
byte[] dataToSend;
…
byte[] mask = new byte[4];
rand.nextBytes(mask);
buffer.put(mask);
int oldpos = buffer.position();
buffer.put(data);
int newpos = buffer.position();
// 按位异或
for (int i = oldpos; i != newpos; ++i) {
int maskIndex = (i – oldpos) % mask.length;
buffer.put(i, (byte) (buffer.get(i) ^ (byte) mask[maskIndex]));
}
下面讨论一下这个协议的某些设计:
为什么要做这个异或操作呢?
说来话长。首先从Connection:Upgrade这个header讲起。本来它是留给TLS用的。就是,假如我从80端口去连接一个服务器,然后通过发送Connection:Upgrade,仿照上面所说的流程,把http协议”升级”成https协议。但是实际上根本没人这么用。你要用https,你就去连接443。我80端口根本不处理https。由于这个header只是出现在rfc中,并未实际使用,于是大多数cache server看不懂这个header。这样的结果是,cache server可能以为后面的传输数据依然是普通的http协议,然后按照原来的规则做cache。那么,如果这个client和server都已经被黑客很好的操控,他就可以往这个cache server上投毒。比如,从client发送一个websocket frame,但是伪装成普通的http GET请求,指向一个JS文件。但是这个GET请求的目的地未必是之前那个websocket server,可能是另外一台web server。然后他再去操控这个web server,做好配合,给一个看起来像http response的答复(实际是websocket frame),里面放的是被修改过的js文件。然后cache server就会把这个js文件错误的缓存下来,以后发给其他人。
首先,client是谁?是浏览器。它在一个不很安全的环境中,很容易受到XSS或者流氓插件的攻击。假如我们的页面遭到了xss,使得攻击者可以利用JS从受害者的页面上发送任意字符串给服务器,如果没有这个异或操作,那么他就可以控制什么样的二进制数据出现在信道上,从而实现上述攻击。但是我还是觉得有点问题。proxy server一般都会对目的地做严格的限制,比如,sina的squid肯定不会帮new.163.com做cache。那么既然你已经控制了一个web server,为什么不让js直接这么做呢?那篇paper的名字叫《Talking to Yourself for Fun and Pro?t》,有空我继续看。貌似是中国人写的。
还有,为什么要把message分成frame呢? 因为HTTP协议有chunk功能,可以让服务器一边生数据,一边发。而websocket协议也考虑到了这点。如果没有framing功能,那么我必须知道整个message的长度之后,才能开始发送message的data。
更多阅读
websocket简介 websocket java
一WebSocket是html5新增加的一种通信协议,目前流行的浏览器都支持这个协议,例如Chrome,Safrie,Firefox,Opera,IE等等,对该协议支持最早的应该是chrome,从chrome12就已经开始支持,随着协议草案的不断变化,各个浏览器对协议的实现也在不停的更
TCP/IP三次握手详解 tcp ip协议详解
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一
融资租赁 企业所得税 第19节:第三章 企业融资租赁(5)
系列专题:《经济低增长企业对策:中小企业融资渠道》 4. 签署租赁协议与供应协议 根据当事人确定的国际融资租赁的结构,出租入和承租人可能需签署一系列协议和法律文件,但其中最重要和最通常的是供货协议和融资租赁协议。根据《国
国产大飞机发动机 大飞机发动机项目锁定上海临港新城
《中国经济周刊》记者 谈佳隆/上海报道 11月初,上海市副市长赵雯与中国航空工业集团公司总经理林左鸣在珠海正式签署了上海市人民政府与中航工业集团战略合作框架协议。根据“框架协议”,双方将在上海共同投资组建商用航
巴塞尔新资本协议研究 新资本协议,中国银行业的机遇
文/黄志凌 自1999年第一次征求意见稿发布至今,新资本协议(《统一资本计量和资本标准的国际协议:修订框架》,简称新资本协议或basel2)一直是国际银行业的热门话题。2007年中国银监会《中国银行业实施新资本协议指导意见》出台