404频道

学习笔记

本文提供简易shell脚本来更改mac地址,在其他linux发行版中去掉sudo即可。脚本内容如下:

1
2
3
4
#!/bin/sh
sudo ifconfig eth0 down
sudo ifconfig eth0 hw ether 08:00:27:DF:B3:7B
sudo ifconfig eth0 up

[TOC]

VNC(Virtual Network Computing)是一套由AT&T实验室所开发的可操控远程的计算机的软件,其采用了GPL授权条款,任何人都可免费取得该软件。VNC软件主要由两个部分组成:VNC server及VNC viewer。用户需先将VNC server安装在被控端的计算机上后,才能在主控端执行VNC viewer控制被控端。VNC与操作系统无关,因此可跨平台使用,例如可用Windows连接到某Linux的电脑,反之亦同。

该软件在RedHat或CentOS中默认是安装的,但是没有启用,一可以通过which vncserver命令来查看该命令是否安装。本文讲解在Linux下的搭建server,在Windows下搭建client的步骤。

设置vncserver的密码

vncserver需要设置一个密码,该密码并不等同于系统帐号的密码,而是vnc客户端登录的时候输入的密码。执行vncpasswd命令来创建密码。

修改vncserver配置文件

修改文件/etc/sysconfig/vncservers,在该文件末尾添加如下内容:

1
2
VNCSERVERS="1:root"
VNCSERVERARGS[1]="-geometry 1024x768 -alwaysshared -depth 24"

启动vncserver

执行service vncserver start命令来开启vncserver服务。

客户端连接到服务端

这里采用windows下的vnc viewer工具来连接到vncserver。在输入IP的地方输入:IP地址:1来连接到vncserver端,其中:1要跟/etc/sysconfig/vncservers文件中的对应标号一致。这样就可以连接上vncserver,但是连接后界面非常简单,跟命令行界面类似。还需要对vncserver进一步配置。

配置vncserver

vncserver的配置文件在~/.vnc/xstartup文件中,该文件默认创建的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/sh

# Uncomment the following two lines for normal desktop:
# unset SESSION_MANAGER
# exec /etc/X11/xinit/xinitrc

[ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup
[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources
xsetroot -solid grey
vncconfig -iconic &
xterm -geometry 80x24+10+10 -ls -title "$VNCDESKTOP Desktop" &
twm &

将其中的注释打开,即文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
#!/bin/sh

unset SESSION_MANAGER
exec /etc/X11/xinit/xinitrc

[ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup
[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources
xsetroot -solid grey
vncconfig -iconic &
xterm -geometry 80x24+10+10 -ls -title "$VNCDESKTOP Desktop" &
twm &

然后执行service vncserver restart重新启动vncserver服务。客户端再重新连接vncserver既可以看到正常的界面了。

参考文章

CentOS Linux下VNC Server远程桌面配置详解

上次文章《Windows和Linux之间的中文编码问题》中提到的在Windows下的源代码程序放到Linux下出现中文编码问题,解决方法为通过iconv工具转换源代码文件的编码为UTF8格式。最近多学习了些字符编码的知识,发现了解决此问题的另外一种办法。

基础知识

我们在编译程序的时候会涉及到几个编码问题,包括C++源文件的编码、C++程序的内码和运行环境编码,其中C++程序的内码较难理解。

C++程序的内码是指在可执行文件中字符串常量是以什么编码形式存放的,其中字符串常量为窄字符形式。在Windows系统中C++的内码通常为GB18030,在Linux下的gcc/g++使用的内码默认为utf8,可以通过fexec-charset参数来修改。

运行环境编码即为操作系统的编码,通常情况下,简体中文Windows操作系统编码为GB18030,而Linux下默认为UTF8。

gcc命令的参数

gcc有两个参数可以用来解决编码问题。
-finput-charset:用来指定源文件的编码。
-fexec-charset:用来指定生成的可执行文件的编码。

如果这两个参数均未指定,则GCC不会对编码进行转换。
以上这两个参数就可以用来在不修改源文件编码的基础上来达到正确的效果,达到和上篇文章中解决问题同样的效果。

关于Unicode编码

一直对Unicode编码比较糊涂,Unicode只是编码方法规范,而不是具体的存储方法。
常用的Unicode又分为UCS-2和UCS-两种编码,其中UCS-2采用固定的2个字节存储,UCS-4采用固定的4个字节存储。
通常情况下提到的Unicode编码即为UCS-2编码,比如Windows记事本中的保存为Unicode编码,其实就是保存为了UCS-2编码,由于每个字符均为2个字节,所以下次读取的时候仍然可以通过存储格式还原出来。

参考文章

字符编码笔记:ASCII,Unicode和UTF-8
字符编码详解
关于c++的一些编码问题

最近设置一个Linux下的截屏程序的开机自启动,Linux的XWindow系统为gnome。

最先想到的方式是修改/etc/inittab文件,因为Linux在开机自启动的时候会执行该文件,该文件的读取时机是在界面启动之前。我写了一个在main函数中睡眠10分钟的小程序,然后将程序添加到/etc/inittab文件中,结果开不了机了,因为需要睡眠10分钟后才能往下执行程序。幸好我用的虚拟机做的测试,并且在做测试之前备份了虚拟机。

仔细想一下在/etc/rc.d/rc.local文件中启动截屏程序肯定是不合适的,因为如果用户默认是以运行级别非5启动,则程序仍然会被调用,但是没有XWindow,谈何截屏。

接下来考虑将程序放到/etc/rc5.d目录下,这样就可以保证程序在XWindow环境下运行了。仔细一想也不合适,rc5.d仅在系统启动时运行,而Linux系统是多用户系统,允许多个用户同时登陆,多个用户登陆时截屏程序会怎样处理呢?这样显然不合适。

然后想到程序既然为截屏程序,多个用户登陆的时候应该有几个用户就跑几个程序,这样才能保证每个用户的屏幕都能截取到。因此应该放到用户登录后的启动程序列表中。类似于windows系统中的开机启动项。我用的桌面为gnome,找到了gnome-session-properties命令来启动添加程序启动的界面,然后将我的程序添加到界面中即可。

如果桌面系统为KDE,则应该也可以找到相关的设置界面。

题外:利用/etc/inittab和rc5.d目录下添加脚本的方式来启动程序的用户为root,很多程序未了避免root权限带来的安全问题,程序内部采用了su - 用户名的方式切换到一半用户执行代码。程序还可以通过chroot的方式更改根目录的路径达到保护系统的目的。

在开发Linux程序的时候通常会在Windows下编码,然后拿到Linux下编译调试。而两个操作系统之间的默认编码往往有差别。

文件编码问题

在Windows下查看文件编码可以使用记事本打开文件,然后点击“另存为”在右下角即可看到当前文件的编码方式。如果显示为ANSI编码,在简体中文系统下,ANSI 编码代表 GB2312 编码。不同 ANSI 编码之间互不兼容,ANSI是American National Standards Institute的缩写, 记事本默认是以ANSI编码保存文本文档的。

在Linux可以通过vi命令查看文件编码,用vi打开文件,然后输入:set encoding即可显示文件编码。

在VS2008中创建文件的默认编码是根据当前系统的编码格式确定的。VS2008编译器可以同时支持GB2312和UTF-8两种编码。

为了解决在Linux下的乱码问题,Linux下的编码格式为utf8编码,这里采用在Windows下将gb2312编码更改为utf8的方式来解决。iconv是一个可以转换文件编码的工具,编写一个批处理脚本来实现批量转换文件编码的功能。批处理文件的内容如下:

1
2
3
4
5
6
7
@ECHO OFF
FOR /R %%F IN (*.h,*.cpp) DO (
echo %%~nxF
iconv.exe -f GB2312 -t UTF-8 %%F > %%F.utf8
move %%F.utf8 %%F >nul
)
PAUSE

本脚本来自网络,不是我自己写的。
注意:在使用文件编码之前一定要备份文件,防止意外发生,否则后果自负。

文件名编码问题

Windows的中文系统下文件名的编码默认为gbk,在Linux默认编码为UTF-8。如果将Windows下的中文文件名的文件复制到Linux下肯定会出现乱码的问题。可以利用convmv工具来解决编码的问题。

具体执行操作为:在Linux系统下的要转换编码的目录下执行命令:convmv -f GBk -t UTF-8 --notest -r *,这样就会将该文件夹下的所有文件递归的转换编码为UTF-8。

convmv的帮助文档点这里

相关下载

脚本和iconv程序下载链接

无继承情况下的对象构造

在《Unix环境高级编程》的7.6节中提到C程序的内存空间可以分为正文段、初始化数据段、非初始化数据段、栈、堆。其中初始化数据段包含程序中需明确赋初值的变量,如C语言中的全局变量int maxcount = 99;。非初始化数据段又称为bss(block started by symbol)段,在程序开始之前,内核将此段初始化为0或空指针,如出现在函数外面的long sum[1000];,该变量没有明确赋初值,因此放到了bss段中。
而在C++语言中,将所有的全局对象当做初始化过的数据来对待,因此不会将全局变量放到bss段中。

POD数据类型

书中提到Plain ol’ data,查了下应该叫Plain Old Data,简称POD,指C风格的struct结构体定义的数据结构,其中struct结构体中只能定义常规数据类型,不可以含有自定义数据类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct Point
{
float x, y, z;
} Point;

Point global;

Point foobar()
{
Point local;
Point *heap = new Point();
*heap = local;
delete heap;
return local;
}

首先看全局变量global,按照常规的理解,在程序启动的时候编译器会调用Point的合成的默认构造函数来初始化global变量,在程序退出时会调用Point的合成的析构函数来销毁global变量。实际上,C++编译器会将Point看成是一个POD对象,既不会调用合成的构造函数也不会调用合成的析构函数,但C++编译器会将global当成初始化过的数据来对待,不放入BSS段。

foobar函数中的local局部变量不会自动初始化,意味着local.x中的值是不可控的,但是local变量分配了栈空间。

*heap = local;执行时仅简单执行按字节复制操作,不会产生赋值操作符,因为Point是一个POD类型。

return local;同样仅通过字节复制操作产生一个临时对象。

抽象数据类型

这次将上面的Point类型从struct变换为class

1
2
3
4
5
6
7
8
class Point
{
public:
Point(float x=0.0, float y=0.0, float z=0.0) : _x(x), _y(y), _z(z){}

private:
float _x, _y, _z;
};

在上节中的foobar函数中,各个对象的默认复制构造函数、赋值操作符和析构函数仍然不会调用,因为调用是没有意义的,因此编译器干脆就不产生。

为继承做准备

再次更改Point类,引入虚函数。

1
2
3
4
5
6
7
8
class Point
{
public:
Point(float x=0.0, float y=0.0) : _x(x), _y(y) {}
virtual float z();
private:
float _x, _y;
};

引入虚函数后,类对象就需要一个vtbl来存放虚函数的地址,类对象中需要添加vptr指针。而vptr的初始化是在对象构造的时候,因此对象初始化的时候需要调用构造函数,同时默认构造函数和赋值构造函数会自动在构造函数的最前面插入初始化vptr的代码。

继承体系下的对象构造

C++时会自动扩充类的每一个构造函数。扩充步骤如下:

  1. 如果类含有虚基类,则所有虚基类的构造函数被调用,调用顺序为从左到右,从最深到最浅。
  2. 如果类含有基类,则基类构造函数会被调用,以基类的声明顺序为顺序。
  3. 如果类对象中含有vptr,必须在初始化类的成员变量之前为vptr指定初值,使其指向vtbl。
  4. 将成员初始化列表中数据成员的初始化操作放入构造函数内部,并且按照成员在类中的声明顺序。
  5. 如果类成员变量不在构造函数的初始化列表中,但是成员变量含有默认构造函数,则默认构造函数必须被调用。

虚拟继承

本小节将学习一下引入了虚继承机制之后构造函数的生成是什么样子的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Point
{
public:
Point(float x=0.0, float y=0.0) : _x(x), _y(y) {}
virtual float z();
private:
float _x, _y;
};

class Point3d : public virtual Point
{
public:
Point3d(float x=0.0, float y=0.0, float z=0.0)
: Point(x, y), _z(z) {}
~Point3d();

virtual float z() {return _z;}
protected:
float _z;
};

class Vertex : virtual public Point
{
// 不是重点忽略
};

class Vertex3d : public Point3d, public Vertex
{
// 不是重点忽略
};

class PVertex : public Vertex3d
{
// 不是重点忽略
};

类之间的继承关系如下图所示,已经属于最复杂的继承模型了。
Image Title
如果要构造Vertex3d的实例,在内存中必须仅能有一个Point类型的对象,而如果在Point3d和Vertex基类中都构造一个Point实例显然是不合适的。答案是编译器会在Vertex3d的构造函数中生成Point的对象,在Point3d和Vertex的构造函数中均不会生成Point的对象。Vertex3d和Point3d的构造函数伪码如下面所示,Vertex构造函数的伪码和Point3d类似,这里就不再列出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Point3d* Point3d::Point3d(Point3d *this, bool __most_derived, float x, float y, float z)
{
// 如果子类初始化基类则本构造函数不需要初始化基类
if (__most_derived != false)
{
this->Point::Point(x, y);
}
this->__vptr_Point3d = __vtbl_Point3d; // 初始化指向本类的vptr
this->__vptr_Point3d_Point = __vtbl_Point3d_Point; // 初始化指向基类的vptr
this->_z = z;
return *this;
}

Vertex3d* Vertex3d::Vertex3d(Vertex3d *this, bool __most_derived, float x, float y, float z)
{
if (__most_derived != false)
{
this->Point::Point(x, y);
}
this->Point3d::Point3d(false, x, y, z);
this->Vertex::Vertex(false, x, y);
// 初始化vptr
// 用户代码
return this;
}

编译器在类的构造函数中增加了一个bool变量来判断本类是否需要初始化基类,虚基类的初始化始终在继承最底层的类构造函数中初始化。对于PVertex类来说,Point类的构造函数在该类的构造函数中调用。

vptr初始化语意学

vptr的在构造函数中的初始化时机为:在基类构造函数调用操作之后,在成员初始化列表和构造函数中显式代码之前。
构造函数的执行先后顺序为:

  1. 所有虚基类、基类的构造函数会被调用。
  2. 对象的vptr初始化,指向相关的vtbl。
  3. 在构造函数内展开成员的初始化列表。
  4. 执行显式代码。

对象复制语意学

没有一成不坏的硬件,尤其是数据放到物理硬盘中,说不定哪天硬盘闹脾气就崩掉了,硬盘不值钱,可是里面的数据值钱。下面分享下我的数据备份方案,我的原则是数据无论何时都至少留有一个备份。

博客

我的博客是放到Dropbox中的,在云端和本地均有备份,确保了博客数据的绝对安全,即使云端坏掉还有本地,本地丢了还有云端。

个人照片

由于照片都较大,放到本地硬盘很容易占满空间,而且还不经常用。除了在自己电脑上留有照片之外,选择将照片压缩并加密后按照年份放到百度云上。

代码

工作几年了,已经积攒了一些代码,有些代码时不时的会查看到。对于可以公开的自己写的代码我以后打算放到我的Github上,一方面是由于Github上可以在线浏览代码,另一方面可以向别人分享我的代码。
对于私有的代码,暂时放到了金山快盘上,没有找到可以方便浏览代码的云端。

文档

由于文档之类的资料也是经常用到,我选择了金山快盘。

大小端问题跟CPU的架构直接相关,我们常见的80x86系列CPU采用小端字节序模式。Windows平台就采用的80x86系列CPU,因此为小端字节序。
而主机之间进行网络通信时往往采用大端字节序,因此小端字节序机器在发送数据前需要进行字节序转换,在接收到数据处理处理数据之前要将网络字节序转换成本地字节序。

在Linux平台下提供了四个函数用来字节序转换:

1
2
3
4
5
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

Windows平台下也提供了相关的自己序转换函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <WinSock2.h>
unsigned __int64 __inline htond(
double value
);

unsigned __int32 __inline htonf(
float value
);

u_long WSAAPI htonl(
_In_ u_long hostlong
);

unsigned __int64 __inline htonll(
unsigned __int64 value
);

u_short WSAAPI htons(
_In_ u_short hostshort
);


double __inline ntohd(
unsigned __int64 value
);

float __inline ntohf(
unsigned __int32 value
);

u_long WSAAPI ntohl(
_In_ u_long netlong
);

u_long __inline ntohll(
unsigned __int64 value
);

u_short WSAAPI ntohs(
_In_ u_short netshort
);

这里有个技巧需要说明以下,比如要发送如下的结构体:

1
2
3
4
5
struct foo
{
int a;
long b;
};

为了避免每个成员都调用字节序转换函数,可以在结构体的内部定义两个方法用于转换字节序,添加字节序后的foo如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct foo
{
int a;
long b;
void ntoh()
{
a = ntohl(a);
b = ntohl(b);
}
void hton()
{
a = htonl(a);
b = htonl(b);
}
};

需要特别注意的是,在发送结构体类型的数据时要注意字节对齐的问题,这里不再展开讨论,不同的平台有不同的解决办法。大体分为Winodws平台、AIX平台和GNU类平台。

近段时间写了两个通过http协议来获取指定网页的内容并将内容解析出来的程序。程序一可以解析出目前本博客的内容页面的内容、时间、访问次数参数,采用Qt类库实现;程序二可以解析出新浪博客页面的内容、时间等参数,采用Linux下的tcp相关API实现。均采用C++语言实现。

程序一

该程序采用Qt类库实现,其中Http协议的发送和接收采用Qt类库封装的类,网页内容的解析采用Qt封装的解析XML的相关类。
该程序仅能解析标准的Html语言,对于网页中的所有”<>”标签必须有结尾才行。例如本页面源码中的

1
<meta content="black" name="apple-mobile-web-app-status-bar-style" />

必须是闭合的。如果是下面这样则无法正确解析网页内容,这是由于采用的Qt类库决定的。

1
<meta content="black" name="apple-mobile-web-app-status-bar-style">

程序二

该程序的Http协议部分采用Linux的tcp协议api实现,解析网页直接采用搜索字符串的方式实现,较上一种方式要底层,仅能运行在Linux系统下运行。

相关下载

程序一和二的下载链接

本文的安装环境为ubuntu13.04。为了以后便于查阅,本文将相关插件的使用放到了文章的开始部分。这里不作插件的相关介绍,相关介绍看文章底部的参考文章。

插件使用

本插件快捷键会跟随下文安装内容一块同步。

ctags

在源码目录执行ctags -R可生成ctags文件。该文件在源码修改后并不会改变,需要重新生成ctags文件。
ctrl+]:转到函数定义处。
ctrl+T:回到执行ctrl+]的地方。

taglist

:TlistOpen:打开taglist窗口
:TlistClose:关闭taglist窗口。
:TlistToggle:在打开和关闭间切换。

NERD tree

:NERDTree:打开窗口。

winmanager

wm:打开和关闭taglist和NERD tree窗口。

a.vim

:A:在新Buffer中切换到c/h文件
:AS:横向分割窗口并打开c/h文件
:AV:纵向分割窗口并打开c/h文件
:AT:新建一个标签页并打开c/h文件
F12:代替:A命令

MiniBufExplorer

<Tab>:向前循环切换到每个buffer名上
<S-Tab>:向后循环切换到每个buffer名上
<Enter>:在打开光标所在的buffer
d:删除光标所在的buffer

插件安装

安装ctags

执行: sudo apt-get install ctags

安装taglist

  1. 下载页面:http://www.vim.org/scripts/script.php?script_id=273。下载后得到taglist_46.zip文件。
  2. 执行unzip taglist_46.zip解压文件。
  3. 将解压出的文件复制到~/.vim目录下。sudo cp ~/tmp/ ~/.vim/
  4. 在~/.vimrc文件中添加如下:
    1
    2
    3
    let Tlist_Show_One_File = 1            "不同时显示多个文件的tag,只显示当前文件的
    let Tlist_Exit_OnlyWindow = 1 "如果taglist窗口是最后一个窗口,则退出vim
    let Tlist_Use_Right_Window = 1 "在右侧窗口中显示
    参考网址:http://www.cnblogs.com/mo-beifeng/archive/2011/11/22/2259356.html

安装文件浏览器NERD tree

  1. 下载页面:http://www.vim.org/scripts/script.php?script_id=1658。
  2. 将下载后的nerdtree.zip文件解压到~/.vim目录下。

安装winmanager

  1. 下载页面:http://www.vim.org/scripts/script.php?script_id=95
  2. 将下载后的winmanager.zip文件解压到~/.vim目录下
  3. 修改.vimrc文件,添加:
    1
    2
    let g:winManagerWindowLayout='FileExplorer|TagList'
    nmap wm :WMToggle<cr>
    这样利用winmanager工具将taglist和NERD tree工具整合到了一个块,输入wm可以打开和关闭窗口。

安装cscope

  1. 下载页面:http://cscope.sourceforge.net,下载后得到文件cscope-15.8a.tar.gz。
  2. ./configure
  3. make。可能会出现错误,执行如下命令:
    1
    2
    3
    apt-get install libncurses-dev
    sudo apt-get install flex
    sudo apt-get install byacc
    然后执行make clean后重新make。
  4. sudo make install

安装在h/c文件之间切换插件a.vim

  1. 下载页面:http://www.vim.org/scripts/script.php?script_id=31。
  2. 将下载的a.vim文件复制到~/.vim/plugin文件夹下。
  3. 在~/.vimrc文件中添加nnoremap <silent> <F12> :A<CR>
  4. 下面内容为快捷键列表:
    :A switches to the header file corresponding to the current file being edited (or vise versa)
    :AS splits and switches
    :AV vertical splits and switches
    :AT new tab and switches
    :AN cycles through matches
    :IH switches to file under cursor
    :IHS splits and switches
    :IHV vertical splits and switches
    :IHT new tab and switches
    :IHN cycles through matches
    ih switches to file under cursor
    is switches to the alternate file of file under cursor (e.g. on <foo.h> switches to foo.cpp)
    ihn cycles through matches

安装快速浏览和操作Buffer

  1. 下载页面:http://www.vim.org/scripts/script.php?script_id=159
  2. 将下载的 minibufexpl.vim文件丢到 ~/.vim/plugin 文件夹中即可
  3. 在~/.vimrc文件中增加如下行:
    1
    2
    3
    let g:miniBufExplMapCTabSwitchBufs = 1
    let g:miniBufExplMapWindowNavVim = 1
    let g:miniBufExplMapWindowNavArrows = 1
  4. 快捷键:
    向前循环切换到每个buffer名上
    向后循环切换到每个buffer名上
    在打开光标所在的buffer
    d 删除光标所在的buffer

参考文章

0%