存档在 2013年3月

Linux netstat命令详解

2013年3月18日

简介

Netstat 命令用于显示各种网络相关信息,如网络连接,路由表,接口状态 (Interface Statistics),masquerade 连接,多播成员 (Multicast Memberships) 等等。

输出信息含义

执行netstat后,其输出结果为


Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 2 210.34.6.89:telnet 210.34.6.96:2873 ESTABLISHED
tcp 296 0 210.34.6.89:1165 210.34.6.84:netbios-ssn ESTABLISHED
tcp 0 0 localhost.localdom:9001 localhost.localdom:1162 ESTABLISHED
tcp 0 0 localhost.localdom:1162 localhost.localdom:9001 ESTABLISHED
tcp 0 80 210.34.6.89:1161 210.34.6.10:netbios-ssn CLOSE

Active UNIX domain sockets (w/o servers)
Proto RefCnt Flags Type State I-Node Path
unix 1 [ ] STREAM CONNECTED 16178 @000000dd
unix 1 [ ] STREAM CONNECTED 16176 @000000dc
unix 9 [ ] DGRAM 5292 /dev/log
unix 1 [ ] STREAM CONNECTED 16182 @000000df

从整体上看,netstat的输出结果可以分为两个部分:

一个是Active Internet connections,称为有源TCP连接,其中”Recv-Q”和”Send-Q”指%0A的是接收队列和发送队列。这些数字一般都应该是0。如果不是则表示软件包正在队列中堆积。这种情况只能在非常少的情况见到。

另一个是Active UNIX domain sockets,称为有源Unix域套接口(和网络套接字一样,但是只能用于本机通信,性能可以提高一倍)。
Proto显示连接使用的协议,RefCnt表示连接到本套接口上的进程号,Types显示套接口的类型,State显示套接口当前的状态,Path表示连接到套接口的其它进程使用的路径名。

常见参数

-a (all)显示所有选项,默认不显示LISTEN相关
-t (tcp)仅显示tcp相关选项
-u (udp)仅显示udp相关选项
-n 拒绝显示别名,能显示数字的全部转化成数字。
-l 仅列出有在 Listen (监听) 的服務状态

-p 显示建立相关链接的程序名
-r 显示路由信息,路由表
-e 显示扩展信息,例如uid等
-s 按各个协议进行统计
-c 每隔一个固定时间,执行该netstat命令。

提示:LISTEN和LISTENING的状态只有用-a或者-l才能看到

实用命令实例

 

1. 列出所有端口 (包括监听和未监听的)

  列出所有端口 netstat -a


# netstat -a | more
 Active Internet connections (servers and established)
 Proto Recv-Q Send-Q Local Address Foreign Address State
 tcp 0 0 localhost:30037 *:* LISTEN
 udp 0 0 *:bootpc *:*

Active UNIX domain sockets (servers and established)
 Proto RefCnt Flags Type State I-Node Path
 unix 2 [ ACC ] STREAM LISTENING 6135 /tmp/.X11-unix/X0
 unix 2 [ ACC ] STREAM LISTENING 5140 /var/run/acpid.socket

列出所有 tcp 端口 netstat -at


# netstat -at
 Active Internet connections (servers and established)
 Proto Recv-Q Send-Q Local Address Foreign Address State
 tcp 0 0 localhost:30037 *:* LISTEN
 tcp 0 0 localhost:ipp *:* LISTEN
 tcp 0 0 *:smtp *:* LISTEN
 tcp6 0 0 localhost:ipp [::]:* LISTEN

列出所有 udp 端口 netstat -au

# netstat -au
 Active Internet connections (servers and established)
 Proto Recv-Q Send-Q Local Address           Foreign Address         State
 udp        0      0 *:bootpc                *:*
 udp        0      0 *:49119                 *:*
 udp        0      0 *:mdns                  *:*

2. 列出所有处于监听状态的 Sockets

  只显示监听端口 netstat -l

# netstat -l
 Active Internet connections (only servers)
 Proto Recv-Q Send-Q Local Address           Foreign Address         State
 tcp        0      0 localhost:ipp           *:*                     LISTEN
 tcp6       0      0 localhost:ipp           [::]:*                  LISTEN
 udp        0      0 *:49119                 *:*

  只列出所有监听 tcp 端口 netstat -lt

# netstat -lt
 Active Internet connections (only servers)
 Proto Recv-Q Send-Q Local Address           Foreign Address         State
 tcp        0      0 localhost:30037         *:*                     LISTEN
 tcp        0      0 *:smtp                  *:*                     LISTEN
 tcp6       0      0 localhost:ipp           [::]:*                  LISTEN

只列出所有监听 udp 端口 netstat -lu

# netstat -lu
 Active Internet connections (only servers)
 Proto Recv-Q Send-Q Local Address           Foreign Address         State
 udp        0      0 *:49119                 *:*
 udp        0      0 *:mdns                  *:*

  只列出所有监听 UNIX 端口 netstat -lx


# netstat -lx
 Active UNIX domain sockets (only servers)
 Proto RefCnt Flags Type State I-Node Path
 unix 2 [ ACC ] STREAM LISTENING 6294 private/maildrop
 unix 2 [ ACC ] STREAM LISTENING 6203 public/cleanup
 unix 2 [ ACC ] STREAM LISTENING 6302 private/ifmail
 unix 2 [ ACC ] STREAM LISTENING 6306 private/bsmtp

3. 显示每个协议的统计信息

  显示所有端口的统计信息 netstat -s


# netstat -s
 Ip:
 total packets received
 with invalid addresses
 forwarded
 incoming packets discarded
 incoming packets delivered
 requests sent out
 Icmp:
 ICMP messages received
 input ICMP message failed.
 Tcp:
 active connections openings
 failed connection attempts
 connection resets received
 Udp:
 packets received
 packets to unknown port received.
 .....

显示 TCP 或 UDP 端口的统计信息 netstat -st 或 -su

# netstat -st
# netstat -su

4. 在 netstat 输出中显示 PID 和进程名称 netstat -p

netstat -p 可以与其它开关一起使用,就可以添加 “PID/进程名称” 到 netstat 输出中,这样 debugging 的时候可以很方便的发现特定端口运行的程序。

# netstat -pt
 Active Internet connections (w/o servers)
 Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
 tcp        1      0 ramesh-laptop.loc:47212 192.168.185.75:www        CLOSE_WAIT  2109/firefox
 tcp        0      0 ramesh-laptop.loc:52750 lax:www ESTABLISHED 2109/firefox

5. 在 netstat 输出中不显示主机,端口和用户名 (host, port or user)

当你不想让主机,端口和用户名显示,使用 netstat -n。将会使用数字代替那些名称。

同样可以加速输出,因为不用进行比对查询。

# netstat -an

如果只是不想让这三个名称中的一个被显示,使用以下命令

# netsat -a --numeric-ports
# netsat -a --numeric-hosts
# netsat -a --numeric-users

6. 持续输出 netstat 信息

netstat 将每隔一秒输出网络信息。

# netstat -c
 Active Internet connections (w/o servers)
 Proto Recv-Q Send-Q Local Address Foreign Address State
 tcp 0 0 ramesh-laptop.loc:36130 101-101-181-225.ama:www ESTABLISHED
 tcp 1 1 ramesh-laptop.loc:52564 101.11.169.230:www CLOSING
 tcp 0 0 ramesh-laptop.loc:43758 server-101-101-43-2:www ESTABLISHED
 tcp 1 1 ramesh-laptop.loc:42367 101.101.34.101:www CLOSING
 ^C

7. 显示系统不支持的地址族 (Address Families)

netstat --verbose

在输出的末尾,会有如下的信息

netstat: no support for `AF IPX' on this system.
netstat: no support for `AF AX25' on this system.
netstat: no support for `AF X25' on this system.
netstat: no support for `AF NETROM' on this system.

8. 显示核心路由信息 netstat -r

# netstat -r
 Kernel IP routing table
 Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
 192.168.1.0     *               255.255.255.0   U         0 0          0 eth2
 link-local      *               255.255.0.0     U         0 0          0 eth2
 default         192.168.1.1     0.0.0.0         UG        0 0          0 eth2

注意: 使用 netstat -rn 显示数字格式,不查询主机名称。

9. 找出程序运行的端口

并不是所有的进程都能找到,没有权限的会不显示,使用 root 权限查看所有的信息。

# netstat -ap | grep ssh
 tcp        1      0 dev-db:ssh           101.174.100.22:39213        CLOSE_WAIT  -
 tcp        1      0 dev-db:ssh           101.174.100.22:57643        CLOSE_WAIT  -

找出运行在指定端口的进程

# netstat -an | grep ':80'

10. 显示网络接口列表

# netstat -i
 Kernel Interface table
 Iface   MTU Met   RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg
 eth0       1500 0         0      0      0 0             0      0      0      0 BMU
 eth2       1500 0     26196      0      0 0         26883      6      0      0 BMRU
 lo        16436 0         4      0      0 0             4      0      0      0 LRU

显示详细信息,像是 ifconfig 使用 netstat -ie:


# netstat -ie
 Kernel Interface table
 eth0 Link encap:Ethernet HWaddr 00:10:40:11:11:11
 UP BROADCAST 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)
 Memory:f6ae0000-f6b00000

11. IP和TCP分析

  查看连接某服务端口最多的的IP地址

wss8848@ubuntu:~$ netstat -nat | grep "192.168.1.15:22" |awk '{print $5}'|awk -F: '{print $1}'|sort|uniq -c|sort -nr|head -20
 221.136.168.36
 154.74.45.242
 78.173.31.236
 62.183.207.98
 192.168.1.14
 182.48.111.215
 124.193.219.34
 119.145.41.2
 114.255.41.30
 75.102.11.99

TCP各种状态列表

wss8848@ubuntu:~$ netstat -nat |awk '{print $6}'
established)
Foreign
LISTEN
TIME_WAIT
ESTABLISHED
TIME_WAIT
SYN_SENT

先把状态全都取出来,然后使用uniq -c统计,之后再进行排序。

wss8848@ubuntu:~$ netstat -nat |awk '{print $6}'|sort|uniq -c
 ESTABLISHED
 FIN_WAIT1
 Foreign
 LAST_ACK
 LISTEN
 SYN_SENT
 TIME_WAIT
 established)

最后的命令如下:

netstat -nat |awk '{print $6}'|sort|uniq -c|sort -rn

分析access.log获得访问前10位的ip地址

awk '{print $1}' access.log |sort|uniq -c|sort -nr|head -10

[C++]简单工厂模式

2013年3月17日

工厂模式有几种形态

工厂模式有以下几种形态:

简单工厂(Simple Factory)模式

工厂方法(Factory Method)模式,又称多形性工厂(Polymorphic Factory)模式

抽象工厂(Abstract Factory)模式,又称工具箱(Kit或Toolkit)模式

简单的工厂模式就是用工厂去生产产品,当需要某种产品时,则交给相应的工厂来产生这个产品。

 

就产品方面来说:产品的基类为ProductBase,生产的产品的指针交给产品基类保存来实现多态。

就工厂方面:工厂的基类为FactoryBase,可以针对每件产品来设计一个继承于FactoryBase的工厂类。

也可以通过给生产产品的函数传递不同参数来生产不同产品(这种情况,我觉得不需要弄成继承关系,只需一个类就可以了)。但是这种依靠提供参数来生产对象的方法使得工厂类不封闭(也就是每增加一种产品,都需要修改Factory的代码)

所以提供一个工厂抽象基类(FactoryBase),需要生产什么产品,我们则派生出不同的工厂。

以下的代码为根据传入参数不同来生产不同的产品。

Factory.h


#ifndef _DP_FACTORY_H_
#define _DP_FACTORY_H_

#include <iostream>
using namespace std;

#include "Product.h"
class FactoryBase
{
public:
 virtual ~FactoryBase() = 0;
 virtual ProductBase* makeProduct(int) =0;
 FactoryBase(){}
};

FactoryBase::~FactoryBase(){}
class MyFactory : public FactoryBase
{
public:
 MyFactory(){cout<<"工厂构造函数"<<endl;}
 ~MyFactory(){}
 ProductBase* makeProduct(int i)
 {
 if(i ==1 )
 return new AProduct();
 else
 return new BProduct();
 }
};
#endif

Product.h


#ifndef _PRODUCT_H_
#define _PRODUCT_H_
class ProductBase
{
public:
 virtual ~ProductBase() = 0;
 ProductBase(){}
};
ProductBase::~ProductBase(){}
class AProduct :public ProductBase
{
public:
 ~AProduct(){}
 AProduct(){cout<<"A产品构造函数"<<endl;}

};

class BProduct :public ProductBase
{
public:
 ~BProduct(){}
 BProduct(){cout<<"B产品构造函数"<<endl;}
};
#endif

main.cpp


#include "Factory.h"
#include "Product.h"
int main()
{
 FactoryBase* factory = new MyFactory();
 ProductBase* product = factory->makeProduct(2);
 delete factory;
 delete product;
 return 0;
}

我采用了给工厂提供不同的参数来生产不同的产品。

如果不采用工厂模式,如果有很多产品,AProduct,BProduct..

AProfuct = new AProduct();

BProduct = new BProduct();

AProduct.func();

BProduct.func();

采用工厂模式后,就可以使用产品的抽象基类来实现多态。

采用继承抽象共产来针对不同的产品而派生工厂的方法类似。

23种设计模式

2013年3月17日

创建模式
一、Singleton,单例模式:保证一个类只有一个实例,并提供一个访问它的全局访问点
二、Abstract Factory,抽象工厂:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们的具体类。
三、Factory Method,工厂方法:定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method使一个类的实例化延迟到了子类。
四、Builder,建造模式:将一个复杂对象的构建与他的表示相分离,使得同样的构建过程可以创建不同的表示。
五、Prototype,原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型来创建新的对象。

行为模式
六、Iterator,迭代器模式:提供一个方法顺序访问一个聚合对象的各个元素,而又不需要暴露该对象的内部表示。
七、Observer,观察者模式:定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知自动更新。
八、Template Method,模板方法:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,TemplateMethod使得子类可以不改变一个算法的结构即可以重定义该算法得某些特定步骤。
九、Command,命令模式:将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队和记录请求日志,以及支持可撤销的操作。
十、State,状态模式:允许对象在其内部状态改变时改变他的行为。对象看起来似乎改变了他的类。
十一、Strategy,策略模式:定义一系列的算法,把他们一个个封装起来,并使他们可以互相替换,本模式使得算法可以独立于使用它们的客户。
十二、China of Responsibility,职责链模式:使多个对象都有机会处理请求,从而避免请求的送发者和接收者之间的耦合关系
十三、Mediator,中介者模式:用一个中介对象封装一些列的对象交互。
十四、Visitor,访问者模式:表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素类的前提下定义作用于这个元素的新操作。
十五、Interpreter,解释器模式:给定一个语言,定义他的文法的一个表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
十六、Memento,备忘录模式:在不破坏对象的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。

结构模式
十七、Composite,组合模式:将对象组合成树形结构以表示部分整体的关系,Composite使得用户对单个对象和组合对象的使用具有一致性。
十八、Facade,外观模式:为子系统中的一组接口提供一致的界面,fa?ade提供了一高层接口,这个接口使得子系统更容易使用。
十九、Proxy,代理模式:为其他对象提供一种代理以控制对这个对象的访问
二十、Adapter,适配器模式:将一类的接口转换成客户希望的另外一个接口,Adapter模式使得原本由于接口不兼容而不能一起工作那些类可以一起工作。
二十一、Decrator,装饰模式:动态地给一个对象增加一些额外的职责,就增加的功能来说,Decorator模式相比生成子类更加灵活。
二十二、Bridge,桥模式:将抽象部分与它的实现部分相分离,使他们可以独立的变化。
二十三、Flyweight,享元模式

Google编程规范头文件篇

2013年3月17日

1. 头文件

通常每一个 .cc文件都有一个对应的 .h文件. 也有一些常见例外, 如单元测试代码和只包含 main()函数的 .cc文件.

正确使用头文件可令代码在可读性、文件大小和性能上大为改观.

下面的规则将引导你规避使用头文件时的各种陷阱.

1.1. #define 保护

所有头文件都应该使用 #define防止头文件被多重包含, 命名格式当是: <PROJECT>_<PATH>_<FILE>_H_

为保证唯一性, 头文件的命名应该依据所在项目源代码树的全路径. 例如, 项目 foo中的头文件 foo/src/bar/baz.h可按如下方式保护:

#ifndef FOO_BAR_BAZ_H_

#define FOO_BAR_BAZ_H_

#endif // FOO_BAR_BAZ_H_

1.2. 头文件依赖

能用前置声明的地方尽量不使用 #include.

当一个头文件被包含的同时也引入了新的依赖, 一旦该头文件被修改, 代码就会被重新编译. 如果这个头文件又包含了其他头文件, 这些头文件的任何改变都将导致所有包含了该头文件的代码被重新编译. 因此, 我们倾向于减少包含头文件, 尤其是在头文件中包含头文件.

使用前置声明可以显著减少需要包含的头文件数量. 举例说明: 如果头文件中用到类 File, 但不需要访问 File类的声明, 头文件中只需前置声明 class File;而无须 #include “file/base/file.h”.

不允许访问类的定义的前提下, 我们在一个头文件中能对类 Foo做哪些操作?

  • 我们可以将数据成员类型声明为 Foo *Foo &.

  • 我们可以将函数参数 / 返回值的类型声明为 Foo (但不能定义实现).

  • 我们可以将静态数据成员的类型声明为 Foo, 因为静态数据成员的定义在类定义之外.

反之, 如果你的类是 Foo的子类, 或者含有类型为 Foo的非静态数据成员, 则必须包含 Foo所在的头文件.

有时, 使用指针成员 (如果是 scoped_ptr更好) 替代对象成员的确是明智之选. 然而, 这会降低代码可读性及执行效率, 因此如果仅仅为了少包含头文件,还是不要这么做的好.

当然 .cc文件无论如何都需要所使用类的定义部分, 自然也就会包含若干头文件.

1.3. 内联函数

只有当函数只有 10 行甚至更少时才将其定义为内联函数.

定义:

当函数被声明为内联函数之后, 编译器会将其内联展开, 而不是按通常的函数调用机制进行调用.

优点:

当函数体比较小的时候, 内联该函数可以令目标代码更加高效. 对于存取函数以及其它函数体比较短, 性能关键的函数, 鼓励使用内联.

缺点:

滥用内联将导致程序变慢. 内联可能使目标代码量或增或减, 这取决于内联函数的大小. 内联非常短小的存取函数通常会减少代码大小, 但内联一个相当大的函数将戏剧性的增加代码大小. 现代处理器由于更好的利用了指令缓存, 小巧的代码往往执行更快。

结论:

一个较为合理的经验准则是, 不要内联超过 10 行的函数. 谨慎对待析构函数, 析构函数往往比其表面看起来要更长, 因为有隐含的成员和基类析构函数被调用!

另一个实用的经验准则: 内联那些包含循环或 switch语句的函数常常是得不偿失 (除非在大多数情况下, 这些循环或 switch语句从不被执行).

有些函数即使声明为内联的也不一定会被编译器内联, 这点很重要; 比如虚函数和递归函数就不会被正常内联. 通常, 递归函数不应该声明成内联函数.YuleFox : 递归调用堆栈的展开并不像循环那么简单, 比如递归层数在编译时可能是未知的, 大多数编译器都不支持内联递归函数). 虚函数内联的主要原因则是想把它的函数体放在类定义内, 为了图个方便, 抑或是当作文档描述其行为, 比如精短的存取函数.

1.4. -inl.h文件

复杂的内联函数的定义, 应放在后缀名为 -inl.h的头文件中.

内联函数的定义必须放在头文件中, 编译器才能在调用点内联展开定义. 然而, 实现代码理论上应该放在 .cc文件中, 我们不希望 .h文件中有太多实现代码, 除非在可读性和性能上有明显优势.

如果内联函数的定义比较短小, 逻辑比较简单, 实现代码放在 .h文件里没有任何问题. 比如, 存取函数的实现理所当然都应该放在类定义内. 出于编写者和调用者的方便, 较复杂的内联函数也可以放到 .h文件中, 如果你觉得这样会使头文件显得笨重, 也可以把它萃取到单独的 -inl.h. 这样把实现和类定义分离开来, 当需要时包含对应的 -inl.h即可。

-inl.h文件还可用于函数模板的定义. 从而增强模板定义的可读性.

别忘了 -inl.h和其他头文件一样, 也需要 #define保护.

1.5. 函数参数的顺序

定义函数时, 参数顺序依次为: 输入参数, 然后是输出参数.

C/C++ 函数参数分为输入参数, 输出参数, 和输入/输出参数三种. 输入参数一般传值或传 const引用, 输出参数或输入/输出参数则是非const指针. 对参数排序时, 将只输入的参数放在所有输出参数之前. 尤其是不要仅仅因为是新加的参数, 就把它放在最后; 即使是新加的只输入参数也要放在输出参数.

这条规则并不需要严格遵守. 输入/输出两用参数 (通常是类/结构体变量) 把事情变得复杂, 为保持和相关函数的一致性, 你有时不得不有所变通.

1.6. #include的路径及顺序

使用标准的头文件包含顺序可增强可读性, 避免隐藏依赖: C , C++ , 其他库的 .h, 本项目内的 .h.

项目内头文件应按照项目源代码目录树结构排列, 避免使用 UNIX 特殊的快捷目录 . (当前目录) .. (上级目录). 例如, google-awesome-project/src/base/logging.h应该按如下方式包含

#include “base/logging.h”

又如, dir/foo.cc的主要作用是实现或测试 dir2/foo2.h的功能, foo.cc中包含头文件的次序如下:

  1. dir2/foo2.h (优先位置, 详情如下)

  2. C 系统文件

  3. C++ 系统文件

  4. 其他库的 .h文件

  5. 本项目内 .h文件 


这种排序方式可有效减少隐藏依赖. 我们希望每一个头文件都是可被独立编译的 (yospaly 译注: 即该头文件本身已包含所有必要的显式依赖), 最简单的方法是将其作为第一个 .h文件 #included进对应的 .cc.dir/foo.ccdir2/foo2.h通常位于同一目录下 (base/basictypes_unittest.ccbase/basictypes.h), 但也可以放在不同目录下.按字母顺序对头文件包含进行二次排序是不错的主意 (yospaly 译注: 之前已经按头文件类别排过序了).举例来说, google-awesome-project/src/foo/internal/fooserver.cc的包含次序如下

#include “foo/public/fooserver.h” // 优先位置

#include <sys/types.h>

#include <unistd.h>

#include <hash_map>

#include <vector>

#include “base/basictypes.h”

#include “base/commandlineflags.h”

#include “foo/public/bar.h”

  1. 避免多重包含是学编程时最基本的要求;

  2. 前置声明是为了降低编译依赖,防止修改一个头文件引发多米诺效应;

  3. 内联函数的合理使用可提高代码执行效率;

  4. -inl.h可提高代码可读性 (一般用不到吧:D);

  5. 标准化函数参数顺序可以提高可读性和易维护性 (对函数参数的堆栈空间有轻微影响, 我以前大多是相同类型放在一起);

  6. 包含文件的名称使用 ...虽然方便却易混乱, 使用比较完整的项目路径看上去很清晰, 很条理, 包含文件的次序除了美观之外, 最重要的是可以减少隐藏依赖, 使每个头文件在 “最需要编译” (对应源文件处 :D) 的地方编译, 有人提出库文件放在最后, 这样出错先是项目内的文件, 头文件都放在对应源文件的最前面, 这一点足以保证内部错误的及时发现了.  

FILE* 到 iostream的转换

2013年3月17日

Writing a unix daemon using C++, the idea might seem odd. But it’s desirable in many ways :

  • C++ provides a lot of methods which are inherently safe and neat regarding string treatments,
  • STL(sgi) provides a lot of nice containers, particularly the associative map,
  • convenient simple text input/output using iostream …

Now, coding a daemon implies a lot of very unix-native C details such as : properly detach from terminals, output some errors via syslog …

When one realises all those functionnalites have simple posix-C functions to take care of, a very convenient bridge from C to C++ is to bind a regular iostream to a FILE* descriptor.

Example : reading from a pipe using istream
The below posix-C function :

FILE *popen(const char *command, const char *type);

provides a very simple way to fork and read to (or write from) another process. The gnu iostream library documentation mentions some methods usable to turn FILE* into stream, but I would not say the said documentation made it straighforward. So here’s an example of such a pipe :

#include <errno.h>
#include <string.h>     // strerror

#include <iostream>
#include <ext/stdio_filebuf.h>  // __gnu_cxx::stdio_filebuf

using namespace std;

void filter_uggly_uppercase (istream &in, ostream &out) {
    char c;
    while (in && in.get(c)) {
        if ((c>='a') && (c<='z'))
            out << (char)('A' - 'a' + c);
        else
            out << c;
    }
}

int main (void) {
    const char *command = "find . -type f";

    FILE *f_pipe_in = popen (command, "r");
    if (f_pipe_in == NULL) {
        int e = errno;
        cerr << "error at piping from " << command << " : " << strerror (e) << endl;
        return 1;
    }

    // let's build a buffer from the FILE* descriptor ...
    __gnu_cxx::stdio_filebuf<char> pipe_buf (f_pipe_in, ios_base::in);

    // there we are, a regular istream is build upon the buffer :
    istream stream_pipe_in (&pipe_buf);

    // let's read from that pipe now, the usual way ...
    filter_uggly_uppercase (stream_pipe_in, cout);

    pclose (f_pipe_in);

    return 0;
}

 

D-BUS

2013年3月17日
介绍

dbus的是一个低延迟,低开销,高可用性的ipc机制。是desktop-bus的简称
更多细节:
* 低延迟:dbus一开始就是用来设计成避免来回传递和允许异步操作的。很像x协议
* 低开销 : dbus使用一个二进制的协议,不需要转化成像xml这样的文本格式。因为dbus是主要用来机器内部的ipc ,而不是为了网络上的ipc机制而准备的.所以它才能够在本机内部达到最优效果。
*高可用性:dbus是基于消息机制而不是字节流机制。它能自动管理一大堆困难的ipc问题。同样的,dbus库被设计来让程序员能够使用他们已经写好的 代码。而不会让他们放弃已经写好的代码,被迫通过学习新的ipc机制来根据新的ipc特性重写这些代码。
基本的d-bus协议是一对一协议(用户对用户或者客户端对服务器),又叫做消息协议.因为他是一个处理一个应用程序与另一个应用程序单独交流的系统.应 用程序通讯首选dbus消息总线.bbus消息总线是一个非常特别的程序,它接受其他应用程序发出的消息,并且转发这些消息到其他任何希望知道这些消息的 应用程序中去
dbus被用来取代CORBA和DCOP,DCOP是desktop Communication pootocol的简称。采用了server和client机制,可以发送接收消息,可以异步和同步发送消息,可完成methord call。KDE 4以前采用DCOP,在kde 4开始采用DBUS。

 
IBM上的介绍

D-BUS 本质上是 进程间通信(inter-process communication)(IPC)的一个实现。不过,有一些 特性使得 D-BUS 远远不是“只是另一个 IPC 实现”。有很多不同的 IPC 实现,因为每一个都定位于解决 特定的明确定义的问题。CORBA 是用于面向对象编程中复杂的 IPC 的一个强大的解决方案。DCOP 是一个 较轻量级的 IPC 框架,功能较少,但是可以很好地集成到 K 桌面环境中。SOAP 和 XML-RPC 设计用于 Web 服务,因而使用 HTTP 作为其传输协议。D-BUS 设计用于桌面应用程序和 OS 通信。

桌面应用程序通信

典型的桌面都会有多个应用程序在运行,而且,它们经常需要彼此进行通信。DCOP 是一个用于 KDE 的 解决方案,但是它依赖于 Qt,所以不能用于其他桌面环境之中。类似的,Bonobo 是一个用于 GNOME 的 解决方案,但是非常笨重,因为它是基于 CORBA 的。它还依赖于 GObject,所以也不能用于 GNOME 之外。 D-BUS 的目标是将 DCOP 和 Bonobo 替换为简单的 IPC,并集成这两种桌面环境。由于尽可能地减少了 D-BUS 所需的依赖,所以其他可能会使用 D-BUS 的应用程序不用担心引入过多依赖。

桌面/操作系统通信

术语“操作系统”在这里不仅包括内核,还包括系统后台进程。例如,通过使用 D-BUS 的 udev (Linux 2.6 中取代 devfs 的, 提供动态 /dev 目录),当设备(比如一个 USB 照相机)插入时会发放出一个信号。 这样可以更紧密地将硬件集成到桌面中,从而改善用户体验。

D-BUS 特性

D-BUS 有一些有趣的特性,使其像是一个非常有前途的选择。

协议是低延迟而且低开销的,设计得小而高效,以便最小化传送的往返时间。另外,协议是二进制的,而 不是文本的,这样就排除了费时的序列化过程。由于只面向本地机器处理的使用情形,所以所有的消息 都以其自然字节次序发送。字节次序在每个消息中声明,所以如果一个 D-BUS 消息通过网络传输到 远程的主机,它仍可以被正确地识别出来。

从开发者的角度来看,D-BUS 是易于使用的。有线协议容易理解,客户机程序库以直观的方式对其进行 包装。

程序库还设计用于为其他系统所包装。预期,GNOME 将使用 GObject 创建包装 D-BUS 的包装器 (实际上这些已经部分存在了,将 D-BUS 集成入它们的事件循环),KDE 将使用 Qt 创建类似的 包装器。由于 Python 具有面向对象特性和灵活的类型,已经有了具备类似接口的 Python 包装器。

最后,D-BUS 正在 freedesktop.org 的保护下进行开发,在那里,来自 GNOME、KDE 以及 其他组织的对此感兴趣的成员参与了设计与实现。

D-BUS 的内部工作方式

典型的 D-BUS 设置将由几个总线构成。将有一个持久的 系统总线(system bus),它在 引导时就会启动。这个总线由操作系统和后台进程使用,安全性非常好,以使得任意的应用程序 不能欺骗系统事件。还将有很多 会话总线(session buses),这些总线当用户登录后启动,属于 那个用户私有。它是用户的应用程序用来通信的一个会话总线。当然,如果一个应用程序需要接收 来自系统总线的消息,它不如直接连接到系统总线 —— 不过,它可以发送的消息将是受限的。

一旦应用程序连接到了一个总线,它们就必须通过添加 匹配器(matchers) 来声明它们希望 收到哪种消息。匹配器为可以基于接口、对象路径和方法进行接收的消息指定一组规则(见后)。 这样就使得应用程序可以集中精力去处理它们想处理的内容,以实现消息的高效路由,并保持总线 上消息的预期数量,以使得不会因为这些消息导致所有应用程序的性能下降并变得很慢。

对象

本质上,D-BUS 是一个对等(peer-to-peer)的协议 —— 每个消息都有一个源和一个目的。这些地址 被指定为 对象路径。概念上,所有使用 D-BUS 的应用程序都包括一组 对象,消息发送到 或者发送自特定对象 —— 不是应用程序 —— 这些对象由对象路径来标识。

另外,每个对象都可以支持一个或多个 接口(interfaces)。这些接口看起来类似于 Java 中 的接口或者 C++ 中的纯粹的虚类(pure virtual classes)。不过,没 有选项来检查对象是否实现了它们所声明的接口,而且也没有办法可以调查对象内部以使列出其支持的接口。 接口用于名称空间和方法名称,因此一个单独的对象可以有名称相同而接口不同的多个方法。

消息

在 D-BUS 中有四种类型的消息:方法调用(method calls)、方法返回(method returns)、信号(signals) 和错误(errors)。要执行 D-BUS 对象的方法,您需要向对象发送一个方法调用消息。它将完成一些处理并返回 一个方法返回消息或者错误消息。信号的不同之处在于它们不返回任何内容:既没有“信号返回”消息,也没有 任何类型的错误消息。

消息也可以有任意的参数。参数是强类型的,类型的范围是从基本的非派生类型(布尔(booleans)、 字节(bytes)、整型(integers))到高层次数据结构(字符串(strings)、数组( arrays)和字典(dictionaries))。

服务

服务(Services) 是 D-BUS 的最高层次抽象,它们的实现当前还在不断发展变化。应用程序 可以通过一个总线来注册一个服务,如果成功,则应用程序就已经 获得 了那个服务。其他应用 程序可以检查在总线上是否已经存在一个特定的服务,如果没有可以要求总线启动它。服务抽象的细节 —— 尤其是服务活化 —— 当前正处于发展之中,应该会有变化。

用例

尽管 D-BUS 相对较新,但是却迅速地得到了采用。如前所述,可以构建具有 D-BUS 支持的 udev 以使得当热插拔(hot-plug)设备时它可以发送一个信号。 任何应用程序都可以侦听这些事件并当接收到这些事件时执行动作。例如,gnome-volume-manager 可以检测到 USB 存储棒的插入并自动挂载它;或者,当插入一个数码相机时它可以自动下载照片。

一个更为有趣但很不实用的例子是 Jamboree 和 Ringaling 的结合。Jamboree 是一个简单的音乐播放器, 它具有 D-BUS 接口,以使得它可以被告知播放、到下一首歌、改变音量等等。Ringaling 是一个小程序, 它打开 /dev/ttyS0(一个串行端口)并观察接收到的内容。当 Ringaling 发现文本“RING”时,就通过 D-BUS 告知 Jamboree 减小音量。最终的结果是,如果您的计算机上插入了一个调制解调器,而且电话 铃响,则音乐音量就会为您减小。 正是计算机所追求的!

代码示例

现在,让我们来接触一些使用 D-BUS 代码的示例。

dbus-ping-send.c 每秒通过会话总线发送一个参数为字符串“Ping!”的信号。我使用 Glib 来 管理总线,以使得我不需要自己来处理总线的连接细节。
清单 1. dbus-ping-send.c

#include <glib.h>
#include <dbus/dbus-glib.h>
static gboolean send_ping (DBusConnection *bus);
int
main (int argc, char **argv)
{
  GMainLoop *loop;
  DBusConnection *bus;
  DBusError error;
  /* Create a new event loop to run in */
  loop = g_main_loop_new (NULL, FALSE);
  /* Get a connection to the session bus */
  dbus_error_init (&error);
  bus = dbus_bus_get (DBUS_BUS_SESSION, &error);
  if (!bus) {
    g_warning ("Failed to connect to the D-BUS daemon: %s", error.message);
    dbus_error_free (&error);
    return 1;
  }
  /* Set up this connection to work in a GLib event loop */
  dbus_connection_setup_with_g_main (bus, NULL);
  /* Every second call send_ping() with the bus as an argument*/
  g_timeout_add (1000, (GSourceFunc)send_ping, bus);
  /* Start the event loop */
  g_main_loop_run (loop);
  return 0;
}
static gboolean
send_ping (DBusConnection *bus)
{
  DBusMessage *message;
  /* Create a new signal "Ping" on the "com.burtonini.dbus.Signal" interface,
   * from the object "/com/burtonini/dbus/ping". */
  message = dbus_message_new_signal ("/com/burtonini/dbus/ping",
                                     "com.burtonini.dbus.Signal", "Ping");
  /* Append the string "Ping!" to the signal */
  dbus_message_append_args (message,
                            DBUS_TYPE_STRING, "Ping!",
                            DBUS_TYPE_INVALID);
  /* Send the signal */
  dbus_connection_send (bus, message, NULL);
  /* Free the signal now we have finished with it */
  dbus_message_unref (message);
  /* Tell the user we send a signal */
  g_print("Ping!\n");
  /* Return TRUE to tell the event loop we want to be called again */
  return TRUE;
}

main 函数创建一个 GLib 事件循环,获得会话总线的一个连接, 并将 D-BUS 事件处理集成到 Glib 事件循环之中。然后它创建了一个名为 send_ping 间隔为一秒的计时器,并启动事件循环。

send_ping 构造一个来自于对象路径 /com/burtonini/dbus/ping 和 接口 com.burtonini.dbus.Signal 的新的 Ping 信号。然后,字符串 “Ping!”作为参数添加到信号中并通过总线发送。在标准输出中会打印一条消息以让用户知道发送了 一个信号。

当然,不应该向总线发送了信号而没有任何程序在侦听它们……于是我们需要:
清单 2. dbus-ping-listen.c

#include <glib.h>
#include <dbus/dbus-glib.h>
static DBusHandlerResult signal_filter 
      (DBusConnection *connection, DBusMessage *message, void *user_data);
int
main (int argc, char **argv)
{
  GMainLoop *loop;
  DBusConnection *bus;
  DBusError error;
  loop = g_main_loop_new (NULL, FALSE);
  dbus_error_init (&error);
  bus = dbus_bus_get (DBUS_BUS_SESSION, &error);
  if (!bus) {
    g_warning ("Failed to connect to the D-BUS daemon: %s", error.message);
    dbus_error_free (&error);
    return 1;
  }
  dbus_connection_setup_with_g_main (bus, NULL);
  /* listening to messages from all objects as no path is specified */
  dbus_bus_add_match (bus, "type='signal',interface='com.burtonini.dbus.Signal'");
  dbus_connection_add_filter (bus, signal_filter, loop, NULL);
  g_main_loop_run (loop);
  return 0;
}
static DBusHandlerResult
signal_filter (DBusConnection *connection, DBusMessage *message, void *user_data)
{
  /* User data is the event loop we are running in */
  GMainLoop *loop = user_data;
  /* A signal from the bus saying we are about to be disconnected */
  if (dbus_message_is_signal 
        (message, DBUS_INTERFACE_ORG_FREEDESKTOP_LOCAL, "Disconnected")) {
    /* Tell the main loop to quit */
    g_main_loop_quit (loop);
    /* We have handled this message, don't pass it on */
    return DBUS_HANDLER_RESULT_HANDLED;
  }
  /* A Ping signal on the com.burtonini.dbus.Signal interface */
  else if (dbus_message_is_signal (message, "com.burtonini.dbus.Signal", "Ping")) {
    DBusError error;
    char *s;
    dbus_error_init (&error);
    if (dbus_message_get_args 
       (message, &error, DBUS_TYPE_STRING, &s, DBUS_TYPE_INVALID)) {
      g_print("Ping received: %s\n", s);
      dbus_free (s);
    } else {
      g_print("Ping received, but error getting message: %s\n", error.message);
      dbus_error_free (&error);
    }
    return DBUS_HANDLER_RESULT_HANDLED;
  }
  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

这个程序侦听 dbus-ping-send.c 正在发出的信号。 main 函数 和前面一样启动,创建一个到总线的连接。然后它声明愿意当具有com.burtonini.dbus.Signal 接口的信号被发送时得到通知,将 signal_filter 设置为通知函数, 然后进入事件循环。

当满足匹配的消息被发送时, signal_func 会被调用。不过,它也将会 收到来自总线本身的总线管理信号。要确定接收到消息时应该做些什么,仅仅需要检验消息头。如果消息是 总线断开信号,则事件循环终止,因为侦听一个不存在的总线是没有意义的。(告知总线信号已经处理)。 然后,将到来的消息与期望的消息相比较,如果成功,则解出参数并输出。如果到来的消息不是其中的任何一个, 则告知总线没有处理那个消息。

那两个示例使用了低层的 D-BUS 程序库,这个程序库是完全的,但是当您想创建服务和很多对象时, 使用起来冗长得令人厌倦。有正在开发中的 C# 和 Python 包装器,提供了非常接近于 D-BUS 的逻辑 模型的编程接口。作为一个示例,这里是用 Python 重新对 ping/listen 示例进行了更为精致的实现。 由于 Python 绑定模拟了逻辑接口,所以不可能不通过一个服务来发送信号。所以这个例子也要创建 一个服务:
清单 3. dbus-ping-send.py

#! /usr/bin/env python
import gtk
import dbus
# Connect to the bus
bus = dbus.Bus()
# Create a service on the bus
service = dbus.Service("com.burtonini.dbus.SignalService", bus)
# Define a D-BUS object
class SignalObject(dbus.Object):
    def __init__(self, service):
        dbus.Object.__init__(self, "/", [], service)
# Create an instance of the object, which is part of the service
signal_object = SignalObject(service)
def send_ping():
    signal_object.broadcast_signal("com.burtonini.dbus.Signal", "Ping")
    print "Ping!"
    return gtk.TRUE
# Call send_ping every second to send the signal
gtk.timeout_add(1000, send_ping)
gtk.main()

代码大部分是不言而明的:获得一个到总线的连接并注册 com.burtonini.dbus.SignalService 服务。然后创建一个最小限度的 D-BUS 对象, 这个对象每秒广播一个信号。代码比相应的 C 代码 更简单, 但是需要做 Python 绑定的工作。(例如,没有方法向信号添加参数。)
清单 4. dbus-ping-listen.py

#! /usr/bin/env python
import gtk
import dbus
bus = dbus.Bus()
def signal_callback(interface, signal_name, service, path, message):
    print "Received signal %s from %s" % (signal_name, interface)
# Catch signals from a specific interface and object, and call signal_callback
# when they arrive.
bus.add_signal_receiver(signal_callback,
                        "com.burtonini.dbus.Signal", # Interface
                        None, # Any service
                        "/" # Path of sending object
                        )
# Enter the event loop, waiting for signals
gtk.main()

此代码比 dbus-ping-listen.c 中相应的 C 代码 更简明,也更容易读懂。此外, 有些地方需要做绑定的工作(当调用bus.add_signal_receiver 时,用户必须传入 一个接口和一个对象路径;否则会创建不正常的匹配器)。这是一个微不足道的缺陷,一旦这个缺陷被修正,就可以 去服务和对象路径参数除,这将进一步提高代码的可读性。

结束语

D-BUS 是一个轻量级但是很强大的远程过程调用系统,为希望使用它的应用程序带来最小的开销代价。 D-BUS 正由一组非常有经验的程序员进行积极的公开开发。D-BUS 得到了早期采用者的迅速接受,因而它在 Linux 桌面领域似乎有一个乐观的未来。
参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文.
  • 在 D-BUS 主页可以找到资料、 下载、文档及更多内容。
  • D-BUS 作为 freedesktop.org/的一部分进行开发。
  • CORBA是一个强大的、标准化的远程过程调用规范。
  • ORBit 是 GNOME 中使用的 一个 CORBA 实现。(GNOME 组件系统 Bonobo 构建于 ORBit 之上)。
  • KDE 的远程过程调用实现是 DCOP
  • Project Utopia的目标是通过 D-BUS 实现 Linux 中硬件的无缝集成。
  • 想要在电话铃响时减少您的音乐播放器的音量吗?您需要 ringaling.c (以及一个可以识别 D-BUS 的音乐播放器,比如Jamboree)。
  • Ross 以前为 developerWorks 撰写了 在 Python 中封装 GObject(developerWorks,2003 年 3 月)一文,该文展示了您如何在 Python 中 随时使用 C-coded GObject,而不管您是否特别精通 C 。
  • 教程 Bridging XPCOM/Bonobo: Techniques(developerWorks,2001 年 5 月) 讨论了一些必要的概念和技术,它们用于将两个组件体系结构连接起来,以使一个体系结构中的组件可以在另一个环境中使用。
  • 用 DCOP 连接 KDE 应用程序(developerWorks,2004 年 2 月)介绍了 KDE 的进程间通信协议及其脚本化。
  • 使进程和线程同步 (developerWorks,2001 年 10 月)考察了利用进程间同步原语作为控制两个进程访问同一资源的一种方式。
  • CORBA Component Model (CCM)概述了 CORBA 规范以及 CORBA 与其他组件模型的互操作性。
  • 在 developerWorks Linux 专区 可以找到 更多为 Linux 开发者准备的参考资料。
  • 在 Developer Bookstore Linux 区中定购 打折出售的 Linux 书籍
  • 从 developerWorks 的 Speed-start your Linux app 专区下载可以运行于 Linux 之上的经过挑选的 developerWorks Subscription 产品免费测试版本,包括 WebSphere Studio Site Developer、WebSphere SDK for Web services、WebSphere Application Server、DB2 Universal Database Personal Developers Edition、Tivoli Access Manager 和 Lotus Domino Server。要更快速地开始上手,请参阅针对各个产品的 how-to 文章和技术支持。

一些Linux使用技巧

2013年3月17日
这篇文章来源于Quroa的一个问答《What are some time-saving tips that every Linux user should know?》—— Linux用户有哪些应该知道的提高效率的技巧。我觉得挺好的,总结得比较好,把其转过来,并加了一些自己的理解。 首先,我想告诉大家,在Unix/Linux下,最有效率技巧的不是操作图形界面,而是命令行操作,因为命令行意味着自动化。如果你看过《你可能不知道的Shell》以及《28个Unix/Linux的命令行神器》你就会知道Linux有多强大,这个强大完全来自于命令行,于是,就算你不知道怎么去做一个环保主义的程序员,至少他们可以让你少熬点夜,从而有利于你的身体健康和性生活。下面是一个有点长的列表,正如作者所说,你并不需要知道所有的这些东西,但是如果你还在很沉重地在使用Linux的话,这些东西都值得你看一看。 (注:如果你想知道下面涉及到的命令的更多的用法,你一定要man一点。对于一些命令,你可以需要先yum或apt-get来安装一下,如果有什么问题,别忘了Google。如果你要Baidu的话,我仅代表这个地球上所有的生物包括微生物甚至细菌病毒和小强BS你到宇宙毁灭)

基础

  • 学习 Bash 。你可以man bash来看看bash的东西,并不复杂也并不长。你用别的shell也行,但是bash是很强大的并且也是系统默认的。(学习zsh或tsch只会让你在很多情况下受到限制)
  • 学习 vim 。在Linux下,基本没有什么可与之竞争的编辑器(就算你是一个Emacs或Eclipse的重度用户)。你可以看看《简明vim攻略》和 《Vim的冒险游戏》以及《给程序员的Vim速查卡》还有《把Vim变成一个编程的IDE》等等。
  • 了解 ssh。明白不需要口令的用户认证(通过ssh-agent, ssh-add),学会用ssh翻墙,用scp而不是ftp传文件,等等。你知道吗?scp 远端的时候,你可以按tab键来查看远端的目录和文件(当然,需要无口令的用户认证),这都是bash的功劳。

 

  • 熟悉bash的作业管理,如: &, Ctrl-Z, Ctrl-C, jobs, fg, bg, kill, 等等。当然,你也要知道Ctrl+\(SIGQUIT)和Ctrl+C (SIGINT)的区别。
  • 简单的文件管理 : ls 和 ls -l (你最好知道 “ls -l” 的每一列的意思), less, head, tail 和 tail -f, ln 和 ln -s (你知道明白hard link和soft link的不同和优缺点), chown, chmod, du (如果你想看看磁盘的大小 du -sk *), df, mount。当然,原作者忘了find命令。
  • 基础的网络管理: ip 或 ifconfig, dig。当然,原作者还忘了如netstat, ping, traceroute, 等
  • 理解正则表达式,还有grep/egrep的各种选项。比如: -o, -A, 和 -B 这些选项是很值得了解的。
  • 学习使用 apt-get 和 yum 来查找和安装软件(前者的经典分发包是Ubuntu,后者的经典分发包是Redhat),我还建议你试着从源码编译安装软件。

日常

  • 在 bash 里,使用 Ctrl-R 而不是上下光标键来查找历史命令。
  • 在 bash里,使用 Ctrl-W 来删除最后一个单词,使用 Ctrl-U 来删除一行。请man bash后查找Readline Key Bindings一节来看看bash的默认热键,比如:Alt-. 把上一次命令的最后一个参数打出来,而Alt-* 则列出你可以输入的命令。
  • 回到上一次的工作目录: cd –  (回到home是 cd ~)
  • 使用 xargs。这是一个很强大的命令。你可以使用-L来限定有多少个命令,也可以用-P来指定并行的进程数。如果你不知道你的命令会变成什么样,你可以使用xargs echo来看看会是什么样。当然, -I{} 也很好用。示例:
1
2
3
find . -name \*.py | xargs grep some_function
cat hosts | xargs -I{} ssh root@{} hostname
  • pstree -p 可以帮你显示进程树。(读过我的那篇《一个fork的面试题》的人应该都不陌生)
  • 使用 pgrep 和 pkill 来找到或是kill 某个名字的进程。 (-f 选项很有用).
  • 了解可以发给进程的信号。例如:要挂起一个进程,使用 kill -STOP [pid]. 使用 man 7 signal 来查看各种信号,使用kill -l 来查看数字和信号的对应表
  • 使用 nohup 或  disown 如果你要让某个进程运行在后台。
  • 使用netstat -lntp来看看有侦听在网络某端口的进程。当然,也可以使用 lsof。
  • 在bash的脚本中,你可以使用 set -x 来debug输出。使用 set -e 来当有错误发生的时候abort执行。考虑使用 set -o pipefail 来限制错误。还可以使用trap来截获信号(如截获ctrl+c)。
  • 在bash 脚本中,subshells (写在圆括号里的) 是一个很方便的方式来组合一些命令。一个常用的例子是临时地到另一个目录中,例如:
1
2
3
# do something in current dir
(cd /some/other/dir; other-command)
# continue in original dir
  • 在 bash 中,注意那里有很多的变量展开。如:检查一个变量是否存在: ${name:?error message}。如果一个bash的脚本需要一个参数,也许就是这样一个表达式 input_file=${1:?usage: $0 input_file}。一个计算表达式: i=$(( (i + 1) % 5 ))。一个序列: {1..10}。 截断一个字符串: ${var%suffix} 和 ${var#prefix}。 示例: if var=foo.pdf, then echo ${var%.pdf}.txt prints “foo.txt”.
  • 通过 <(some command) 可以把某命令当成一个文件。示例:比较一个本地文件和远程文件 /etc/hosts: diff /etc/hosts <(ssh somehost cat /etc/hosts)
  • 了解什么叫 “here documents” ,就是诸如 cat <<EOF 这样的东西。
  • 在 bash中,使用重定向到标准输出和标准错误。如: some-command >logfile 2>&1。另外,要确认某命令没有把某个打开了的文件句柄重定向给标准输入,最佳实践是加上 “</dev/null”,把/dev/null重定向到标准输入。
  • 使用 man ascii 来查看 ASCII 表。
  • 在远端的 ssh 会话里,使用 screen 或 dtach 来保存你的会话。(参看《28个Unix/Linux的命令行神器》)
  • 要来debug Web,试试curl 和 curl -I 或是 wget 。我觉得debug Web的利器是firebug,curl和wget是用来抓网页的,呵呵。
  • 把 HTML 转成文本: lynx -dump -stdin
  • 如果你要处理XML,使用 xmlstarlet
  • 对于 Amazon S3, s3cmd 是一个很方便的命令(还有点不成熟)
  • 在 ssh中,知道怎么来使用ssh隧道。通过 -L or -D (还有-R) ,翻墙神器。
  • 你还可以对你的ssh 做点优化。比如,.ssh/config 包含着一些配置:避免链接被丢弃,链接新的host时不需要确认,转发认证,以前使用压缩(如果你要使用scp传文件):
1
2
3
4
5
6
TCPKeepAlive=yes
ServerAliveInterval=15
ServerAliveCountMax=6
StrictHostKeyChecking=no
Compression=yes
ForwardAgent=yes
  • 如果你有输了个命令行,但是你改变注意了,但你又不想删除它,因为你要在历史命令中找到它,但你也不想执行它。那么,你可以按下 Alt-# ,于是这个命令关就被加了一个#字符,于是就被注释掉了。

数据处理 

  • 了解 sort 和 uniq 命令 (包括 uniq 的 -u 和 -d 选项).
  • 了解用 cut, paste, 和 join 命令来操作文本文件。很多人忘了在cut前使用join。
  • 如果你知道怎么用sort/uniq来做集合交集、并集、差集能很大地促进你的工作效率。假设有两个文本文件a和b已解被 uniq了,那么,用sort/uniq会是最快的方式,无论这两个文件有多大(sort不会被内存所限,你甚至可以使用-T选项,如果你的/tmp目录很小)
1
2
3
4
5
cat a b | sort | uniq > c   # c is a union b 并集
cat a b | sort | uniq -d > c   # c is a intersect b 交集
cat a b b | sort | uniq -u > c   # c is set difference a - b 差集
  • 了解和字符集相关的命令行工具,包括排序和性能。很多的Linux安装程序都会设置LANG 或是其它和字符集相关的环境变量。这些东西可能会让一些命令(如:sort)的执行性能慢N多倍(注:就算是你用UTF-8编码文本文件,你也可以很安全地使用ASCII来对其排序)。如果你想Disable那个i18n 并使用传统的基于byte的排序方法,那就设置export LC_ALL=C (实际上,你可以把其放在 .bashrc)。如果这设置这个变量,你的sort命令很有可能会是错的。
  • 了解 awk 和 sed,并用他们来做一些简单的数据修改操作。例如:求第三列的数字之和: awk ‘{ x += $3 } END { print x }’。这可能会比Python快3倍,并比Python的代码少三倍。
  • 使用 shuf 来打乱一个文件中的行或是选择文件中一个随机的行。
  • 了解sort命令的选项。了解key是什么(-t和-k)。具体说来,你可以使用-k1,1来对第一列排序,-k1来对全行排序。
  • Stable sort (sort -s) 会很有用。例如:如果你要想对两例排序,先是以第二列,然后再以第一列,那么你可以这样: sort -k1,1 | sort -s -k2,2
  • 我们知道,在bash命令行下,Tab键是用来做目录文件自动完成的事的。但是如果你想输入一个Tab字符(比如:你想在sort -t选项后输入<tab>字符),你可以先按Ctrl-V,然后再按Tab键,就可以输入<tab>字符了。当然,你也可以使用$’\t’。
  • 如果你想查看二进制文件,你可以使用hd命令(在CentOS下是hexdump命令),如果你想编译二进制文件,你可以使用bvi命令(http://bvi.sourceforge.net/ 墙)
  • 另外,对于二进制文件,你可以使用strings(配合grep等)来查看二进制中的文本。
  • 对于文本文件转码,你可以试一下 iconv。或是试试更强的 uconv 命令(这个命令支持更高级的Unicode编码)
  • 如果你要分隔一个大文件,你可以使用split命令(split by size)和csplit命令(split by a pattern)。

系统调试

  • 如果你想知道磁盘、CPU、或网络状态,你可以使用 iostat, netstat, top (或更好的 htop), 还有 dstat 命令。你可以很快地知道你的系统发生了什么事。关于这方面的命令,还有iftop, iotop等(参看《28个Unix/Linux的命令行神器》)
  • 要了解内存的状态,你可以使用free和vmstat命令。具体来说,你需要注意 “cached” 的值,这个值是Linux内核占用的内存。还有free的值。
  • Java 系统监控有一个小的技巧是,你可以使用kill -3 <pid> 发一个SIGQUIT的信号给JVM,可以把堆栈信息(包括垃圾回收的信息)dump到stderr/logs。
  • 使用 mtr 会比使用 traceroute 要更容易定位一个网络问题。
  • 如果你要找到哪个socket或进程在使用网络带宽,你可以使用 iftop 或 nethogs。
  • Apache的一个叫 ab 的工具是一个很有用的,用quick-and-dirty的方式来测试网站服务器的性能负载的工作。如果你需要更为复杂的测试,你可以试试 siege。
  • 如果你要抓网络包的话,试试 wireshark 或 tshark。
  • 了解 strace 和 ltrace。这两个命令可以让你查看进程的系统调用,这有助于你分析进程的hang在哪了,怎么crash和failed的。你还可以用其来做性能profile,使用 -c 选项,你可以使用-p选项来attach上任意一个进程。
  • 了解用ldd命令来检查相关的动态链接库。注意:ldd的安全问题
  • 使用gdb来调试一个正在运行的进程或分析core dump文件。参看我写的《GDB中应该知道的几个调试方法
  • 学会到 /proc 目录中查看信息。这是一个Linux内核运行时记录的整个操作系统的运行统计和信息,比如: /proc/cpuinfo, /proc/xxx/cwd, /proc/xxx/exe, /proc/xxx/fd/, /proc/xxx/smaps.
  • 如果你调试某个东西为什么出错时,sar命令会有用。它可以让你看看 CPU, 内存, 网络, 等的统计信息。
  • 使用 dmesg 来查看一些硬件或驱动程序的信息或问题。

无锁队列的实现

2013年3月17日
关于无锁队列的实现,网上有很多文章,虽然本文可能和那些文章有所重复,但是我还是想以我自己的方式把这些文章中的重要的知识点串起来和大家讲一讲这个技术。下面开始正文。

 

关于CAS等原子操作

在开始说无锁队列之前,我们需要知道一个很重要的技术就是CAS操作——Compare & Set,或是 Compare & Swap,现在几乎所有的CPU指令都支持CAS的原子操作,X86下对应的是 CMPXCHG 汇编指令。有了这个原子操作,我们就可以用其来实现各种无锁(lock free)的数据结构。

这个操作用C语来描述就是下面这个样子:(代码来自Wikipedia的Compare And Swap词条)意思就是说,看一看内存*reg里的值是不是oldval,如果是的话,则对其赋值newval。

1
2
3
4
5
6
7
int compare_and_swap (int* reg, int oldval, int newval)
{
  int old_reg_val = *reg;
  if (old_reg_val == oldval)
     *reg = newval;
  return old_reg_val;
}

这个操作可以变种为返回bool值的形式(返回 bool值的好处在于,可以调用者知道有没有更新成功):

1
2
3
4
5
6
7
8
bool compare_and_swap (int *accum, int *dest, int newval)
{
  if ( *accum == *dest ) {
      *dest = newval;
      return true;
  }
  return false;
}

与CAS相似的还有下面的原子操作:(这些东西大家自己看Wikipedia吧)

注:在实际的C/C++程序中,CAS的各种实现版本如下:

1)GCC的CAS

GCC4.1+版本中支持CAS的原子操作(完整的原子操作可参看 GCC Atomic Builtins

1
2
bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)
type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...)

2)Windows的CAS

在Windows下,你可以使用下面的Windows API来完成CAS:(完整的Windows原子操作可参看MSDN的InterLocked Functions

1
2
3
InterlockedCompareExchange ( __inout LONG volatile *Target,
                                __in LONG Exchange,
                                __in LONG Comperand);

3) C++11中的CAS

C++11中的STL中的atomic类的函数可以让你跨平台。(完整的C++11的原子操作可参看 Atomic Operation Library

1
2
3
4
5
6
template< class T >
bool atomic_compare_exchange_weak( std::atomic* obj,
                                   T* expected, T desired );
template< class T >
bool atomic_compare_exchange_weak( volatile std::atomic* obj,
                                   T* expected, T desired );

无锁队列的链表实现

下面的东西主要来自John D. Valois 1994年10月在拉斯维加斯的并行和分布系统系统国际大会上的一篇论文——《Implementing Lock-Free Queues》。

我们先来看一下进队列用CAS实现的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
EnQueue(x) //进队列
{
    //准备新加入的结点数据
    q = new record();
    q->value = x;
    q->next = NULL;
    do {
        p = tail; //取链表尾指针的快照
    } while( CAS(p->next, NULL, q) != TRUE); //如果没有把结点链上,再试
    CAS(tail, p, q); //置尾结点
}

我们可以看到,程序中的那个 do- while 的 Re-Try-Loop,就是说,很有可能我在准备在队列尾加入结点时,别的线程已经加成功了,于是tail指针就变了,于是我的CAS返回了false,于是程序再试,直到试成功为止。

你会看到,为什么我们的“置尾结点”的操作不判断是否成功,因为:

  1. 如果有一个线程T1,它的while中的CAS如果成功的话,那么其它所有的 随后线程的CAS都会失败,然后就会再循环,
  2. 此时,如果T1 线程还没有更新tail指针,其它的线程继续失败,因为tail->next不是NULL了。
  3. 直到T1线程更新完tail指针,于是其它的线程就可以得到新的tail指针,继续往下走了。

这里有一个潜在的问题——如果T1线程在用CAS更新tail指针的之前,整个线程停掉了,那么其它线程就进入死循环了。下面是改良版的EnQueue()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
EnQueue(x) //进队列改良版
{
    q = new record();
    q->value = x;
    q->next = NULL;
    p = tail;
    oldp = p
    do {
        while (p->next != NULL)
            p = p->next;
    } while( CAS(p.next, NULL, q) != TRUE); //如果没有把结点链上,再试
    CAS(tail, p, q); //置尾结点
}

我们让第个线程,自己fetch p 到链表尾。但是这样的fetch会很影响性能。实现下来,99.9%的情况所以,更好的做法是,你可以接合这两个版本,如果retry的次数超了一个值的话(比如说2次),那么,就自己fetch指针。

好了,我们解决了EnQueue,我们再来看看DeQueue的代码:(很简单,我就不解释了)

1
2
3
4
5
6
7
8
9
10
DeQueue() //出队列
{
    do{
        p = head;
        if (p->next == NULL){
            return ERR_EMPTY_QUEUE;
        }
    while( CAS(head, p, p->next);
    return p->next->value;
}

我们可以看到,DeQueue的代码操作的是 head->next,而不是head本身。这样考虑是因为一个边界条件,我们需要一个dummy的头指针来解决链表中如果只有一个元素,head 和tail都指向同一个结点的问题,这样EnQueue和DeQueue要互相排斥了

注:上图的tail正处于更新之前的装态。

DAS的ABA问题

所谓ABA(见维基百科的ABA词条),问题基本是这个样子:

  1. 进程P1在共享变量中读到值为A
  2. P1被抢占了,进程P2执行
  3. p2把共享变量里的值从A改成了B,再改回到A,此时被P1抢占。
  4. P1回来看到共享变量里的值没有被改变,于是继续执行。

虽然P1以为变量值没有改变,继续执行了,但是这个会引发一些潜在的问题。ABA问题最容易发生在lock free 的算法中的,DAS首当其冲,因为DAS判断的是指针的地址。如果这个地址被重用了呢,问题就很大了。

比如上述的DeQueue()函数,因为我们要让head和tail分开,所以我们引入了一个dummy指针给head,当我们做CAS的之前,如果head的那块内存被回收并被重用了,而重用的内存又被EnQueue()进来了,这会有很大的问题。(重用内存基本上是一种很常见的行为

这个例子你可能没有看懂,维基百科上给了一个活生生的例子——

你拿着一个装满钱的手提箱在飞机场,此时过来了一个火辣性感的美女,然后她很暖昧地挑逗着你,并趁你不注意的时候,把用一个一模一样的手提箱和你那装满钱的箱子调了个包,然后就离开了,你看到你的手提箱还在那,于是就提着手提箱去赶飞机去了。

这就是ABA的问题。

解决ABA的问题

维基百科上给了一个解——使用double-CAS(双保险的CAS),例如,在32位系统上,我们要检查64位的内容

1)一次用CAS检查双倍长度的值,前半部是指针,后半部分是一个计数器。

2)只有这两个都一样,才算通过检查,要吧赋新的值。并把计数器累加1。

这样一来,ABA发生时,虽然值一样,但是计数器就不一样(但是在32位的系统上,这个计数器会溢出回来又从1开始的,这还是会有ABA的问题)

当然,我们这个队列的问题就是不想让那个内存重用,这样明确的业务问题比较好解决,论文《Implementing Lock-Free Queues》给出一这么一个方法——使用内存的引用计数refcnt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SafeRead(q)
{
    loop:
        p = q->next;
        if (p == NULL){
            return p;
        }
        Fetch&Add(p->refcnt, 1);  //1
        if (p == q->next){
            return p;
        }else{
            Release(p);  //2
        }
    goto loop
}

其中的 Fetch&Add和Release分是是加引用计数和减引用计数,都是原子操作,这样就可以阻止内存被回收了。

用数组实现无锁队列

本实现来自论文《Implementing Lock-Free Queues

使用数组来实现队列是很常见的方法,因为没有内存的分部和释放,一切都会变得简单,实现的思路如下:

1)数组队列应该是一个ring buffer形式的数组(环形数组)

2)数组的元素应该有三个可能的值:HEAD,TAIL,EMPTY(当然,还有实际的数据)

3)数组一开始全部初始化成EMPTY,有两个相邻的元素要初始化成HEAD和TAIL,这代表空队列。

4)EnQueue操作。假设数据x要入队列,定位TAIL的位置,使用double-CAS方法把(TAIL, EMPTY) 更新成 (x, TAIL)。需要注意,如果找不到(TAIL, EMPTY),则说明队列满了。

5)DeQueue操作。定位HEAD的位置,把(HEAD, x)更新成(EMPTY, HEAD),并把x返回。同样需要注意,如果x是TAIL,则说明队列为空。

算法的一个关键是——如何定位HEAD或TAIL?

1)我们可以声明两个计数器,一个用来计数EnQueue的次数,一个用来计数DeQueue的次数。

2)这两个计算器使用使用Fetch&ADD来进行原子累加,在EnQueue或DeQueue完成的时候累加就好了。

3)累加后求个模什么的就可以知道TAIL和HEAD的位置了。

如下图所示:

 小结

以上基本上就是所有的无锁队列的技术细节,这些技术都可以用在其它的无锁数据结构上。

1)无锁队列主要是通过CAS、FAA这些原子操作,和Retry-Loop实现。

2)对于Retry-Loop,我个人感觉其实和锁什么什么两样。只是这种“锁”的粒度变小了,主要是“锁”HEAD和TAIL这两个关键资源。而不是整个数据结构。

还有一些和Lock Free的文章你可以去看看: