Skip navigation.

Fat R笔记……与减肥无关

Fat awful terrible Rubbish-bin

Posts tagged with "Programming"

Sina blog的用户评论问题and..Opera的bug?

, , ,

研究了一下,sina blog是用ajax来获取用户评论的,在ajax部分作了针对不同浏览器的处理,然而在将获取到的html代码输出到页面时有一句:
<a name="comment" style="display:none;"/>

这个代码的目的是利用name属性创建一个anchor,但根据w3c的标准元素的end tag是必须的,也就是说,这种的写法是不标准的,应该写成

有趣的是Opera对这种不标准的tag的处理。我做了两个sample:
http://files.myopera.com/Returner/blog/disappear.htm
http://files.myopera.com/Returner/blog/1disappear.htm
可以看到,相同的代码直接写在html中,opera是会把它显示出来的。但如果是用innerHTML来动态插入,opera就会把内容隐藏了……似乎A标签中的style="display:none;"在动态插入时对后面的元素起了作用?同样的代码的两种解释行为,不知道是不是Opera的bug。

然而在FireFox中,无论直接写进html中还是用innerHTML来插入,效果都是一致的。但在第一个sample中,A元素后边紧接着的是一个DIV,这个DIV没有显示出来。而第二个sample中则是一个TABLE,这个TABLE却显示出来了。这就是为什么FireFox下能看到sina blog的用户评论。

IE下两个sample的行为更加一致,什么都不会显示。但我没研究在IE中那些用户评论最终是怎么显示出来的。

至于解决的办法,自然还是UserJS。需要做的事情很简单,在innerHTML插入之前将A标签正则替换为
<a name="comment" style=" "></a>

这里我把style去掉了,因为在opera中如果这个tag的style是display:none,Opera是不会用它作为一个anchor的,这就不能用#comment跳转到评论的位置了。

由于opera.defineMagicVariable只能覆盖全局变量,经过考虑最后还是选择对base.js中的output函数动手。output函数就是将ajax获取到的代码用innerHTML插入到指定元素中的“最终输出函数”。

UserJS代码如下:
// ==UserScript==
// @name             Show user comments in sina blog
// @author           Returner
// @include          http://blog.sina.com.cn/u/*
// ==/UserScript==

opera.defineMagicFunction(
  'output',
  function (real, oThis, _sHtml, _box)
  { _sHtml = _sHtml.replace(/(<a\s[^>]+)display\:none;([^>]+)\/>/ig,"$1 $2></a>");  
    real.apply( oThis, arguments.slice(2) );}
);

或下载下面文件到Opera的UserJS目录
sinablogcomment.js

不过添加评论还是有点问题(尤其是使用代理服务器上网时),问题的根源在于验证码的图片没有及时更新,从而总是提示验证码错误。在不使用代理时刷多几次就好了,但使用代理时似乎总是刷新不了验证码图片,郁闷……



新浪可能修改过程序,原来的脚本会找不到新浪脚本里的output函数,于是real.apply时实际上会无限递归地调用自己,从而造成cpu占用率和内存占用率的飙升。
于是只好修改程序,直接把新浪的output函数写到脚本里。不过这样毕竟不是长久之计,新浪如果到时改了程序,脚本又要跟着改,只能算是暂时解决了问题……
新的脚本在这里:
sinablogcomment_v2.js

faint! gthread初始化后崩溃的问题

,

这样一幅鸟德性的程序,执行时居然在g_print那就segmentation fault了:
int
main (int argc, char *argv[])
{
  g_thread_init (NULL);
  g_print("alive?\n");
  return 0;
}

我的实际情况更复杂一些,我是在gtk-2.0的程序中使用gthread的,在anjuta里面的Compiler and linker options里面加入了gthread库,结果程序一运行就立马崩溃。然而anjuta创建的gnome程序却可以正常使用gthread,我自己另外写一个简单的程序(就是上面那个),用`pkg-config --cflags --libs gthread-2.0`编译,运行起来也正常。那么看来还是编译选项的问题。于是只好把anjuta链接时使用的参数复制出来自己在命令行下面试。结果发现只要把glib-2.0链接进去了就会出错。然而程序依赖gtk+-2.0,必然需要把glib-2.0链进去。当时没明白过来到底怎么回事,后来在打算尝试把那个小程序的编译参数改成`pkg-config --cflags --libs gtk+-2.0 gthread-2.0`时才留意到那个是gthread-2.0,而我在anjuta里面选择的是gthread(没有gthread-2.0这项),它生成的编译参数用的是/usr/lib/libgthread.so,这个其实是1.2版本的,所以跟glib-2.0不兼容。于是在anjuta的Compiler and linker options的libraries标签页里面手动把gthread改成gthread-2.0,重新autogen、make,这样就可以正常使用gthread了

非常简单地……用esd播放PCM音频数据

,

网上关于esd的资料似乎不多,所以只好用google搜索函数名来看别人怎么用的……
esd本身提供了wav文件播放、sample播放和stream播放等相关的函数,可以在/usr/include/esd.h中看到所有的函数原型已经少得可怜的说明文字。下面要说的就是用它的stream播放功能来实现PCM音频数据流的播放。

首先程序要有#include <esd.h>,链接时也要加上-lesd开关。

获取esd服务器信息的方法:
  gint efd;
  esd_server_info_t *info;
  efd = esd_open_sound (NULL); //NULL表示本机的esd
  if (efd == -1)
    return;
  info = esd_get_server_info(efd);
  if (info)
    esd_print_server_info(info);
  esd_close(efd);

esd_server_info_t结构的成员可要在esd.h中看到,也就版本、数据格式、采样率三个信息。
数据格式的低4(0x000F)位标明数据是8位还是16位,0x00F0位标明是单声道还是立体声。在esd.h中定义了:
#define ESD_BITS8 ( 0x0000 )
#define ESD_BITS16( 0x0001 )
#define ESD_MONO( 0x0010 )
#define ESD_STEREO( 0x0020 )

即format == 0x0021时,表示server的输出数据是16位有符号数,双声道。

嗯,播放音频流可以不用调用esd_open_sound(),用esd_play_stream_fallback()打开一个player,获取对应的file descriptor,对这个fd进行write()操作就可以了。打开一个player的代码:
  gint player; //player is a FD

  player = esd_play_stream_fallback(ESD_BITS16 | ESD_STEREO, 44100,
    NULL, "dummy");
  if (player == -1)
    return;

第一个参数指明了程序使用的pcm音频数据的格式为signed int16,立体声(即一个数据为左声道一个数据为右声道地交替),第二个参数44100为采样率。如果pcm数据是单声道,或者采样率不是44100,就要对参数做相应的修改。pcm数据的这些格式参数可以在wav文件的文件头里提取出来。第三个参数用于指定esd server,NULL表示local,第四个参数为player设置一个名称标示。

然后就是打开pcm数据文件并向player写数据:
  gint fd, rlen;
  gchar buf[2048];
  fd_set rfds;

  if ((fd = open("081.raw", O_RDONLY))<0){
    g_error("failed to open input file\n");
    return;
  }
  do {
    FD_ZERO(&rfds);
    FD_SET(player, &rfds);
    rlen = read (fd, buf, 530 );
    if (select(player + 1, NULL, &rfds, NULL, NULL) < 0) {
      perror("select");
      break;
    }
    if (FD_ISSET(player, &rfds) ){
      write(player, buf, rlen);
    }
  } while (rlen > 0);

  esd_close(player);
  close (fd);

可以看到,跟前面说的socket操作类似,也是使用select检查fd是否可以写,当可以写的时候就把读到的数据写到player中,从而实现音频播放。因此如果fd是一个socket连接,就可以实现把网络上收到的音频流数据播放出来。同样,如果是蓝牙的sco音频连接,就可以把蓝牙耳机的mic收到的声音播放出来。
另外,可以参考一下xmms源码中的Output/esd目录,这是一个很好的esd音频输出范例。

datapipe.c笔记

,

拖啊拖,拖到现在才写……
前面的一堆include就不说了,大概就是针对win和*nix平台进行一些统一的定义。注意在*nix下:
  #define recv(x,y,z,a) read(x,y,z)
  #define send(x,y,z,a) write(x,y,z)
  #define closesocket(s) close(s)

然后是结构体:
struct client_t
{
  int inuse; //是否已经使用
  SOCKET csock, osock;
  //csock为远程客户端与本机连接的socket
  //osocks为远程服务器端与本机的连接的socket
  time_t activity;
  //上次活动时间,用于idle超时断开
};

在main()中声明了
struct client_t clients[MAXCLIENTS];

表示可以接受的客户端连接数
其它的声明:
  SOCKET lsock; //本地监听socket
  char buf[4096]; //收发buffer
  struct sockaddr_in laddr, oaddr; //本机ip地址和远端服务器ip地址与端口


接下来程序会从命令行解析本机ip和远程服务器ip,并创建监听socket:
  /* create the listener socket */
  if ((lsock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    perror("socket");
    return 20;
  }
  if (bind(lsock, (struct sockaddr *)&laddr, sizeof(laddr))) {
    perror("bind");
    return 20;
  }
  if (listen(lsock, 5)) {
    perror("listen");
    return 20;
  }

这里socket()创建了一个ipv4的socket,这个socket是没有地址/名字的。所以接下来将其bind到本机ip上,并用listen()来允许socket接受连接请求(在后面用accept()来接受并产生一个新的socket来表示建立起来的连接)。

至此初始化就完成了。接下来,如果是*nix,则用fork来产生子进程
  if ((i = fork()) == -1) {
    perror("fork");
    return 20;
  }
  if (i > 0)
    return 0;
  setsid();

对于原进程,得到的i > 0,因此会终止,留下子进程在后台继续运行。setsid()使子进程不会随用户logout而终止。

然后进入主循环:

    fd_set fdsr;
    int maxsock;
    struct timeval tv = {1,0}; //select超时为1秒

这几个是配合select使用的。
    /* build the list of sockets to check. */
    FD_ZERO(&fdsr);
    FD_SET(lsock, &fdsr);
    maxsock = (int) lsock;
    for (i = 0; i < MAXCLIENTS; i++)
      if (clients[i].inuse) {
        FD_SET(clients[i].csock, &fdsr);
        if ((int) clients[i].csock > maxsock)
          maxsock = (int) clients[i].csock;
        FD_SET(clients[i].osock, &fdsr);
        if ((int) clients[i].osock > maxsock)
          maxsock = (int) clients[i].osock;
      }      
    if (select(maxsock + 1, &fdsr, NULL, NULL, &tv) < 0) {
      return 30;
    }

首先FD_ZERO把列表清空,然后用FD_SET将监听lsock加入列表,接着将各个客户端连接的csock和osock也加入列表。这样所有在用的socket都在列表中了。这些列表中的fd(file descriptor)都是int型变量,maxsocks是其中数值最大者。
然后就是用select()来监视列表中哪个fd可读。前面用tv指定了时间为1秒,即如果有任一fd可读或超过1秒无任何fd可读,则程序继续执行。
select的原型如下:
int select(
  int nfds,
  fd_set* readfds,
  fd_set* writefds,
  fd_set* exceptfds,
  const struct timeval* timeout
);


接下来程序用FD_ISSET()检查是哪个fd可读。首先检查lsock,如果它可读,说明有客户端请求连接。
SOCKET csock = accept(lsock, NULL, 0);

用于接受请求,并得到这个连接的fd。然后在clients数组中寻找一个未使用的子元素,用它来保存这个新连接的信息。如果没有任何空闲的子元素,表示客户端连接已满,用closesocket(csock)来关闭连接。如果有空闲的子元素,就建立一个本机到远端服务器的连接,这样就建立起了这样的连接关系:
客户端<--csock-->本机<--osock-->服务器

接下来的程序是检查clients数组中已使用的元素的csock和osock是否可读,如果csock可读,就把数据读入buf并发送到osock,并设置activity为当前时间,从而实现将客户端发送的数据转发到服务器端。同样的,如果osocks可读,就是服务器发送数据到客户端。recv()的返回值是接收到的数据长度,如果从csock或osock接收到的数据长度<0,表示连接关闭,设置closeneeded = 1
然后检查各个元素的activity(即上次活动时间),如果超过设定的时间没有数据,也同样设置closeneeded = 1
然后就是当closeneeded == 1时关闭csock和osock

如此循环,就完成了portmap的功能。

在anjuta中使gtk+项目能够使用libxml2(或别的"第三方"开发库)

, ,

在anjuta中,gnome项目可以直接使用libxml等库,但gtk+项目中就不行,可以看到编译时会提示找不到头文件,链接也不会加上-lxml2开关。对于gtk+项目,要使用libxml2这样的第三方库,可以在compiler and linker settings里手动进行设置:

1 在Settins->Compiler and linker options->Include Paths(就是第二个标签页,中文翻译成了"引用路径"--真不习惯)
输入/usr/include/libxml2,并点add

2 在Settins->Compiler and linker options->Libraries,输入xml2并点add,确认xml2加入在左边"Libraries and modules"列表并选中

3 点close关闭Compiler and linker options对话框,会看到一个提示对话框,选Yes

4 Build->Auto generate(这个中文翻译成"生成建程文件")

5 这时再shift+f11来Build all就应该可以了

使用gnome-sound播放声音

,

嗯……我就喜欢这种简单的东西……gnome-sound只有几个函数,可以用来实现简单的声音播放。它的具体文档:http://developer.gnome.org/doc/API/2.0/libgnome/libgnome-gnome-sound.html

目前这个模块只是esound功能的封装(正合我意呢……),以后可能会进行扩展。提供的函数有:
void        gnome_sound_init                (const char *hostname);
void        gnome_sound_shutdown            (void);
void        gnome_sound_play                (const char *filename);
int         gnome_sound_sample_load         (const char *sample_name,
                                             const char *filename);
int         gnome_sound_connection_get      (void);


本机启用了esd后,会监听一个tcp端口,客户端连接这个tcp端口并发送声音数据就可以实现声音播放了。gnome-sound简化了这些细节,应用程序只需要先调用gnome_sound_init()连接esd服务器,再调用gnome_sound_play()播放声音就可以了。例如:
  gnome_sound_init(NULL);
  gnome_sound_play ("/usr/share/sounds/login.wav");

这样就可以在后台播放声音文件/usr/share/sounds/login.wav了。不用担心混音的问题,也不用担心任何错误。如果播放失败它会返回程序继续执行。与init对应,gnome_sound_shutdown ()用于关闭声音(应该就是关闭程序与esd的连接),似乎不是必须要调用的,反正即使shutdown了退出程序后正在播放的音频好像也是不会停止的。

另外两个函数,gnome_sound_sample_load ()和gnome_sound_connection_get (),相对来说low-level一些。这两个变量主要是配合esd_sample_play()函数来播放声音的。
简单的例子:
  int sampleID,connection;
  gnome_sound_init(NULL);
  sampleID =  gnome_sound_sample_load (NULL, "/usr/share/sounds/login.wav");
  connection = gnome_sound_connection_get();
  esd_sample_play (connection, sampleID);

gnome_sound_sample_load的第一个参数是sample name,不太清楚用途,估计是跟esd有关,反正写NULL也能工作……