Jul 11
在模板里引入其他模板应该是很常见的一种需求,但是webpy默认的template居然没有提供这种机制,挺神奇的。

官方的解决办法是把某个模板的输出给另一个模板,看起来和用起来都超级不爽;知乎网友给出的解决方案也很不爽:用render初始化时指定的layout,但是这个跟include差很多,不灵活。

实际上,由于render渲染模板后的输出本身是一个字符串,所以如果能在模板里头直接调用render渲染其他模板就最好了:而且这是可以实现的,只是略带tricky。

由于webpy给render的默认globals是空的,所以模板里只能用基本的python语法,默认连builtin的东西都用不了(比如zip、str),但是可以通过初始化render时指定globals的方式来引入:
render = web.template.render('view/', cache=False, globals = __builtins__.__dict__)


所以我们只需要把render自己也加到这个globals里头去,就可以在模板里引用它了:
render_globals = {}
render = web.template.render('view/', cache=False, globals = render_globals)
render_globals['render'] = render


第三句看起来虽然tricky,但是由于python的对象传的是引用,所以可以达到预期的效果。

这样只要在模板里这样写就行了:
$:render.header('首页') #那个冒号别漏掉:)
Feb 4

探探gethostbyname 不指定

felix021 @ 2013-2-4 23:18 [IT » 网络] 评论(0) , 引用(0) , 阅读(4793) | Via 本站原创
开篇先说一下,在它的manpage里面,有这么一句话:
引用
The gethostbyname*() and gethostbyaddr*() functions are obsolete. Applications should use getaddrinfo(3) and getnameinfo(3) instead.
这是因为gethostbyname只能处理ipv4请求(经测试ipv6.google.com不能解析,ipv6.tsinghua.edu.cn只能解析出ipv4地址),因此推荐使用 getaddrinfo 来替代它。

忽略上述问题不管的话, gethostbyname 还是有点意思的。它的原型和用法大概可以这样:
struct hostent *gethostbyname(const char *name);

struct hostent *he = gethostbyname(argv[1]);
printf("%s\n", inet_ntoa(*(struct in_addr *)he->h_addr));

用法特别简单吧,如果不是在提到这个函数的时候附带说明“它使用了内部分配的空间,因此不是线程安全的”,估计会大受欢迎的。
//注:由于windows版用了 thread local 的变量,所以在windows下它是线程安全的(只要不是同一个线程马上再调用覆盖它的话)。

相应的,像ctime、localtime之类的posix接口一样,它的线程安全版就是带了 _r 后缀的 gethostname_r ,虽然名字只复杂了一点点,但是接口可不只是复杂了一点点啊:
int gethostbyname_r(const char *name, struct hostent *ret,
    char *buf, size_t buflen, struct hostent **result, int *h_errnop);
其中的name是hostname(或者ip地址),ret应当指向一个分配好的struct hostent结构体(函数把内容填进去),buf应当指向一块分配好的空间(不小于buflen),result是一个struct hostent的二级指针(如果失败会存个NULL),h_errnop则指向某个int(失败则填对应的herr,这个奇葩函数的errno跟errno.h里面那个还不是一套)。

用起来真是相当地恶心,比如下面这个函数通过它来解析ip地址,跟前面的代码的复杂度完全不是一个量级的:
int host2addr(const char *host, struct in_addr *addr)
{
    struct hostent he, *result;
    int herr, ret, bufsz = 512;
    char *buff = NULL;
    do {
        char *new_buff = (char *)realloc(buff, bufsz);
        if (new_buff == NULL) {
            free(buff);
            return ENOMEM;
        }
        buff = new_buff;
        ret = gethostbyname_r(host, &he, buff, bufsz, &result, &herr);
        bufsz *= 2;
    } while (ret == ERANGE);

    if (ret == 0 && result != NULL)
        *addr = *(struct in_addr *)he.h_addr;
    else if (result == NULL)
        ret = herr;
    free(buff);
    return ret;
}


代码是写出来了,但是觉得不太对头,仔细看了一下 struct hostent 的定义,里头还有多个指针(h_name、h_aliases、h_addr_list)什么的:
struct hostent {
  char  *h_name;            /* official name of host */
  char **h_aliases;        /* alias list */
  int    h_addrtype;        /* host address type */
  int    h_length;          /* length of address */
  char **h_addr_list;      /* list of addresses */
}
#define h_addr h_addr_list[0] /* for backward compatibility */
如果不释放的话,不会造成内存泄漏么?

于是去看了下gethostbyname和gethostbyname_r的源码,进入 eglibc-2.15 的源码, ctags -r 然后 vim -t gethostbyname ,竟然没有这个函数。然后翻了翻,看到有个 inet 目录,里面有个 gethstbynm.c 这样一个奇葩的文件,里面除了include一些头文件之外,只有一些简单的宏定义和一个奇怪的include:
#define LOOKUP_TYPE struct hostent
#define FUNCTION_NAME  gethostbyname
#define DATABASE_NAME  hosts
#define ADD_PARAMS  const char *name
#define ADD_VARIABLES  name
#define BUFLEN      1024
#define NEED_H_ERRNO    1

#define HANDLE_DIGITS_DOTS  1

#include <nss/getXXbyYY.c>

打开一看 getXXbyYY.c 这个文件更奇葩了,根本就是用宏来实现C++中的模板功能啊。。。grep一下还发现,这个文件被18个其他文件include,也就是说,有18个函数(诸如gethostbyaddr, getnetbyaddr, 甚至getpwuid、getpwnam)都是用的这个模板。

凑合着看了一下,代码倒是不复杂(具体就不贴出来了),基本逻辑是这样的:
LOOKUP_TYPE *
FUNCTION_NAME (ADD_PARAMS)
{
    获取锁
    如buffer==NULL,分配初始化的空间
    如定义了HANDLE_DIGITS_DOTS(如gethostbyname)则检查name是否是ip地址并处理(是的话就不用发dns请求了)
    类似的while循环:调用对应的线程安全版函数,并检查是否buffer大小不够(如果是的话realloc)
    释放锁
    如果定义了NEED_H_ERRNO,设置对应的值
    返回
}

也就是说,代码流程基本上跟前面调用 gethostbyname_r 的代码一样,只是多了一个获取/释放锁的操作。根据里面的流程可以看出,代码里面不是使用静态的空间,而是动态malloc的(所以要获取锁,否则在libc内部崩溃,那不好)。由此,所谓的不是线程安全的,其实并不是函数本身,而是返回的数据。

但是,看懂了这个代码,并没有解决上面的问题啊。。。于是到 stackoverflow 上面去提了个问,很快就得到大牛的回答了:
引用
You should print out the values of the pointers in that struct to find out the answer to your question. You'll discover that they all point to data inside the buffer you allocated.

So a single free is all you need to free up all the memory.

But this also means that you must not free that allocation until you've finished using or copying whatever data you're interested in.

也就是说,我完全忽略了塞给 gethostbyname_r 的 buf 这个参数:hostent 里面的指针指向的数据就存在 buf 里面,因此最后只要 free 掉它就够了。

由于有些域名可能绑定了好多个ip(比如google.com可能就绑了数十个),buf 的空间可能不够大,所以才需要一个realloc的机制。根据实测,buf的大小从512或者1024开始比较合适,普通的域名就不需要realloc了。

到此差不多该结束了,最后吐槽一句,这两个接口真奇葩啊。。。
Oct 23

Don't Track Me Google 不指定

felix021 @ 2012-10-23 19:16 [IT » 网络] 评论(4) , 引用(0) , 阅读(6596) | 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。
Sep 26

12306刷存在感@chrome 不指定

felix021 @ 2012-9-26 10:25 [IT » 网络] 评论(1) , 引用(0) , 阅读(5374) | Via 本站原创
尽管 https://dynamic.12306.cn/otsweb 的 jsessionid 有效期是一年,但是如果不几分钟发个请求的话,照样会被T出来。

所以写了这么个小代码,可以每隔一段时间自动“点”一下重新查询按钮。

=== update @ 16:40 ===
其实简单一点,按下F12,在console里面执行这个就行了...

var f=document.getElementById('main'); var btn=f.contentDocument.getElementById('submitQuery'); setInterval("btn.click()", 30000);



=== OLD ===

所以写了这么个小代码,可以每隔一段时间自动“点”一下重新查询按钮。

<div>
<textarea id="code">
var x=setInterval("document.getElementById('submitQuery').click();", 30000);alert(x);
</textarea>
<input type="button" value="run" onclick="javascript:try{eval(document.getElementById('code').value);}catch(e){alert(e);}"/>
</div>


用法:在chrome下打开订票页面,按F12,在框架内找个地方把这段代码扔进去,确定,然后在页面上点击多出来Run按钮,这样就会被个30s自动“点击”一次 [重新查询] 按钮。


Sep 3
#!/bin/bash

API=https://dnsapi.cn/Record.Ddns

IP_FILE=/tmp/dnspod_ip

function get_old_ip()
{
    ip=
    if [ -e "$IP_FILE" ]; then
        ip=`cat $IP_FILE`
    fi
    echo $ip
}

function save_ip()
{
    echo -n $1 > $IP_FILE
}

function get_new_ip()
{
    echo `nc ns1.dnspod.net 6666`
}

email=帐号邮箱
password=帐号密码  #dnspod就不能搞成个secret_key么!明文密码让人很不舒服啊。

domain_id=XXXXX #使用Domain.List API获取
record_id=YYYYY #使用Record.List API获取
sub_domain="zzz" #DDNS的二级域名

new_ip=`get_new_ip`
old_ip=`get_old_ip`

if [ "$new_ip" != "$old_ip" ];
then
    curl $API -d "format=json&login_email=$email&login_password=$password&domain_id=$domain_id&record_id=$record_id&sub_domain=$sub_domain&record_line=默认"
    save_ip $new_ip
fi

然后加入crontab,每隔15分钟跑一次进行更新

*/15 * * * * ~/bin/dnspod.sh
Jul 26
如果到Google去搜索,"How to find out which process is listening upon a port"这是第一篇文章。

事实上大部分文章都是告诉你,要么 lsof -i :80 要么 netstat -antulp | grep :80 就能找到httpd。

可是如果就这样的话,这篇BLOG就变成微博了。

事实上我的目的是希望通过编程找出这个PID,而不是调用某个命令。

第一个尝试是去看lsof的源码。找源码容易,apt-get source lsof 就行。但是源码跟大部分linux软件包一样,看起来相当晦涩。

第二个尝试是Google,但是能找到的都是命令版的。

第三个尝试是stackoverflow,没有直接搜到问题,于是准备自己提问,但是在“Questions that may already have your answer”里头找到了一篇“How to get the pid of a process that is listening on a certain port programmatically?” ( http://stackoverflow.com/questions/10996242 )。

Answer给出的步骤非常清晰:与netstat的实现一样,先读取 /proc/net/tcp 这个文件,第二个字段(local_address)冒号后面的是端口号(十六进制),第四个字段st为0A表示TCP_LISTEN,第10个字段是十进制的inode编号;而通过遍历 /proc/PIDs/fd 下面的链接,找到链接到形如 socket:[端口号] 的fd进行对比,就能知道哪些进程与该端口有一腿。

我在机器上监听的是8888端口,换成hex是22B8,但是在 /proc/net/tcp 中却找不到。幸而那篇文章的第二个Answer给了个提示,于是 strace /usr/sbin/lsof -i :8888 ,发现它还打开了 /proc/net/tcp6 (也就是对应IPv6的那个文件了)。过去一查,果然有,再顺着inode,对照lsof的结果一看,的确符合。

于是再去grep一把 lsof 的源码目录,发现在 00FAQ 文件中的 10.2.2 一节就说明了 lsof 的实现机制:
引用
Lsof identifies protocols by matching the node number associated
    with the /proc//fd entry to the node numbers found in
    selected files of the /proc/net sub-directory.  Currently
    /proc-based lsof examines these protocol files:
        /proc/net/ax25      (untested)
        /proc/net/ipx      (needs kernel patch)
        /proc/net/raw        /proc/net/raw6
        /proc/net/tcp        /proc/net/tcp6
        /proc/net/udp        /proc/net/udp6
        /proc/net/unix

看来在 Linux 下的实现方式确实只有这一种。顺便提一下,Stackoverflow上面的另一篇 http://stackoverflow.com/questions/4041003/c-what-process-is-listening-on-a-certain-port-in-windows 提到了,在Windows下可以用GetExtendedTcpTable/GetExtendedUdpTable来实现。

最后附上PHP实现的源码(这个代码用C/C++写确实蛋疼)
<?php

$filelist = array("/proc/net/tcp6", "/proc/net/tcp"); //udp/unix的就先不管了

$port2inode = array();

foreach ($filelist as $file)
{
    $lines = file($file);
    array_shift($lines);
    foreach ($lines as $line)
    {
        $values = split(" +", $line);
        list($addr, $port) = explode(":", $values[2]);
        $port = hexdec($port);
        $port2inode[$port] = $values[10];
    }
}

if ($argc < 2)
    die("usage: php {$argv[0]} PORT\n");
$findport = $argv[1];

if(!isset($port2inode[$findport]))
    die("$findport not listened\n");

$procdir = scandir("/proc");
natcasesort($procdir);

foreach ($procdir as $pid)
{
    $path = "/proc/$pid/fd";
    if (!is_readable($path) || !is_numeric($pid) || !is_dir($path)) continue;
    $dir = scandir($path);
    foreach ($dir as $file)
    {
        $link = "$path/$file";
        if (!is_link($link)) continue;
        $real = readlink($link);
        if (substr($real, 0, 7) == "socket:")
        {
            $port = substr($real, 8, -1);
            if ($port == $port2inode[$findport])
            {
                echo $pid, ": ", readlink("/proc/$pid/exe") . "\n";
                continue;
            }
        }
    }
}
?>
Apr 20
很多应用层协议都有HeartBeat机制,通常是客户端每隔一小段时间向服务器发送一个数据包,通知服务器自己仍然在线,并传输一些可能必要的数据。使用心跳包的典型协议是IM,比如QQ/MSN/飞信等协议。

学过TCP/IP的同学应该都知道,传输层的两个主要协议是UDP和TCP,其中UDP是无连接的、面向packet的,而TCP协议是有连接、面向流的协议。

所以非常容易理解,使用UDP协议的客户端(例如早期的“OICQ”,听说OICQ.com这两天被抢注了来着,好古老的回忆)需要定时向服务器发送心跳包,告诉服务器自己在线。

然而,MSN和现在的QQ往往使用的是TCP连接了,尽管TCP/IP底层提供了可选的KeepAlive(ACK-ACK包)机制,但是它们也还是实现了更高层的心跳包。似乎既浪费流量又浪费CPU,有点莫名其妙。

具体查了下,TCP的KeepAlive机制是这样的,首先它貌似默认是不打开的,要用setsockopt将SOL_SOCKET.SO_KEEPALIVE设置为1才是打开,并且可以设置三个参数tcp_keepalive_time/tcp_keepalive_probes/tcp_keepalive_intvl,分别表示连接闲置多久开始发keepalive的ack包、发几个ack包不回复才当对方死了、两个ack包之间间隔多长,在我测试的Ubuntu Server 10.04下面默认值是7200秒(2个小时,要不要这么蛋疼啊!)、9次、75秒。于是连接就了有一个超时时间窗口,如果连接之间没有通信,这个时间窗口会逐渐减小,当它减小到零的时候,TCP协议会向对方发一个带有ACK标志的空数据包(KeepAlive探针),对方在收到ACK包以后,如果连接一切正常,应该回复一个ACK;如果连接出现错误了(例如对方重启了,连接状态丢失),则应当回复一个RST;如果对方没有回复,服务器每隔intvl的时间再发ACK,如果连续probes个包都被无视了,说明连接被断开了。

这里有一篇非常详细的介绍文章: http://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO ,包括了KeepAlive的介绍、相关内核参数、C编程接口、如何为现有应用(可以或者不可以修改源码的)启用KeepAlive机制,很值得详读。

这篇文章的2.4节说的是“Preventing disconnection due to network inactivity”,阻止因网络连接不活跃(长时间没有数据包)而导致的连接中断,说的是,很多网络设备,尤其是NAT路由器,由于其硬件的限制(例如内存、CPU处理能力),无法保持其上的所有连接,因此在必要的时候,会在连接池中选择一些不活跃的连接踢掉。典型做法是LRU,把最久没有数据的连接给T掉。通过使用TCP的KeepAlive机制(修改那个time参数),可以让连接每隔一小段时间就产生一些ack包,以降低被T掉的风险,当然,这样的代价是额外的网络和CPU负担。

前面说到,许多IM协议实现了自己的心跳机制,而不是直接依赖于底层的机制,不知道真正的原因是什么。

就我看来,一些简单的协议,直接使用底层机制就可以了,对上层完全透明,降低了开发难度,不用管理连接对应的状态。而那些自己实现心跳机制的协议,应该是期望通过发送心跳包的同时来传输一些数据,这样服务端可以获知更多的状态。例如某些客户端很喜欢收集用户的信息……反正是要发个包,不如再塞点数据,否则包头又浪费了……

大概就是这样吧,如果有大牛知道真正的原因,还望不吝赐教。


@2012-04-21

p.s. 通过咨询某个做过IM的同事,参考答案应该是,自己实现的心跳机制通用,可以无视底层的UDP或TCP协议。如果只是用TCP协议的话,那么直接使用KeepAlive机制就足够了。

@2015-09-14
补充一下 @Jack的回复:
“心跳除了说明应用程序还活着(进程还在,网络通畅),更重要的是表明应用程序还能正常工作。而 TCP keepalive 有操作系统负责探查,即便进程死锁,或阻塞,操作系统也会如常收发 TCP keepalive 消息。对方无法得知这一异常。摘自《Linux 多线程服务端编程》”
Feb 27

高延迟SSH部分解决方案 不指定

felix021 @ 2012-2-27 21:27 [IT » 网络] 评论(1) , 引用(0) , 阅读(12133) | Via 本站原创
vps在国外,延迟总有那么200~300ms,一来一回,500ms是免不了了。可是默认情况下,你每输入一个字符,ssh客户端(openssh/putty/securecrt)都会发送给服务器,然后服务器将响应返回。

典型ssh情况下是执行命令,比如ls,网络交互是:发送 l 给svr, svr返回 l ,显示 l ,发送 s 给svr,svr返回 s ,显示 s ,发送回车给svr,svr执行 ls ,返回 ls 的输出。也就是说,光输入一个ls命令就至少需要1s+的时间。但如果是要输入一个很复杂的命令,也许还没输入完,你就崩溃了。

采用putty(windows版ok,linux版未测试)内建的Local Echo和Local Line Editing支持,可以部分地解决这个问题:默认配置下,登录以后点击左上角的Putty图标,选择change settings=>Terminal,将Local Echo和Local line editing改成force on,就可以允许你在本地编辑一行命令,按下回车,然后命令才被发送到服务器。结果是服务器接收一整条命令,然后显示一整条命令,然后再输出这条命令的执行结果。

相应的代价就是:
1. 没法使用自动补全和其他bash/readline的快捷键了;
2. 使用vi这类程序的时候,就没法正常编辑了,这时需要再把这两个选项关闭。。。(为什么没有快捷键………………)
分页: 2/26 第一页 上页 1 2 3 4 5 6 7 8 9 10 下页 最后页 [ 显示模式: 摘要 | 列表 ]