Linux pipe读写问题

之前的那个mplayer播放mms电台需要一个控制接口,mplayer只支持控制台输入命令,所以要控制mplayer只能用管道控制标准输入stdin。为了灵活就用FIFO文件作为mplayer的输入端。

mkfifo radio_pipe
cat radio_pipe | mplayer mms://xxx
echo -n p > radio_pipe

这里有个问题是cat radio_pipe读到文件EOF会退出,导致mplayer退出,所以每次只能控制一次:(。

前几天看mini2440附带的led-player程序,突然意识到可以自己写个pipe_read程序忽略文件EOF,这样就解决了mplayer的控制。记得之前写TCP程序时知道read返回0说明到文件EOF了,TCP就代表关闭了。我只要忽略掉read返回0的状态就行了(之后证明大错特错-_-!!!),错误是因为select认为文件EOF是读信号,除非你退出否则一直触发。最后的解决方法是这一句

int null_pipe_write_rd = open(pstrPipe, O_WRONLY | O_NONBLOCK);

再打开一个不操作的pipe读fd,这样才不至返回文件EOF,具体原因不是很明白:(,不过达到了我要的效果:)。

mkfifo radio_pipe
./pipe_read radio_pipe | mplayer mms://xxx
echo -n p > radio_pipe

这样就不会退出了,不过就如我们CEO说的一句话:解决了一个问题,往往带来了其他问题,这样pipe_read永远也不会退出了,尽管mplayer已经退出了-_-!!!。

Talk is cheap, show me the code:

#include <cstdio>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

void usage(const char *pstrProgram)
{
    printf("Usage:\n"
           "  %s pipefile\n", pstrProgram);
}


int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        usage(argv[0]);
        return 1;
    }

    bool bExit = false;
    const char *pstrPipe = argv[1];

    int pipe_rd = open(pstrPipe, O_RDONLY | O_NONBLOCK);
    if (pipe_rd == -1)
    {
        fprintf(stderr, "open pipe fail\n");
        return 1;
    }

    int null_pipe_write_rd = open(pstrPipe, O_WRONLY | O_NONBLOCK);

    while (!bExit)
    {
        fd_set fdset;
        //struct timeval tv;

        FD_ZERO(&fdset);
        FD_SET(pipe_rd, &fdset);

        int ret = select(pipe_rd+1, &fdset, NULL, NULL, NULL);

        if (ret < 0)
        {
            fprintf(stderr, "select return error\n");
            return 1;
        }
        else
        {
            if (FD_ISSET(pipe_rd, &fdset))
            {
                char buffer[128];
                while (true)
                {
                    int nLen = read(pipe_rd, buffer, sizeof(buffer));
                    if (nLen == 0)
                    {
                        fprintf(stderr, "End of file\n");
                        break;
                    }
                    else if (nLen == -1)
                    {
                        if (errno == EAGAIN)
                        {
                            fprintf(stderr, "nonblocking read fail\n");
                        }
                        else
                        {
                            fprintf(stderr, "read fail\n");
                            return 1;
                        }
                        break;
                    }
                    else
                    {
                        // write stdout
                        int ret = write(STDOUT_FILENO, buffer, nLen);
                        if (ret == -1 && errno != EAGAIN)
                        {
                            fprintf(stderr, "write fail\n");
                            return 1;
                        }
                    }
                }
            }
        }
    }

    return 0;
}

mplayer在线收音机

最近搬家后一直苦于没有电视看(想念以前的IPTV,看不完的电影和电视:)),所以我一直想在mini2440板子上做一个在线收音机弥补一下,正好有个音响空着。一开始用的方案是这里

wget -q -O -http://example.com/mystream | madplay -Q --no-tty-control-

可惜的是只支持mp3类型的广播(这个只有国外的shoutcast,而且貌似都是私人的,中文的很少,只听到台湾一哥们几年前的录音-_-!!!)。madplay里面是MAD定点解码库只支持mp3之类的,要想播放mms需要微软的解码库,这个估计没戏。而国内的网络电台清一色的mms,没办法继续找。

好不容易找到mplayer,花了大力气交叉编译好,发现居然有15M--!!!。好吧,随便找个中国之声的电台测试,在板子上接上音响,发现效果很好,以后吃饭时候听广播就靠它了。现在的问题是播放只能远程登陆到mini2440上启动mplayer,下一步是做到远程控制。初步的方案是用无线鼠标HID控制,切换电台和控制声音(之前还想到过用人声音控制,例如拍手切换电台,这个估计不现实吧--!!!)。

这里记几个mplayer的命令:q退出,9和0控制声音,p Pause。

BTW:最近这段时间天天加班,这个要什么时候能弄完啊!!!,之前想写的FTP服务器还没写呢:(

转战tmux

一时兴起觉得用tmux配合vim打造一个工作平台,这样应该很酷吧:)。因为用的是Ubuntu系统,它有一个tmux的包装软件,叫Byobu(日语的屏风),简单试用了一下觉的没必要多次一举,所以直接就安装了tmux。

tmux这东西差不多是screen加上了切屏功能(pane),应该有的detach和attach都有。既然用tmux就应该用它的pane了,否则window跟screen一样。

最使我纠结的是怎么绑定pane切换而不与VIM的windows切换冲突,而且必须尽可能方面。其实最方便的直接C-H/J/K/L搞定,但VIM已经占用了,最后只好用C-A H/J/K/L多按了个扩展键-_-!!!。

下面的配置参考了网上的例子,不过挑选合适的状态栏颜色花费了我很多时间,tmux的颜色很古怪,最后只好当前的反转颜色算了。而且说实话调到后来,我自己都怀疑是否有必要花打力气去配置tmux,毕竟大部分时间都在用VIM。

# base settings
set -g default-terminal "screen-256color"
#set -g display-time 3000
set -g escape-time 0
set -g base-index 1
set -g pane-base-index 1

# bind key
unbind-key -n C-a
set -g prefix ^A
set -g prefix2 ^A
bind a send-prefix

unbind '"'
bind - splitw -v -p 30
unbind %
bind \ splitw -h -p 30

setw -g mode-keys vi
#setw -g automatic-rename off

unbind h
unbind j
unbind k
unbind l
bind  h select-pane -L
bind  j select-pane -D
bind  k select-pane -U
bind  l select-pane -R
#bind  C-a      last-window # last window
bind  C-a      last-pane # last window

unbind [
unbind ]
bind [ swap-pane -U
bind ] swap-pane -D

# reload config (prefix r)
bind r source ~/.tmux.conf \; display "Configuration reloaded!"

# misc
unbind-key x
#bind x killw # kill windows (prefix x)
bind x killp # kill pane (prefix x)

# statusbar
set -g status-justify centre
set -g status-bg  black
set -g status-fg  white

setw -g window-status-current-bg black
setw -g window-status-current-fg white
setw -g window-status-current-attr "reverse"
#setw -g window-status-current-format "[#I:#W#F]"
setw -g window-status-current-format "#I:#W#F"

set -g status-left "#[default]» #[fg=colour187]#S #[default]• #[fg=colour187]W#I P#P#[default]"
set -g status-left-attr bright
set -g status-left-length 20
set -g status-right "#H #[default]• %H:%M"
set -g status-right-attr bright
set -g status-right-length 80

参考:

http://linuxtoy.org/archives/from-screen-to-tmux.html

http://blog.xeonxu.info/blog/2012/11/04/shi-yong-tmuxgai-jin-zhong-duan-ti-yan/

帮同事分析Makefile文件

同事想看开源项目contiki,无奈被其复杂的Makefile搞晕而找我帮忙。我大致看了一些,由于这个项目涉及到多个目标平台,结构复杂可想而知啦-_-!!!。这里用相对简单的原生平台(native)来剖析一下。

大致上有四个重要的Makefile文件:

  1. 自己的应用程序Makefile,如examples/email/Makefile:

    这个定义了最终的目标应用程序名,如email:

    CONTIKI_PROJECT = email-client
    all: $(CONTIKI_PROJECT)
    
    APPS = email
    
    CONTIKI = ../..
    include $(CONTIKI)/Makefile.include
    
  2. 根目录的Makefile.include:

    这个提供了统一的Makefile框架,提供一般的编译选项和功能,各个平台可以覆盖这些默认规则。

  3. platform/native/Makefile.native:

    这个是平台相关的硬件,如串口、led等,以及定义最终链接生成的目标文件%.$(Target)的编译规则。原生平台(native)直接在Makefile.include里定义了最终目标%.$(Target)的规则了。其他的平台要定义宏CUSTOM_RULE_LINK来进行覆盖。

  4. cpu/native/Makefile.native

    这个定义目标文件和库的编译。如果这里不是native平台,要定义自己的链接和编译规则,要预先定义几个宏覆盖系统默认的规则:

    CUSTOM_RULE_LINK=1
    CUSTOM_RULE_C_TO_OBJECTDIR_O=1
    CUSTOM_RULE_ALLOBJS_TO_TARGETLIB=1
    

    上面分别对应链接、编译和打包成库。编译和打包成库直接在这个Makefile里,最终的链接在platform//Makefile.里。

讲了这么多,来看一下最技巧性的地方。

CONTIKI_PROJECT = email-client
all: $(CONTIKI_PROJECT)

这里只指定了一个email-client,不带任何扩展名。那make如果知道依赖什么能?这里就要用到make的隐式规则了。

隐式规则

make看到email-client这个目标会采用两条隐式规则:

%:%.c
    gcc $(CFLAGS) %< -o $@

或者

# 这个会生成中间文件xxx.o,make会在完成时删除。
%:%.o
    gcc %< -o $@

%.o:%.c
    gcc $(CFLAGS) -c %< -o $@  

那如果要让make执行我预定义的依赖项:

# @ 表示占位符,如果没有则代表删除这条规则
%:%.$(TAGET)
    @

则要删除%:%.c这条隐式规则才能执行我的%:%.$(TAGET)。至于第二条隐式规则相比我的%:%.$(TAGET)要复杂,make是比较聪明的不回绕远路的。

删除%:%.c规则(不加命令项):

%:%.c

重看Makefile.include

有了上面的知识,下面就比较好理解了,最终的目标是email-client.native,链接规则是:

ifndef CUSTOM_RULE_LINK
%.$(TARGET): %.co $(PROJECT_OBJECTFILES) $(PROJECT_LIBRARIES) contiki-$(TARGET).a
    $(LD) $(LDFLAGS) $(TARGET_STARTFILES) ${filter-out %.a,$^} ${filter %.a,$^} $(TARGET_LIBFILES) -o $@
endif

编译规则是:

ifndef CUSTOM_RULE_C_TO_OBJECTDIR_O
$(OBJECTDIR)/%.o: %.c
    $(CC) $(CFLAGS) -MMD -c $< -o $@
    @$(FINALIZE_DEPENDENCY)
endif

打包生产库规则:

ifndef CUSTOM_RULE_ALLOBJS_TO_TARGETLIB
contiki-$(TARGET).a: $(CONTIKI_OBJECTFILES)
    $(AR) $(AROPTS) $@ $^
endif

其他平台的类似,只是中间有很多步骤,这里不细讲。

Makefile自动产生依赖文件(二)

昨天写的Makefile自动产生依赖文件(一)是我以前用的方法总结,但近期发现了GCC本来就有这个功能,只是我没发现罢了-_-!!!。

这还说着摆弄Eclipse CDT开发环境时,无意中发现Eclipse产生的Makefile异常简洁,其核心的src/subdir.mk文件如下:

CPP_SRCS += 
../src/HelloWorld.cpp 

OBJS += 
./src/HelloWorld.o 

CPP_DEPS += 
./src/HelloWorld.d 

src/%.o: ../src/%.cpp
    @echo 'Building file: $<'
    @echo 'Invoking: Cygwin C++ Compiler'
    g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o "$@" "$<"
    @echo 'Finished building: $<'
    @echo ' '

细看src/%.o: ../src/%.cpp的隐规则,发现多了几个参数”-MMD -MP -MF -MT”。”-MMD“我还认的,就是直接产生.d文件而不是输出到屏幕,其他就陌生了。遇到问题找Man啦,发现正好解决了我前一篇博客所说的两个问题,gcc果然不我欺也。

  • -MP: 解决重命名头文件导致的依赖破裂,跟上次的思路一样,生成了dummy目标。

  • -MF: 这个是指定输出的.d文件格式。

  • -MT: 这个解决了输出的目标文件不带路径问题,可以指定目标文件的格式。

现在可以抛弃之前的makedepend,一个Makefile文件搞定一切:)。


更新

今天发现头文件更新不起作用,看来一下.d文件如下:

xxx.d: xxx.h //这里应为xxx.o:xxx.h
xxx.h:

惊讶的发现竟然没有xxx.o的依赖,再仔细检查了gcc的依赖选项发现之前没仔细看-MT修改了依赖项变成了.d了。其实-MT不用加上,加上就修改了目标名,最后的命令是:

$(OBJS):%.o:%.c
    gcc -c $(CFLAGS) -MMD -MP -MF"$(@:%.o=%.d)" -o $@ $<

这里用到了makefile静态规则(要详细看那边《跟我一起学Makefile》),这样就保证文件名不会被gcc给截断造成依赖文件的目标名错误。

最后贴个标准的吧:

SRC_DIR= src others
CFLAGS+=-Wall -g 
CFLAGS+=$(addprefix -I, $(SRC_DIR))
CFILES=$(shell find $(SRC_DIR) -maxdepth 1 -name "*.c")
OBJS=$(CFILES:%.c=%.o)
LIBS+= 

all:$(TARGET)

-include $(addsuffix /*.d, $(SRC_DIR))

$(TARGET):$(OBJS)
    gcc $(LDFLAGS) $^ -o $@ $(LIBS)

$(OBJS):%.o:%.c
    gcc -c $(CFLAGS) -MMD -MP -MF"$(@:%.o=%.d)" -o $@ $<

clean:
    -rm -f $(addsuffix /*.d, $(SRC_DIR)) $(addsuffix /*.o, $(SRC_DIR)) $(TARGET)

Makefile自动产生依赖文件(一)

一直以来用Makefile自动产生源文件(C/C++)的关联文件(.d)是一大问题。这里主要涉及到的是gcc的-MM选项(-M会包含系统头文件)。但gcc的-MM不是很完美,主要涉及到两方面:

  1. 没有目标文件路径

    g++ -MM src/string_parse.cpp
    

    输出:

    string_parse.o: src/string_parse.cpp src/string_parse.h
    

    而我们需要的是:

    src/string_parse.o: src/string_parse.cpp src/string_parse.h
    
  2. 头文件重命名时出现错误

    万一我们重命名了string_parse.h为string_other_parse.h则

    string_parse.o: src/string_parse.cpp src/string_other_parse.h
    

    make会找不到依赖文件str/string_other_parse.h报错。

参考网上的方案,我的解决方法是写个makedepend脚本处理:

#!/usr/bin/env bash
# Generate C/C++ file make depend file: Makefile.depends.
# Usage:
#   makedepend -f files -c (gcc|g++) -p flags

help() 
{
    echo "${0#*/} -f files -c (gcc|g++) -p flags"
}

while getopts "hf:c:p:" opt; do
    case $opt in
        h)
            help
            exit 0
            ;;
        f)
            files=$OPTARG
            ;;

        c)
            compiler=$OPTARG
            ;;

        p)
            flags=$OPTARG
            ;;
        *)
            exit 1
            ;;
    esac
done


if [ -z "$files" ] || [ -z "$compiler" ] || [ -z "$flags" ] ; then
    exit 1
fi

for file in $files
do
    obj="${file%.*}.o"
    result=`$compiler -MM $flags $file`
    if [ $? != 0 ] ; then
        exit 1
    fi

    result=${result/*:/$obj:}
    echo $result | sed -e 's/\//g'
    # add all file as target, so rename file will not error.
    echo $result | sed -e 's/.*://' -e 's/\//g' | fmt -1 | sed -e 's/^ *//' -e 's/$/:/'
done

在Makefile是这么用的:

Makefile.deps: $(FILES) $(HEADERS)
    makedepend -f "$(FILES)" -c "$(CXX)" -p "$(CPPFLAGS)" >$@

最后生成的Makefile.deps是这样的:

src/string_parse.o: src/string_parse.cpp src/string_parse.h
src/string_parse.cpp:
src/string_parse.h:

为了防止修改文件名后找不到,makedepend为每个依赖文件都建了一个空规则,make如果找不到文件,可以在这里找到。由于文件不存在,成为了伪目标,而伪目标永远是最新的,从而触发编译命令

参考:

陈皓的《跟我一起写Makefile》

Autodependencies with GNU make

http://blog.vjeux.com/category/makefile

看Bash项目logdotsh

无聊中看到了团子的小屋写的Bash日志库logdotsh。下下来大致看了一下,觉的不错。我本是Bash新手,正需要这种项目练练手,看完以后收获很多。

大致上我知道Bash里没有库的概念,所以一般都单独一个配置脚本,用source命令进行包含。里面提供各种功能函数供用户调用。logdotsh也是这种库的形式,你用用它的时候必须在自己的脚本中包含它。

. ./log.sh

但Bash里有没提供像C/C++那样的#ifndef/#endif防止重复包含文件,所以里面设置了全局变量_log_set_default,每次检查是否已经包含,防止覆盖内部变量。

if [ -z "$_log_set_default" ]; then
    _log_set_default=1
    ...
fi

接下来是各种日志输出接口:do_log、log_msg、debug_msg、info_msg、warn_msg、error_msg等。每次调用先保存在/tmp日志文件上,再根据level觉得是否打印在屏幕上(有对应的颜色区分)。

我看到了从未遇到的Bash数组,看了ABS的介绍,感觉很不错。

DNS其他

DNS服务器类型

  1. Cache only服务器
    这类没有自己负责解析的域名,只有一个DNS根服务器文件用来进行递归查询。

  2. Forwarding服务器(例如:dnsmasq)
    这类干脆连DNS根服务器文件列表也没有,有什么查询抛给上层DNS服务器处理。

查询域名托管DNS服务器

如果想知道域名放在哪个DNS服务器上,一般那个DNS服务器的域名会有NS记录表示当前放在DNS服务器域名。

$dig freezhongzi.info

;; AUTHORITY SECTION:
freezhongzi.info.   600 IN  NS  f1g1ns1.dnspod.net.
freezhongzi.info.   600 IN  NS  f1g1ns2.dnspod.net.

返回结果中有两条NS记录对应托管的DNS服务器。如果在dnspod停用那两条NS记录,则返回的结果就只有A记录了。

DNS反解:

反解查询首先要转化为IP.in-addr.arpa的格式(xxx.xxx.xxx.xxx.in-addr.arpa),流程跟上面一样,从DNS根服务器一直查到我的IP地址拥有方burst的DNS服务器:

dns.burst.net
dns1.burst.net

为了进行DNS反向查询,有专门的域名.in-addr.arpa进行转换,拥有IP地址的人进行设置(不是注册域名或VPS的人,而是申请IP地址的人)。

这有什么用呢,一般的邮件服务器为了验证垃圾邮件,都对发送方的IP地址进行反向解析看看是不是固定的IP地址,从而杜绝垃圾邮件。

查询网站主机提供商

比如要查看本站的VPS提供商:

$dig -x xxx.xxx.xxx.xxx

;; ANSWER SECTION:
xxx.xxx.xxx.xxx.in-addr.arpa. 14313 IN    PTR xxx.xxx.xxx.xxx.static.hostnoc.net.

;; AUTHORITY SECTION:
xxx.xxx.xxx.xxx.in-addr.arpa. 14313  IN  NS  NS2.HOSTNOC.EU.
xxx.xxx.xxx.xxx.in-addr.arpa. 14313  IN  NS  NS1.HOSTNOC.EU.
xxx.xxx.xxx.xxx.in-addr.arpa. 14313  IN  NS  dns1.burst.net.
xxx.xxx.xxx.xxx.in-addr.arpa. 14313  IN  NS  NS1.hostnoc.net.
xxx.xxx.xxx.xxx.in-addr.arpa. 14313  IN  NS  dns.burst.net.
xxx.xxx.xxx.xxx.in-addr.arpa. 14313  IN  NS  NS2.hostnoc.net.

可以发现NS记录中的有burst的dns,所以VPS是Burst。不要小看一个DNS,如果挖掘的够深,会有很多信息。

整个过程其实可以用dig完全看的清清楚楚:

$ dig +trace  www.freezhongzi.info

;; global options: +cmd
.           25684   IN  NS  f.root-servers.net.
.           25684   IN  NS  m.root-servers.net.
.           25684   IN  NS  d.root-servers.net.
.           25684   IN  NS  e.root-servers.net.
.           25684   IN  NS  k.root-servers.net.
.           25684   IN  NS  c.root-servers.net.
.           25684   IN  NS  j.root-servers.net.
.           25684   IN  NS  g.root-servers.net.
.           25684   IN  NS  h.root-servers.net.
.           25684   IN  NS  l.root-servers.net.
.           25684   IN  NS  a.root-servers.net.
.           25684   IN  NS  b.root-servers.net.
.           25684   IN  NS  i.root-servers.net.

info.           172800  IN  NS  c0.info.afilias-nst.info.
info.           172800  IN  NS  d0.info.afilias-nst.org.
info.           172800  IN  NS  b2.info.afilias-nst.org.
info.           172800  IN  NS  a2.info.afilias-nst.info.
info.           172800  IN  NS  b0.info.afilias-nst.org.
info.           172800  IN  NS  a0.info.afilias-nst.info.

freezhongzi.info.   86400   IN  NS  f1g1ns2.dnspod.net.
freezhongzi.info.   86400   IN  NS  f1g1ns1.dnspod.net.

www.freezhongzi.info.   600 IN  A   xxx.xxx.xxx.xxx
freezhongzi.info.   600 IN  NS  f1g1ns1.dnspod.net.
freezhongzi.info.   600 IN  NS  f1g1ns2.dnspod.net.

DNS初探

DNS是一个分布式系统,没有一个单独的主机知道所有的信息。

最简单的DNS系统就是/etc/hosts文件了,每行内容是”IP 主机名 主机名1 主机名2 …”。

域名(domain name)和主机名(hostname)组成完整的DNS标识,例如

www.freezhongzi.info,www是主机名,freezhongzi.info是域名,而

freezhongzi.info,freezhongzi是主机名,.info是域名。

以句号.结尾的域名,如www.freezhongzi.info.叫Fully Qualified Domain Name(FQDN),句号表示根域名,在DNS协议里用FQDN标识。

DNS递归查询(recursion query)

默认的递归查询都是由你的当前DNS服务器执行的,所以一般都设置RD位(例外是DNS根服务器是没有递归查询功能的,所以当DNS服务器向根服务器查询时不设置RD位)。

DNS查询的操作流程是:

  1. 客户端(linux/unix一般是gethostbyname和gethostbyaddr)向当前DNS服务器查询域名,一般查询A记录(IP地址)。
  2. DNS服务器收到客户端的请求之后,向DNS根服务器发送一样的A记录请求,因为DNS根服务器不支持递归查询,所以返回知道这个域名的DNS服务器列表(NS记录)和对应的IP地址。
  3. DNS服务器选择最靠前的下一跳DNS服务器(NS记录里)接着发送递归查询。
  4. 下一跳DNS服务器返回域名的IP地址,如果下一跳DNS服务器不支持递归查询而返回下下一跳DNS服务器列表重复步骤3。

从中可以看出递归查询与非递归查询的区别:

  • 非递归查询返回知道那个域名的DNS服务器地址,让你继续去查。
  • 递归查询是让DNS服务器一级级往下查直到查到IP地址为止,很像一个递归过程。

实例:

第一步:找出DNS根服务器:

查询DNS根的NS记录获得13个DNS根服务器域名和IP地址:

$dig . ns 或直接 $dig

;; AUTHORITY SECTION:
.           37383   IN  NS  b.root-servers.net.
.           37383   IN  NS  c.root-servers.net.
.           37383   IN  NS  f.root-servers.net.
.           37383   IN  NS  l.root-servers.net.
.           37383   IN  NS  i.root-servers.net.
.           37383   IN  NS  d.root-servers.net.
.           37383   IN  NS  e.root-servers.net.
.           37383   IN  NS  a.root-servers.net.
.           37383   IN  NS  k.root-servers.net.
.           37383   IN  NS  m.root-servers.net.
.           37383   IN  NS  g.root-servers.net.
.           37383   IN  NS  h.root-servers.net.
.           37383   IN  NS  j.root-servers.net.

;; ADDITIONAL SECTION:
k.root-servers.net. 3580415 IN  A   193.0.14.129
k.root-servers.net. 3581367 IN  AAAA    2001:7fd::1
a.root-servers.net. 3579080 IN  A   198.41.0.4
a.root-servers.net. 3567490 IN  AAAA    2001:503:ba3e::2:30
e.root-servers.net. 3580333 IN  A   192.203.230.10
d.root-servers.net. 3581308 IN  A   128.8.10.90
d.root-servers.net. 3530630 IN  AAAA    2001:500:2d::d
i.root-servers.net. 3580233 IN  A   192.36.148.17
i.root-servers.net. 3568782 IN  AAAA    2001:7fe::53
l.root-servers.net. 3579724 IN  A   199.7.83.42
l.root-servers.net. 3593302 IN  AAAA    2001:500:3::42
f.root-servers.net. 3580192 IN  A   192.5.5.241

上面的AAAA是IPv6地址记录。

而且貌似默认的电信DNS服务器的TTL(就是DNS服务器Cache时间)很不准(一下子15到515,不知道TTL到底是多少),而google的8.8.8.8很准一直从600秒(10钟)递减:)。

$dig www.freezhongzi.info @8.8.8.8
...
www.freezhongzi.info.   600 IN  A   xxx.xxx.xxx.xxx
...

$dig www.freezhongzi.info @8.8.8.8
...
www.freezhongzi.info.   576 IN  A   xxx.xxx.xxx.xxx
...

$dig www.freezhongzi.info @8.8.8.8
...
www.freezhongzi.info.   573 IN  A   xxx.xxx.xxx.xxx
...

第二步:随便选一个m.root-servers.net的IP地址执行递归查询:

$dig +norecurse www.freezhongzi.info @202.12.27.33

;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 6, ADDITIONAL: 12

;; QUESTION SECTION:
;www.freezhongzi.info.      IN  A

;; AUTHORITY SECTION:
info.           172800  IN  NS  b2.info.afilias-nst.org.
info.           172800  IN  NS  b0.info.afilias-nst.org.
info.           172800  IN  NS  a0.info.afilias-nst.info.
info.           172800  IN  NS  c0.info.afilias-nst.info.
info.           172800  IN  NS  d0.info.afilias-nst.org.
info.           172800  IN  NS  a2.info.afilias-nst.info.

;; ADDITIONAL SECTION:
a0.info.afilias-nst.info. 172800 IN A   199.254.31.1
a2.info.afilias-nst.info. 172800 IN A   199.249.113.1
b0.info.afilias-nst.org. 172800 IN  A   199.254.48.1
b2.info.afilias-nst.org. 172800 IN  A   199.249.121.1
c0.info.afilias-nst.info. 172800 IN A   199.254.49.1
d0.info.afilias-nst.org. 172800 IN  A   199.254.50.1
a0.info.afilias-nst.info. 172800 IN AAAA    2001:500:19::1
a2.info.afilias-nst.info. 172800 IN AAAA    2001:500:41::1
b0.info.afilias-nst.org. 172800 IN  AAAA    2001:500:1a::1
b2.info.afilias-nst.org. 172800 IN  AAAA    2001:500:49::1
c0.info.afilias-nst.info. 172800 IN AAAA    2001:500:1b::1
d0.info.afilias-nst.org. 172800 IN  AAAA    2001:500:1c::1

可以看到DNS根服务器不支持递归查询(没有RA标记)。

第三步:选择下一跳.info DNS服务器接着查询:

$dig www.freezhongzi.info @d0.info.afilias-nst.org.

;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;www.freezhongzi.info.      IN  A

;; AUTHORITY SECTION:
freezhongzi.info.   86400   IN  NS  f1g1ns2.dnspod.net.
freezhongzi.info.   86400   IN  NS  f1g1ns1.dnspod.net.

发现.info也不支持递归查询,返回的两个DNS服务器:

f1g1ns1.dnspod.net
f1g1ns2.dnspod.net

如果熟悉dnspod的会知道那是它的DNS服务器域名,我的freezhongzi.info就是由它托管的:)

最后一部就是向dnspod索要了:

$dig www.freezhongzi.info @f1g1ns1.dnspod.net

;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; ANSWER SECTION:
www.freezhongzi.info.   600 IN  A   xxx.xxx.xxx.xxx

;; AUTHORITY SECTION:
freezhongzi.info.   600 IN  NS  f1g1ns2.dnspod.net.
freezhongzi.info.   600 IN  NS  f1g1ns1.dnspod.net.

dnspod也不支持递归查询,但终于返回我要的IP地址了。

整个过程就是如图:

C++ trivial和non-trivial构造函数及POD类型

今天看书看到侯捷的《STL源码剖析》里提到trivial和non-trivial及POD类型,查了些资料理解了一下。

trivial意思是无意义,这个trivial和non-trivial是对类的四种函数来说的:

  • 构造函数(ctor)
  • 复制构造函数(copy)
  • 赋值函数(assignment)
  • 析构函数(dtor)

如果至少满足下面3条里的一条:

  1. 显式(explict)定义了这四种函数。
  2. 类里有非静态非POD的数据成员。
  3. 有基类。

那么上面的四种函数是non-trivial函数,比如叫non-trivial ctor、non-trivial copy…,也就是说有意义的函数,里面有一下必要的操作,比如类成员的初始化,释放内存等。

那个POD意思是Plain Old Data,也就是C++的内建类型或传统的C结构体类型。POD类型必然有trivial ctor/dtor/copy/assignment四种函数。

//整个T是POD类型
class T
{
    //没有显式定义ctor/dtor/copy/assignemt所以都是trivial
    int a; //POD类型
};

//整个T1是非POD类型
class T1
{
    T1() //显式定义了构造函数,所以是non-trivial ctor
    {}
    //没有显式定义ctor/dtor/copy/assignemt所以都是trivial
    int a;//POD类型
    std::string b; //非POD类型
};

那这有什么用处呢?

如果这个类都是trivial ctor/dtor/copy/assignment函数,我们对这个类进行构造、析构、拷贝和赋值时可以采用最有效率的方法,不调用无所事事正真的那些ctor/dtor等,而直接采用内存操作如malloc()、memcpy()等提高性能,这也是SGI STL内部干的事情。

比如STL的copy算法最基本的想法是这样的:

// 非POD重载指针数值
template <class T> void copy(T* source, T* destination, int n, __false_type)
{
    // 省略异常处理
    for (; n > 0; n--,source++,destination++)
    {
        // 调用source的复制构造函数
        constructor(source, *destination);
    }
}

// POD重载指针数值
template <class T> void copy(T* source, T* destination, int n, __false_type)
{
    // 省略异常处理
    memmove(source, destination, n);
}

当然实际的copy比这个复杂多了,有非常多的特化等,这个只是其中一方面而已。