• websocket 通信协议(已更新到version 13


    UPDATE:前些天有网友mail和我讨论websocket协议,当时颇忙!更惭愧的是,此篇文章竟已不能使用,原因是当时我写的还是websocket草稿时候的协议!终于,我将websocket 协议更新到了version 13版本 —–2012.5.19

    websocket通信协议实现的是基于浏览器的原生socket,在客户端用JS即可轻松完成,前些天都在学习websocket 协议(但实际上websocket 协议甚为简约),并且粗略的思考过websocket的对于下一代web应用会产生怎样的影响,我想最大的巨变应该是就是实时性上吧!另外诸如上传大文件之类的优于http的应用。但问题也随之而来,服务端怎么办?前些天我弄了个websocket 聊天室的demo,现在还得在服务器上专门开个进程来跑呢,也许到时候不再是简单架设个web server就能跑应用的了。也许过不了多久,会出不同的服务端方案吧!先期待一下。

    websocket的协议是很简单的,这里我把它分成客户端和服务端来讲。在客户端,new WebSocket即可实例化一个新的websocket对象,但其参数略微有一点不一样,参数格式是这样的ws://yourdomain:port/path ,这个从我的聊天室demo里面就可以轻松看出(ws = new WebSocket( “ws://www.zendstudio.net:9108/chat” ); ),WebSocket对象会自动解析这段字符串,发送到指定服务器端口,首先执行的是双方握手(handshake),客户端发送数据格式类似这样:

    GET /chat HTTP/1.1
    Upgrade: websocket
    Connection: Upgrade
    Host: www.zendstudio.net:9108
    Origin: http://www.zendstudio.net
    Sec-WebSocket-Key: U00QUfV1CRfIIU0NkcUCnA==
    Sec-WebSocket-Version: 13
    Sec-WebSocket-Extensions: x-webkit-deflate-frame

    这很是有些类似于http的头信息,同样每行都是以”\r\n”结尾的,上面这段格式无需我们去构造,WebSocket对象会自动发送,对客户端这是透明的。此时服务端应该返回的信息是:

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: 7GGzyIJjf9bX7pej+3tc5Vv87S0=
    WebSocket-Origin: http://www.zendstudio.net
    WebSocket-Location: ws://www.zendstudio.net:9108/chat

    从这里我们太容易看出来,websocket协议的握手部分根本就是个类http的协议,所不同的是http每次都会有这样子的头信息交互,这在某些时候不得不显得很糟糕。而websocket只会执行一次这个过程,之后的传输信息就变得异常简洁了。

    2012.5.19新增内容)这里我们发现,version 13这个版本的websocket协议,与我之前文中叙述之最大不同就是多了一个验证,客户端会发送一个“Sec-WebSocket-Key”的base64编码的密钥,要求服务端必须返回一个“Sec-WebSocket-Accept”,否则客户端会抛出一个“Error during WebSocket handshake: Sec-WebSocket-Accept mismatch”错误之后,关闭连接,当然,这个Sec-WebSocket-Accept的值是计算出来的,胡乱的返回也是要遭到历史唾弃的。Sec-WebSocket-Accept的算法很简单:将客户端提交的Sec-WebSocket-Key+”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″,然后sha1,然后base64_encode,以下列出我用nodejs写的代码片段:

    sha1 = crypto.createHash('sha1');
    sha1.update(headers["Sec-WebSocket-Key"]+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
    ws_accept=sha1.digest('base64');

    客户端在握手成功后,会触发WebSocket对象的onopen事件,告诉客户端连接已经成功建立了。客户端的WebSocket对象一共绑定了四个事件:1、onopen:连接建立时触发;2、onmessage:收到服务端消息时触发;3、onerror:连接出错时触发;4、onclose:连接关闭时触发;有了这4个事件,我们就可以很容易很轻松的驾驭websocket,并且需要说明的是websocket支持二进制数据的传输,因此,它远不止聊天室应用这么简单。

    服务端呢?服务端也是非常简单的,但是仍然需要注意的问题是,作为服务器,安全和性能是不可忽略的,除此之外,只管往socket里面写数据就可以了,websocket的通信数据全部是以”\x00″开头以”\xFF”结尾的,无论是服务端发出的数据还是客户端发送的数据都遵从这个格式,唯一不同的是客户端的WebSocket对象能够自动将头尾去除,获得主体数据,这就省却了我们在客户端处理原始数据的必要,真是个体贴周到的对象啊!顺便说一句,WebSocket通信数据的编码总是UTF-8格式的

    服务端相对于草稿版还是改动很大的,从Sec-WebSocket-Extensions部分的声明来看,websocket是会支持压缩的,现在这个version 13版本已经不是当初的utf-8传字符的版本了,而是完全的二进制传输,服务端和客户端的通信经过了mask转换,这样就不太容易看出原文的内容了。这其中的算法颇有些复杂,并非一两句能够说的清楚,这里我写了两个函数来打包和解包数据:

    /**
     *
     * 打包数据,实际上payload_len == 127时的打包方法是有待商榷的,这里先这样简单实现
     *
     **/
    function parse_msg(data){
    	data = data || null;
    	if( ( data.length <= 0 ) || ( !Buffer.isBuffer(data) ) ){
    		return null;
    	}
     
    	var mask_flag = (data[1] & 0x80 == 0x80) ? 1 : 0;//All frames sent from client to server have this bit set to 1.
    	var payload_len = data[1] & 0x7F;//0111 1111
     
    	if( payload_len == 126 ){
    		masks = data.slice(4,8);
    		payload_data = data.slice(8);
    		payload_len = data.readUInt16BE(2);
    	}else if( payload_len == 127 ){
    		masks = data.slice(10,14);
    		payload_data = data.slice(14);
    		payload_len = data.readUInt32BE(2) * Math.pow(2,32) + data.readUInt32BE(6);
    	}else{
    		masks = data.slice(2,6);
    		payload_data = data.slice(6);
    	}
    	//console.log(payload_len);
    	//console.log(payload_data.length);
    	for( var i=0;i< payload_len;i++ ){
    		payload_data[i]= payload_data[i] ^ masks[i%4];
    	}
     
    	return payload_data;
    }
     
    /**
     * 很简陋的实现打包,并且不支持mask,不支持其他命令,不支持拆包、装包、不支持大于16位长度的数据……
     **/
    function build_msg( str_msg, mask ){
    	str_msg = str_msg || "";
    	mask = mask || false;
     
    	var msg_len = Buffer.byteLength(str_msg,"utf-8"), packed_data;
    	if( msg_len <= 0 ){
    		return null;
    	}
     
    	if( msg_len < 126 ){
    		packed_data = new Buffer(2+msg_len);
    		packed_data[0] = 0x81;
    		packed_data[1] = msg_len;
    		packed_data.write( str_msg, 2 );
    	}else if( msg_len <= 0xFFFF ){//用16位表示数据长度
    		packed_data = new Buffer(4 + msg_len);
    		packed_data[0] = 0x81;
    		packed_data[1] = 126;
    		packed_data.writeUInt16BE( msg_len, 2 );
    		packed_data.write( str_msg, 4 );
    	}else{//用64位表示数据长度
    		/*packed_data = new Buffer(10+msg_len);
    		packed_data[0] = 0x81;
    		packed_data[1] = 127;
    		packed_data.writeUInt32BE(msg_len & 0xFFFF0000 >> 32, 2);
    		packed_data.writeUInt32BE(msg_len & 0xFFFF, 6);
    		packed_data.write( str_msg, 10 );*/
    	}
     
    	return packed_data;
    }

    这两个都是没有完整实现的方法,要完美实现,得需要根据rfc6455来做了。(另外我同时把demo给更新了,欢迎围观

    好了,websocket协议就是这么简单。到这里,写一个服务端应该不是什么困难的事情了吧?这仅仅需要一点点socket编程知识,任何语言都可以轻松实现。另外,我想说下源码的事情,有童鞋给我留言希望看看我的服务端源码,我想想还是算了,当我公布的源码徒增自己一堆麻烦,因为一部分人把我当成写应用的了,他们总是会说:“这代码怎么不能用?”,或者说“你能再修改下源代码,以便实现下我们公司当前需要用到的XXX功能吗?”我本为技术交流,之前公布飞信php源代码的时候,就遇到太多这样的情况,我并没有为飞信php建立项目,我不可能花很多时间去跟踪飞信协议变化,不断维护我的代码。同样的,这次的websocket php服务端源代码我也不打算献丑了,还是不了。非常感谢大家持久以来的支持,希望我们能继续讨论技术本身。

    参考文章:http://dev.w3.org/html5/websockets/
    http://tools.ietf.org/html/rfc6455

    转载:http://www.zendstudio.net/archives/websocket-protocol/

  • 相关阅读:
    解决 Mac launchpad 启动台 Gitter 图标无法删除的问题
    React 与 React-Native 使用同一个 meteor 后台
    解决 React-Native mac 运行报错 error Failed to build iOS project. We ran "xcodebuild" command but it exited with error code 65. To debug build logs further, consider building your app with Xcode.app, by ope
    一行命令更新所有 npm 依赖包
    swift学习笔记
    IOS语言总结
    focusSNS学习笔记
    别小看锤子,老罗真的很认真
    windowsphone开发页面跳转到另一个dll中的页面
    【令人振奋】【转】微软潘正磊谈DevOps、Visual Studio 2013新功能、.NET未来
  • 原文地址:https://www.cnblogs.com/canphp/p/2626674.html
Copyright © 2020-2023  润新知