find、grep、wc使用方法简单总结

原文链接:find、grep、wc使用方法简单总结
发布时间:2016-06-09 09:54:43

简单总结一下几个常用的 Linux 命令。

一、find

用来查找特定文件,在 Linux 下一切皆是文件,因此十分有用。

基本格式:find path expression [-exec command {} \;],默认会遍历子目录。

1. 查找 /home/work/ 下所有以 .cpp 结尾的文件(目录也会匹配):

1
find /home/work/ -name "*.cpp"

2. 只查找普通文件(-type f 排除目录):

1
find /home/work/ -type f -name "*.cpp"

3. -iname 忽略大小写,-name 精确匹配:

1
find /home/work/ -iname "*.cpp"

4. 同时查找多个目录:

1
find /home/work/ /etc/ /usr -name "*.cpp"

扩展——查找 a.cppb.cpp

1
find /home/work/ /etc/ /usr -name "[ab].cpp"

? 表示任意一个字符:

1
find /home/work/ /etc/ /usr -name "?.cpp"

5. 按大小查找,+20M 大于 20M,-20M 小于 20M:

1
find /home/work/ -size +20M

6. 按访问时间查找,-5 表示 5 天内访问过,+5 表示超过 5 天未访问:

1
find /home/work -atime -5

stat 命令可获得相关时间戳:

  • atime — 最近一次访问时间
  • mtime — 最近一次内容修改时间
  • ctime — 最近一次属性修改时间

以上单位是天;aminmmincmin 单位是分钟。

7. 组合条件——5 天内访问过 小于 1M:

1
find /home/work -atime -5 -a -size -1M
  • -a 与连接
  • -o 或连接
  • -not 条件取反

二、grep

用来匹配特定的文本行,全称 Global Regular Expression Print。

基本格式:grep [OPTIONS] PATTERN [FILE...]

1. 在当前目录所有文件中查找含 hello 的行:

1
grep "hello" ./*

-r 遍历子目录:

1
grep -r "hello" ./*

2. 常用参数:

参数 作用
-i 忽略大小写
-n 显示行号
-c 只显示匹配到的行数
-l 只显示匹配到的文件名
-v 反向匹配,显示未匹配的行

三、wc

文本统计工具,全称 word count,能将文件的行数、字数、字节数打印出来。

1. 统计行数:

1
wc -l test.txt

2. 统计字节数:

1
wc -c test.txt

3. 统计字数(单词由空格、tab、换行符分割):

1
wc -w test.txt

4. 统计字符数(与字节数不同,一个中文字符占 3 字节):

1
wc -m test.txt

四、实例操作

1. 统计 /home/work/ 下所有 .txt 文件个数(排除目录):

1
find /home/work/ -type f -name "*.txt" | wc -l

2. 删除 /home/work/ 下所有 .txt 文件:

1
find /home/work/ -type f -name "*.txt" -exec rm -f {} \;

-exec 后跟执行动作,{} 代表查找到的文件,\; 表示命令结束(注意 {}\; 之间有空格)。

也可以用 xargs

1
find /home/work/ -type f -name "*.txt" | xargs rm -f

3. 统计 /home/work 当前目录下有多少普通文件(不遍历子目录):

1
ls /home/work -l | grep '^-' | wc -l

-l 显示文件属性,开头 - 表示普通文件,'^-' 匹配以 - 开头的行,从而排除目录。

以上仅为简单总结,之后再继续补充。

select 和 epoll 的区别总结

原文链接:select 和 epoll 的区别总结
发布时间:2016-01-19 21:15:21

在Linux中,select 和epoll函数,都是为了监控大量的描述符,是一种I/O多路复用技术。下面总结它们的区别:

select 与 epoll区别

1、打开的最大描述符数量限制

   select 文件描述符使用的是linux ext3,因此打开数量受限制,一般默认为1024

   epoll自己实现了一个虚拟文件系统,因此打开的描述符数就和机器内存有关,这个数值会很大

2、描述符传递方式

  a、 select 每次调用时,都要传入描述符集,都要从用户空间拷贝内核空间,不仅如此,还会把传入的fd_set描述符                    集清空,还必须对重新把每个描述符加入到fd_set中。

         epoll 只需第一次传入,内核会保存描述符集,是保存在内核空间和用户空间共同mmap的一块空间,这样省去                    由用户空间拷贝到内核空间的过程,并且epoll_ctl 可以对描述符增、删、改。

  b、 select 返回的只是就绪描述符的个数,必须遍历描述符集找到就绪的描述符,显然描述符过多,成为效率瓶颈。

         epoll  把就绪描述符保存在传出参数 epoll_event数组中

3、内部轮询机制

   select 每次调用都要遍历所有的描述符来发现描述符是否就绪,因此随着描述符数量增加,效率直线下降

   epoll  只是判断链表rdlist是否为空即可,因为每次添加描述符时,会注册一个回调函数,使该事件与相应的网卡设               备驱动程序建立回调关系,当描述符就绪时,就会调用回调函数,把这个描述符添加到rdlist,rdlist 就是一个               就绪描述符的链表。 

4、事件触发模式

**   **select 只支持水平触发

   epoll   支持两种触发模式ET(边角触发)和LT(水平触发),这其实和电路中高电平触发和边沿触发的模式是一样              的,ET模式是指描述符由不可读或者不可写变为可读或者可写时,epoll才通知有事件发生LT模式则是描述符             只要可读或者可写,epoll就通知有事件发生。

由于笔者的水平有限,出错在所难免,恳请读者拍砖指正,谢谢阅读

谈谈TCP三次握手

原文链接:谈谈TCP三次握手
发布时间:2016-01-09 17:27:34

      TCP协议是一个面向连接的,可靠的传输协议,两台计算机进行网络通信之前,需要进行TCP连接,其中连接发起方发包2次,连接接收方发包1次,这就是著名的TCP三次握手。

一、TCP首部

      上层的数据传到TCP层,会用TCP首部封装数据,TCP首部至少20字节,有关TCP首部的字段组成及其含义,网络上很多说明,本文不详述,请参见有关TCP首部的文章http://blog.chinaunix.net/uid-26413668-id-3408115.html

二、三次握手过程

       第一次握手:主机A发状态码syn=1,ack=0,seq number=x(x是一个随机数)的数据包到主机B,主机B由SYN=1知道,A要求建立联机;

      第二次握手:主机B收到请求后要确认联机信息,向A发送状态码ack number=x+1,syn=1,ack=1,随机产生seq number=y(y也是一个随机数)的包;

      第三次握手:主机A收到后检查ack number是否正确,即是否等于x+1,以及状态码ack是否为1,若正确,主机A会再发送ack number=y+1,ack=1,syn=0,主机B收到后确认seq值与ack=1则连接建立成功。

完成三次握手,建立通信。

过程分析:

     第一次握手syn=1,ack=0,表示这是一个请求连接的包,seq number表示发包数据字节序号,由于下层的IP层可能会对TCP的数据拆包,因此IP层会对每个字节编号,但是这是建立连接期间,根本没有数据,发送只是只有首部的空包,因此seq number 是一个随机数,这个随机数是有用处的,用来作为认证标记。

     第二次握手syn=1,ack=1,表示这是一个接受连接的包,seq number=y(同理y也是一个随机数),ack number=x+1,ack number 表示期望下一次接受到的包的起始序号,尽管没有收到应用数据,但是TCP规定,一次连接请求包要消耗一个seq number,因此ack number=x+1,必须等于x+1。

     第三次握手ack=1,syn=0,ack number=y+1,seq number=x+1。

为什么要三次握手?

      既然TCP是面向连接的可靠协议,那么怎么样才能叫可靠连接呢?我个人理解,即在一次有效连接中,双方都必须确定对方能收到自己的信息,这样连接才真正可靠

      第一次握手,A发包出去,此时A不能确定B有没有收到自己的消息,A发的包中seq number=x,是一个随机数,可以理解为是“认证密码”。第二次握手,A收到syn=1,ack=1,seq number=y,ack number=x+1,A通过检测ack number等于x+1,可知道B收到了自己数据,此时可以A能确定B能接收到自己的数据 ,但是对于B,它并不能确定A能否收到自己的数据,因为B只是接受一次A的包,然后发送给A一次包,它并不知道A是否收到自己的包。因此需要第三次握手,过程与第二次类似,B要确认A是否收到了自己的数据。

      由以上分析可知,双方确定对方能收到自己的消息,必须3次握手,其实严格的说是至少3次握手,但是这三次握手必须发生**在一次有效的连接中,**比如A第一次发包,由于网络延迟没有及时到达B,于是A重发,但是之前的那个包B又接受到了,此时A和B的连接已经不是同一次连接了,这种情况肯定经常发生,但是TCP协议本身就很好地解决了这个问题,那就是seq number  ack
number 认证机制,由于seq这个数是随机数,三次握手都要用到,可保证三次握手是一次有效连接。

Tinyxml解析过程源码分析

原文链接:Tinyxml解析过程源码分析
发布时间:2016-01-09 09:02:26

tinyxml是一个优秀的,易用的,开源的xml解析库,xml解析的最关键之处,就是如何将xml文件内容解析成内存中的可用、易用的程序数据—DOM(Document Object Model)树。DOM其实就是多叉树,每个节点只需知道自己的第一个子节点(first child)和下一个兄弟节点(next sibling),即可实现元素数据的解析。

有关tinyxml内部的结构设计,本文不详述,网上的已有很多分析,请参见 http://www.cnblogs.com/kex1n/archive/2010/09/23/1833468.html 。本文重点分析tinyxml是如何生成DOM树的过程,完成这个任务的函数就是 TiXmlDocument::LoadFile() 函数,下面分析其代码实现。

1.TiXmlDocument::LoadFile()

打开xml文件

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
/**
* @brief TiXmlDocument::LoadFile
* @param _filename xml文件名
* @param encoding 文件编码类型
* @return
*
* 这个方法只是打开xml文件,然后调用另一个LoadFile()方法
*
*/
bool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding )
{
TIXML_STRING filename( _filename );
value = filename;

// reading in binary mode so that tinyxml can normalize the EOL
FILE* file = TiXmlFOpen( value.c_str (), "rb" );

if ( file )
{
bool result = LoadFile( file, encoding );
fclose( file );
return result;
}
else
{
SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN );
return false;
}
}

2.TiXmlDocument::LoadFile() 读取xml到数组,统一换行符为\n

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/**
* @brief TiXmlDocument::LoadFile
* @param file
* @param encoding
* @return
*
* 1.将文件内容读到一个字符数组中
* 2.换行符统一替换成\n,文件换行符在不同的系统实现不同,有\n,\r\n,\r三种形式。
* 3.调用Parse()方法
*/

bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding )
{
if ( !file )
{
SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN );
return false;
}

// Delete the existing data:
Clear();
location.Clear();

// Get the file size, so we can pre-allocate the string. HUGE speed impact.
long length = 0;
fseek( file, 0, SEEK_END );
length = ftell( file );
fseek( file, 0, SEEK_SET );

// Strange case, but good to handle up front.
if ( length <= 0 )
{
SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN );
return false;
}

// Subtle bug here. TinyXml did use fgets. But from the XML spec:
// 2.11 End-of-Line Handling
// ...the XML processor MUST behave as if it normalized all line breaks in external
// parsed entities (including the document entity) on input, before parsing, by translating
// both the two-character sequence #xD #xA and any #xD that is not followed by #xA to
// a single #xA character.
//
// It is not clear fgets does that, and certainly isn't clear it works cross platform.
// Generally, you expect fgets to translate from the convention of the OS to the c/unix
// convention, and not work generally.

/*
while( fgets( buf, sizeof(buf), file ) )
{
data += buf;
}
*/

char* buf = new char[ length+1 ];
buf[0] = 0;

if ( fread( buf, length, 1, file ) != 1 ) {
delete [] buf;
SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN );
return false;
}

// Process the buffer in place to normalize new lines.
const char* p = buf; // the read head
char* q = buf; // the write head
const char CR = 0x0d;
const char LF = 0x0a;

buf[length] = 0;
while( *p ) {
assert( p < (buf+length) );
assert( q <= (buf+length) );
assert( q <= p );

if ( *p == CR ) {
*q++ = LF;
p++;
if ( *p == LF ) { // check for CR+LF (and skip LF)
p++;
}
}
else {
*q++ = *p++;
}
}
assert( q <= (buf+length) );
*q = 0;

Parse( buf, 0, encoding ); //解析xml

delete [] buf;
return !Error();
}

3.TiXmlDocument::Parse() 解析整个xml文档,生成DOM树

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/**
* @brief TiXmlDocument::Parse
* @param p
* @param prevData
* @param encoding
* @return
*
* 完成DOM的建立
*
*/
const char* TiXmlDocument::Parse( const char* p, TiXmlParsingData* prevData, TiXmlEncoding encoding )
{
ClearError();

// Parse away, at the document level. Since a document
// contains nothing but other tags, most of what happens
// here is skipping white space.
if ( !p || !*p )
{
SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN );
return 0;
}

// Note that, for a document, this needs to come
// before the while space skip, so that parsing
// starts from the pointer we are given.
location.Clear();
if ( prevData )
{
location.row = prevData->cursor.row;
location.col = prevData->cursor.col;
}
else
{
location.row = 0;
location.col = 0;
}
TiXmlParsingData data( p, TabSize(), location.row, location.col );
location = data.Cursor();

if ( encoding == TIXML_ENCODING_UNKNOWN )
{
// Check for the Microsoft UTF-8 lead bytes.
const unsigned char* pU = (const unsigned char*)p;
if ( *(pU+0) && *(pU+0) == TIXML_UTF_LEAD_0
&& *(pU+1) && *(pU+1) == TIXML_UTF_LEAD_1
&& *(pU+2) && *(pU+2) == TIXML_UTF_LEAD_2 )
{
encoding = TIXML_ENCODING_UTF8;
useMicrosoftBOM = true;
}
}

/*这个方法的功能是判断的当前的指针p指向的字符
*是不是空白字符(即空格或换行符),如果是,则指针
*前移,找到一个不是空白字符的字符,返回当前的指针位置
*如果不是,还返回这个指针
*
*由此可见这个方法也非常重要,不断地跳过空白字符,不停地解析数据
*/
p = SkipWhiteSpace( p, encoding );
if ( !p )
{
SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN );
return 0;
}

/*
*重点在这里
*解析xml字符串,直到结束'\0'
*
*/
while ( p && *p )
{
/*根据头部判断当前的指针指向哪种节点,然后new一个
*相应的节点,并返回该节点指针,并且设置该节点的父节点为this
*TiXmlNode是一个基类
*是对xml的元素、注释、文本、文档声明的抽象
*/
TiXmlNode* node = Identify( p, encoding );
if ( node )
{
/*下面是多态执行的,不同的节点类型,实现是不同的
*假设node是一个元素节点,那么这个元素就会有属性
*就会有子元素等信息,所以要继续解析,因此这个node也有子节点,
*直到这个节点,这就是多叉树形成的原因
*直到这个节点内容结束,返回当前位置指针。
*/
p = node->Parse( p, &data, encoding );

/*
*将这个节点,连接到父节点树上
*/
LinkEndChild( node );
}
else
{
break;
}

// Did we get encoding info?
if ( encoding == TIXML_ENCODING_UNKNOWN
&& node->ToDeclaration() )
{
TiXmlDeclaration* dec = node->ToDeclaration();
const char* enc = dec->Encoding();
assert( enc );

if ( *enc == 0 )
encoding = TIXML_ENCODING_UTF8;
else if ( StringEqual( enc, "UTF-8", true, TIXML_ENCODING_UNKNOWN ) )
encoding = TIXML_ENCODING_UTF8;
else if ( StringEqual( enc, "UTF8", true, TIXML_ENCODING_UNKNOWN ) )
encoding = TIXML_ENCODING_UTF8; // incorrect, but be nice
else
encoding = TIXML_ENCODING_LEGACY;
}

p = SkipWhiteSpace( p, encoding );
}

// Was this empty?
if ( !firstChild ) {
SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, encoding );
return 0;
}

// All is well.
return p;
}

4.TiXmlElement::Parse() 解析元素,生成元素多叉树

还有注释类、文档声明类实现的 Parse() 方法,在此省略不述。

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/**
* @brief TiXmlElement::Parse
* @param p
* @param data
* @param encoding
* @return
*
*是基类TiXmlNode::Parse()的一种实现,用来解析元素类型的多叉树
*
*/
const char* TiXmlElement::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )
{
p = SkipWhiteSpace( p, encoding );
TiXmlDocument* document = GetDocument();

if ( !p || !*p )
{
if ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, 0, 0, encoding );
return 0;
}

if ( data )
{
data->Stamp( p, encoding );
location = data->Cursor();
}

if ( *p != '<' )
{
if ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, p, data, encoding );
return 0;
}

p = SkipWhiteSpace( p+1, encoding );

// Read the name.
const char* pErr = p;

//获取元素名 (value是类成员)
p = ReadName( p, &value, encoding );
if ( !p || !*p )
{
if ( document ) document->SetError( TIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME, pErr, data, encoding );
return 0;
}

TIXML_STRING endTag ("</");
//获取这个元素的结束标记
endTag += value;

// Check for and read attributes. Also look for an empty
// tag or an end tag.
while ( p && *p )
{
pErr = p;
p = SkipWhiteSpace( p, encoding );
if ( !p || !*p )
{
if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, pErr, data, encoding );
return 0;
}
if ( *p == '/' )
{
++p;
// Empty tag.
if ( *p != '>' )
{
if ( document ) document->SetError( TIXML_ERROR_PARSING_EMPTY, p, data, encoding );
return 0;
}
return (p+1);
}

// 读取元素的值
else if ( *p == '>' )
{
// Done with attributes (if there were any.)
// Read the value -- which can include other
// elements -- read the end tag, and return.
++p;
//有可能这个元素没有值,接着又是子元素,如<Person><Boy>Jim</Boy></Person>
p = ReadValue( p, data, encoding ); // Note this is an Element method, and will set the error if one happens.
if ( !p || !*p ) {
// We were looking for the end tag, but found nothing.
// Fix for [ 1663758 ] Failure to report error on bad XML
if ( document ) document->SetError( TIXML_ERROR_READING_END_TAG, p, data, encoding );
return 0;
}

// We should find the end tag now
// note that:
// </foo > and
// </foo>
// are both valid end tags.
if ( StringEqual( p, endTag.c_str(), false, encoding ) )
{
p += endTag.length();
p = SkipWhiteSpace( p, encoding );
if ( p && *p && *p == '>' ) {
++p;
return p;
}
if ( document ) document->SetError( TIXML_ERROR_READING_END_TAG, p, data, encoding );
return 0;
}
else
{
if ( document ) document->SetError( TIXML_ERROR_READING_END_TAG, p, data, encoding );
return 0;
}
}

//读取元素的属性
else
{
// Try to read an attribute:
TiXmlAttribute* attrib = new TiXmlAttribute();
if ( !attrib )
{
return 0;
}

attrib->SetDocument( document );
pErr = p;
p = attrib->Parse( p, data, encoding );

if ( !p || !*p )
{
if ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, pErr, data, encoding );
delete attrib;
return 0;
}

// Handle the strange case of double attributes:
#ifdef TIXML_USE_STL
TiXmlAttribute* node = attributeSet.Find( attrib->NameTStr() );
#else
TiXmlAttribute* node = attributeSet.Find( attrib->Name() );
#endif
if ( node )
{
if ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, pErr, data, encoding );
delete attrib;
return 0;
}

attributeSet.Add( attrib );
}
}
return p;
}

5.TiXmlElement::ReadValue() 读取元素的值,解析子元素

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/**
* @brief TiXmlElement::ReadValue
* @param p
* @param data
* @param encoding
* @return
* 读取元素的值和解析子元素
*
*/

const char* TiXmlElement::ReadValue( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding )
{
TiXmlDocument* document = GetDocument();

// Read in text and elements in any order.
const char* pWithWhiteSpace = p;
p = SkipWhiteSpace( p, encoding );

while ( p && *p )
{
if ( *p != '<' )
{
// Take what we have, make a text element.
TiXmlText* textNode = new TiXmlText( "" );

if ( !textNode )
{
return 0;
}

if ( TiXmlBase::IsWhiteSpaceCondensed() )
{
p = textNode->Parse( p, data, encoding );
}
else
{
// Special case: we want to keep the white space
// so that leading spaces aren't removed.
p = textNode->Parse( pWithWhiteSpace, data, encoding );
}

if ( !textNode->Blank() )
LinkEndChild( textNode );
else
delete textNode;
}

//一个子元素标签的开始,解析子元素
else
{
// We hit a '<'
// Have we hit a new element or an end tag? This could also be
// a TiXmlText in the "CDATA" style.
if ( StringEqual( p, "</", false, encoding ) )
{
return p;
}
else
{
TiXmlNode* node = Identify( p, encoding );
if ( node )
{
p = node->Parse( p, data, encoding );
LinkEndChild( node );
}
else
{
return 0;
}
}
}
pWithWhiteSpace = p;
p = SkipWhiteSpace( p, encoding );
}

if ( !p )
{
if ( document ) document->SetError( TIXML_ERROR_READING_ELEMENT_VALUE, 0, 0, encoding );
}
return p;
}

Linux下程序开机自启动

原文链接:Linux下程序开机自启动
发布时间:2015-12-12 21:23:51

在windows下使一个应用程序开机自启动,只需要把它加入开机启动项即可,那么在Linux下如何设置呢?

下面介绍两种方法可以使Linux下的应用程序开机自启动。

第一种方法:在启动脚本/etc/rc.local添加启动命令。

下面编译生成一个小程序:StartMain

1
2
3
4
5
6
7
8
9
10
11
12
13
/*************************************************************************
> File Name: StartMain.c
> Author: KentZhang
> Mail: zhchk2010@qq.com
> Created Time: 2015年12月12日 星期六 20时57分42秒
************************************************************************/

#include<stdio.h>
int main(){
printf("StartMain start!\n");
while(1);
return 0;
}

上面实例代码,仅仅3行非常简单,注意这个程序不会退出。

编译:gcc StartMain.c -o StartMain

可执行文件StarfMain的绝对路径是 /home/kent/StartMain

然后将执行命令添加到rc.local文件中,注意修改这个文件要有root权限,修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
/home/kent/StartMain>/dev/null 2>&1 &

exit 0

就这样,如此简单,下次系统重启,就会自启动这个小程序StartMain。

显然仅仅添加了一句代码:

1
/home/kent/StartMain>/dev/null 2>&1 &

系统启动后会执行它,这个程序就会启动。

/home/kent/StartMain 表示执行这个可执行程序。

/dev/null 表示将程序的标准输出重定向到/dev/null,/dev/null是一个空设备,将丢弃一切写入的数据。

2>&1 表示将标准出错重定向到标准输出,2和1,其实都是文件描述符,但是1前面有&,如果不加&,会认为1是一个文件名。

& 表示这个程序在后台执行,这个&符号,一定要有!!!!本例中的程序不会退出,不在后台执行,那么这句shell 命令会一直等待返回,但是程序不会结束,所以不会返回,那么系统将会卡死在这里,不能正常启动,这一点要切记!!!

第二种方法:将程序注册成服务。

在这里首先介绍一下Linux系统运行级别的概念,在windows系统启动时,可以以安全模式启动,显然与正常启动的不同,可以类似看做不同的运行级别。而Linux系统有7种不同的运行级别,即0~6,7个运行级别,可以用命令runlevel查看当前系统的运行级别。

但是不同的Linux的系统运行级别定义不一样,这里主要是Debian和Redhat系列的不同,Ubuntu是基于的Debian,CentOS是基于Redhat。

debian的runlevel级别定义如下:

0:Halt,关机模式

1:Single,单用户模式

2:Full multi-user with display manager (GUI)

3:Full multi-user with display manager (GUI)

4:Full multi-user with display manager (GUI)

5:Full multi-user with display manager (GUI)

6:Reboot,重启

Ubuntu的默认开机的runlevel是2,但是可以发现runlevel 2~5都是一样的,即多用户图形模式运行,这一点和Redhat系列是有区别的。

redhat的runlevel级别定义如下:

0:系统停机状态,系统默认运行级别不能设置为0,否则不能正常启动,机器关闭。

1:单用户工作状态,root权限,用于系统维护,禁止远程登陆,就像Windows下的安全模式登录。

2:多用户状态,没有NFS支持。

3:完整的多用户模式,有NFS,登陆后进入控制台命令行模式。

4:系统未使用,保留一般不用,在一些特殊情况下可以用它来做一些事情。例如在笔记本电脑的电池用尽时,可以 切换到这个模式来做一些设置。

5:X11控制台,登陆后进入图形GUI模式,XWindow系统。

6:系统正常关闭并重启,默认运行级别不能设为6,否则不能正常启动。运行init6机器就会重启。

Redhat系列的runlevel 2~5划分的更为详细,我想强调的是Redhat系列的启动级别一般都是 runlevel 3 或者runlevel 5,分别表示命令行模式启动运行和图形界面模式启动运行。

下面正式介绍如何将一个程序注册服务并使之开机启动,本例是以Ubuntu 12.04为测试机:

第一步:编写服务脚本。

代码如下,是一个shell脚本StartMain,该脚本的文件名要与程序名保持一致。

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
43
44
45
46
47
48
49
50
51
52
53
#########################################################################
# File Name: StartMain
# Author: KentZhang
# mail: zhchk2010@qq.com
# Created Time: 2015年12月13日 星期日 08时32分11秒
#########################################################################
#!/bin/bash

ProgramPath=/home/kent/StartMain
ARG=$1

FunStart(){ #启动程序的函数

if [ -e $ProgramPath ];then #判断可执行文件是否存在
pid=$(pidof ${ProgramPath}) #判断这个程序是否已经启动
expr $pid + 0 >/dev/null
if [ $? -eq 0 ] && [ $pid -gt 0 ];then
echo "StartMain process already exists."
exit 0
fi
nohup $ProgramPath > /dev/null 2>&1 & #启动程序
else
echo $CollectorManger is not exists.
fi

}

FunStop(){ #停止程序的函数
pid=$(pidof ${ProgramPath}) #获取这个程序的PID
expr $pid + 0 >/dev/null #判断获得的PID是否是整数,是否大于0
if [ $? -eq 0 ] && [ $pid -gt 0 ];then
kill -9 $pid >/dev/null #杀死该程序
fi

}

#根据传进脚本的参数分别执行对应的分支
case $ARG in
start): #启动
FunStart

;;

stop): #停止
FunStop

;;

restart): #重启
FunStop
FunStart

esac

第二步:注册服务

将上面编写的脚本复制到/etc/init.d目录下,注意需要root权限。

此时,程序StartMain程序已经注册成服务,在任意工作目录下,执行shell:

1
2
3
4
5
service StartMain start

servive StartMain stop

service StartMain restart

可以分别完成StartMain程序的启动、停止、重启。注意,权限不够,要加sudo。

第三步:将服务加入启动项

这一步,就是创建几个软链接,分别执行:

1
2
3
4
5
6
7
sudo ln -s  /etc/init.d/StartMain  /etc/rc0.d/K100StartMain

sudo ln -s /etc/init.d/StartMain /etc/rc2.d/S100StartMain

sudo ln -s /etc/init.d/StartMain /etc/rc3.d/S100StartMain

sudo ln -s /etc/init.d/StartMain /etc/rc5.d/S100StartMain

这样就完成了启动项的添加,程序开机会启动程序,关机会停止程序。

对于Ubuntu只需要做前面2个链接即可,后面2个链接是为了兼容Redhat系列。

系统运行在0,2,3,5级别就回去执行对应目录下的脚本即/etc/rc0.d,/etc/rc2.d/,/ect/rc3.d,/etc/rc5.d目录下脚本。

软链接的命名很有意思,都是 K或者S+数字+服务脚本名,K表示kill,即stop,表示执行/etc/init.d/StartMain时,传入参数stop,S表示start,表示传入参数start,中间的数字表示执行顺序,因为在rcn.d目录下,有许多这样的链接,因此中间的数字决定了脚本的执行顺序,数字越小越先执行,有时候程序之间有依赖性,这个顺序也是非常重要。

以上就是设置Linux程序开机自启动的2种方法,如有错误,欢迎指正。

Linux下POSIX正则表达式API使用

原文链接:Linux下POSIX正则表达式API使用
发布时间:2015-12-12 11:01:33

一、概述

在Linux环境中,经常使用正则表达式,如grep、sed、find等等,目前正则表达式有2中不同的标准,分别是Perl标准和POSIX标准,这2种风格,大体相同,稍有差别。在 C/C++的标准库均不支持表达式,不过在C++11标准中,貌似引入了boost的正则库,在Linux环境中也自带了一组API支持正则,即POSIX标准的C接口。

常用的一组API如下:

1
2
3
4
5
6
7
8
int regcomp (regex_t *compiled, const char *pattern, int cflags);

int regexec (regex_t *compiled, char *string, size_t nmatch,
regmatch_t matchptr [], int eflags);

void regfree (regex_t *compiled);

size_t regerror (int errcode, regex_t *compiled, char *buffer, size_t length);

二、实例解析

代码RegexDemo.c

代码的注释,已经很清楚,无需多言,如有错误,欢迎指正。

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
43
44
45
46
47
48
49
50
51
52
/*************************************************************************
> File Name: RegexDemo.c
> Author: KentZhang
> Mail: zhchk2010@qq.com
> Created Time: 2015年12月12日 星期六 09时22分26秒
************************************************************************/

#include<stdio.h>
#include<sys/types.h>
#include<regex.h>
#include<string.h>
#include<stdlib.h>
#define BUFSIZE 256
int main(){
/************************************************************************************************
1、编译正则表达式regcomp
2、匹配正则表达式regexec
3、释放正则表达式regfree
************************************************************************************************/

char bufError[BUFSIZE] = {0};
const char* strRule = "c[a-z]t"; //正则表达式
const char* strSrc = "123citabcat+-cot"; //源字符串
regex_t reg; //用来存放编译后的正则表达式
int nResult = regcomp(&reg, strRule, 0); //编译正则表达式
if (0 != nResult){ //如果出错,获取出错信息
regerror(nResult, &reg, bufError, sizeof(bufError));
printf("regcomp() failed:%s\n", bufError);
}

regmatch_t pm[1]; //这个结构体数组用来存放匹配的结果信息,本例是循环获取所有字串,数组长度为1即可
const size_t nMatch = 1; //表示上面数组的长度
char bufMatch[BUFSIZE];
/**************************************************************************************************
1、regmatch_t 这个结构体非常重要,包含2个成员rm_so,rm_eo,即匹配到的子串的首,尾在源字符串的偏移位置
显然根据源字符串首指针和这2个成员,可以获取字串的内容
2、下面的regexec函数的第二个参数即源字符串的首指针,当然必须是UTF-8字符串,若要循环匹配,源字符串的指针
必须不断后移,因为前面的已经匹配过
**************************************************************************************************/
while(!regexec(&reg, strSrc, nMatch, pm, 0)){ //循环匹配出所有子串
bzero(bufMatch,sizeof(bufMatch));
strncpy(bufMatch, strSrc+pm[0].rm_so, pm[0].rm_eo-pm[0].rm_so); //取出匹配的结果,并打印
printf("Match result is:%s, rm_so=%d, rm_eo=%d\n", bufMatch, pm[0].rm_so, pm[0].rm_eo);
strSrc += pm[0].rm_eo; //将指针后移,接着匹配
if ('\0' == *strSrc)
break;
}

regfree(&reg);
return 0;

}

编译执行后的结果:

1
2
3
4
5
6
7
kent@ubuntu:~/workspace$ ./a.out

Match result is:cit, rm_so=3, rm_eo=6

Match result is:cat, rm_so=2, rm_eo=5

Match result is:cot, rm_so=2, rm_eo=5

再谈单例模式

原文链接:再谈单例模式
发布时间:2015-11-17 17:04:35

之前写过一篇博客 《C++单例模式的模板基类》 http://blog.kentonly.com/2015/09/04/2015-09-04-C++单例模式的模板基类/

但是,最近才发现实际 上 static T* Instance() 的实现是有一点bug 的,下面分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static T *Instance()
{
if(NULL != m_Instance)
{
return m_Instance;
}
m_Mutex.Lock();
if(NULL != m_Instance)
{
m_Mutex.UnLock();
return m_Instance;
}
m_Instance = new(std::nothrow) T();
m_Mutex.UnLock();
return m_Instance;
}

这种实现在多线程环境下,可能会有bug ,原因就在 m_Instance = new (std::nothrow) T() 这行代码。

这句代码实际上分三步执行:

1、分配内存

2、调用构造函数,初始化内存空间

3、将内存地址赋值给单例指针

代码可转化为如下形式:

1
2
3
4
5
T*  p  =  newMemory(sizeof(T))

p->BaseSingleton::BaseSingleton()

m_Instance = p

编译器出于优化的目的,可能会将第一步和第三步合并,变成如下:

1
2
3
m_Instance  =  newMemory(sizeof(T))

m_Instance->BaseSingleton::BaseSingleton()

那么在执完第一行代码时,有可能会切换到其他线程,其他线程如果调用 Instance()函数,由于m_Insatnce已经赋值,将返回这个指针,但是

指针指向的内存并未初始化,所以会造成不可预知的错误。

在Boost库中,有一种很好的单例实现模式,我做了一点修改,就是引用改成指针。代码如下:

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
43
44
45
46
47
48
49
50
#ifndef BASESINGLETON_H
#define BASESINGLETON_H

/**
单例模式采用boost源码
由于boost单例返回的是引用,下面改成指针
*/
template<class T>
class BaseSingleton
{
public:
BaseSingleton(){}
private:
struct object_creator
{
// This constructor does nothing more than ensure that instance()
// is called before main() begins, thus creating the static
// T object before multithreading race issues can come up.
object_creator() { BaseSingleton<T>::Instance(); }
inline void do_nothing() const { }
};
static object_creator create_object;

//BaseSingleton();

public:
typedef T object_type;

// If, at any point (in user code), BaseSingleton<T>::instance()
// is called, then the following function is instantiated.
static object_type* Instance()
{
// This is the object that we return a reference to.
// It is guaranteed to be created before main() begins because of
// the next line.
static object_type obj;

// The following line does nothing else than force the instantiation
// of BaseSingleton<T>::create_object, whose constructor is
// called before main() begins.
create_object.do_nothing();

return &obj;
}
};
template <class T>
typename BaseSingleton<T>::object_creator
BaseSingleton<T>::create_object;

#endif // BASESINGLETON_H

我之前实现的单例模式中,必须是第一次手动调用Instance()才生成单例对象,这样就引入了多线程的问题。

而Boost实现方式,在单例类中定义一个内嵌的静态的结构体,这个结构体生成时调用自己的构造函数,构造函数中执行Instance()函数,由于单例类内的静态的结构体生成时,是在main()执行之前,所以巧妙地绕开了多线程的问题。

C++单例模式的模板基类

原文链接:C++单例模式的模板基类
发布时间:2015-09-04 09:35:52

单例模式是很常用的设计模式,如果希望系统中某个类的对象只能有一个或者有一个就够了,那么便可以采用单例模式来解决。

下面用C++实现一个单例模板类,那么其他的类只需继承它,便可以成为单例类。

本例中使用了 CMutex类,是考虑多线程的情况,这个类的定义请参见笔者的另一篇博客《C++简单封装互斥量》,

链接 http://blog.kentonly.com/2015/09/02/2015-09-02-C++简单封装互斥量/

代码如下:

BaseSingleton.h

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
43
44
45
46
47
48
/*************************************************************************
> File Name: BaseSingleton.h
> Author: KentZhang
> Mail: zhchk2010@qq.com
> Created Time: Wed 04 Sep 2015 09:02:12 PM CST
************************************************************************/

#ifndef __BASE_SINGLETON_H__
#define __BASE_SINGLETON_H__
#include<new>
#include "CMutex.h"

template<typename T>
class BaseSingleton
{
public:
static T *Instance()
{
if(NULL != m_Instance)
{
return m_Instance;
}

m_Mutex.Lock();
if(NULL != m_Instance)
{
m_Mutex.UnLock();
return m_Instance;
}

m_Instance = new(std::nothrow) T();
m_Mutex.UnLock();
return m_Instance;
}

BaseSingleton(){}
virtual ~BaseSingleton(){}
private:
static T *m_Instance;
static CMutex m_Mutex;
};

template<typename T>
T *BaseSingleton<T>::m_Instance = NULL;
template<typename T>
CMutex BaseSingleton<T>::m_Mutex;

#endif

测试用例:

TestSingleton.cpp

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
/*************************************************************************
> File Name: TestSingleton.cpp
> Author: KentZhang
> Mail: zhchk2010@qq.com
> Created Time: Wed 04 Sep 2015 09:21:10 PM CST
************************************************************************/

#include "BaseSingleton.h"
#include <string>
#include <iostream>
class TestSingleton : public BaseSingleton<TestSingleton>
{
private:
TestSingleton(){}; //构造函数,拷贝构造函数,声明为私有,不允许在类外创建对象,保证该类只有一个对象
TestSingleton(const TestSingleton&){};
public:
~TestSingleton(){};
void Show(){std::cout<<"I am a singleton."<<std::endl;};
friend BaseSingleton<TestSingleton>; //由于构造函数为私有,而基类必须调用,所以这里声明基类为友元类
};

int main()
{
//TestSingleton ts; //这里编译报错,显然是不允许在类外创建对象

TestSingleton* p1 = TestSingleton::Instance();
p1->Show();
std::cout<<"p1="<<p1<<std::endl;
//TestSingleton ts(*p1); //这种方式,同样报错

TestSingleton* p2 = TestSingleton::Instance();
p2->Show();
std::cout<<"p2="<<p2<<std::endl;

TestSingleton* p3 = TestSingleton::Instance();
p3->Show();
std::cout<<"p3="<<p3<<std::endl;

return 0;
}

编译:g++ CMutex.cpp TestSingleton.cpp -lpthread -o main

执行:./main

1
2
3
4
5
6
7
8
9
10
11
I am a singleton.

p1=0x82ad028

I am a singleton.

p2=0x82ad028

I am a singleton.

p3=0x82ad028

在本例中,有个明显的缺陷,即那个静态的单例指针才会何时delete,当然delete会自动调用它的析构函数完成终止化操作。

如果手动判断delete,但是在大多时候,并不能准确的判断单例何时不再使用,另一方面的单例类在内存中只有一个对象,

并不会造成内存泄露,因此大多数时候单例对象的析构是在程序结束的时候完成的。

上面的单例对象是在堆上分配的,需要手动delete,下面采用在静态区分配对象,单例基类修改如下:

BaseSingleton.h

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
43
#ifndef __BASE_SINGLETON_H__
#define __BASE_SINGLETON_H__
#include<new>
#include "CMutex.h"

template<typename T>
class BaseSingleton
{
public:
static T *Instance()
{
if(NULL != m_Instance)
{
return m_Instance;
}

m_Mutex.Lock();
if(NULL != m_Instance)
{
m_Mutex.UnLock();
return m_Instance;
}

//m_Instance = new(std::nothrow) T();
static T tInstance; //这里采用静态局部变量,保证程序运行时一直存在,程序结束时,会自动析构这个静态变量
m_Instance = &tInstance;
m_Mutex.UnLock();
return m_Instance;
}

BaseSingleton(){}
virtual ~BaseSingleton(){}

static T *m_Instance;
static CMutex m_Mutex;
};

template<typename T>
T *BaseSingleton<T>::m_Instance = NULL;
template<typename T>
CMutex BaseSingleton<T>::m_Mutex;

#endif

由于笔者的水平有限,出错在所难免,恳请读者拍砖指正,谢谢阅读

C++封装Linux消息队列

原文链接:C++封装Linux消息队列
发布时间:2015-09-03 21:41:36

                消息队列是Linux进程间通信方式之一,在面向对象编程中,需要对其封装。 

一、消息队列的特点

1、异步通信,消息队列会保存进程发送的消息,其他进程不一定要及时取走消息。

2、可以发送不同类型的消息,消息的头部用long类型的字段标记。

3、取消息时,不一定按先进先出的方式,可以按消息的类型来取。

4、消息队列内部是用一个链表来保存消息,当消息被取走后,这个消息就从链表中删除了,而且这个链表的长度

       是有限制的,当消息存满后,不能再存入消息。

二、msgid和关键字

消息队列在内核中,要用一个非负整数来标记(类似于描述符或者句柄),这个非负整数称作msqid,即消息

队列的ID,但是要创建或者打开一个消息队列需要一个关键字,这个关键字其实是一个长整型数据。在进程

通信时,必须要约定使用同一个关键字,这样就可以得到同一个消息队列,因为消息队列是由内核维护的,

不同的进程使用相同关键字打开或者创建的消息队列,获得的msgid是相同的。

本例中使用一个文件路径加上课题ID(0~255)调用ftok函数产生一个关键字。

C++简单封装互斥量

原文链接:C++简单封装互斥量
发布时间:2015-09-02 17:13:12

在编写多线程程序中,经常需要遇到多线程同步操作,例如操作sqlite3数据库,写一个日志文件,操作一个链表等,只能同时一个线程操作,因此需要经常用到互斥锁。

笔者为了方便使用,采用C++简单封装Linux互斥锁,根据构造函数的参数,可以创建递归互斥锁和非递归互斥锁。

代码如下:

CMutex.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*************************************************************************
> File Name: CMutex.h
> Author: KentZhang
> Mail: zhchk2010@qq.com
> Created Time: Wed 02 Sep 2015 01:49:15 PM CST
************************************************************************/

#ifndef __CMUTEX_H__
#define __CMUTEX_H__
#include <pthread.h>
#include <sys/types.h>
class CMutex
{
public:
CMutex(bool IsRecursive=false);//默认创建非递归互斥锁
~CMutex();
bool Lock();
bool UnLock();
bool TryLock();
private:
pthread_mutex_t *m_pMutex;
};

#endif

CMutex.cpp

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/*************************************************************************
> File Name: CMutex.cpp
> Author: KentZhang
> Mail: zhchk2010@qq.com
> Created Time: Wed 02 Sep 2015 01:46:36 PM CST
************************************************************************/

//封装互斥锁
#include "CMutex.h"
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define DEBUG_ERROR(format,...) do{ printf(""format", FileName:%s, FuncName:%s, LineNum:%d\n",\
##__VA_ARGS__, __FILE__, __func__, __LINE__);}while(0)

CMutex::CMutex(bool IsRecursive)
{
m_pMutex = new pthread_mutex_t;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
int nResult = 0;
do{
if (IsRecursive)
{
//设置属性为可递归
nResult = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
nResult = 20;
if ( 0 != nResult )
{
DEBUG_ERROR("pthread_mutexattr_settype() failed:%s", strerror(nResult));
break;
}
}
else
{
//设置属性为不可递归
nResult = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
if ( 0 != nResult )
{
DEBUG_ERROR("pthread_mutexattr_settype() failed:%s", strerror(nResult));
break;
}
}

nResult = pthread_mutex_init(m_pMutex, &attr);
if( 0 != nResult )
{
DEBUG_ERROR("pthread_mutexattr_settype() failed:%s", strerror(nResult));
break;
}
}while(0);
}

CMutex::~CMutex()
{
int nResult = pthread_mutex_destroy(m_pMutex);
if( nResult != 0 )
{
DEBUG_ERROR("CMutex ~CMutex() pthread_mutex_destroy() failed:%s", strerror(nResult));
}
if ( m_pMutex != NULL )
delete m_pMutex;
}

bool CMutex::Lock()
{
int nResult = pthread_mutex_lock(m_pMutex);
if( nResult < 0 )
{
DEBUG_ERROR("CMutex lock() pthread_mutex_lock() failed:%s", strerror(nResult));
return false;
}
return true;
}

bool CMutex::UnLock()
{
int nResult = pthread_mutex_unlock(m_pMutex);
if ( nResult < 0 )
{
DEBUG_ERROR("CMutex unlock() pthread_mutex_unlock() failed:%s", strerror(nResult));
return false;
}
return true;
}

bool CMutex::TryLock()
{
int nResult = pthread_mutex_trylock(m_pMutex);
if ( nResult != 0 )
{
DEBUG_ERROR("CMutex trylock() pthread_mutex_trylock() failed:%s", strerror(nResult));
return false;
}
return true;
}

由于笔者的水平有限,出错在所难免,恳请读者拍砖指正,谢谢阅读