Dec 31
昨天@Zind同学找到我之前的一篇blog(已经修改),里面提到了mysql_ping和MYSQL_OPT_RECONNECT的一些事情。

之所以写那篇blog,是因为去年写的一些代码遇到了“2006:MySQL server has gone away”错误。这个问题是因为wait_timeout这个参数的默认值是28800,也就是说,如果一个连接连续8个小时没有任何请求,那么Server端就会把它断开。在测试环境中一个晚上没有请求很正常……于是第二天早上来的时候就发现这个错误了。

其实我有考虑这个问题的,真的……因为我知道php里面有个函数叫做mysql_ping(),PHP手册上说:“mysql_ping() 检查到服务器的连接是否正常。如果断开,则自动尝试连接。本函数可用于空闲很久的脚本来检查服务器是否关闭了连接,如果有必要则重新连接上。”

回想起来,以前真是很傻很天真。根据MySQL官方C API里mysql_ping()的文档:"Checks whether the connection to the server is working. If the connection has gone down and auto-reconnect is enabled an attempt to reconnect is made. ... Auto-reconnect is disabled by default. To enable it, call mysql_options() with the MYSQL_OPT_RECONNECT option",也就是说,它实际上还依赖于MYSQL_OPT_RECONNECT这个配置,而这个配置默认(自5.0.3开始)是关闭的!

虽然想起来很愤怒很蛋疼,不过看到 libmysql/client.c: mysql_init() 里的注释就淡定了:
引用
By default we don't reconnect because it could silently corrupt data (after reconnection you potentially lose table locks, user variables, session variables (transactions but they are specifically dealt with in mysql_reconnect()).  This is a change: < 5.0.3 mysql->reconnect was set to 1 by default. 


好吧,既然有问题,那就正视它。解决办法是调用 mysql_options ,将MYSQL_OPT_RECONNECT设置为1:
char value = 1;
mysql_options(mysql, MYSQL_OPT_RECONNECT, &value);


但是!! 在mysql 5.0.19 之前,mysql->reconnect = 0 这一句是放在 mysql_real_connect() 里面的!也就是说,如果你不能像处理其他选项一样,而是必须在mysql_real_connect()之前设置MYSQL_OPT_RECONNECT,坑爹啊!

好吧好吧,总之,关于坑的问题暂告一段落,结论就是,不管是哪个版本,如果你想要启用自动重连,最好都是在mysql_real_connect()之后,反正不会错。

然后这篇的重点来了(前面似乎太罗嗦了点):MYSQL_OPT_RECONNECT的文档里头说了,这个选项是用来启用/禁用(当发现连接断开时的)自动重连,那么,MYSQL什么时候会发现链接断开呢?

这个问题可能太大了,不过不妨先去追一下,mysql_ping()做了啥。

下载源码 http://cdn.mysql.com/Downloads/MySQL-5.1/mysql-5.1.67.tar.gz ,解压以后ctags -R,再vim -t mysql_ping ,马上就定位到了,似乎太简单了点:
int STDCALL
mysql_ping(MYSQL *mysql)
{
  int res;
  DBUG_ENTER("mysql_ping");
  res= simple_command(mysql,COM_PING,0,0,0);        //试着向服务器发送一个ping包
  if (res == CR_SERVER_LOST && mysql->reconnect)    //如果server挂了,而mysql->reconnect为true
    res= simple_command(mysql,COM_PING,0,0,0);      //再ping一次??
  DBUG_RETURN(res);
}


好吧,看来关键在于这个simple_command了。ctrl+],原来是这样:
#define simple_command(mysql, command, arg, length, skip_check) \
  (*(mysql)->methods->advanced_command)(mysql, command, 0, 0, arg, length, skip_check, NULL)


好吧,先去追一下MYSQL,里头有个 const struct st_mysql_methods *methods ,再追一下 st_mysql_methods ....
typedef struct st_mysql_methods
{
  my_bool (*read_query_result)(MYSQL *mysql);
  my_bool (*advanced_command)(MYSQL *mysql, enum enum_server_command command,
                  const unsigned char *header, unsigned long header_length,
                  const unsigned char *arg, unsigned long arg_length,
                  my_bool skip_check, MYSQL_STMT *stmt);
  ......

坑爹啊!又是这种鸟代码!蛋疼的C语言!struct只有属性没有方法!没办法,只能暴力了:
引用
find -name '*.c' -exec /bin/grep '{}' -Hne 'mysql->methods *=' ';'
./libmysql_r/client.c:1907:  mysql->methods= &client_methods;
./sql-common/client.c:1907:  mysql->methods= &client_methods;
./libmysql/client.c:1907:  mysql->methods= &client_methods;
./libmysqld/libmysqld.c:120:  mysql->methods= &embedded_methods;
./sql/client.c:1907:  mysql->methods= &client_methods;


果断追到client_methods:
static MYSQL_METHODS client_methods=
{
  cli_read_query_result,                      /* read_query_result */
  cli_advanced_command,                        /* advanced_command */
  ...

也就是说simple_command最后调用了cli_advanced_command这个函数。前面的 simple_command(mysql,COM_PING,0,0,0) 相当于是调用了 cli_advanced_command(mysql, COM_PING, 0, 0, 0, 0, 0, NULL) 。

这个函数做了啥呢。。。其实也不复杂:
1. 设置默认返回值为1 (意外出错goto时被返回)
2. 设置sigpipe的handler(以便忽略它)
3. 如果 mysql->net.vio == 0 ,那么调用mysql_reconnect重连,失败的话就返回1
4. mysql没准备好,返回1
5. 清除之前的信息(错误码、缓冲区、affected_rows)等等
6. 调用net_write_command将命令发送给server,如果失败:
    6.1 检查错误信息,如果是因为发送包太大,goto end
    6.2 调用end_server(mysql)关闭连接
    6.3 调用mysql_reconnect尝试重连,如果失败goto end
    6.4 再次调用net_write_command将命令发送给server,失败则goto end
7. 设置result = 0(发送成功)
8. 如果参数中要求检查server的返回,则读取一个packet进行检查(失败的话就result=1)
9. (end标签)
10. 恢复sigpipe
11. 返回result

可以看到,这里两次调用了mysql_reconnect,但都是有条件的:第一次是在mysql->net.vio == 0的情况下,第二次是net_write_command失败且不是因为包太大的情况。vio相关的代码看得一头雾水,实在找不出头绪,于是决定暴力一点:直接修改这个函数,加入一堆fprintf(stderr, ...)(具体加在哪里就不说了,反正使劲塞就是了),然后写了一个C代码:
#include <stdio.h>
#include <stdlib.h>
#include <mysql/mysql.h>

void do_err(MYSQL *mysql) {
    if (mysql_errno(mysql)) {
        fprintf(stderr, "%d:%s\n", mysql_errno(mysql), mysql_error(mysql));
        exit(mysql_errno(mysql));
    }
}

int main()
{
    MYSQL * mysql = mysql_init(NULL);
    do_err(mysql);

    mysql_real_connect(mysql, "127.0.0.1", "root", "123456", "test", 3306, NULL, 0);
    do_err(mysql);

    char value = 1;
    mysql_options(mysql, MYSQL_OPT_RECONNECT, &value);
   
    char cmd[1024] = "SELECT * FROM t";
    while (1) {
        mysql_query(mysql, cmd);
        do_err(mysql);

        MYSQL_RES *result = mysql_store_result(mysql);

        MYSQL_ROW  row;
        while ((row = mysql_fetch_row(result)) != NULL) {
            int i, num_fields = mysql_num_fields(result);
            for (i = 0; i < num_fields; i++)
                printf("%s\t", row[i] ? row[i] : "NULL");
            //注意上一句是不是二进制安全的,因为row里头可能包含\0,也可能末尾没有\0
            printf("\n");
        }

        mysql_free_result(result);
        printf("press enter..."); getchar();
    }
    mysql_close(mysql);
    return 0;
}


运行输出:
引用
inside mysql_real_query
mysql->net.vio = 0x90e760
mysql->status = 0
net write_command
after send_query
---
1
2
press enter...//按回车之前先重启一下mysql server,下面这几句按照函数调用层次进行手动缩进了……
inside mysql_real_query
    mysql->net.vio = 0x90e760 //进入cli_advanced_command
    mysql->status = 0
    net_write_command
    end_server //说明net_write_command失败了
        inside mysql_reconnect //它会调用mysql_real_query
            inside mysql_real_query
                mysql->net.vio = 0x919990 //于是又回到了cli_advanced_command
                mysql->status = 0
                net_write_command //这次成功了
            after send_query  //这句我是写在mysql_real_query里面的
        reconnect succeded
    after reconnect: mysql->status = 0
after send_query //所以又来一次。。


根据fprintf的输出,发现在正常情况下,mysql->net.vio这个指针并不等于0,所以第一个mysql_reconnect不会被调用。而net_write_command也是正确执行,第二个reconnect也没被调用。

而在执行完一个query,然后重启mysql server再执行query (mysql_query => mysql_real_query => mysql_send_query => cli_advanced_command),就会发现,mysql->net.vio仍然不等于0,但是net_write_command失败了,于是先调用了end_server()(这里面会将mysql->net.vio设置为0,不过不影响后面的流程...),然后调用了第二个reconnect,这个reconnect会调用mysql_init()以及mysql_real_query()执行一些初始化的命令,于是又回到cli_advanced_command,再一步一步回溯。。。

综上可知,如果设置了MYSQL_OPT_RECONNECT(),那么mysql_query()是可以完成自动重连的。实际上,由于cli_advanced_command会在必要情况下调用mysql_reconnect(实际上这个函数也只在这里被调用),因此,所有用到了cli_read_query_result的地方(或者simple_command),也都可以完成自动重连。

完结。

//混蛋,这篇纯粹是为了凑一月至少一篇这个目标啊!
Dec 13
这事儿其实只要有了拼音库,就挺简单的。我从 pinyin4j 这个项目里搞了一份出来。虽然这个库是为java写的,不过要提取倒是相当简单:到这里 下载pinyin4j-2.5.0.zip,解压得到里头的 lib/pinyin4j-2.5.0.jar ,再解压得到里头的 pinyindb/unicode_to_hanyu_pinyin.txt 。

这个文件的结构很简单,每一行的基本结构是这样的:
引用
4E04 (shang4,shang3)

前面的 4E04 是汉字的Unicode编码,空格分隔,然后括号里面包含了所有读音(而且看起来像是按频率排序好了的),每个读音分别给出了拼音的声母韵母以及声调。

在这个基础上解析它就相当简单了。
f = open("unicode_to_hanyu_pinyin.txt", "r")

py = {}
for l in f:
    l = l.strip()  #行末回车
    key, val = l.split(' ')
    sd = val[1:-1].replace('u:', 'v').split(',')  #去掉左右括号,把u:转为v(驴 lv),然后按逗号分隔
    arr_sd = []
    for i in sd:
        arr_sd.append({'py': i[0:-1], 'tone': i[-1]})  #把每个读音的声调分离出来
    py[int(key, 16)] = arr_sd  #把unicode编码转成10进制作为key

f.close()


当然,你也可以把它存入一个key/value数据库(memcachedb什么的就挺好)。以上面的例子的话,把一个字符串中的汉字转换成拼音就很简单了:
def convert(str, encoding = 'utf-8'):
    ret = ''
    for i in str.decode(encoding):
        w = ord(i)
        if py.has_key(w):
            ret += "%s-%s " % (py[w][0]['py'] , py[w][0]['tone'])
        else:
            ret += i
    return ret


要注意的一点是,“驴”的拼音被标记为 lu:2 ,而不是打字时习惯使用的 lv ,如果有需要的,还得再加个简单的转换逻辑。

实际上这个库里头还支持通用拼音(貌似是台湾地区使用过的)等其他转换方式,有需要的同学可以自己考据一下其结构。

p.s. 在python里头可以用 unichr(0x4E04) 得到这个unicode对应的字符,也可以用 ord('率'.decode('utf-8'))  得到这个字符的unicode编码(注意替换字符的原始编码)。至于十进制和十六进制的转换,也很简单, hex(32768) 得到 '0x8000' 而int('0x8000', 16)就能得到32768。python真好用。
Nov 27
昨天关注了下 微信公众平台,的确是个好东西,赞一下tx近来的开放。

它的api 乍一看还是挺简单的,于是就写了个小东西玩玩。

但是在开发的过程中遇到了多个坑,通过有故事王国的@ctqmumu同学把问题转了过去。实际开发的过程中遇到了好几个问题,但是其他问题(包括发一条消息请求我两次)都莫名其妙消失了,只有一个问题(具体见后文)通过被喷的方式解决了。

总之最后是可以用了,于是多花了点时间,把代码完善了下,写好了注释和样例,放在了Google Code上面,有需要的同学可以拿去用。


下载地址:http://code.google.com/p/mmsdk/downloads/list
    注:目前callback url仅支持80端口(@2012.11.27)

#UPDATE 2012.11.29 添加了对图片消息的支持(样例)、对调试的支持(DUMP请求/回复的xml文件),测试SAE可用(调试功能除外)

== 分割线,下面是纯吐槽 ==
Nov 25
C/C++标准里头都没有正则表达式,C++还好,可以用上boost::regex,C的话,最简单的还是用系统自带的正则库了。

这个正则库真是相当简单,如果不关心内部琐碎的细节,实际上它只有2个类型、4个函数和7个常量,详细的后面会列出来(或者直接man regex),这里还是直接看例子比较实在:

代码1:email格式检测
#include <stdio.h>
#include <regex.h>
#include <assert.h>

int main() {
    //分配一个regex_t
    regex_t reg;
    //编译(使用POSIX扩展模式、并忽略大小写),确认编译成功(返回0)
    assert(regcomp(&reg, "^[a-z0-9_]+@([a-z0-9-]+\\.)+[a-z0-9]+$", REG_EXTENDED | REG_ICASE) == 0);
    int ret = regexec(&reg, "steve@rim.jobs", 0, NULL, 0); //执行搜索
    //看看返回值:0表示匹配成功,1表示REG_NOMATCH
    printf("ret = %d, nomatch = %d\n", ret, REG_NOMATCH);
    regfree(&reg); //记得释放空间
}

Nov 9

气球 不指定

felix021 @ 2012-11-9 16:25 [IT » 操作系统] 评论(0) , 引用(0) , 阅读(4455) | Via 本站原创
当主机内存想要回收一些虚拟机的内存时,应该怎么办?根据主机OS记录的内存使用情况来swap虚拟机占用的内存吗?不是的。

因为只有虚拟机的OS知道哪些内存是它最需要的,所以让它自己释放内存才是最好的选择。

怎么让它自己释放内存呢?对虚拟机比较了解的同学应该知道气球驱动(Balloon Driver)。其实原理很简单,就是在虚拟机里,使用一个进程申请并占用*一批内存,于是虚拟机的OS便会把它最不需要的内存释放掉一部分。然后这个进程再告诉主机OS我拿到这么多空间啦,你拿去用吧。

*注:申请内存和占用内存通常是两回事。实际上每次调用malloc(其实关键是brk系统调用)等函数分配内存时,系统只是在页表中标记,某些页已经分配给进程XXX,但是对应的地址空间并不是可用的。当进程使用到这个页的时候,会产生一个缺页中断,在中断例程中,系统才会真的从可用空间中分配一个页,然后再让进程继续使用。这个过程对于进程而言是透明的(不过可能会有一些延迟)。这种延迟机制在OS中大量使用,被实践证明是相当有效的,所以有拖延症的同学们,不要太担心哟,适当的拖延不见得是坏事

说了这么多废话,其实没到这篇的主题。

最近觉得公司配的台式机内存不够了(4G),由于我经常开大量的程序和网页,直到内存不够用的时候才关(拖延。。),因此常常是开个虚拟机的时候空闲内存立刻下降到0,这时候各种卡顿,连鼠标都卡。于是自费在X东上买了一条南亚易胜的 4GB DDR3 1600(这个牌子最便宜。。据说这个牌子给许多其他品牌代工)。安装上去以后,内存不够的现象显著缓解,一口气开三个虚拟机,腰不酸腿不痛的。

但是才用了两天,又发现空闲内存*不多了,经常是才剩下几百MB,有时竟然只有几十MB。虽然任务管理器里说了,8G内存中有6G+是“已缓存”,但是还是怀疑是不是某个进程占用了太多内存。于是一个一个截,全都截掉,空闲内存也还是不到2GB。

*注:Win7任务管理器中的”空闲内存“是malloc以来马上就可以给分配的。“已缓存”是操作系统用内存来保存硬盘上的数据(因为硬盘太慢了)。“可用内存”包括了空闲内存和已缓存。当进程申请的内存超过空闲内存的大小后,OS可以将已缓存的一部分释放掉,但是这个过程需要占用一段时间,效率不如直接使用空闲内存。

于是写了个气球程序来测试。最初的程序很简单,就是while(1)死循环,申请4M内存,然后memset清零。编译,运行,梆!出错了。看了下,进程只申请了2G的内存就出错了,简单分析就知道了,因为我用的是mingw32,生成的32位程序只能申请最多2G的用户空间,再一次malloc的时候,返回的是NULL,于是memset就会导致非法内存访问。

解决办法很简单…………跑3个,然后就申请并占用了6G内存,结束掉之后,发现任务管理器中的已缓存只剩一点点,空闲内存有了6G+,效果显著。看来的确是都被缓存了,不是某个进程太坑爹。注意:如果多跑了一个,就会发现第四个进程速度显著变慢,甚至拖累系统变得相当相当慢,说明这个时候已经在使用swap(或者叫交换空间,虚拟内存,pagefile,页面文件,分页文件……)了,所以不得不慢。

当然,更好的解决方法是编译生成一个64位的可执行程序,这样就能够申请足够多的空间了。到mingw64去下载一个rubenvb编译好的gcc套装直接编译,或者安装个visual studio,都行。

实际上,让系统放弃所有的缓存并不是好事。在XP以及更早的windows系统中,大概是开发人员假设用户的内存总是不够用的,所以系统不会大量使用内存来进行缓存。所以在内存大降价而vista/win7还没出来的时候(大概是08~09年),在XP机器上看到1~2G甚至更多内存并不奇怪,用不完怎么办呢?RAMDISK大行其道,虚拟一个分区出来,速度快得不行。XP的继任者Vista和Win7效仿Unix/Linux系的做法,尽量将内存用于缓存,在很多情况下都可以通过避免磁盘IO来提高系统的反应速度。

因此这里给出个最终版本,默认只回收2G的缓存,再怎么样,缓存3~4G的数据应该已经足够应付日常应用了。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const int block = 1024 * 1024 * 8; //8M

int main(int argc, char *argv[])
{
    int i = 0, reclaim = 2048;
    if (argc > 1)
        sscanf(argv[1], "%d", &reclaim);

    for (i = 0; i < reclaim; i += 8)
    {
        char *c = (char *)malloc(block);
        if (c == NULL)
            return 0;
        memset(c, 0, block);
        printf("%4d MB\n", i);
    }
    return 0;
}
Oct 31

pexpect 使用范例 不指定

felix021 @ 2012-10-31 10:13 [IT » Python] 评论(0) , 引用(0) , 阅读(4878) | Via 本站原创
在shell里头,有些程序(比如ssh)的交互是从pty中读取的数据,直接的重定向无法解决,因此需要用到expect这种东西。不过这货的编译比较麻烦,各种依赖。幸好有个pexpect是纯python的,存档留个记录。
#!/usr/bin/python

import sys
import pexpect

password = 'password'
expect_list = ['(yes/no)', 'password:']

p = pexpect.spawn('ssh username@localhost ls')
try:
    while True:
        idx = p.expect(expect_list)
        print p.before + expect_list[idx],
        if idx == 0:
            print "yes"
            p.sendline('yes')
        elif idx == 1:
            print password
            p.sendline(password)
except pexpect.TIMEOUT:
    print >>sys.stderr, 'timeout'
except pexpect.EOF:
    print p.before


如果密码错误的话,输出
引用
The authenticity of host 'localhost (127.0.0.1)' can't be established.
RSA key fingerprint is fe:00:00::00:00:00:00:00:00:00:00:00:00:00:00.
Are you sure you want to continue connecting ((yes/no) yes
)? yes
Warning: Permanently added 'localhost' (RSA) to the list of known hosts.
username@localhost's password: password

Permission denied, please try again.
username@localhost's password: password

Permission denied, please try again.
username@localhost's password: password

Permission denied (publickey,gssapi-with-mic,password).


如果正确,则输出
引用
username@localhost's password: password

a
b
bin
code
Oct 30

短代码比赛 不指定

felix021 @ 2012-10-30 17:11 [IT » 程序设计] 评论(0) , 引用(0) , 阅读(4434) | Via 本站原创
比赛的起因是这样的,@Tranch同学在SegmentFault.com提了个问题,求一个代码,可以列出字符串"qwerty"被 "." 分割的所有情况,比如 q.werty qwe.rty q.w.e.r.t.y 等等。

这个问题其实很简单,qwerty中间最多可以塞5个". ",每个地方用1表示塞,0表示不塞,也就是正好循环 2^5 次就行了(对于全0的情况不做特别要求,可有可无),实现起来也非常容易,这里是写这篇文章时补充的一个C语言实现:
#include <stdio.h>

int main()
{
    char str[] = "qwerty";
    int i, j;
    for (i = 0; i < (1<<5); i++)
    {
        for (j = 0; j < 5; j++)
        {
            putchar(str[j]);
            if (((i >> j) & 1) == 1)
                putchar('.');
        }
        printf("y\n");
    }
    return 0;
}


不过当时没想写这样的代码,而是特意脑抽用python写了个递归的版本:
def add_dots_l(str):
    ret = []
    for i in range(1, len(str)):
        left  = str[:i]
        right = str[i:]
        ret.append(left + '.' + right)
        ret += [j + '.' + right for j in add_dots_l(left)]
        ret += [left + '.' + j  for j in add_dots_l(right)]
    return set(ret)

因为前一段时间看到 这里用21行python代码实现了一个拼写检查器,于是一时兴起,简化成了这个等价但是更难读的版本:
def add_dots(s):
    r = [s[:i] + '.' + s[i:] for i in range(1, len(s))]
    r += [j + '.' + s[i:] for i in range(1, len(s)) for j in add_dots(s[:i])]
    r += [s[:i] + '.' + j for i in range(1, len(s)) for j in add_dots(s[i:])]
    return set(r)

虽然已经很短了,但是我还是想知道,是否有更简单些的实现(一定程度上可以忽略效率和可读性),于是在MSTC的群里发了这个问题,简单起见,把字符串改成了"abcde",问问有没有更短的代码来给出各种组合。

然后 @杭神 扔了个代码出来,被喷“能不能用人话”。这段代码看起来是有些费解,主要思路是,生成 ['****', '***.', '**..', '*...', '....'] 的各种排列,然后用zip('abcd', p)交错组合起来(再删掉'*'):
from itertools import permutations as p  #itertools.permutations是python2.6引入的
map(lambda p: ''.join(j for i in zip('abcd', p) for j in i).replace('*', '') + 'e', [''.join(y) for x in map(lambda i: set(p('*' * (4-i) + '.' * i)), range(5)) for y in x])

然后 @霄妹纸 说,实际上那个是笛卡尔积。于是用上itertools.product,再改善下语法,可以写成这样,看起来就清晰多了:
from itertools import product  #itertools.product也是2.6引入的
map(lambda p: ''.join(i + j for i, j in zip('abcd', p)) + 'e', product(['.', ''], repeat = 4))

@霄妹纸 还给出了另外两个奇葩的代码,一个是 C 的,充分利用和宏、main函数的参数和递归:
#define z(a,b) printf(#a"%s",(x>>b)&1?".":""),
main(x){z(a,3)z(b,2)z(c,1)z(d,0)puts("e");16-x&&main(x+1);}

另一个是ruby的:
p ("b".."e").inject(["a"]){|a,q|a.product [q,"."+q]}.map &:join
如果是ruby 1.9+的话,还可以再少几个字符:
p (?b..?e).inject([?a]){|a,q|a.product [q,?.+q]}.map&:join

由于不懂ruby语法,所以这个代码我也只能勉强看看,不过思路上跟上面的python代码是一样的,使用笛卡尔积生成组合序列,然后再与'abcde'交错组合。

结果是,ruby赢了(58个字节),python紧随其后(80字节,不包括import),C语言则意外地以106个字节的代码实现了这个目标。

这个问题从实践的角度上来说没有太大意义,不过可以对比下,不同的语言(C/Ruby/Python)、不同的编程范型(过程式/函数式)的表达方式,一窥函数式编程的魅力~
Oct 23

Don't Track Me Google 不指定

felix021 @ 2012-10-23 19:16 [IT » 网络] 评论(4) , 引用(0) , 阅读(8770) | Via 本站原创
用Google的第一大烦恼是随时被墙。

番羽土啬吧。

用Google的第二大烦恼是,点击的链接总要过一道Google的统计。比如搜索test,第一条是www.test.com,但是点击的时候,打开的页面是

http://www.google.com.hk/url?sa=t&rct=j&q=&esrc=s&frm=1&source=web&cd=1&ved=0CCoQFjAA&url=http%3A%2F%2Fwww.test.com%2F&ei=LHuGUMSUDKf-iAeg7YCAAg&usg=AFQjCNH21KLjC0CBkjon2DwD_CZ0HApLMw

经常发生的事情是,一直卡在这个链接上,不管是否番羽土啬,反正总是跳不过去;而大部分情况下,目标网页不需要番羽土啬也是可以顺利打开的。

至于Google呢,他才不管你是不是总卡死在这个链接,反正他们要的是你这次点击的数据。

这个问题,可以借助一个叫"Don't Track Me Google"的grease monkey脚本来实现:

http://userscripts.org/scripts/show/121923

对于Firefox,需要安装Grease Monkey插件,而Chrome用户则方便了,直接可以作为插件使用。

页面右上方有个不起眼的 Install 按钮,点一下,会下载一个 xxxx.user.js ,旧版本的chrome就直接当插件安装上去了,新版本的会提示你,不能这么安装非官方来源的插件。解决方法是:打开扩展管理页面,把这个 js 文件拖进去,会问你是否要安装,点击“添加”,done。
分页: 18/103 第一页 上页 13 14 15 16 17 18 19 20 21 22 下页 最后页 [ 显示模式: 摘要 | 列表 ]