Feb 4

探探gethostbyname 不指定

felix021 @ 2013-2-4 23:18 [IT » 网络] 评论(0) , 引用(0) , 阅读(6791) | 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了。

到此差不多该结束了,最后吐槽一句,这两个接口真奇葩啊。。。
Jan 20
这周写了个模块,让python可以跟后台的C服务通信了。

总体上来说给python写个模块还是比较容易的,比给php写模块要舒服多了,但是还是遇到一个问题:给php写模块的时候可以用MINIT完成初始化、MSHUTDOWN完成清理;给Python写模块,有对应的模块初始化函数,但是却没有对应的清理函数,实在是令人蛋疼。

需求其实很简单(应该也很普遍吧?),只是要在python退出之前执行一点代码,保证在初始化的时候分配的一些资源能够被释放;但是模块本身并没有提供这样的机制,只好想其他办法了。

最简单的是用C语言本身提供的 atexit ,不过这是在main()结束或者调用exit()时 - 也就是说,在整个C环境要结束了的情况下才会运行(但是在关闭所有打开的文件之前)。

虽然有效,但是由于它不是python提供的机制,多少让人有点不太放心,所以还是看看别人怎么用的吧。

Google搜了一下"python  module  destructor",stackoverflow上说的是,可以用python的 atexit 模块。这是一个纯python模块,源码可以在 /usr/lib/pythonX.X/atexit.py 看到,其实就是通过 atexit.register() 注册退出函数,并将 atexit._run_exitfuncs() 函数绑定到 sys.exitfunc 。sys.exitfunc会在Py_Finalize()这个函数中被调用(call_sys_exitfunc())。

在C模块里调用它,最简单的办法是在模块的初始化函数里加入类似这样一句(注意换行和缩进):
    PyRun_SimpleString(
            "import atexit\n"
            "def __modname_clean():\n"
            "    import modname\n"
            "    modname.clean()\n"
            "atexit.register(__modname_clean)\n");

这大致相当于在python脚本里用eval执行了一段代码(没有指定globals、locals和compile_flag)。也可以用如下几乎等价的纯C代码
//注:m是在模块初始化函数中的定义的当前模块对象
    PyObject *pClean = PyObject_GetAttrString(m, "clean");
    if (pClean != NULL)
    {
        PyObject *pName = PyString_FromString("atexit");
        PyObject *pMod = PyImport_Import(pName);
        Py_DECREF(pName);
        if (pMod != NULL)
        {
            PyObject *pFunc = PyObject_GetAttrString(pMod, "register");
            if (pFunc && PyCallable_Check(pFunc))
            {
                PyObject *pArgs = Py_BuildValue("(O)", pClean);
                PyObject *ret = PyObject_CallObject(pFunc, pArgs);
                //if (ret == NULL) ; //sth went wrong
                Py_XDECREF(pArgs);
                Py_XDECREF(ret);
            }
            Py_XDECREF(pFunc);
        }
        Py_XDECREF(pMod);
    }
    Py_XDECREF(pClean);

由于错误处理、引用计数的代码占了一大半,所以代码这么长……

这种方法的好处是,它在python运行环境结束之前执行注册的函数,所以注册的函数仍然可以使用绝大部分python提供的功能(你甚至可以再import新的模块,但是最好别用thread...)。然而atexit有一个蛋疼的缺陷:尽管python在sys模块的doc里写的是“Assigning to sys.exitfunc is deprecated; use the atexit module instead.”,但是sys.exitfunc仍然是任意python脚本都可以修改的!所以通过atexit.register()注册的函数并不能100%保证被运行。

再回过头来看Py_Finalize()函数,这里其实是有很大槽点的:它有一个长达十一行的"sundry finalizers"段!
    /* Sundry finalizers */
    PyMethod_Fini();
    PyFrame_Fini();
    PyCFunction_Fini();
    PyTuple_Fini();
    PyList_Fini();
    PySet_Fini();
    PyString_Fini();
    PyByteArray_Fini();
    PyInt_Fini();
    PyFloat_Fini();
    PyDict_Fini();

也就是说,它为许多内置的module逐一硬编码了清理函数,却没有实现一个清理机制!

甚至在这后面还有一段注释:
    /* XXX Still allocated:
      - various static ad-hoc pointers to interned strings
      - int and float free list blocks
      - whatever various modules and libraries allocate
    */
坑爹啊。。。

再往下看,在Py_Finalize()的最后一行终于出现了本篇的另一个主角:call_ll_exitfuncs(); 这个函数的内容很简单:逐一执行 exitfuncs 这个数组里保存的函数。而 exitfuncs 这个数组的内容,则是由 Py_AtExit() 函数填进去的:
#define NEXITFUNCS 32  //另一个坑:最多只能注册32个exitfunc
static void (*exitfuncs[NEXITFUNCS])(void);
static int nexitfuncs = 0;

int Py_AtExit(void (*func)(void))
{
    if (nexitfuncs >= NEXITFUNCS)
        return -1;
    exitfuncs[nexitfuncs++] = func;
    return 0;
}

我最终采用的解决方案是这个:Py_AtExit()。

最后总结对比一下上述3种方案:

1. C语言的 atexit() : 使用链表保存注册的函数,只要内存够,数量没限制。在Python完全结束后执行。
2. Python的 atexit 模块:使用 list 保存注册的函数,在register的时候还可以带参数,在Python解释器仍然完整的情况下执行,一般来说很够用;但由于使用的sys.exitfunc可能会被其他脚本使用,并不能100%保证有效,故,有强迫症的慎用。或者应当在doc里明确说明。
3. Python C API的 Py_AtExit() :在Python环境几乎完全结束的时候被调用,最多只能注册32个函数(所以最好要检查返回值)。其实从流程上来说,在这之后马上就是main的return或者exit()函数调用,所以跟atexit基本上差不多。

p.s. 另一个尝试但是失败了的方案:建立一个新的type X_Type,在它的tp_dealloc里头写入清理代码,以X_Type建立一个新的class X,在模块的初始化函数中new一个X,撂着,并期望Py_Finalize()里的PyGC_Collect()会把它回收掉。不知道还差了点什么,如果哪位大牛知道,还望不吝赐教。
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) , 阅读(4779) | 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) , 阅读(5211) | 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
分页: 18/103 第一页 上页 13 14 15 16 17 18 19 20 21 22 下页 最后页 [ 显示模式: 摘要 | 列表 ]