• Linux下的FIFO、pipe、unix domain socket漫谈

    在做Linux开发时,经常会接触 管道AF_UNIX 等相关词汇,为了弄清他们之间的关系,查阅了一些资料,将结果整理并记录下来。

  • Linux下使用gdb的调试技巧(2)

    本文重点介绍在gdb模式下的常用命令。

  • 使用命令行修改Linux系统的时区

    系统时区目录 系统支持时区的配置文件存放在 /usr/share/zoneinfo/ 目录下,可以进入到该目录下查看和搜索目标时区的相关配置文件。

    时区配置文件 Linux系统的当前时区配置文件存放在 /etc/localtime , 可以将时区配置文件拷贝到该位置下,也可以建立一个软连接指向系统时区目录中的配置文件。

    操作示例 以将系统的时区改为中国时间为例,以下操作先将旧的时区配置文件删掉,然后建立新的时区配置文件的符号链接。

    rm /etc/localtime
    ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
    

    完成以上操作后,可以执行 date 命令查看修改效果。

  • Linux常用命令行指令 - netstat

    常见的选项有:
    -a (all)显示所有选项,netstat默认不显示LISTEN相关
    -t (tcp)仅显示tcp相关选项
    -u (udp)仅显示udp相关选项
    -n 拒绝显示别名,能显示数字的全部转化成数字。(重要)
    -l 仅列出有在 Listen (监听) 的服务状态
    -p 显示建立相关链接的程序名(macOS中表示协议 -p protocol)
    -r 显示路由信息,路由表
    -e 显示扩展信息,例如uid等
    -s 按各个协议进行统计 (重要)
    -c 每隔一个固定时间,执行该netstat命令。

  • Linux常用命令行指令 - tcpdump

    tcpdump常用于网络抓包,能够将抓取的包保存下来,这样就能进一步深入分析。抓包保存的文件可以使用tcpdump进行查看,也可以借助第三方软件(如wireshark)进行辅助分析。

    1. 抓取指定网络设备上的包

    抓取网卡eth0上的包

    tcpdump -i eth0
    

    2. 限制抓包的数量

    只抓取5个包

    tcpdump -c 5 -i eth0
    

    3. 将抓取的包以ASCII的方式打印出来

    tcpdump -A -i eth0
    

    4. 将抓取的包以十六进制的方式打印出来

    tcpdump -XX -i eth0
    

    5. 对抓取到的包上打出更方便阅读的时间戳

    增加 -t 参数能够将抓取到的包中的时间以方便阅读的形式打印出来,使用 -tttt 参数能够将包中的时间戳以更详细的方式打印出来。

    tcpdump -t -i eth0
    tcpdump -tttt -i eth0
    

    6. 将抓取的包写入到文件中

    tcpdump -w netdump.pcap -i eth0
    

    7. 读取抓到的包

    tcpdump -r netdump.pcap
    

    8. 在抓到的包中显示ip地址

    增加 -n 参数不会解析主机名,增加 -nn 参数不会解析主机名和端口名

    tcpdump -n -i eth0
    tcpdump -nn -i eth0
    

    9. 以绝对值显示包的ISN号

    tcpdump -S -i eth0
    

    10. 对抓取到的包显示更详细的信息

    参数 -v 是verbose的缩写,以下命令表示对抓取到的包显示更详细的信息。

    tcpdump -v -i eth0
    tcpdump -vv -i eth0
    tcpdump -vvv -i eth0
    

    11. 对抓到的包进行协议类型过滤

    支持的协议类型有:fddi, tr, wlan, ip, ip6, arp, rarp, decnet, tcp, udp
    以下命令只会抓取网卡eth0上的arp包

    tcpdump -i eth0 arp
    

    12. 在抓包时对包进行大小过滤

    下面的命令会分别抓取大于1024字节和小于1024字节的包

    tcpdump -i eth0 greater 1024
    tcpdump -i eth0 less 1024
    

    13. 抓取指定端口上的包

    tcpdump -i eth0 port 22
    

    14. 抓取发往指定目的和端口上包

    可以使用 andor 对过滤条件进行组合以实现精确的抓包。

    tcpdump -w net_dump.pcap -i eth0 dst 10.181.140.216 and port 22
    

    常用tcpdump命令示例

    tcpdump –i any 'port 8888'
    tcpdump –i any 'tcp port 8888'
    tcpdump –i any 'tcp src port 8888'
    tcpdump –i any 'tcp src port 8888 and udp dst port 9999'
    tcpdump -i any 'src host 127.0.0.1 and tcp src port 12345' -XX -nn -vv
    
  • Linux常用命令行指令 - nc

    nc 命令是 netcat 的缩写,能够使用TCP或UDP协议读写网络连接中的数据,是一个方便可靠的调试工具。

  • Linux常用命令行指令 - lsof

    lsof是list open files的简称,用于列出系统中所有打开的文件。由于在linux系统下,所有 对象都是文件,所以这个指令十分有用。

    下面就给出了几种常见的用法,需要注意的是有些指令需要root权限执行。

    1. 查看打开指定文件的进程

    lsof /var/syslog.log
    

    2. 查看指定目录下打开的文件

    lsof +D /home/
    

    3. 查看以指定进程名所有打开的文件

    使用 -c 命令能够查看指定进程名使用的文件,允许在一个命令行指令中使用多个 -c 参数。

    lsof -c procname
    

    4. 查看使用指定挂载点的进程

    在停止挂载某个目录时,系统会告诉我们“设备资源繁忙”,所以我们需要找出使用挂载点的程序,终止这些进程,解除目录的占用状态。

    lsof /home
    


    lsof +D /home/
    

    5. 查看指定用户打开的文件

    lsof -u username
    

    另外还可以进行反向过滤,在输出中不显示指定用户的文件,语法如下。

    lsof -u ^username
    

    6. 查看指定进程打开的文件

    lsof -p pid
    

    7. 终止属于某个用户的所有进程

    kill `lsof -t -u username`
    

    相似的,可以使用 -t 参数打印使用某个文件的进程id。

    lsof -t /var/log
    

    8. 让结果同时满足多个选项

    默认情况下,多个参数是以或(or)进行操作的,可以使用 -a 选项让结果同时满足多个参数。

    lsof -a -u username -c procname
    

    9. 让命令重复执行

    可以使用 -r 参数让命令重复执行。以下命令会每隔5s执行一次,每次的执行结果会用 “====” 分割开。

    lsof -u username -c procname -a -r5
    

    10. 列出所有的网络连接

    lsof -i
    

    让结果中的ip地址和端口号显示数字而不是英文别名

    lsof -i -Pn
    

    显示所有的 AF_UNIX socket

    lsof -U
    

    11. 列出指定进程使用的网络连接

    lsof -i -a -p pid
    

    12. 查看指定端口上的监听

    lsof -i :portid
    

    13. 列出所有tcp和udp连接

    lsof -i tcp; lsof -i udp
    

    14. 列出所有的网络文件系统

    网络文件系统(network file system)

    lsof -N -u username
    
  • 重载、覆盖和隐藏的区别

    重载(overload) :就是函数或者方法有相同的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。
    多个重载函数在调用的时候根据函数的参数来区别不同的函数。
    关键点:函数名相同,参数表不同

    覆盖(override) :是指在派生类中重新对基类中的虚函数重新实现,即函数名和参数都一样,只是函数的实现体不一样。
    关键词:派生类中,虚函数,函数名和参数表完全相同

    隐藏(hide) :派生类中的函数把基类中相同名字的函数屏蔽掉了。
    隐藏一词可以这么理解:在调用一个类的成员函数的时候,编译器会沿着类的继承链逐级的向上查找函数的定义,如果找到了那么就停止查找了,所以如果一个派生类和一个基类都有同一个同名(暂且不论参数是否相同)的函数,而编译器最终选择了在派生类中的函数,那么我们就说这个派生类的成员函数"隐藏"了基类的成员函数,也就是说它阻止了编译器继续向上查找函数的定义.

    关于这三种情况的示例代码如下

    #include <iostream.h>
    class Base
    {
    public:
      virtual void f(float x)
      {
        cout << "Base::f(float) " << x << endl;
      }
    
      void g(float x)
      {
        cout << "Base::g(float) " << x << endl;
      }
    
      void h(float x)
      {
        cout << "Base::h(float) " << x << endl;
      }
    };
    
    class Derived : public Base
    {
    public:
      virtual void f(float x)
      {
        cout << "Derived::f(float) " << x << endl;
      }
    
      void g(int x)
      {
        cout << "Derived::g(int) " << x << endl;
      }
    
      void h(float x)
      {
        cout << "Derived::h(float) " << x << endl;
      }
    };
    

    从以上代码能够看出:
    (1)函数Derived::f(float)覆盖了Base::f(float)。
    (2)函数Derived::g(int)隐藏了Base::g(float),而不是重载。
    (3)函数Derived::h(float)隐藏了Base::h(float),而不是覆盖。

    考虑代码在运行时的结果:

    void main(void)
    {
      Derived  d;
      Base *pb = &d;
      Derived *pd = &d;
    
      // Good : behavior depends solely on type of the object
      pb->f(3.14f); // Derived::f(float) 3.14
      pd->f(3.14f); // Derived::f(float) 3.14
    
      // Bad : behavior depends on type of the pointer
      pb->g(3.14f); // Base::g(float) 3.14
      pd->g(3.14f); // Derived::g(int) 3        (surprise!)
    
      // Bad : behavior depends on type of the pointer
      pb->h(3.14f); // Base::h(float) 3.14      (surprise!)
      pd->h(3.14f); // Derived::h(float) 3.14
    }
    

    在第一种调用中,函数的行为取决于指针所指向的对象。在第二第三种调用中,函数的行为取决于指针的类型。所以说,隐藏破坏了面向对象编程中多态这一特性,会使得开发人员产生混乱。

    函数接口设计注意要点
    在设计基类的函数接口时,不要出现既是虚函数,又对该虚函数进行了重载的情况。这样会对派生类的多态函数实现造成不便。
    示例代码如下

    class Base
    {
      virtual foo();
      virtual foo(int n);
    }
    
    class Derived : public Base
    {
      virtual foo();
    }
    

    从以上代码能够看出:

    1. Derived::foo()Base::foo() 构成了多态关系
    2. Derived::foo() 隐藏了 Base::foo(int)

    所以,如果派生类希望实现 foo() 函数的多态,也就必须实现 foo(int) 的多态,否则就会出现隐藏。
    如果在现有代码中已经发生了上述 Base 的情况,在不改变原有代码的情况下,可以使用在派生类中使用这样的语法 using Base::foo; ,这会将基类中所有的名为 foo 的函数引入到派生类中来。

  • 关于Qt中使用中文编码的一些问题

    Qt库作为一个开源库,并且支持多语言。在开发时需要考虑字符编码问题。
    我现在的使用Qt开发环境为VS2012+Qt5.1.1,所以一下所讨论的也是基于这个版本而言的。目前Qt5已经将 tr() 删掉了。

    在windows下使用Qt库进行开发
    有两种主流开发环境:

    1. 使用Qt Creator作为开发环境
    2. 使用Visual Studio + Visual Assist + Qt Visual Studio Addon作为开发环境

    目前我使用的就是第二种开发环境,鉴于在Windows平台下没有哪个开发环境能与Visual Studio相媲美。

    使用VS开发Qt程序时需要注意源文件的编码格式
    Qt5官方推荐的源文件编码格式为UTF-8,QString内部的编码格式就是UTF-8,使用QtCreator创建的源文件的编码格式也是UTF-8 without BOM。
    但是VS在中文操作系统上,如果不进行特殊的设置,默认创建的含有中文的源文件编码格式为gb3212。这样的原始字符串就是gb2312格式的,在使用时需要进行特殊转换,这样显示在界面上才不会乱码。
    推荐使用一下两种方法解决gb2312编码问题:

    • QString::fromLocale8Bit() 会将gb2312编码的字符串转换为UTF-8格式以便存放在QString中。
    • QStringLiteral() 是一个宏定义,会在编译期将字符串实例化,对静态字符串使用这个字符串也是一个提高效率的方法(因为减少了运行时的内存申请开销)。

    在VS中使用utf-8 with BOM的源文件格式
    如果使用了utf-8 with BOM的源文件格式,VS会将其中的字符串转换为gb2312编码的中文,这样是为了兼容旧版本的编译器。
    可以使用编译选项让VS编译器不进行这项转换,这样就可以直接使用字符串 char* 初始化QString了。

    • 微软在VS2010中提供了 #pragma execution_character_set("utf-8") 这样的编译选项,能够防止编译器进行文件编码转换,保证字符串保留utf-8格式。
    • VS2012 并不支持以上编译选项,VS2012将这个特性取消了
    • VS2015 可以使用 /utf-8 让编译器能够识别 utf-8 without BOM格式的文件,并保留utf-8的字符串编码。

    C++11的编码格式支持
    对于支持C++11标准的编译器,可以采用如下的写法产生utf-8格式的字符串。

    const char* szMsg = u8"字符串";
    
  • 为git设置socks5代理

    在某些时候,为了绕过网络封锁,在使用git时需要借助代理完成相关网络操作。

    一个可用的VPN代理是先决条件

    关于VPN的架设就不在此讨论了。

    将repo的链接由ssh改为为https

    已经使用https的repo跳过此步骤。
    本教程需要通过https(http)的方式使用代理,所以如果现有的repo是使用ssh方式clone到本地的,需要修改其为https方式。
    需要修改 repo_name/.git/config 中的url配置,将其中的git格式改为https格式。使用文本编辑工具将其打开进行修改,以github为例,需要将 [email protected]:user_name/repo_name.git 改为 https://github.com/user_name/repo_name.git
    可以使用sed指令完成替换。

    sed -i 's/[email protected]:/git:\/\/github.com\//' ./.git/config
    

    设置git使用代理

    将git配置为使用socks5代理,以socks5的ip和端口是 127.0.0.1:1080 为例。

    git config --global http.proxy 'socks5://127.0.0.1:1080'
    git config --global https.proxy 'socks5://127.0.0.1:1080'
    

    将登录信息缓存下来

    使用https时,在向远程仓库进行推送(git push)时会要求验证用户名和密码,由于每次都输入验证信息是十分繁琐的,考虑将验证信息缓存下来。

    git config --global credential.helper cache
    

    git默认会将密码缓存15分钟,在这期间进行的操作是不需要再次进行密码验证的,可以使用下面命令修改缓存的时长。

    git config --global credential.helper "cache --timeout=3600"
    

    将登陆信息保存下来

    也可以考虑将用户名和密码永久保存下来,这样就不需要再次进行密码验证了。
    注意: 使用这种配置会将明文的账户和密码保存在 ~/.git-cretidentials 文件中,所以仅推荐在个人电脑中使用这个配置。

    git config --global credential.helper store
    

    参考资料

    (全文完)

  • windows平台下的C++开发框架

    在开发windows应用程序时,经常会遇到win32、mfc、atl的函数。下面就对三种API(或者说是开发框架)进行了总结。

    Win32开发框架

    win32开发框架是最基础的函数接口,更接近操作系统底层。
    使用Win32接口的优点是程序的依赖最小,生成的可执行文件的体积也最小。

    MFC开发框架

    MFC是Microsoft Foundation Class的缩写,将win32的相关接口按照OOP的思想封装了起来,目的是提供一个快速开发原生应用的框架。
    使用MFC开发的程序体型略显臃肿,而且MFC也比较陈旧了,并不推荐使用MFC进行开发。

    ATL开发框架

    ATL是Active Template Library的缩写,是针对COM组件的开发实现的一套开发框架。ATL简化了ActiveX COM组件的开发。

  • 使用emacs在目录中递归搜索字符串

    对文件中的字符串搜索是开发中经常遇到的情景,下面就对常见的搜索字符串的方式进行了总结。

    对当前buffer进行搜索(单个文件)
    C-s 向后搜索
    C-r 向后搜索
    M-s o 使用occur对当前buffer中的指定关键字进行统计
    在安装了helm以后可以使用 helm-occur 完成当前buffer的搜索

    对目录中的指定文件进行搜索(一个目录下的多个文件)
    C-x d 进入dired模式,在dired模式中使用 M-x <dired-do-find-regexp> (快捷键 A )对标记的文件进行搜索。

    对目录中的所有文件进行递归搜索(一个目录下的所有文件和所有子目录中的文件)
    M-x <rgrep> 能够对一个目录下所有的文件进行正则表达式匹配

    注意事项
    在windows平台上使用grep和find指令时,需要借助仿linux环境中的程序完成,如cygwin中的程序。
    需要将程序的路径添加到系统环境变量path中,并且为了覆盖windows系统自带的find命令,需要将该路径放在系统find路径之前。
    tips: 对于安装了 git bash 的同学,直接将其中的 mingw64\bin 的绝对路径加入系统path中即可。