最初发表在QQ空间中,全文参见 小问题有大智慧-代理服务器的监测
公司里的电脑都在一个域内,上网使用PAC自动化脚本,内部会自动解析所需要的代理,这些都是网络管理员自动设置,用今天流行语来说,你被设置了。有的时候这挺烦人的,特别是当你需要自己设定代理时,虽然当时你可以在浏览器的选项里这么做,不过一会儿,又会被域Policy自动更改回来。有没有方法可以让你自己设置呢?
做开发的一个好处这个时候体现出来,你可以自由的做你需要的东西,只要你不懒。
正好以前做过类似BHO的东西,第一时间,我最直观的想法就是做个BHO,监测代理设置,发现被修改了就再改回来。想到就做,而且确实也简单,使用ATL做了BHO插件,只要实现一个接口,
STDMETHOD(SetSite)(IUnknown *pUnkSite);
浏览器启动的时候自动加载BHO,调用SetSite接口。SetSite里创建了一个线程,监听代理设置,如果有改动,再改回来。大概100行代码不到
测试下,功能上没有问题,有问题的是BHO的机制,BHO会在浏览器启动时加载,但并不是进程绑定的。在IE7,IE8以及后续版本中,由于多个Tab,每开个Tab,就会加载一个BHO对象,关闭Tab时BHO也会关闭。这样打开多个Tab时会启动多个线程做同样的一件事情,低效而且危险。怎么办?
一开始想的Solution是对象计数,在BHO里添加了对象计数功能,只在第一个BHO对象里创建工作线程,后续创建的其它对象什么都不做,这样可不可以?很快被自己否决了,IE8是多进程模型,每个TAB是个进程,对象计数不能跨进程工作。另一个Solution随之出来,并不需要对象计数,使用一个进程间共享的变量来标记代理工作线程的存在。
代码如下:
#pragma data_seg( "Share" )
volatile LONG proxyInstances = 0;
#pragma data_seg( )
下面是DEF文件,
SECTIONS
Share READ WRITE SHARED
proxyInstances 这个变量是进程间共享,BHO启动时检测这个标记,如果是FALSE,创建代理工作线程,将它设置为TRUE; 否则什么都不用做。创建代理线程的BHO退出时,终止线程,并清除标记。
测试了这个实现,总体上比较满意,最多只会有一个线程在工作,轻量而且效率高。请注意最多2个字,这个方案有明显的一个缺陷,第2天我才想到,假设碰巧关闭了创建工作线程的Tab,BHO里虽然会设置标记成FALSE,但工作线程也会关闭。这个时候的游览器是失去监测的,直到使用者新开Tab。
仔细考虑之后,想了2个方案:
1. 把这个功能做在service里,单独运行,与游览器无关。
这个方案理论上可行,并且不用考虑游览器生命周期的问题,实现简单。但很多用户包括我并不喜欢自己的系统里安装上一大堆莫名其妙的进程啊,Service什么的。我更想的是那种on demand的方式,因此,放弃它。
2. 还是BHO实现,不要进程间共享变量,而是创建一个命名的内核对象Event,每个BHO都会创建工作线程,去Wait这个Event,获得Event的线程就进入工作状态,当这个BHO退出的时候,调用SetEvent去通知其它正在Wait的线程。
这个方案是on demand方式的,游览器打开即开始工作。缺点是会创建多个线程(取决于用户打开Tab的数量),虽然某个时刻只有一个线程在工作状态,其它的线程都在Wait。但线程本身也是一种消耗。
仔细想想原来的那种进程间共享变量的方案,确实有明显的缺陷,但真的不能用嘛?公司域Policy更新的时间并不频繁,假设1小时一次,那么会有多大的可能我恰好关闭了那个Tab(拥有工作线程的)之后,1小时之内不去开新Tab?我的习惯是什么?想通了这点,答案豁然开朗。目前的方案应该完全可行。随后2周的使用,也证实了这点。正象Rework一书所说的,刚刚好就是真的好