WebSocket Draft 10向客户端写文本数据的实现

Posted on November 03, 2011 by Fdream

在Draft 10中,如果解析数据的过程弄清楚了,这个就更简单了,返回数据的格式和之前接受到的数据格式非常类似,只是你不用生成mask了,头部的其他格式还是一模一样的。

第一个字节还是固定的,是0×81,意义和接受数据的意义一样,第二个字节也是,后七个位表示数据长度,由于没有mask,所以第一位是0;长度的表示方法和接受的标识方法一致,可能用7位表示,也可能用16位表示。

用NodeJS实现如下:

var socketWriter = {
    'draft10': function(socket, data){
        var frames,
            length = new Buffer(data, 'utf8').length;
        if(data.length > 0x7d){
            frames = new Buffer(4);
            frames[0] = 0x81;
            frames[1] = 0x7e;
            frames[2] = length >> 8;
            frames[3] = length & 0xFF; //1111 1111
        }
        else{
            frames = new Buffer(2);
            frames[0] = 0x81;
            frames[1] = length;
        }

        if (socket.writable) {
            socket.write(frames, 'binary');
            socket.write(data, 'utf8');
            return true;
        }
        return false;
    },
    'draft76': function(socket, data){
        var byteLen = Buffer.byteLength(data, 'utf8'),
            bytes = new Buffer(byteLen + 2);

        bytes[0] = 0x00;
        bytes.write(data, 1, 'utf8');
        bytes[byteLen + 1] = 0xFF;

        return socket.write(bytes);
    }
};

socketWriter['draft75'] = socketWriter['draft76'];
Posted in Ajax Web | Tagged , , | Leave a comment | 864 Views |

WebSocket草案10文本数据解析实现

Posted on October 27, 2011 by Fdream

握手协议实现了,接下来就是解析数据了,这个相对来说要麻烦很多,相比草案7.6变得更复杂了。下图是数据传输的格式:

各个值的具体含义可以参考这个中文翻译:http://blog.csdn.net/fenglibing/article/details/6852497,英文原文在这里:http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10#section-4.2

简单概括一下就是:FIN一直为1,RSV1、RSV2、RSV3一直为0,当发送内容为文本时,opcode为1,MASK如果为1就使用掩码,需要获取四位掩码,依次轮流和数据做异或运算,第一个内容数据与第一个掩码异或,第二个内容数据与第二个掩码异或……第五个内容数据与第一个掩码异或……一直到结束,然后再对内容进行编码就可以了。

在Chrome/Firefox下面,FIN永远为1,如果只是文本消息,opcode一直是1,RSV1、RSV2、RSV3都为0,所以第一个数据是0×81;MASK为1,再跟上数据长度,所以第二个数据会大于0×80,由于数据长度不一样,其需要占用的字节数也不一样,因此掩码的位置也不一样。根据规范,换算下来是这样:数据总长度(包含FIN、RSV等等信息)小于0×84,则掩码为第三个数据到第六个数据;数据总长度(包含FIN、RSV等等信息)小于0xfe81,则掩码为第五个数据到第八个数据;其他掩码则为第十三个数据到第十六个数据。当数据长度超过一个包的长度时,后面的数据包不再包含RIN、RSV和MASK等信息,二是直接为内容数据,需要根据上一次取到的MASK来做异或运算。在客户端主动要求断开链接时,会没有内容数据,只有头信息和掩码,长度固定为6,为了避免混淆,Chrome会保证正常内容发送时不会出现6字节的数据,基本上可以把6字节当断开连接请求处理。区分客户端主动要求断开连接和正常的连续数据包要根据长度区分:如果是连续的数据包,则已解析的包的长度会小于首包头信息里的长度,否则则认为是客户端主动要求断开链接。主动断开连接时会发送的一个额外的数据包,此时第一个字节不会为正常的0×81,通常为0×88,NodeJS写出来大致是这样的:

module.exports = Parser;

var util = require('util'),
    events = require('events');

/**
* http.Server 数据解析类
* */
function Parser(version) {
     events.EventEmitter.call(this);

     this.version = version || 'draft10';
    this.length = 0;
    this.parsed = 0;
     this.maskData = [];
     this.frameData = [];
     this.order = 0;
     this.closing = false;
}

util.inherits(Parser, events.EventEmitter);

Parser.prototype.write = function(data) {
    console.log(data.length);
     dataParser[this.version](data, this);
};

// 数据解析工具
var dataParser = {
     'draft10': function(data, parser){
        var pkt, i =0, len = 0, start = 0;

        // 如果不包含包头,而且数据已经解析完成
        // 则认为这个包为结束包,首字节通常为0x88
        if(data[0] != 0x81 && parser.length == 0){
            parser.emit('close');
            parser.closing = false;
            return;
        }

        // 草案10
        // 首包会包含掩码信息
        if(data[0] == 0x81){
            // 使用了掩码
            if(data[1] >= 0x80){
                // 数据长度不一样,掩码位置不一样
                if(data.length < 0x84){
                    // firefox下,超过一个包长度的数据会被拆分为多个包
                    // 其中首包只包含头信息,第二个字节为0xfe
                    if(data[1] == 0xfe){
                        len = data.length;
                        // firefox
                        parser.maskData = [data[len - 4], data[len - 3], data[len - 2], data[len - 1]];
                        parser.length = data[len - 5];
                        for(i = len - 6; i > 1; i--){
                            parser.length += data[i] * (len - 5 - i) * 256;
                        }
                        console.log('firefox multi packages, length: ', parser.length);
                        start = data.length;
                    }
                    else{
                        // chrome
                        parser.length = data[1] - 0x80;
                        console.log('7bit, length: ', parser.length);
                        parser.maskData = [data[2], data[3], data[4], data[5]];
                        start = 6;
                    }
                }
                else if(data.length < 0xfe80){                     parser.length = data[2] * 256 + data[3];                     console.log('7 + 16bit, length: ', parser.length);                     parser.maskData = [data[4], data[5], data[6], data[7]];                     start = 8;                 }                 else{                     dparser.length = data[11];                     for(i = 10; i > 3; i--){
                        parser.length += data[i] * (11 - i) * 256;
                    }
                    console.log('7 + 64bit, length: ', parser.length);
                    parser.maskData = [data[12], data[13], data[14], data[15]];
                    start = 16;
                }

                for(i = start, len = data.length; i < len; i++){
                    parser.frameData.push(parser.maskData[(i - start) % 4] ^ data[i]);
                }
            }
            else{
                if(data.length < 0x80){
                    start = 2;
                }
                else if(data.length < 0xfe81){
                    start = 4;
                }
                else{
                    start = 12;
                }
                // find contents
                parser.frameData = data.splice(start);
            }
            console.log('1st packge frame length: ', parser.frameData.length);
            if(parser.frameData.length == parser.length){
                pkt = new Buffer(parser.frameData);
                // console.log(pkt.toString('utf8', 0, pkt.length));
                parser.emit('message', pkt.toString('utf8', 0, pkt.length));
                // 数据包结束,重置长度信息
                parser.frameData = [];
                parser.length = 0;
            }
            return;
        }

        // 连续的数据包
        if(parser.maskData.length){
            // continue to parse data
            for(i = 0, l = data.length; i < l; i++){
                parser.frameData.push(parser.maskData[i % 4] ^ data[i]);
            }
            console.log('frame length: ', parser.frameData.length);
            if(parser.frameData.length == parser.length){
                pkt = new Buffer(parser.frameData);
                // console.log(pkt.toString('utf8', 0, pkt.length));
                parser.emit('message', pkt.toString('utf8', 0, pkt.length));
                // 数据包结束,重置长度信息
                parser.frameData = [];
                parser.length = 0;
            }
            return;
        }
     }
};
Posted in Ajax Web | Tagged , , | 3 Comments | 937 Views |

WebSocket草案10握手协议的实现

Posted on October 27, 2011 by Fdream

Chrome早就采用了websocket草案10的协议,很多以前支持websocket的代码突然之间都用不了了,很多作者也没空更新,索性自己看了下草案,自己实现了一套。

握手协议还是非常简单的,Chrome发过来的header大概是这样的:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 8

要答复的头大概是这样的:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

实际上这里要处理的就是算出这个Sec-WebSocket-Accept的值。这个非常简单,首先拿到Sec-WebSocket-Key,也就是dGhlIHNhbXBsZSBub25jZQ==,把它和字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼在一起:

var key = request.headers['sec-websocket-key'] + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';

然后对这个key做一个SHA-1的加密,对加密后的字符串再base64就可以了,用nodejs写出来就是这样的:

var sha1 = Crypto.createHash('sha1');
key = sha1.update(key).digest('base64');

这个里面的key值就是最终要返回给客户端的值了,这样就算完成握手了。

Posted in Ajax Web | Tagged , , | Leave a comment | 600 Views |

Mac下SSH代理软件Secret Socks修改版

Posted on October 02, 2011 by Fdream

Secret Socks是Mac下不错的SSH代理软件,不过有两点非常的不爽:一是不能记住密码(可能原作者是考虑到安全因素),二是超时时间设置得有点过短,而且不能修改,导致经常半天都连不上,实在是让人很郁闷。

令人欣慰的是,作者公开了此软件的源代码,索性直接拿来改了下,重新发布一个新版本。专门针对以上两点做了修改,基于安全因素(相对安全)考虑,给保存的密码用DES进行了加密,key是一个随机的字符串,非公开的。

打包后的文件只在Lion(10.7.x)下进行了测试,要求系统的最低版本是10.5.x,但未进行测试。

安装包下载地址:http://vdisk.weibo.com/s/IAQS(新浪微盘)

 

Posted in 计算机相关 | Tagged , , | 8 Comments | 1,520 Views |

js中函数预解析在Firefox下的异常表现

Posted on September 09, 2011 by Fdream

前几天同事发现的一个诡异现象,猜猜浏览器执行下面这段代码会弹出什么提示?

if(1){
    f(1);
    function f(a){
        alert(a);
    }
}

通常情况下,都认为javascript中function会被预解析,所以这里应该会弹出一个对话框,显示字符“1”。没错,在IE6-IE9,Chrome,Safari,Opera等待浏览器下也都是这个结果。但是偏偏Firefox有点特立独行,会提示“引用错误,f未定义”。

我们再试试,把if去掉,只留两个花括号看看:

{
    f(1);
    function f(a){
        alert(a);
    }
}

还是一样!现在我们在试试几种情况:

if(1){
    function f(a){
        alert(a);
    }
    f(1);
}

这个没有问题,正常显示1,再试试这个:

if(1){
    function f(a){
        alert(a);
    }
}
f(1);

也没有问题,再试试这个:

if(0){
    function f(a){
        alert(a);
    }
}
f(1);

这个果然不正常了,提示f未定义!看来是Firefox对花括号里面的函数声明做了特殊处理,并没有对花括号内部的函数声明做预解析,而只是等到要执行的时候才去解析,也许是为了性能优化?或者这本身就是一个bug?

Posted in Ajax Web | Tagged , , | 1 Comment | 703 Views |

解决Opera不能访问内网资源的问题

Posted on August 25, 2011 by Fdream

开发测试时,经常会把公网页面上的部分域名指向内网时,在Opera下并不会发送对应请求,导致页面不正常,而几乎所有的浏览器都正常。这个并不是页面兼容性问题,这其实是Opera独有的一种安全策略。解决此问题的方法如下:

菜单->设置->首选项->高级->安全性->信任的网站->安全的内部主机->添加,输入你内网服务器的IP即可。

opera 设置

 

Posted in Ajax Web | Tagged , | Leave a comment | 401 Views |

也谈JavaScript代码性能优化

Posted on June 01, 2011 by Fdream

差不多两年前写了个选择器whiz,除在DOM查找方面做了许多优化工作之外,还在代码优化上做了很多工作,一直没有分享。抽空总结一下,基本上在jQuery、Mootools和YUI的源码里面都可以看到这些写法。有些是已经在网上分享很多遍了,众所周知的,也有一些可能写了多年的JavaScript的开发人员也不一定想得到的。如果有说得不正确的地方,还请大家指出。还有特别说明的是,其中某些写法不是很推荐,虽然代码简洁了,但是有可能造成阅读困难。

1.尽量使用源生方法(Native Method)

js是解释性语言,相比编译性语言执行速度要慢。如果浏览器已经实现了该方法,就不要再用js再去实现一遍了。另外,绝大部分情况下,浏览器已经实现的方法已经在算法方面做了很多优化,再重复实现一遍只是浪费时间和精力还有带宽。当然,如果你只是为了练习算法,那另当别论。

2.尽可能减少循环次数

代码的瓶颈大多在循环,少一层循环,就能数倍地提高性能。如果要对一个数组的每个元素进行多次操作,尽可能使用一次循环,多次操作,而不是多次循环,每次循环执行一次操作。尤其是在进行多个正则匹配的时候,尽可能合并正则表达式,在一次遍历中尽可能找到相应的匹配。

3.循环的另外一种写法

通常循环的写法:

var objs = [obj1, obj2, obj3], i = 0; len = objs.length;
for(i = 0; i < len; i++){
    dosth(obj);
}

当循环遍历的对象是object时,可以采用下面的方式来写:

var objs = [obj1, obj2, obj3], obj, i = 0;
while(obj = objs[i++]){
    dosth(obj);
}

特别注意,如果你的数组里面有可能出现0, false, null等等在条件判断为false的值,这种写法是不正确的。

4.某些情况下switch的另外一种写法

通常的switch写法:

function fun_a(){}
function fun_b(){}
function fun_c(){}
switch(con){
    case 'a':
        fun_a();
        break;
    case 'b':
        fun_b();
        break;
    case 'c':
        fun_c();
        break;
}

另外一种写法:

function fun_a(){}
function fun_b(){}
function fun_c(){}
var funs = {
        'a': fun_a,
        'b': fun_b,
        'c': fun_c
    };
funs[con]();

取值或者函数调用都可以用类似的方式来做。

5.条件判断的另外一种写法

通常情况下:

if(a){
  dosth();
}

可以这样写:

a && dosth();

6.创建对象的另外一个办法,不使用new

很多时候,我们要连续创建一些简单的object对象,并且拥有默认的属性,很多人会这么写:

function Klass(){
    this.prop_a = '';
    this.prop_b = [];
    this.prop_c = 0;
}
var objs = [], i = 0, obj;
while(i<100){
    obj = new Klass();
    obj.prop_c = i;
    objs.push(obj);
}

换种写法看看:

function create(){
    return {
        prop_a : '',
        prop_b : [],
        prop_c : 0
    };
}
var objs = [], i = 0, obj;
while(i<100){
    obj = create();
    obj.prop_c = i;
    objs.push(obj);
}

当然还有直接声明的方式,但是复用性会差一些。

7.用来做标记的变量尽可能使用布尔类型

直接用true和false做标记,不要使用数字或者字符串的1和0来做标记,直接也高效。

其他的优化方法已经有很多分享了,大家可以Google一下,另外《javascript高级程序设计》一书中也对JavaScript性能优化做了较为详细的阐述。

Posted in Ajax Web | Tagged , , | 2 Comments | 788 Views |

Merpressor——自动合并压缩与无缝调试

Posted on June 01, 2011 by Fdream

这个是在第19期WEB标准交流会上的分享PPT,分享的内容很简单,主要是大家讨论,都很诚恳地提出了各种想法和意见。在前端这一块,平常的交流太多都在于各种技术交流,而太少关注调试、测试和发布环境。

大的互联网公司可能都有各自的调试、测试和发布流程,但是在这方面的资料实在是不多。Bobby(曾在yahoo台湾,现在盛大创新院) 讲到yahoo内部其实也有这么一套,我的和他们的很类似,遗憾的是内部使用的,也无迹可寻。Hax(盛大创新院)也提到一些想法,比如自动解决依赖关系,部分更新等,都是不错的想法。


解决ubuntu下ibus没有输入窗口的问题

Posted on May 29, 2011 by Fdream

在用ubuntu 10的时候为了装google拼音输入法,把ibus给卸载了,装来scim,而且一直用google拼音也用得挺好。直到后来升级到了ubuntu 11,发现google拼音变得极不稳定,经常在选第二个词的时候挂掉,然后就再也没办法输入中文,只能重新启动scim,挺烦人的。于是考虑再次回到默认的ibus输入法,于是卸载scim,重新装上ibus,结果更悲剧的事情发生了,ibus一直提示没有输入窗口,现象就是下面的这张图。

ibus没有输入窗口

Google了N下发现有很多人和我碰到一样的问题,但似乎都没有解决办法。折腾了几天之后,终于找到一个办法,就是修改gtk配置文件。首先进入这个目录:/usr/lib/gtk-2.0/2.10.0(gtk版本可能略有区别),里面有个gtk.immodules文件,用vim或者gedit等文本编辑器打开它,找到这两行:

"/usr/lib/gtk-2.0/2.10.0/immodules/im-xim.so"
"xim" "X Input Method" "gtk20" "/usr/share/locale" "ko:ja:th:zh"

把其中的xim换成ibus,然后重启一下电脑,世界就太平了:

"/usr/lib/gtk-2.0/2.10.0/immodules/im-ibus.so"
"ibus" "X Input Method" "gtk20" "/usr/share/locale" "ko:ja:th:zh"

重启后正常的截图:

ibus正常

 

正式加入WordPress阵营

Posted on March 28, 2011 by Fdream

其实想转WordPress很久了,只是苦于以前的博客程序是自己写的,需要写个转换程序,又还要重新设计制作一套皮肤,所以一直懒得去做,怕麻烦。

实在是很久不写blog了,很多次想写点什么,发现自己也已经受不了原来自己写的博客程序了,终于狠下新来研究WP的数据库,然后开始写转换程序。幸运的是这个过程没有想像的那么复杂,那么难。制作皮肤花的时间比写转换程序花的时间多太多了,新手上路总是有那么多问题的。

在blog程序转换的过程中,也顺便捣腾了一下个人页面,纯粹是无聊的小练习,用了一些简单的HTML5和CSS3,博客的皮肤也是基于此构建的,非常匆忙,甚至在IE6和IE7下都没测试过,还不知道到底如何。

先这样,边用边修改吧!如果大家有好的参考资料或者优秀的插件推荐,那就多谢了!

Posted in 博客与我 | Tagged , , | 6 Comments | 975 Views |