ifree's Blog - it's my way

emacs 小黄鸡插件

周末耍了下simsim,发现这只小鸡的对话能力简直超强啊,心想写代码些无聊了有只小鸡陪聊还是不错的,于是这个插件就诞生了,平时主要用emacs,所以就只写了emacs版.

惯例,上图上代码:

 

;simsim robot


(defun http-url-encode (str content-type)
  "URL encode STR using CONTENT-TYPE as the coding system."
  (apply 'concat
	 (mapcar (lambda (c)
		   (if (or (and (>= c ?a) (<= c ?z))
			   (and (>= c ?A) (<= c ?Z))
			   (and (>= c ?0) (<= c ?9)))
		       (string c)
		     (format "%%%02x" c)))
		 (encode-coding-string str content-type))))

(defun ask-simsim (content &optional lc)
  (let ((dest (concat "http://www.simsimi.com/func/req?lc=" (or lc "zh") "&msg=" 
		      (http-url-encode content 'utf-8)
		      ))
	)
    (get-url-content dest)
    )
)

(defun get-url-content (url)
  "simplily get content from url"
  (let (ret )
    (with-current-buffer
      (url-retrieve-synchronously  url) 
;      (setq status url-http-response-status)
      (goto-char (point-min))
      (if  (search-forward-regexp "{" nil t)
	  (setq ret (decode-coding-string (substring (car (split-string (buffer-substring (point) (point-max)) "," )) 12 -1) 'utf-8)
		)
	(message "invalid request")
	)
      )
    ret
    )
 )

(defun sim-eof ()
  (=
   (count-lines (point-min) (point-max))
   (count-lines (point-min) (point))
   )
)

(defun talk-to-sim (&optional arg)
       (interactive)	
       ;(save-excursion
	 (if (sim-eof)
	     (if (re-search-backward "me:\\(.+\\)" nil t)
		 ((lambda (msg)
		    "insert msg to content"
		    (goto-char (point-max))
		    (insert (concat "\n\n" "simsim:" msg "\n\n"  ))
		    (insert "me:")
		    )
		  (ask-simsim (match-string-no-properties 1))
		  )
	       )
	   (message "unknow error")
	   )
	; )
)

(defun simsim-start ()
  (let ((buffer-name "simsim"))
    (if (get-buffer buffer-name)
	(pop-to-buffer buffer-name)
      (pop-to-buffer (get-buffer-create buffer-name))
      (insert "simsim:主人好! \n\n")
      (insert "me:")
      )
     )
)

(defvar simsim-mode-hook nil "hook for simsim")
(defvar simsim-mod-map nil "key map")
(if simsim-mod-map
    ()
  (setq simsim-mod-map (make-sparse-keymap))
  (define-key simsim-mod-map  [return] 'talk-to-sim)
)


(defun simsim ()
  "simsim bot mod.... just for fun"
  (interactive)
  (simsim-start);//switch to simsim buffer
  (kill-all-local-variables)
  (use-local-map simsim-mod-map)
;  (setq major-mode 'simsim-mod
;  	mode-name  "simsim"
;	)

)

(provide 'simsim)

### 补

Orz,官方竟然发现了....下面是解决办法,referr和ua换了 ps,emacs的ua还

 

(defun get-url-content (url)
  "simplily get content from url"
  (let ((ret nil)
;	(url-proxy-services '(("http" . "localhost:8888")))
	(url-request-extra-headers 
	  '(("Content-Type" . "application/json; charset=utf-8")
	    ("X-Requested-With" . "XMLHttpRequest")
	    ("User-Agent" . " Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.75 Safari/537.1")
	    ("Referer" . "http://www.simsimi.com/talk.htm")
	    )
	))
    (with-current-buffer
      (url-retrieve-synchronously  url) 
;      (setq status url-http-response-status)
      (goto-char (point-min))
      (if  (search-forward-regexp "{" nil t)
	  (setq ret (decode-coding-string (substring (car (split-string (buffer-substring (point) (point-max)) "," )) 12 -1) 'utf-8)
		)
;	  (message (buffer-substring (point-min) (point-max)))
	(message "invalid request")
	)
      )
    ret
    )
 )

###补

官方采用了api形式对请求做了限制,请访问http://developer.simsimi.com/获取授权

 

用box2d制作完美绳子

前一段时间发现一个有意思的游戏叫 cut the rope ,凭借其简易的交互和巧妙的构思着实让我着迷了一把, 如果以一个游戏人的角度来分析这款游戏,最amazing的还是其流畅的物理场景,这类游戏往 往让玩家感受更多的真实性和趣味性. cut the rope是用cocos2d实现的,这里我用box2d来实现它的核心元素(rope),先看看demo:

(看不到请reload frame)

demo只是简单的实现了绳子,如果想看完整版的割绳子实现近期我会把实现的完整版放上来(完整版:http://mgp.qq.com/flash/76169/)

今天的主题是"make a perfect rope with box2d",so 如果大家感兴趣就跟着我这个 tutorial走吧.:)

 

box2d基础

为了不混淆你的代码,box2d的所有类型都以b2开头,整个box2d物理系统由b2world驱动,通过 b2Wrold的step方法来模拟时间经过.box2d的单位系统完全遵照现实世界,用米,秒,牛等单位, 所以使用代码时务必转换(有人因此吐槽box2d,为什么还要开发人员来转,原因是box2d根本 不需要关心你如何处理pixel,你如何显示资源,它就像我们无法触摸但真是存在的物理世界,= =).b2body则是物理系统中的物件,游戏中就是靠它们来模拟现实中的物理模型,结 合 b2Joint关节来连接 b2Body.

 

 

具体实现

好了,看了上面的介绍你应该对box有一些了解,现在上代码了,关键部分都有注释.

  //为了效率能高点推荐使用requestAnimationFrame
  window.requestAnimFrame = (function(){  //requestAnimFrame fallback
          return  window.requestAnimationFrame       || 
                  window.webkitRequestAnimationFrame || 
                  window.mozRequestAnimationFrame    || 
                  window.oRequestAnimationFrame      || 
                  window.msRequestAnimationFrame     || 
                  function(/* function */ callback, /* DOMElement */ element){
		    window.setTimeout(callback, 1000 / 60); }
})();

(function(){
//命名空间的导入
    var   b2Vec2 = Box2D.Common.Math.b2Vec2
                ,       b2BodyDef = Box2D.Dynamics.b2BodyDef
                ,       b2Body = Box2D.Dynamics.b2Body
                ,       b2FixtureDef = Box2D.Dynamics.b2FixtureDef
                ,       b2Fixture = Box2D.Dynamics.b2Fixture
                ,       b2World = Box2D.Dynamics.b2World
                ,       b2MassData = Box2D.Collision.Shapes.b2MassData
                ,       b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape
                ,       b2CircleShape = Box2D.Collision.Shapes.b2CircleShape
                ,       b2DebugDraw = Box2D.Dynamics.b2DebugDraw
            ;
   //这里先创建一个box2d包装器,可以更方便的创建物体啥的而无须关心太多不必要的东西
    window.b2dWrapper=function(stage,scale){
	var gravity = new b2Vec2(0,10);
	var doSleep = true;
	this.world = new b2World( gravity, doSleep); 	
	this.debug=false;
	this.scale=scale;
	//setup debug draw
	var debugDraw = new b2DebugDraw();
	debugDraw.SetSprite(stage);
	debugDraw.SetDrawScale(scale);
	debugDraw.SetFillAlpha(0.3);
	debugDraw.SetLineThickness(1.0);
	debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
	this.world.SetDebugDraw(debugDraw);

	var updateListeners=[];	
	this.addUpdateListener=function(l){
	    updateListeners.push(l);
	}
        //shape工厂
	this.makeShape=function(param){
            //定义常用的参数,创建body时就不必传递太多不必要的参数
	    var defaults = { x: 3,
			     y: 3,
			     width: 3,
			     height: 3,
			     type:'box',
			     scale: 0, 
			     linearDamping: 0,
			     angularDamping: 0,
			     fixedRotation: false, 
			     allowSleep: true, 
			     isSleeping: false,
			     isSensor: false, 
			     isBullet: false, 
			     isDynamic: true,
			     skin: null, //...
			     scaleSkin: true,
			     density: 1.0,
			     friction: 0.5,
			     restitution: 0.2, 
			     angle: 0.0,
			     categoryBits:0x0001,
			     maskBits:0xFFFF,
			     groupIndex:0
			   };
			var p=Object.extend(defaults,param);
			if (p.width)
				p.width /= 2;
			if (p.height)
				p.height /= 2;
			
			var bodyDef = new b2BodyDef();
			var fixtureDef = new b2FixtureDef();
			var shape=null;
			var body;
			
			if (p.scale == 0)
				p.scale = this.scale;
			
			bodyDef.position.x = p.x / p.scale;
			bodyDef.position.y = p.y / p.scale;
			bodyDef.angle = p.angle;
			bodyDef.linearDamping = p.linearDamping;
			bodyDef.angularDamping = p.angularDamping;
			bodyDef.fixedRotation = p.fixedRotation;
			bodyDef.allowSleep = p.allowSleep;
			bodyDef.linearDamping = p.linearDamping;
			bodyDef.angularDamping = p.angularDamping;
			bodyDef.awake = !p.isSleeping;
			bodyDef.bullet = p.isBullet;
			bodyDef.type = p.isDynamic ? b2Body.b2_dynamicBody : b2Body.b2_staticBody;
			//filter data
			fixtureDef.filter.groupIndex = p.groupIndex;
			fixtureDef.filter.maskBits = p.maskBits;
			fixtureDef.filter.categoryBits = p.categoryBits;
			
			fixtureDef.friction = p.friction;
			fixtureDef.density = p.density;
			fixtureDef.restitution = p.restitution;
			fixtureDef.isSensor = p.isSensor;
			
			if (p.type == 'circle')//only support two type now
			{
				shape = new b2CircleShape(p.width / p.scale);
				p.height = p.width; //workaround....
			}
			else
			{
				shape = new b2PolygonShape();
				if (p.type == 'box')
				{
					shape.SetAsBox(p.width / p.scale, p.height / p.scale);
				}
				else if (type == 'SHAPE_POLYGON')
				{
					throw new Error("to be implemented.");
				}
			}
			
			fixtureDef.shape = shape;
			if (p.parent)
			{
				body = p.parent;
				shape.SetLocalPosition(bodyDef.position); //根据父对象设置相对位置
			}
			else
			{
				body = this.world.CreateBody(bodyDef);
			}
			
			//body.SetUserData(ud);//notice here
			body.CreateFixture(fixtureDef);
			return body;
    
	}
	var This=this;
	var update=function(){
	    
	    This.world.Step(1/60,10,10);
	    if(This.debug)
		This.world.DrawDebugData();
	    This.world.ClearForces();
	    if(updateListeners.length>0){
		updateListeners.forEach(function(itm){if(itm)itm();});
	    }

	    requestAnimFrame(update);
	}
	this.start=function(){
	    update();
	}
    }
})();  
//现在基本框架已经出来了,现在开始造绳子了!
b2dWrapper.prototype.createRope=function(bodyA,bodyB,params){
	//package import
	var b2DistanceJointDef= Box2D.Dynamics.Joints.b2DistanceJointDef,
	b2Vec2 = Box2D.Common.Math.b2Vec2,
	b2Joint= Box2D.Dynamics.Joints.b2Joint,
	b2RevoluteJointDef= Box2D.Dynamics.Joints.b2RevoluteJointDef,
    b2RevoluteJoint= Box2D.Dynamics.Joints.b2RevoluteJoint;
	var This=this;
	//创建旋转关节
	function revoluteJoint(bodyA,bodyB,anchorA,anchorB){
			var revoluteJointDef = new b2RevoluteJointDef();
			revoluteJointDef.localAnchorA.Set(anchorA.x,anchorA.y);
			revoluteJointDef.localAnchorB.Set(anchorB.x,anchorB.y);
			revoluteJointDef.bodyA=bodyA;
			revoluteJointDef.bodyB = bodyB;
			revoluteJointDef.collideConnected = true;
			revoluteJointDef.userData = 'chainParts';
			//revoluteJointDef.enableLimit = true;
			//revoluteJointDef.referenceAngle = -30 * Math.PI / 180;
			return This.world.CreateJoint(revoluteJointDef);
	}
        //连接两个物体
	function connectChain(bodyA,bodyB,chainLen/*CHAIN_LEN*/,isHead) 
	{
		if(!chainLen)
			chainLen=5;
		return revoluteJoint(bodyA, bodyB, new b2Vec2( isHead?0:chainLen / 2 , 0 ), new b2Vec2( -chainLen / 2 , 0));
	}
	var startP=bodyA.GetPosition().Copy(),endP=bodyB.GetPosition().Copy();
	var detY=endP.y-startP.y,detX=endP.x-startP.x;
	var angle_rad=Math.atan2(detY,detX);//获取头尾两点的角度
	var len=Math.sqrt(detX*detX*900+detY*detY*900);//总长
	var subLen=len/10;//每个节点长 
	var offset=new
	b2Vec2(Math.cos(angle_rad)*subLen,Math.sin(angle_rad)*subLen);//获取每个
	绳子节点相对于前一节点的偏移
	for(var i=0;i<10;i++){
		var box=this.makeShape(//创建绳子节点
			{
				type:'box',
				isDynamic:true,
				width:subLen,
				height:2,
				angle:angle_rad,
				isSensor:true,
				x:bodyA.GetPosition().x*30+offset.x,
				y:bodyA.GetPosition().y*30+offset.y
			}	
		);		
		var j=connectChain(bodyA,box,subLen/30,i==0);
		bodyA=box;
	}
	connectChain(bodyA,bodyB,subLen/30);

}


 

 

后记

没用多少时间的实现了这个小demo,我想说的是互联网越来越开放,技术交流也应该如此, 人生苦短,拥抱开源 :>

 


使用putty实现自动部署

最近开始了一个小项目,然后给配了服务器裸机.这意味着服务器配置也只有我来搞定(不想麻烦系统部出动杀器- -)...起初我想配台arch,后来出于稳定性的考虑, 我选择了centos.

下面是大概的步骤(没啥可赘述的,各种wiki可以查):

linux部分:

装好build-essential

编译 nginx+mysql+php+openssl+各种依赖

配好iptables

win (主要提一下putty的配置)

下载并配置好putty的坏境

用puttygen 生成ssh格式的密钥 ,复制openssh格式的公钥到服务器上 /home/username/.ssh/authorized_keys2,(ps, ~/.ssh 700,authorized_keys2 400)

现在基本的环境已经ok,我在项目里用的是make,下面是我其中一个target

submit:$(dest)
    pscp  -i PATH-TO-PRICATEKEY $(dest) ifree@10.249.16.249:/path-to-server

pscp -i 可以指定刚才用puttygen生成的密钥地址.免除繁琐的登录环节.

that's all.

有时间了看看可以可以用rsync算法实现一个简单的rsync吧

mario0 作弊指南

今天一个妹子来家里玩游戏,指明要玩超级玛丽= =.于是下载了mario0这个娱乐版的超级玛丽. 可以用管道穿梭真有趣.

可以妹子来了之后对穿梭一点不感兴趣,陪她耍了几次,一直用管道作弊,不过由于操作不熟死的也很快.....妹子走了.跟另一个朋友玩了一会.

我擦,这游戏真没趣啊,于是准备破解,游戏设置里提示了可以作弊,打开游戏的源码一看,是lua写的 哈哈哈,这就更简单了,找到入口文件,

文件一开头就是作者的几句脏话...视若无睹,继续看,一下是发现: 配置文件地址: %appdata%/love/mario0/setting.txt  根据代码的提示,在配置文件中添加gamefinished;

重新开始游戏.

我擦.....minecraft!!!!!!!!!!!! 这游戏还支持3个模式.  portal,minecraft,gelcannon 哈哈,下次妹子来就好耍了 截几个图,今天事情太多了 就到这里 happy cracking!

 

 

 

Kindle Library Bulk Delete

every time when you click the confirm button page begin to refresh..... so
 

amznJQ.jQuery(".headerStatus","#orderList").each(function(idx,itm){Fion.deleteItem('deleteItem_'+itm.id.replace(/Row\d+_/,""))});

above code will delete all items at current page,so you may need to execute it carefully :>

 

豆瓣fm的全局快捷键

最近用上了pentadacty, 感觉不错,高效强大. 个人有一个习惯,只要不是思考密集的时间都会抽时间来听会歌,同时也有开大量tabs的习惯,所以在听豆瓣电台的时候免不了频繁的切换,于是就有了下面的代码:

"eval script in tabs 
js <<EOF
function execScriptInTabs(filter,domjs){
	 tabs.allTabs.forEach(function(itm){
	var ctab=gBrowser.getBrowserForTab(itm);
	if(filter(ctab)){
		var s=ctab.contentDocument.createElement('script')
		s.textContent=domjs;
		ctab.contentDocument.body.appendChild(s);
	}
	 })
}
EOF

"douban.fm utilities

js <<EOF
group.mappings.add(
[modes.NORMAL],
[",ds"],"skip",
function(){
execScriptInTabs(function(ctab){
return ctab.contentDocument.location.host.match("douban\.fm");
},"DBR.act('skip')");//skip,pause,love
}
);
group.mappings.add(
[modes.NORMAL],
[",dd"],"ban",
function(){
execScriptInTabs(function(ctab){
return ctab.contentDocument.location.host.match("douban\.fm");
},"DBnnR.act('ban')");//skip,pause,love
}
);
group.mappings.add(
[modes.NORMAL],
[",dl"],"like",
function(){
execScriptInTabs(function(ctab){
return ctab.contentDocument.location.host.match("douban\.fm");
},"DBR.act('love')");//skip,pause,love
}
);
group.mappings.add(
[modes.NORMAL],
[",dp"],"pause",
function(){
execScriptInTabs(function(ctab){
return ctab.contentDocument.location.host.match("douban\.fm");
},"DBR.act('pause')");//skip,pause,love
}
);
EOF


 

基于SESSIONID的攻击与防范

论坛的密码忘了,不想接收会被我删除的重置邮件,也不想在3次尝试密码错误之后封禁15分钟.

于是用fiddler抓包看了看,每次换验证码的时候都会更换一个PHPSESSID .于是,用fiddler的RequestBuilder用相同的SESSIONID构建请求,试了几次常用密码之后登录成功了!

一个网站这样的话该是多大的隐患.这东西的学名叫session fixation attacks(固定session攻击).下面列举一下可以推测的攻击:

  • 钓鱼式登录链接
    对方给你一个链接:http://weak/login?JSESSIONID=youarepig. 当你登录之后,服务器的保存了你的会话,根据SESSIONID就轻松获取对方想要的信息.
  • 绕过验证码
    对方在第一次会话中保留了正确的验证码.可能的举动有:刷SEO,恶意灌水,多次登录尝试等等,前两个比较好处理,如果你的密码策略的够差劲的话对方可以很快破解出你的密码(Java的SESSIONID是32位伪随机数,PHP的好像也是.其他的策略类似.这样的话,由于网速带宽限制,GPU破解是不可能的,但是诚心破你还是没问题).如果密码破解还不过瘾的话对方还可能穷举SESSIONID(穷举是不可能的...)获取服务器上活动的会话,网站用户的信息就危险了.
  • 利用cookie固定SESSIONID
    都知道session是基于cookie的,对方恶意(cookie攻击有很多)地给你加了个JSESSIONID的cookie,客户端与服务器通讯中一直会传递这个cookie值,然后,就没有然后了.
  • ...参考:wikipedia,msresearch

解决办法:
这个方法很多,每次请求更换SESSIONID,用更负责的SESSIONID算法,请求频率限制...

提醒一下安全意识很重要.一个同学说他是黑客,每次说帮人黑网站,有一次他到我家玩的时候正好在帮别人黑网站我就看了看.他打开自己的"工具箱",里面工具很多各式各样的软件,功能大概有SQL注入(对稍微高级点的网站没用),webshell上传(对稍微有意识的开发人员没用),漏洞扫描(对稍微好点的主机没用,特别是linux),旁注(选好虚拟主机很重要啊,好邻居也很重要),DDOS(贱人,但是拿不到想要的东西).从他开始到第二天早上我醒的时候他正虚着眼看着自己的显示屏,我猜他肯定觉得该继续找点工具试试...
其实我想说的是大家的意识提高一点网站安全性肯定有提高.黑网站的同学也多学点吧,多看点规范,多学学工夫网的牛人们,没文化害死人啊,这样的通宵不值啊.

Wuala网盘,跨平台方便分享

 

Wuala是来自瑞士苏黎世的一家创新型社会化免费网络硬盘,服务器在德国,由于采用了P2P技术,如果你想要获得更大空间的话,你可以通过把你的电脑硬盘上的空间分享给大家来获取。

简介与使用:

Wuala默认1G空间,不限制上传文件大小、文件类型,也不限制流量。邀请朋友注册Wuala,双方都可以增加更多的空间。

如果想获得更多空间,您可以 交换 您硬盘上的空间获取在线存储或者 购买 额外的在线存储。

使用Wuala需要下载安装Java客户端和 Wuala客户端,支持Windows、Mac、Linux操作系统,所有操作都可以在Wuala软件客户端上完成,安装完软件后会自动在我的电脑里生成一个W盘,只需要把同步的文件放到W盘即可自动上传到服务器。

也可以直接在网页上使用Wuala。

文件和文件夹的链接可发送给朋友,对方可直接点击链接并访问文件。

可以把文件或文件夹设为私有。如果你有朋友也使用Wuala,你可以加TA为好友,把文件或文件夹设为分享,你的朋友就可以浏览你的分享的文件或者文件夹了。

Wuala软件客户端采用最新加密技术,相对其他免费网盘安全性更高。 最新的P2P技术,让你的文件被加密分解成若干块储存在wuala服务器及任意wuala用户之中。也因此拥有绝好的备份性和下载速率。

其它:

Wuala 基于大量 开源项目。Wuala也开放源码 Wuala Webstart 和 Wuala Persistent Map。

点击以下链接注册可以直接获得2G空间:

http://www.wuala.com/referral/6P4AF6KFH4573M557GF3

或者在注册时,在“推荐码或LaCie序列号”填上 6P4AF6KFH4573M557GF3  也可以直接获得2G空间。

传送门:Wuala首页

ultraEdit 注释插件

不要跟我说vim,不要跟我说linux,我也是被逼的

直接上代码:(ue还不内部脚本还不支持activeX对象,所以好多功能都没法实现...所以我一直都用vim )

 

//file: auth.js
//desc: pig
//author: ifree
//last modified date: Monday, December 19, 2010 21:54:55
(
function(ue){
	var currentDoc=ue.activeDocument;
	var debug=ue.outputWindow;
	function getFileName(){
		 var path=currentDoc.path;	
		 return path.substring(path.lastIndexOf("\\")+1,path.length);
	}
	function getDate(){
		return new Date().toLocaleString();	
	}
	function desc(obj){
		for(var i in obj){
			debug.write("{"+i+":"+obj[i]+"}\r\n");	
		}
	}
	//render whole or single template
	function templateHandler(){
		var template=[];
		template["file"]="//file: {getFileName()}";
		template["desc"]="//desc: {desc}";
		template["author"]="//author: {author}";
		template["last modified date"]="//last modified date: {getDate()}";
		
		var reg=/{(.+)}/;
		function renderSelf(key,value){
				if(!key)return;
				var str=template[key];
				if(!str)return;
				reg.exec(str);
				
				if(RegExp.$1.indexOf('(')>=0){
					str=str.replace(reg,eval(RegExp.$1));
				}else
					str=str.replace(reg,value);
				return str;
		}
		
		return {
				render:function(obj){
						var ret="";
						for(var i in template){
								ret+=renderSelf(i,obj[i]?obj[i]:'')+"\r\n";
						}
						return ret;
				},
				renderSingle:function(k,v){
					return renderSelf(k,v);	
				}
		}
	}
	//get output
	return function(cfg){
		currentDoc.write("{"+"cursor"+"}");
		
		
		var reg=/\/\/([\w\s]+):(\s?.+)/;
		currentDoc.gotoLine(5);
		currentDoc.selectToTop();
		
		var texts=currentDoc.selection.split("\r\n");
		//detect if comment has added
  	if(reg.test(texts[0])){
  		
	  	for(var i=0,il=texts.length;i<il;i++){
	  			if(reg.test(texts[i])){
	  					texts[i]=templateHandler().renderSingle(RegExp.$1.trim(),RegExp.$2.trim());
	  			}
	  	}
	  	currentDoc.deleteText();
	  	currentDoc.write(texts.join('\r\n'));
  	}else{
  		currentDoc.selectAll();
  		currentDoc.write(templateHandler().render(cfg)+currentDoc.selection);
  	}

  	//set cursor to orign posi
  	currentDoc.top();
  	currentDoc.findReplace.find("{"+"cursor"+"}");
  	currentDoc.deleteText();
	}
}
)(UltraEdit)({author:"ifree"});

 

Websockets everywhere with Socket.IO

最近一个小伙子过来吐槽,说ajax弱了,实现不了长链接,然后就一直哭....其实这未必不能实现,至于长链接,comet,server push都是依托与服务器的,如果服务器实现了server push那就好说了,据我了解现在的服务器大多都支持apache,jetty,fast cgi,ninix(重新加NGiNX_HTTP_Push_Module编译),但是这往往不够...好了,这样说不给力,下面我翻译了一篇Socket.IO.Js的文章里面较详细介绍了关于使用ajax保持http长连接的实现,以及Socket.IO的实现(就是兼容版实现Websockets ),同时涉及了Node.js(自己google)

http://howtonode.org/websockets-socketio

----转载请注明作者信息 ifree

 

正文:

如果你一直致力于实时网络应用,在过去几年你可能了解过一些不同的改善server端和client端数据交互延迟的技术.如果你正在开发一款多人游戏,一个及时聊天系统 或者一个想twitter一样需要频繁的与服务短交互的应用,你可能想'反转'传统的交互模型.所以,与其向服务端发送请求不如让服务端发送数据给你.

如今,当我们开发类似实时的系统是,我们总会想到长连接,comet当然还有websocket.但是,他们各有各的局限性,比如,服务器实现不同,限制太多,浏览器支持不够...

下面是我的Socket.IO的一段代码:

var socket = new io.Socket();
socket.on('connect', function(){
    // connected!
});
socket.on('message', function(msg){
    // message coming
});
socket.send('Hello world!');

如果你比较熟悉WebSocket(不熟悉去看api),其主要目的是简化HTTP的双向协议.你会发现它们非常类似.但是不同的是(由于目前浏览器尚未完全实现html5) Socket.IO支持 IE6-9, Firefox 3-4, Safari 3-5, Chrome 3-6, iOS (iPhone ,iPad)和其他兼容浏览器.

 

回顾当前的实时网络实现

在这个年代,一大部分web开发人员都在使用AJAX来实现实时交互(异步交互),就像Socket.IO一样.一些第三方js库比如jQuery,封装了兼容性较好的ajax对象来达到异步请求的功能.

比如说,如果你现在打算实时地向服务器请求资源,你可能会这样做:

 

setInterval(function(){
    $.ajax({ url: '/my/page', success: function(data){
        // do something with the data
    } });
}, 5000);

这样,每五秒钟从服务器获取数据.在我看来,这几乎是和rfc1149(ip数据报)的规范一样高效.

你可能也会减少等待的时间:

 

setInterval(function(){
    $.ajax({ url: '/my/page', success: function(data){
        // do something with the data
    } });
}, 100);

然而,你可能忽略了下面两个要点:

  • HTTP延迟.一个数据包在高速网络中往返可能消耗200ms,但是情况不总是这样,如果是500或更高,而且有时还不必要.因为:
  • 服务器可能没有我们要的数据,这样一来导致了很多不必要的带宽浪费.

 

长轮询

传统轮询的方法是不间断的向服务器请求数据,要克服它的缺点就要在服务器没有数据时保持长连接.这样也戏剧性的减少了网络延迟(长连接),所以,这也算一种服务器推送的技术.

 

function load(){
    $.ajax({ url: '/my/page', success: function(){
        // do something with the data
    }, complete: load, timeout: 20000 });
}

 

 

如何做到长连接

如果你来自一个更传统的软件开发行业(比如桌面软件),你可能会想,我们为什么不保持长连接呢.

在浏览器上要实现可能有两种方法:

  • XMLHttpRequest 和 multipart/x-mixed-replace MIME 类型 (即在xhr请求中设置multipart为true的XMLHTTP实例);

虽然早在1995年Netsacpe就推出了这项技术(但不是对所有浏览器都有效),唯一在firefox浏览器下被普遍支持.

  • 使用 <iframe>时在头部meta信息设置 Transfer-encoding: chunked and Connection: keep-alive.

这样可以在创建<script>标签,动态执行脚本,iframe内部刷新而保持父窗口不刷新,这样也可以被看作是一种服务器推送的方法.

但这样做有一个缺点,就是浏览器下方出现无休止的进度条,严重的损害了用户体验.在ie环境下,可以把 <iframe> 放到一个隐藏的文档对象中(用ActiveX对象创建的htmlfile对象).在gmail 里面的gtalk实现给就是用的这个技术.是由Alex Russell 分析/发现的.

现在,很明显地看到这些技术在一定的环境下发挥了作用.但是最根本的是现在的服务器有着不同的请求的方法:

  • 请求头部信息(Content-Type, Connection...).
  • 持续连接(这需要长轮询的支持,但不是完全支持).
  • 框架信息.对于multipart,每条消息都要由'-----'分割符隔开解析麻烦(是的,每个请求的数据都被'----------'分割,请看请求数据).
  • 浏览器怪癖(比如ie在请求时需要一些空字节来识别每一条数据).

所有这些所做的都是尽可能减少数据的延迟,但是通常我们只把XMLHTTPRequest作为一个想服务端传输数据的良好解决方案.

 

通用的解决方案

有了WebSocket,"一个致力于解决bs架构的双端交互并且不依靠'multiple HTTP连接' ",像它的作者Hickson说的那样.

WebSocket 利用了HTTP/1.1规范的优势,可以说这是一个全新的协议:(有兴趣看rfc)

 

The Upgrade general-header allows the client to specify what additional communication protocols it supports and would like to use if the server finds it appropriate to switch protocols.
Examples: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

 

 

WebSocket在收发消息的时候并不关闭连接.除了本身的安全模型(基于utf-8编码的框架)以外,它可以完全被称作"web版的TCP".

但是WebSocket仍存在一些问题:

  • 服务器必须单独处理WebSocket请求,执行WebSocket特有的握手协议,WebSocket的协议.
  • 没有广泛的浏览器支持.

 

使用Node.js

Node.js 给开发人员带来了一个独特且令人兴奋的特性:你可以用当下最流行的脚本技术来编写你的无阻塞web服务器.

就像这样:

http.createServer(function (request, response) {
    setTimeout(function(){
        response.writeHead(200, {'Content-Type': 'text/plain'});
      response.end('Hello World\n');
    }, 20000);
}).listen(8124);

 

虽然有了这个简单的api,但是你很难继续加强你的程序的逻辑.但是Socket.IO-node可以提供一个辅助的实现.下面是一个聊天程序差不多10行,同时支持服务器广播:

 

var buffer = [];
io.on('connection', function(client){
    client.send({ buffer: buffer });
    client.broadcast({ announcement: client.sessionId + ' connected' });

    client.on('message', function(message){
        var msg = { message: [client.sessionId, message] };
        buffer.push(msg);
        if (buffer.length > 15) buffer.shift();
        client.broadcast(msg);
    });

    client.on('disconnect', function(){
        client.broadcast({ announcement: client.sessionId + ' disconnected' });
    });
});

最好的是,在浏览里,它会处理你的长连接,flash socket,iframe请求.很棒把.

 

进一步阅读

如果你想进一步研究Socket.IO,你可以在git repositories检出源码进一步研究,也可以看一下几个项目(注:node.js是基于v8js引擎实现的高性能web服务器,很爽有时间去看吧):