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比这个复杂多了,有非常多的特化等,这个只是其中一方面而已。

换博客

博客建立一年了,一开始采用的是Python博客系统kukkaisvoima,好处是可以控制全局,喜欢怎么改就怎么改,不过改来改去仍显单调不好用,今天心血来潮试试传说中的Wordpress,只按了个markdown插件,以后的东西慢慢来吧。

下面是我那Python博客的第一篇博文,算缅怀过去,展望未来吧:)。

写与2011年10月20:

终于有了独立博客,先吐槽一下。网上用Python写的博客系统也太少了吧,好不容易才找到这个kukkaisvoima

今后多写写技术文章,提高一下写作能力,偶尔还可以玩玩服务器,很不错:)。

BTW:在这里缅怀一下刚去世的大师DMR, R.I.P。

 

 

 

近况

很久没跟新博客了,这次是因为上次写的RSS引擎,我发布在豆瓣上测试,今天打开RSS引擎时发现不能创建,提示数据库文件损坏。我想了一下就是用的数据库是Python直接操作shelve,存储在一个文本文件上,没有做多进程访问保护,文件损坏也在情理之中,下次有空一起补上。

还有是最近对3D建模感兴趣,学习了Blender软件建模,下了一个Wiki book看看,感谢还不错。学习过程还是很难的,毕竟也是一门手艺活,感觉能在虚拟世界中用普通不起眼的点线面搭建实际的模型,很好很强大:)。顺便晒一下自己的习作。

 

 

 

 

全文RSS输出程序

这个全文RSS程序我已经断断续续写了差不多一个月了,期间为了其他事而耽搁了一下。一开始写这个程序主要是我一直不习惯Google Reader中某些不输出全文的RSS,非得你再点一下鼠标不可,我等懒人怎能容忍。

一开始上网搜索全文RSS程序,只搜索到阮先生的这篇,大致看了这个PHP写的FiveFilter还算简单,决定用Python在服务器上实现一个。

期间慢慢看了一些RSS标准、Python RSS库之类的东西,大致框架如下:

  1. 前端照搬FiveFilter的Web界面。
  2. 后端用Python实现,包括RSS读取、内容全文解析、RSS保存等等。

一开始因为RSS中的每个网页都要用urllib去读一遍再解析一遍比较耗时,所以打算在后台搞个daemon进行更新。后来改进了方案,直接用Python的多线程进行读取和解析,解决了时间问题。Python的线程库threading是个神器,搞数据挖掘的用这个应该很方便:)。

接下来参考FiveFilter的PHP代码和这篇神作feedcache搞定各种Python库:

最后我自己写了个RSS输出类搞定一切。

其他的细节太多了,比如如何确定RSS要保持多少时间才去更新。我大致看了一下Google Reader的抓包数据,发现Google Reader大致一小时抓一次,所以我把RSS的更新时间也设置成一小时。

[23/May/2012:19:34:46 +0800] "GET /index.cgi/feed/ HTTP/1.1" 200 6272 "-" "Feedfetcher-Google; (+http://www.google.com/feedfetcher.html; 
[23/May/2012:20:44:46 +0800] "GET /index.cgi/feed/ HTTP/1.1" 200 6272 "-" "Feedfetcher-Google; (+http://www.google.com/feedfetcher.html; 
[23/May/2012:21:39:26 +0800] "GET /index.cgi/feed/ HTTP/1.1" 200 6272 "-" "Feedfetcher-Google; (+http://www.google.com/feedfetcher.html; 
[23/May/2012:22:46:56 +0800] "GET /index.cgi/feed/ HTTP/1.1" 200 6272 "-" "Feedfetcher-Google; (+http://www.google.com/feedfetcher.html; 

还有为了提高效率,每次读RSS源都用HTTP的条件判断(ETage and Last-Modified), 减少读取次数等等。

最后晒一下成果,项目源码在这里。我在服务器上搭建的网站程序在这里(已经移除),最后发张效果图。

参考

feedcache

RSS标准

Conditional HTTP GET

Google Feedfetcher文档

Ubuntu 12.04搭建双显示器工作环境

我最近花了点银子上Dell官网买了Dell inspiron 14笔记本,话说Dell直销系统不咋样,过了一周才到货,不过服务到不错:),而且性价比很高。

终于告别用了6年的破Dell台式机,以前一直在台式机上用卡的要死的Ubuntu 10.10,所以对最新的Ubuntu 12.04和体验Unity桌面环境期待已久啊!

废话不多说,马上搭好环境,使用了下还可以,基本符合我等口味:

安装Ubuntu 12.04就不多说了,按老套路搞个USB启动盘就可以安装了。试用了Dash和HUD等特性,感觉尽管Unity已经很努力了,但还不是很完美,程序崩溃时有发生,而且Dash这种交互形式也过于超前了吧!

吐槽完毕,讲一下遇到的问题:

显卡驱动

由于笔记本是A卡独显,默认安装的是开源的Radeon驱动,用起来还过的去。但是笔记本散热不好,开独显发热严重,于是考虑进行双显卡切换,刚刚AMD也相当厚道的发布了闭源的Catalyst 12.6驱动,按照官方指南顺利安装成功。

这里吐槽一下,一开始从附加驱动的官方源安装,但死活不成功,点Catalyst控制中心老是提示错误。逼的我从官网下安装程序一步步安装,但还是不行。最后一通乱搜索,发现了窍门:把 /etc/modprobe.d/blacklist-local.conf 中去掉blacklist fglrx 就可以了,也不知道为什么要设置这个黑名单。

Catalyst的双显卡切换模式,我把独显关了, 风扇声音一下子小下去了:

双屏幕切换

我把台式机的显示器作为外接显示器,组成双显示器。幸亏Ubuntu 12.04对双显示器支持不错,可以方便的用鼠标来回切换。我把左边的高分辨率笔记本显示器用来看电影、上网和收邮件;右边的显示器分辨率低,用来写代码。

如果想体验一下的话,Ubuntu官方有个在线模拟体验中心,模拟Ubuntu 12.04,很好玩:)。

移植SimpleNet到Linux

最近我一直在学嵌入式Linux,总想在ARM板子上写点程序。所谓万事开头难,所以找了本Linux编程入门书(Advanced-linux-programming)看看:

为了练手我就把之前基于Window Socket的SimpleNet移植到Linux下,整个过程花了我两天时间。

其实Window Socket与Linux Socket几乎差不多,我参考了POCO库的net部分的跨平台移植,移植的几点如下:

  1. 加入Linux的头文件:
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/ioctl.h>
    #include <netinet/in.h>
    #include <netinet/tcp.h>
    #include <arpa/inet.h>
    
  2. 修改类型和函数:
    #define SOCKET int
    #define SOCKET_LEN_TYPE socklen_t
    #define INVALID_SOCKET (-1)
    #define SOCKET_ERROR   (-1)
    #define WSAGetLastError() errno
    #define WSAEWOULDBLOCK EWOULDBLOCK
    // linux下close socket用::close
    #define snet_closesocket(x) ::close(x)
    
  3. Linux下fd_set与Windows下完全不同,所以抛弃原来为Windows写的Fdset(无socket限制),直接用Linux默认操作(最多1024个socket)。
  4. Linux下select函数第一个参数必须为所有fd_set的socket的最大值加1,
  5. Linux下设置socket tcp keepalive和block。

大致如此,还有很多细节就不一一描述了,移植果然很痛苦啊:)。

接下来就是写Makefile了,因为之前有过经验,不是很难。不过比较头疼的是gnu make不能自动推导头文件依赖,必须间接生成再导入(我之前用的borland make有自动推导头文件功能,很爽),最后写好的Makefile感觉就有点臃肿了。

CFLAGS=-Wall -O2
CC=g++
SRC=Src
OBJECTS=$(SRC)/snet_log.o $(SRC)/snet_session.o $(SRC)/snet_sessionManager.o $(SRC)/snet_tool.o
TARGET=libsimple_net.a

# pull in dependency info for *existing* .o files
-include $(OBJS:.o=.d)

$(TARGET):$(OBJECTS)
        ar cr $(TARGET) $(OBJECTS)

# compile and generate dependency info;
# more complicated dependency computation, so all prereqs listed
# will also become command-less, prereq-less targets
#   sed:    strip the target (everything before colon)
#   sed:    remove any continuation backslashes
#   fmt -1: list words one per line
#   sed:    strip leading spaces
#   sed:    add trailing colons
$(OBJECTS):%.o:%.cpp
        $(CC) $(CFLAGS) -I./Src -c
 
lt; -o $@ $(CC) -MM $(CFLAGS) $*.cpp > $*.d @cp -f $*.d $*.d.tmp @sed -e 's/.*://' -e 's/\$//' < $*.d.tmp | fmt -1 | sed -e 's/^ *//' -e 's/$/:/' >> $*.d @rm -f $*.d.tmp clean: -rm -f $(OBJECTS) $(OBJS:.o=.d) $(TARGET) 

编译链接后生成SimpleNet lib库libsimple_net.a,用Test目录下的聊天室测试程序链接运行:

目前SimpleNet功能上偏弱,只支持同步Poll轮询方式,下一步打算支持线程异步方式。按惯例,所有的程序源码在我的github上

FTP下载Shell脚本编程

我心血来潮学起了Linux Shell脚本,大概看了一下鸟哥的教材个人感觉不错。刚好手头有个需求,大致是需要从FTP下载zip文件并解压。我之前写了个Python版本,正好用Shell脚本重写一遍练练手:)。

#!/bin/sh
# download file from ftp server.
# write by bobo

VERSION=1.0

if [ "$#" != "3" ]; then
    echo "Version $VERSION"
    echo "Usage:"
    echo "    ftp.sh username@ip password files"
    exit 1
fi

username=$(echo $1 | cut -d'@' -f 1)
if [ ! "$username" ]; then
    echo "username invalid" 
    exit 1
fi

ip=$(echo $1 | cut -d'@' -f 2)
if [ ! "$ip" ]; then
    echo "ip invalid" 
    exit 1
fi

password=$2
file=$3

#echo $username,$ip,$password,$file

#ftp -inv $ip <<EOF
ftp -in $ip <<EOF
user $username $password
bin
get $file
bye
EOF

if [ -e "$file" ]; then
    echo "download success"

    dirname=$(echo $file | cut -d'.' -f 1)
    filetype=$(echo $file | cut -d'.' -f 2)

    # the file type is zip?
    if [ "$filetype" = "zip" ]; then
        mkdir -p $dirname
        unzip -o $file -d $dirname
    fi
fi 

示例:从ftp服务器192.168.14.1上下载14.zip并解压:

ftp.sh username@192.168.14.1 password 14.zip

记录一下心得:

  • ftp命令参数-n表示登录时不进行用户密码验证,可以用user命令输入用户和密码,方便脚本编写。
  • <<EOF表示键盘输入到EOF为止,这样脚本中只要每行输入FTP命令,最后以EOF结束就行了。

打造OpenWrt网络监听器

前言

写了上一篇后我偶然想到网桥也可以用来监听数据:),只要在目标PC或路由器出口上连接一个网桥,监听网桥网卡就可以了。

实现

话说原理其实很简单啦,网桥连接两个子网,因为是二层设备所以对上层的TCP/IP是透明的,而且所有通信都要经过网桥中转,用普通的tcpdump就可以监听了。

在OpenWrt上需要开启VLAN来模拟两个虚拟子网vlan0和vlan1

config switch eth1
        option reset 1
        option enable_vlan 1

config switch_vlan
        option vlan 0
        option device eth1
        option ports '0 1 2 5t'

config switch_vlan
        option vlan 1
        option device eth1
        option ports '3 5t'

两个vlan网络接口分别为eth1.0和eth1.1,0、1、2口为一个子网,3口单独一个子网。现在把eth1.0和eth1.1两个网络接口组成一个网桥br0:

config interface br0
        option type bridge
        option ifname 'eth1.0 eth1.1'
        option proto none

br0默认不设置ip,因为我直接用TTL线获得Shell所以用不着。如果你想远程访问OpenWrt,则必须按情况设置static或dhcp,当然也可以用前一篇说到底Wifi客户端,这样你就可以舒舒服服的坐在电脑边远程登录查看抓包了:)。

最后一步是关闭防火墙,因为网桥用不着:

/etc/init.d/firewall disable

重启网络后可以用ifconfig看到很多网络接口,只要监听br-br0就行了:

br-br0    Link encap:Ethernet  HWaddr 00:74:04:07:4B:CC
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

eth1      Link encap:Ethernet  HWaddr 00:74:04:07:4B:CC
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
          Interrupt:14

eth1.0    Link encap:Ethernet  HWaddr 00:74:04:07:4B:CC
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

eth1.1    Link encap:Ethernet  HWaddr 00:74:04:07:4B:CC
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

测试

  1. 首先在OpenWrt里安装Tcpdump:
    opkg install tcpdump
    
  2. 用网线连接电脑到路由器3口(eth1.1),路由器2口(或0、1口)连接原本电脑的出口。
  3. 运行Tcpdump监听POP邮箱密码:
    tcpdump -X -i br-br0 port 110
    

如果你的邮箱没加SSL的话,应该能看到密码明文啦:)。

OpenWrt无线Wifi客户端模式

前言

前一篇讲到了OpenWrt的网络结构,这一篇讲讲OpenWrt的Wifi客户端模式,这主要是满足那些懒得接网线的人:)。

Wifi一般用的是AP(Access Point)模式:无线路由器作为一个Wifi热点为其他的Wifi客户端提供连接服务。

这里要用到的是Wifi的STA(Station)模式:作为客户端连接AP模式下的Wifi,这个一般笔记本的无线网卡都是这种模式(详细的Linux下支持的Wifi模式看这里),网络结构图:

上图中左边STA客户端模式下的无线网卡作为WAN把192.168.2.0/24划为内网,用NAT方式连接外网的Wifi AP节点(192.168.1.1)。这个很像经典的OpenWrt WAN和LAN网络结构,只不过把WAN的以太网接口换成了Wifi。

OpenWrt设置

在OpenWrt下主要是设置/etc/config/network、/etc/config/wireless这两个文件,其他的都与默认的LAN和WAN模式相同。

/etc/config/network下,关闭VLAN(enable_valn=0),lan接口设置成静态并去掉网桥(默认为’option type bridge’,Wifi通常自动桥接到lan接口),wan去掉’option ifname ‘选项(无线Wifi接口会自动加入wan作为ifname)。

config switch eth1
    option reset 0
    option enable_vlan 0

config interface loopback
    option ifname lo
    option proto static
    option ipaddr 127.0.0.1
    option netmask 255.0.0.0

config interface lan
    option ifname eth1
    option proto static
    option ipaddr 192.168.2.1
    option netmask 255.255.255.0

config interface wan
    option proto dhcp  

配置以后所有交换机上的接口都变为内部LAN,而无线Wifi作为WAN连接外网。LAN和WAN之间用NAT方式进行地址转换(具体在firewall的WAN设置masq=1,默认已经设置好了),firewall的NAT选项叫Masquerade(伪装),就是WAN接口把内网的数据包源地址伪装成自己的,很形象:)。

我一开始连接失败用Tcpdump查看wlan0,发现数据包还没有NAT伪装,最后发现是firewall没启动,所以确保firewall开机启动:

/etc/init.d/firewall enable

查看firewall是否启动:

/etc/init.d/firewall enabled && echo on

/etc/config/wireless下,设置Wifi参数:

config wifi-device radio0
    option type mac80211
    option channel 0
    option hwmode 11g
    option txpower 0

config wifi-iface
    option device     radio0
    option network    wan
    option mode       sta
    option ssid       yourAPssid
    option encryption psk2
    option key        yourkey

主要是设置mode为sta,network选择要自动加入wan,填上要连接Wifi AP的ssid、加密方式encryption和密钥key,全部完成后重启网络,Wifi连接成功后WLAN LED灯会亮起。

/etc/init.d/network restart

把你的台式电脑网线随便插入LAN口,这样你就成为了一台有无线网卡的台式机了,省去了用网线想方设法连接其他房间路由器的烦恼:)。

参考

http://wiki.openwrt.org/doc/howto/clientmode

http://wiki.openwrt.org/doc/recipes/routedclient#using.masquerade

OpenWrt网络结构

OpenWrt的网络配置很丰富,在我看来几乎可以完成任何网络结构。下图为一个支持OpenWrt的路由器网络结构:这个路由器内部交换机有6个口,其中1个WAN口、4个LAN口、Port5默认连接内部网卡eth0,还有连接Wifi的无线网卡接eth2,eth3保留。

从中我们可以看到这个路由器最多支持3个物理网络接口eth0、eth1、eth2。其实一般路由器有两张以太网卡和一张无线网卡,但更多的路由器里只有一张以太网卡和一张无线网卡。

最让我惊奇的是OpenWrt网络的灵活性,它主要靠VLAN和(Bridging)网桥等实现。

  • VLAN一般路由器为节约成本只有一张网卡(我的理解是以太网控制芯片),但如何接入多个网络呢?(路由器按常理至少要两张网卡吧),这就是VLAN的功劳啦。VLAN(Virtual Local Area Network)是虚拟局域网缩写,是把局域网中的同一工作组的主机连在一起,隔绝不同的子网(在一个或多个交换机上),这种局限增强了工作组间的保密性,也减少了全面广播的副作用。用了VLAN也就把连接子网用的路由器给省了,省钱而且效率还很高。

    VLAN需要路由器内置的交换机支持(一般都支持啦),最多可以划分4096个VLAN。一般使用只划分两个VLAN:WAN和LAN,分别对应外网和内网。如果是纯粹的VLAN,则WAN和LAN之间是隔离的,互相不能通信,但在自己的VLAN里可以自由通信。

  • Trunking用一张网卡怎么连接两个网络呢?不知你是否留意到上图中的Port5,Port5一边连接路由器,一边连接网卡eth0,它的作用就是连接vlan0和vlan1。这就是采用VLAN的Trunking技术,在Trunk Port(port5)经过的以太网帧上打标签(Tagging)用以区分来自那个VLAN,然后路由器用网卡eth0接收并处理标签从而处理(在CPU中处理),这就是所谓的单臂路由器,很形象啊:)。
  • Bridging网桥说穿了就是交换机,它就是用软件模拟以太网交换机,连接多个以太网网卡(虚拟的或物理的)分享同一个IP子网。在OpenWrt中,网桥主要用来连接无线WIFI网络和LAN,组成一个统一的内部局域网

最后为了方便理解,我画了一张示意图用普通的交换机和路由器解释上面提到的VLAN、Trunking和Bridging:

hand

参考

VLAN动画详解

OpenWrt network interface

Linux下的网桥