404频道

学习笔记

本文选择安装的hadoop版本为网上资料较多的0.20.2,对于不懂的新技术要持保守态度。遇到问题解决问题的痛苦远比体会用不着功能的新版本的快感来的更猛烈。

安装环境

本文选择了三台机器来搭建hadoop集群,1个Master和2个Slave。本文中的master主机即namenode所在的机器,slave即datanode所在的机器。节点的机器名和IP地址如下

机器名 IP地址 用途 运行模块
server206 192.168.20.6 Master NameNode、JobTracker、SecondaryNameNode
ap1 192.168.20.36 Slave DataNode、TaskTracker
ap2 192.168.20.38 Slave DataNode、TaskTracker

安装Java

  1. 检查本机是否已安装Java
    在命令行中输入java -version判断是否已经安装。如果已经安装检查Java的版本,某些操作系统在安装的时候会安装Jdk,但可能版本会太低。如果版本过低,需要将旧的版本删除。在Redhat操作系统中可以通过rpm命令来删除系统自带的Jdk。
  2. 安装java
    本文选择jdk1.6安装,将解压出的文件夹jdk1.6.0_10复制到/usr/java目录下。
  3. 设置java的环境变量
    添加系统环境变量,修改/etc/profile文件,在文件末尾添加如下内容:

    export JAVA_HOME=/usr/java/jdk1.6.0_10
    export CLASSPATH=.:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar
    export PATH=$JAVA_HOME/bin:$PATH
    export JRE_HOME=$JAVA_HOME/jre

    修改完profile文件后要执行source /etc/profile命令才能使刚才的修改在该命令行环境下生效。
  4. 检查java是否安装成功
    在命令行中输入java -version、javac命令来查看是否安装成功及安装版本。

配置hosts文件

本步骤必须操作,需要root用户来操作,修改完成之后立即生效。在三台机器的/etc/hosts文件末尾添加如下内容:

192.168.20.6 server206
192.168.20.36 ap1
192.168.20.38 ap2

修改完成之后可以通过ping 主机名的方式来测试hosts文件是否正确。

新建hadoop用户

在三台机器上分别新建hadoop用户,该用户的目录为/home/hadoop。利用useradd命令来添加用户,利用passwd命令给用户添加密码。

配置SSH免登录

该步骤非必须,推荐配置,否则在Master上执行start-all.sh命令来启动hadoop集群的时候需要手动输入ssh密码,非常麻烦。
原理:用户将自己的公钥储存在远程主机上。登录的时候,远程主机会向用户发送一段随机字符串,用户用自己的私钥加密后,再发回来。远程主机用事先储存的公钥进行解密,如果成功,就证明用户是可信的,直接允许登录shell,不再要求输入密码。
在本例中,需要实现的是192.168.20.6上的hadoop用户可以无密码登录自己、192.168.20.36和192.168.20.38的hadoop用户。需要将192.168.20.6上的ssh公钥复制到192.168.20.36和192.168.20.38机器上。

  1. 在192.168.20.6上执行ssh-keygen –t rsa命令来生成ssh密钥对。会在/home/hadoop/.ssh目录下生成id_rsa.pub和id_rsa两个文件,其中id_rsa.pub为公钥文件,id_rsa为私钥文件。
  2. 在192.168.20.6上执行cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys命令将公钥添加到授权的key里。
  3. 权限设置。在192.168.20.6上执行chmod 600 ~/.ssh/authorized_keys来修改authorized_keys文件的权限,执行chmod 700 ~/.ssh命令将.ssh文件夹的权限设置为700。如果权限不对无密码登录就配置不成功,而且没有错误提示,这一步特别注意。
  4. 在本机上测试是否设置无密码登录成功。在192.168.20.6上执行ssh -p 本机SSH服务端口号 localhost,如果不需要输入密码则登录成功。
  5. 利用scp命令将192.168.20.6上的公钥文件id_rsa.pub追加到192.168.20.36和192.168.20.38机器上的~/.ssh/authorized_keys文件中。scp命令的格式如下:
    1
    scp -P ssh端口号 ~/.ssh/id_rsa.pub hadoop@192.168.20.36:~/id_rsa.pub
    在192.168.20.36和192.168.20.38机器上分别执行cat ~/id_rsa.pub >> ~/.ssh/authorized_keys命令将192.168.20.6机器上的公钥添加到authorized_keys文件的尾部。
  6. 配置无密码登录完成,在Master机器上执行ssh -P 本机SSH服务端口号 要连接的服务器IP地址命令进行测试。

搭建单机版hadoop

在192.168.20.6上首先搭建单机版hadoop进行测试。

  1. 将hadoop-0.20.2.tar.gz文件解压到hadoop用户的目录下。
  2. 配置hadoop的环境变量。修改/etc/profile文件,在文件的下面加入如下:

    HADOOP_HOME=/home/hadoop/hadoop-0.20.2
    export HADOOP_HOME
    export HADOOP=$HADOOP_HOME/bin
    export PATH=$HADOOP:$PATH

修改完成之后执行source /etc/profile使修改的环境变量生效。
3. 配置hadoop用到的java环境变量
修改conf/hadoop-env.sh文件,添加export JAVA_HOME=/usr/java/jdk1.6.0_10
4. 修改conf/core-site.xml的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<configuration>
<property>
<name>fs.default.name</name>
<value>hdfs://localhost:9000</value>
</property>

<property>
<name>dfs.replication</name>
<value>1</value>
</property>

<property>
<name>hadoop.tmp.dir</name>
<value>/home/hadoop/hadoop-0.20.2/tmp</value>
</property>
</configuration>
  1. 修改/conf/mapred-site.xml的内容如下:
    1
    2
    3
    4
    5
    6
    <configuration>
    <property>
    <name>mapred.job.tracker</name>
    <value>localhost:9001</value>
    </property>
    </configuration>
  2. 至此单击版搭建完毕。可以通过hadoop自带的wordcount程序测试是否运行正常。下面为运行wordcount例子的步骤。
  3. 在hadoop目录下新建input文件夹。
  4. 将conf目录下的内容拷贝到input文件夹下,执行cp conf/* input
  5. 通过start-all.sh脚本来启动单机版hadoop。
  6. 执行wordcount程序:hadoop jar hadoop-0.20.2-examples.jar wordcount input output
  7. 通过stop-all.sh脚本来停止单机版hadoop。

搭建分布式hadoop

在上述基础之上,在192.168.20.6上执行如下操作。
1. 修改/home/hadoop/hadoop-0.20.2/conf目录下的core-site.xml文件。

1
2
3
4
5
6
7
8
9
10
<configuration>
<property>
<name>fs.default.name</name>
<value>hdfs://192.168.20.6:9000</value>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/home/hadoop/hadoop-0.20.2/tmp</value>
</property>
</configuration>

如没有配置hadoop.tmp.dir参数,此时系统默认的临时目录为:/tmp/hadoo-hadoop。而这个目录在每次重启后都会被干掉,必须重新执行format才行,否则会出错。
2. 修改/home/hadoop/hadoop-0.20.2/conf目录下的hdfs-site.xml文件。

1
2
3
4
5
6
7
8
9
10
<configuration>
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
<property>
<name>dfs.support.append</name>
<value>true</value>
</property>
</configuration>

3. 修改/home/hadoop/hadoop-0.20.2/conf目录下的mapred-site.xml文件。

1
2
3
4
5
6
<configuration>
<property>
<name>mapred.job.tracker</name>
<value>http://192.168.20.6:9001</value>
</property>
</configuration>

4. 修改/home/hadoop/hadoop-0.20.2/conf目录下的masters文件。
将Master机器的IP地址或主机名添加进文件,如192.168.20.6。
5. 修改/home/hadoop/hadoop-0.20.2/conf目录下的slaves文件。Master主机特有
在其中将slave节点的Ip地址或主机名添加进文件中,本例中加入

1
2
192.168.20.36
192.168.20.38

6. hadoop主机的master主机已经配置完毕,利用scp命令将hadoop-0.20.2目录复制到两台slave机器的hadoop目录下。命令为:scp -r /home/hadoop hadoop@服务器IP:/home/hadoop/。注意slaves文件在master和slave机器上是不同的。

常用命令

参考文档

下载链接

http://pan.baidu.com/share/link?shareid=1235031445&uk=3506813023 提取码:v8ok

本文选择四台机器作为集群环境,hadoop采用0.20.2,HBase采用0.90.2,zookeeper采用独立安装的3.3.2稳定版。本文所采用的数据均为简单的测试数据,如果插入的数据量大可能会对结果产生影响。集群环境部署情况如下:

机器名 IP地址 用途 Hadoop模块 HBase模块 ZooKeeper模块
server206 192.168.20.6 Master NameNode、JobTracker、SecondaryNameNode HMaster QuorumPeerMain
ap1 192.168.20.36 Slave DataNode、TaskTracker HRegionServer QuorumPeerMain
ap2 192.168.20.38 Slave DataNode、TaskTracker HRegionServer QuorumPeerMain
ap2 192.168.20.8 Slave DataNode、TaskTracker HRegionServer QuorumPeerMain

单线程插入100万行

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
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.util.Bytes;


public class InsertRowThreadTest {

private static Configuration conf = null;

private static String tableName = "blog";

static {
Configuration conf1 = new Configuration();
conf1.set("hbase.zookeeper.quorum", "server206,ap1,ap2");
conf1.set("hbase.zookeeper.property.clientPort", "2181");
conf = HBaseConfiguration.create(conf1);
}

/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// 列族
String[] familys = {"article", "author"};
// 创建表
try {
HBaseAdmin admin = new HBaseAdmin(conf);
if (admin.tableExists(tableName)) {
System.out.println("表已经存在,首先删除表");
admin.disableTable(tableName);
admin.deleteTable(tableName);
}

HTableDescriptor tableDesc = new HTableDescriptor(tableName);
for(int i=0; i<familys.length; i++){
HColumnDescriptor columnDescriptor = new HColumnDescriptor(familys[i]);
tableDesc.addFamily(columnDescriptor);
}
admin.createTable(tableDesc);
System.out.println("创建表成功");
} catch (Exception e) {
e.printStackTrace();
}

// 向表中插入数据
long time1 = System.currentTimeMillis();
System.out.println("开始向表中插入数据,当前时间为:" + time1);

for (int i=0; i<1; i++) {
InsertThread thread = new InsertThread(i * 1000000, 1000000, "thread" + i, time1);
thread.start();
}
}

public static class InsertThread extends Thread {

private int beginSite;

private int insertCount;

private String name;

private long beginTime;

public InsertThread(int beginSite, int insertCount, String name, long beginTime) {
this.beginSite = beginSite;
this.insertCount = insertCount;
this.name = name;
this.beginTime = beginTime;
}

@Override
public void run() {
HTable table = null;
try {
table = new HTable(conf, Bytes.toBytes(tableName));
table.setAutoFlush(false);
table.setWriteBufferSize(1 * 1024 * 1024);
} catch (IOException e1) {
e1.printStackTrace();
}

System.out.println("线程" + name + "从" + beginSite + "开始插入");

List<Put> putList = new ArrayList<Put>();
for (int i=beginSite; i<beginSite + insertCount; i++) {
Put put = new Put(Bytes.toBytes("" + i));
put.add(Bytes.toBytes("article"), Bytes.toBytes("tag"), Bytes.toBytes("hadoop"));
putList.add(put);
if (putList.size() > 10000) {
try {
table.put(putList);
table.flushCommits();
} catch (IOException e) {
e.printStackTrace();
}
putList.clear();
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
try {
table.put(putList);
table.flushCommits();
table.close();
} catch (IOException e) {
System.out.println("线程" + name + "失败");
e.printStackTrace();
}

long currentTime = System.currentTimeMillis();
System.out.println("线程" + name + "结束,用时" + (currentTime - beginTime));
}
}
}

测试5次的结果分布图如下:
Image Title
其中Y轴单位为毫秒。平均速度在1秒插入3万行记录。

10个线程每个线程插入10万行

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
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.util.Bytes;


public class InsertRowThreadTest {

private static Configuration conf = null;

private static String tableName = "blog";

static {
Configuration conf1 = new Configuration();
conf1.set("hbase.zookeeper.quorum", "server206,ap1,ap2");
conf1.set("hbase.zookeeper.property.clientPort", "2181");
conf = HBaseConfiguration.create(conf1);
}

/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// 列族
String[] familys = {"article", "author"};
// 创建表
try {
HBaseAdmin admin = new HBaseAdmin(conf);
if (admin.tableExists(tableName)) {
System.out.println("表已经存在,首先删除表");
admin.disableTable(tableName);
admin.deleteTable(tableName);
}

HTableDescriptor tableDesc = new HTableDescriptor(tableName);
for(int i=0; i<familys.length; i++){
HColumnDescriptor columnDescriptor = new HColumnDescriptor(familys[i]);
tableDesc.addFamily(columnDescriptor);
}
admin.createTable(tableDesc);
System.out.println("创建表成功");
} catch (Exception e) {
e.printStackTrace();
}

// 向表中插入数据
long time1 = System.currentTimeMillis();
System.out.println("开始向表中插入数据,当前时间为:" + time1);

for (int i=0; i<10; i++) {
InsertThread thread = new InsertThread(i * 100000, 100000, "thread" + i, time1);
thread.start();
}
}

public static class InsertThread extends Thread {

private int beginSite;

private int insertCount;

private String name;

private long beginTime;

public InsertThread(int beginSite, int insertCount, String name, long beginTime) {
this.beginSite = beginSite;
this.insertCount = insertCount;
this.name = name;
this.beginTime = beginTime;
}

@Override
public void run() {
HTable table = null;
try {
table = new HTable(conf, Bytes.toBytes(tableName));
table.setAutoFlush(false);
table.setWriteBufferSize(1 * 1024 * 1024);
} catch (IOException e1) {
e1.printStackTrace();
}

System.out.println("线程" + name + "从" + beginSite + "开始插入");

List<Put> putList = new ArrayList<Put>();
for (int i=beginSite; i<beginSite + insertCount; i++) {
Put put = new Put(Bytes.toBytes("" + i));
put.add(Bytes.toBytes("article"), Bytes.toBytes("tag"), Bytes.toBytes("hadoop"));
putList.add(put);
if (putList.size() > 10000) {
try {
table.put(putList);
table.flushCommits();
} catch (IOException e) {
e.printStackTrace();
}
putList.clear();
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
try {
table.put(putList);
table.flushCommits();
table.close();
} catch (IOException e) {
System.out.println("线程" + name + "失败");
e.printStackTrace();
}

long currentTime = System.currentTimeMillis();
System.out.println("线程" + name + "结束,用时" + (currentTime - beginTime));
}
}
}

耗时分布图为:
Image Title
结果比单线程插入有提升。

20个线程每个线程插入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
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
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.util.Bytes;


public class InsertRowThreadTest {

private static Configuration conf = null;

private static String tableName = "blog";

static {
Configuration conf1 = new Configuration();
conf1.set("hbase.zookeeper.quorum", "server206,ap1,ap2");
conf1.set("hbase.zookeeper.property.clientPort", "2181");
conf = HBaseConfiguration.create(conf1);
}

/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// 列族
String[] familys = {"article", "author"};
// 创建表
try {
HBaseAdmin admin = new HBaseAdmin(conf);
if (admin.tableExists(tableName)) {
System.out.println("表已经存在,首先删除表");
admin.disableTable(tableName);
admin.deleteTable(tableName);
}

HTableDescriptor tableDesc = new HTableDescriptor(tableName);
for(int i=0; i<familys.length; i++){
HColumnDescriptor columnDescriptor = new HColumnDescriptor(familys[i]);
tableDesc.addFamily(columnDescriptor);
}
admin.createTable(tableDesc);
System.out.println("创建表成功");
} catch (Exception e) {
e.printStackTrace();
}

// 向表中插入数据
long time1 = System.currentTimeMillis();
System.out.println("开始向表中插入数据,当前时间为:" + time1);

for (int i=0; i<20; i++) {
InsertThread thread = new InsertThread(i * 50000, 50000, "thread" + i, time1);
thread.start();
}
}

public static class InsertThread extends Thread {

private int beginSite;

private int insertCount;

private String name;

private long beginTime;

public InsertThread(int beginSite, int insertCount, String name, long beginTime) {
this.beginSite = beginSite;
this.insertCount = insertCount;
this.name = name;
this.beginTime = beginTime;
}

@Override
public void run() {
HTable table = null;
try {
table = new HTable(conf, Bytes.toBytes(tableName));
table.setAutoFlush(false);
table.setWriteBufferSize(1 * 1024 * 1024);
} catch (IOException e1) {
e1.printStackTrace();
}

System.out.println("线程" + name + "从" + beginSite + "开始插入");

List<Put> putList = new ArrayList<Put>();
for (int i=beginSite; i<beginSite + insertCount; i++) {
Put put = new Put(Bytes.toBytes("" + i));
put.add(Bytes.toBytes("article"), Bytes.toBytes("tag"), Bytes.toBytes("hadoop"));
putList.add(put);
if (putList.size() > 10000) {
try {
table.put(putList);
table.flushCommits();
} catch (IOException e) {
e.printStackTrace();
}
putList.clear();
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
try {
table.put(putList);
table.flushCommits();
table.close();
} catch (IOException e) {
System.out.println("线程" + name + "失败");
e.printStackTrace();
}

long currentTime = System.currentTimeMillis();
System.out.println("线程" + name + "结束,用时" + (currentTime - beginTime));
}
}
}

结果如下:
Image Title
执行结果跟10个线程效果差不多。

10个线程每个线程插入100万行

代码跟前面例子雷同,为节约篇幅未列出。
执行结果如下:
Image Title

20个线程每个线程插入50万行

执行结果如下:
Image Title

总结

  • 多线程比单线程的插入效率有所提高,开10个线程与开20个线程的插入行效率差不多。
  • 插入效率存在不稳定情况,通过折线图可以看出。

相关文章

在Linux上搭建Hadoop集群环境
在Linux上搭建HBase集群环境

Linux处理ctrl+c信号的例子

当按下ctrl+c时如果代码正在执行sleep则会停止睡眠,调用信号处理函数。中断位置可能位于for循环代码段的任意位置,中断位置不可控。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <signal.h>
void h(int s)
{
printf("抽空处理int信号\n");
}
main()
{
int sum=0;
int i;
signal(SIGINT,h);
sigset_t sigs;

for(i=1;i<=10;i++)
{
sum+=i;
sleep(1);
}
printf("sum=%d\n",sum);
printf("Over!\n");
}

信号屏蔽的例子1

当按下ctrl+c时不会调用信号处理函数,当循环执行完毕后会调用信号处理函数,并且printf(“Over!\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
#include <stdio.h>
#include <signal.h>

void h(int s)
{
printf("抽空处理int信号\n");
}

main()
{
int sum=0;
int i;
// 声明信号集合
sigset_t sigs;
signal(SIGINT,h);
// 清空集合
sigemptyset(&sigs);
// 加入屏蔽信号
sigaddset(&sigs,SIGINT);
// 屏蔽信号
sigprocmask(SIG_BLOCK,&sigs,0);
for(i=1;i<=10;i++)
{
sum+=i;
sleep(1);
}
printf("sum=%d\n",sum);
// 消除屏蔽信号
sigprocmask(SIG_UNBLOCK,&sigs,0);
// 如果在上面按下ctrl+c,在此句不执行
printf("Over!\n");
}

当在循环中按下ctrl+c后,该函数输出结果为:

1
2
3
sum=55
抽空处理int信号
Over!

信号屏蔽的例子2

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
#include <stdio.h>
#include <signal.h>
// 信号处理函数
void h(int s)
{
printf("抽空处理int信号\n");
}
main()
{
int sum=0;
int i;
signal(SIGINT,h);
sigset_t sigs,sigp,sigq;
sigemptyset(&sigs);
sigemptyset(&sigp);
sigemptyset(&sigq);

sigaddset(&sigs,SIGINT);
sigprocmask(SIG_BLOCK,&sigs,0);
for(i=1;i<=10;i++)
{
sum+=i;
sigpending(&sigp);
if(sigismember(&sigp,SIGINT))
{
printf("SIGINT在排队!\n");
// 是信号SIGINT有效
sigsuspend(&sigq);
// 函数调用完毕后信号SIGINT无效
}
sleep(1);
}
printf("sum=%d\n",sum);
// 消除屏蔽信号
sigprocmask(SIG_UNBLOCK,&sigs,0);
printf("Over!\n");
}

该例子可以实现在指定的代码处处理信号。
其中sigsuspend函数原先如下:

1
int sigsuspend(const sigset_t *mask);

函数解释:屏蔽新的信号,原来的屏蔽信号失效。是一个阻塞函数,该函数屏蔽mask信号;对非mask信号不屏蔽,信号处理函数调用完毕该函数返回;如果非mask信号没有信号处理函数,则此函数不返回。即返回条件:信号发生且信号为非屏蔽信号且信号必须要调用信号处理函数完毕。

Image Title

我想拥有一只雌性袋鼠作为宠物,我会将其取名为”点点”。之所以是雌性是看中了袋鼠的温暖舒适的育儿袋。

如果可以我会给育儿袋上面缝上个拉链,这样我就不会担心放在袋袋里面的东西会掉出来了。

我可以领着我的点点去超市购物,将购买的东西放到袋袋里面,拉链一拉,然后蹦蹦跳跳的就回家了。

晚上吃完饭,我可以领着点点去大街上走走,我可以将我的钱包、手机放到点点的袋袋里,拉链一拉,完全不用担心手机会摔坏。

如果下起了雨或遇到了寒风,我可以钻到袋袋里,露个头在外面,一跳10米远,然后蹦蹦跳跳的就回家了。

当然,这是不现实的,我在做梦,做一个好笑的梦。。。

参加完姐姐的婚礼回到家后,静下心来之后内心莫名的感伤。也许是早晨三点醒四点起床导致身体在下午已经出现了疲惫。也许是生活本来就应该是平淡的,兴奋的多了,也自然疲惫的多,总之要保持一个符合每个人性格的平衡。也许是因为如女朋友同事的例子中一样,当丈夫娶妻过门的时候,丈夫的妹妹是哭的,因为妹妹有恋哥情节,难不成我也有恋姐情节的存在,还好我仅是有点伤感而已,不严重。也许是因为对时间流逝的无奈,总以为我们还没有长大,转眼间姐姐已经步入了婚姻的殿堂,成立了自己的新家庭,逝者如斯。趁着自己还有点文艺范的精神状态,写写这两天的一些感想,要不然明天一接触需要理性思考的计算机语言,这种感性点的状态又会归于平淡,又会在我鲜有的心路历程文章里少了一篇。

小的时候总是羡慕姐姐在城里的生活,每年在仅有的几次见面中总能感受到一个不一样的姐姐。那时的我对于城市的大小、城市的好坏还没有概念,仅仅知道城市要比农村好不少倍,仅仅知道姐姐是城里的,是城市小朋友的代言人,是引以为荣的小榜样。也许下面这张小时候的珍贵照片最能反应出城市孩子和农村孩子之间的区别。
Image Title
记得大约上八年级寒假的时候见过姐姐写的字,字迹铿锵有力且独特,不是常见的楷体字,由特定的字体加上自己的独到之处柔和而成。分明是经过训练方能造就,我从未在我认识的同学中见过能够写出如此华丽的字体,即使有书法天赋的同学由于没有经过特定的练习,字体往往都是课本上常见的楷体字。因为从上高中起,我已经开始渐渐地融入曾经向往的城市生活,直到如今我也算半个城市人。我和姐姐之间的差距在不断的缩短。当然会发现原来城市生活也有城市生活的弊端,如果让我选择一次我还会选择在农村度过我的童年,那种各种玩各种贴近大自然的生活是在高楼间无法体会到的。

姐姐终于找到了可以寄托的归宿,姐夫是很优秀的。姐姐属于离不开爱情的类型,但身体却不是很好,也许是姐夫喊姐姐玉妹的缘由。期望在外来姐夫能够好好照顾姐姐,同时姐姐应该多注意身体,多吃粗粮。

一个我想象不到的地方是随着现在人民文化水平的提高,婚礼的举办方式正在朝着复杂化的方向发展。现在的婚礼已经将旧有的当地风俗和现代的风俗结合在了一起,而且这两者是叠加的关系,意味着风俗越来越多,需要处理的事情越来越复杂。其实婚礼的作用无非就是热闹、喜庆, 我实在是搞不太明白复杂和热闹之间是一个什么样的关系。我期望我婚礼是简单的,简单的不能再简单,我甚至期望不需要亲朋好友的过多参与,我甚至不期望举办复杂的婚礼仪式,热闹的背后是家人的操劳,是长达数月的准备。我只希望能有一次难忘的旅行,两个人的旅行就足够幸福。

祝福的含义我是不理解的,祝福原本是一个人向另外一个人的未来的美好祝愿,就是说些客套话,说些不切实际的话。其实一个人很难对另外一个人的外来向好的方面发展做出贡献,就如同大臣们天天喊着万岁万岁万万岁,却未见过哪个皇帝超过百岁一般。很多人都会向姐姐的婚礼说出祝福,但有多少是过后还记得自己曾经说过的。我在这里同样祝福姐姐和姐夫新婚快乐,生活幸福,和睦相处,恩恩爱爱,当然我是发自内心的,而且我的话是有文字可考究的。同样在给姐姐的红包里我写下了“原寻寻觅觅,现卿卿我我,年年岁岁情爱深,岁岁年年无不同”的祝福,这里同样做一个备忘。

目前的婚庆已经完全市场化,只要是服务项没有不收费的可能性,而且往死里要。没有信仰的民族是个可怕的民族,如果马克思主义不能所谓一种信仰,那么中国人中的绝大多数是没有信仰的。即使大家上初中、高中、大学、研究生阶段都在马克思的理论,在当前的国情下估计也没有多少人能视马克思如珍宝。如果在国外我估计会出现在教堂举办婚礼非常廉价,甚至免费的可能性,因为能够为新郎新娘主持婚礼本身就是一件非常荣幸的事情,金钱不是最重要的,当然我不了解海外的真实情况。总感觉一件本来可以免费的事情只要跟金钱沾边总会变味。

中国父母的典型形象是碎碎念,对于子女总会絮絮叨叨那么一箩筐。在不经意间,子女已经长大成人,可在父母的眼中子女永远是未长大的孩子。中国的父母在子女身上花费了太多的心血,以至于子女比自己更重要。

一个活动的缺点是非常容易被人察觉的,而活动的优点往往不容易被人发觉。因此,要想顺利完成一个活动是不容易的。举办婚礼的时候我发现有很多事先未准备妥当的地方,而这些恰恰是最容易被发现的,而做得特别好的地方却是较难被人们发现。

年龄之间的代沟还是比较明显的,这一点在酒桌上特别能够体现,在酒桌上话语最多的永远是上一辈人,因为年轻人跟上一辈人在思想上还是有较大的差距,这是好事。如果两代人之间差距过小,说明这个社会变化太慢。年轻人应该吸取上一代人的长处,去其槽粕,取其精华。我想未来的酒桌文化会大变样。

我目前的理想生活状态是平淡充实的,我喜欢静下心来学习,我喜欢那种学习的充实感,我喜欢跟同龄人人心贴心的交谈,而不是在噪杂的环境中大家泛泛而谈。我的性格中腼腆的一面表现在我在与人沟通时内心的想法总会羞于表达,比如很难从我的嘴里向父母说出生日快乐之类的话语,向姐姐当面说出新婚快乐,白头偕老。一方面想着跟人沟通,另一方面却羞与吐露自己的内心想法,貌似有点矛盾的样子。

人类本该是感性的,接触的外部环境多了之后会渐渐趋于理性,当然人类的科技进步需要理性。理性多了,自然感性就会少一些。这几年跟计算机打交道的时间成了生活、工作的最重要部分,我大部分生活状态处于理性状态,心情以平淡居多。包括我现在写这篇文章,也是在用理性的思维方式来写感性的文章,换成在高中时期写的文章却是用感性的思维方式来写感性的文章。

我的性格未曾被工作改变,甚至是相貌在工作三年的时间里也未曾有大的变动。我现在仍然看上去像个学生,路人也经常会将我当成一名学生。女朋友说这是我不成熟的表现,我不知道成熟如何去界定,我认为我每天学到的东西都在助我向成熟迈进,直到我老去的那一刻我也不可能成熟,因为只要我还有进步我就不会成熟。我对于三年来的未改变我引以为豪,至少我没有被这个残缺的社会给同化掉,至少我还拥有自己的思想,同时也证明了一点我在大学期间思想就已经在保持了一种相对稳定的状态。我不认为工作后在官场上混的如鱼得水,在酒桌上的机智多变是一种成熟的表现,我对此持悲观态度。

行行出状元。今天看到我姑在玩QQ农场、QQ牧场的游戏,熟练程度相当高。由于我姑空闲时间较多,常常一个人在家,已经玩了多年的游戏。我姑对电脑的使用还处在初级阶段,平常电脑的作用还定位在玩农场的阶段。只要做的次数多了,行行都可以出状元。

本来想着把自己参加完姐姐婚礼之后的感想记录下来,结果写着写着开始有些怀旧,写自己的居多,困的不知所云,到此为止。不知等我老去的那天当我读到此文章时会是一种什么样的心情,难不成还会跟现在一样有点压抑的感觉?

system函数

函数包含在C语言的标准库中,在头文件stdlib.h中声明如下:

1
int system(const char *command);

该函数会创建一个独立的进程,该进程拥有独立的进程空间,为阻塞函数,只有当新进程执行完毕该函数才返回。

返回值:可以通过返回值来获取新进程的main函数的返回值,返回值保存在int类型的第二个字节即8-15比特,可以通过向右移位或者利用宏WEXITSTATUS(status)来获取新进程的返回值。其中WEXITSTATUS(status)宏包含在头文件<sys/wait.h>中。

这里以调用ls命令为例来展示用法:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
int r=system("ls");
// 用右移位的方式来获取新进程的返回值
//printf("%d\n",r>>8&255);
// 用WEXITSTATUS(status)宏的方式来获取新进程的返回值
printf("%d\n",WEXITSTATUS(r));
}

popen函数

函数包含在头文件stdio.h中,相关函数如下:

1
2
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);

popen函数在父子进程之间建立一个管道,其中type指定管道的类型,可以为”r”或”w”即只读或可写。在shell中的管道符”|”即采用此函数来实现。popen函数为阻塞函数,函数的具体用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
main()
{
char buf[1024];
FILE *f=popen("ls","r");
// 根据管道获取文件描述符
int fd=fileno(f);
int r;
while((r=read(fd,buf,1024))>0)
{
buf[r]=0;
printf("%s\n",buf);
}
close(fd);
// 关闭管道
pclose(f);
}

exec系列函数

该系列函数并不创建新的进程,而是将程序加载到当前进程的代码空间来执行并替换当前进程的代码空间,在exec*函数后面的代码将无法执行。

1
2
3
4
5
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);

fork函数

该函数非常常用。函数原型如下:

1
pid_t fork(void);

调用该函数会产生一个子进程,该子进程不仅复制了父进程的代码空间、堆、栈,而且还复制了父进程的执行位置。之后父子进程同时执行,通常由于操作系统任务调度的原因,子进程会先执行。父进程和子进程之间的并不会共享堆、栈上的数据,可以通过文件或共享内存的方式来通讯。

返回值:该函数父进程返回子进程的id,子进程返回0。通常在代码中通过返回值来判断是子进程还是父进程,用来执行不同的代码。

如果父进程先结束,则子进程会成为孤儿进程,子进程仍然可以继续执行。进程数中的根进程init会成为该子进程的父进程。

如果子进程先结束,则子进程会成为僵尸进程。僵尸进程并不再占用内存和CPU资源,但是会在进程数中看到僵尸进程。因此代码中必须对僵尸进程的情况做处理,通常的处理办法为采用wait函数和信号机制。子进程在结束的时候会向父进程发送一个信号SIGCHLD,整数值为17。父进程扑捉到该信号后通过调用wait函数来回收子进程的资源。其中wait函数为阻塞函数,会一直等待子进程结束。wait函数返回子进程退出时的状态码。下面通过实例演示一下当子进程先结束时父进程怎样回收子进程。

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
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void processChildProcess()
{
printf("receive the child process end\n");
wait();
}

int main()
{
int pid;
pid = fork();
if (pid > 0)
{
// 父进程
signal(SIGCHLD, processChildProcess);
while(1)
{
sleep(1);
}
}
else
{
// 子进程
sleep(1);
printf("child process end\n");
}
}

关于fork的详细理解可以看这里:一个fork的面试题

前段时间公司领导安排我完成一个android的小项目,功能为完成手机短信的分类功能。由于考虑到可能会和某运营商合作,项目需求多变,项目名到项目死掉的那一刻也没有想好,暂定叫短信管家。前段时间参加了今年软考中的高级项目管理师考试,对项目管理有一定的了解,由于在实际的工作中没有参与项目经理的角色,对项目管理的知识理论和实际结合还有一定差距。我一向是喜欢挑人毛病的,本文也不例外,将结合项目管理的知识领域来分析项目中存在的问题,并给出纠正的措施。本文并不想按照项目管理的44个过程组来写,不想受项目管理理论知识条条框框的约束。

项目简介

在2013年元旦后上班第一天,经过领导简单介绍项目后,项目正式开始启动。项目参与人员包括我和一名美工。由于公司没有做android项目的经验,我是公司唯一懂点android项目的技术人员,我之前也仅仅是自学过一点。经过两个星期的学习之后开始了android项目的开发,春节前领导希望看到一个版本出来。离春节放假还有三天,美工搞出一个首界面来,我仿照首界面基本搞定。后来领导一看不符合需求,其实领导之前就看过首界面效果图,只是没有仔细看。当然领导也不知道要做出个什么样的东西,估计心想着技术人员弄出个什么样的东西来再修改。
年后,对需求开会进行了重新整理,通过思维导图的方式跟领导确定需求,就需求中存在的疑问进行确认。不过后来事实证明经过领导确认的需求也是在不断变更。这次的需求要远比上次复杂,将原来的短信分类从一级短信分类更改为了二级短信分类。公司没有互联网行业产品的经验,美工给出的效果图也仅是从桌面端来考虑移动产品的设计。我最终决定自己开始重新设计软件的界面,参考了众多同类软件,花费的时间比较多。大约到2013年4月底产品已经基本可用,经过我自己的测试和领导的使用效果还算可以。后来发现不支持彩信,经过我的一番研究后发现现有的产品功能要对彩信支持需要耗费非常大的工作量,因为软件本身的功能打破了android系统短信的设计,也难怪了android的应用中没有功能相似的产品出现。这也成为了该项目失败的直接原因。

可行性研究

技术可行性

项目的起源来自领导的短信泛滥,领导想着能够搞一个拦截垃圾短信并对短信进行管理的软件将是酷的一件事情。领导接触过一些android,片面的认为这种方案在技术上可行。我也做了一个调研,发现现有的android软件项目中没有此功能的产品。

经济可行性

支出分析:由于项目主要参与人员仅有我一人,支出的成本较小。收益分析:现在的互联网产品都是先圈用户再考虑盈利,我们这个软件产品也不例外。

运行环境可行性

对于产品的运营存在两种方式:1、跟某地方运营商合作;2、自己单独发布。

总体而言,由于项目较小,对可行性研究基本忽略,没有科学、可观、公证的对项目进行可行性分析。

范围管理

在项目立项的时候仅有一个产品的大致方向,做一款android系统中的短信分类管理类应用。从始至终领导对于产品的理解在不断变化,每次变化都需要修改很多代码,跟领导对于导致产品研发过程做了很多无用功。
在项目进行到一个月后,我认识到了项目这样下去反复修改不可能有好的结果,因此跟领导召开了需求定义的会议,就产品的需求用思维导图的方式就项目的范围进行了定义,会后就需求中没有考虑到的问题跟领导进行了沟通确认。此次会议之后项目的需求基本敲定,虽然之后领导有需求变更的情况,但是相对较少。

进度管理

由于公司初次开展android类项目,对项目的进度很难掌控。项目开始时负责市场部的领导给出了一个月之内完成的计划,我心想我自己加把劲应该能搞定吧,因为最初的项目需求还比较简单。我用了两个星期的时间来学习android知识,用两个星期的时间来开发,结果到最后我才刚根据美工提供的效果图完成了首页,我总是不能很好的估算自己的工作进度。这篇《为什么程序员总是不能准确预估工作量》的文章或许能替我解释些什么。
在项目范围确定之后我对自己工作的进度管理也不够好,第一次尝试做android项目,未知因素太多,编码中总能遇到这样那样的问题。

成本管理

项目的整个成本应该我的人力成本占了绝大部分,本来软件项目中的人力成本就是占的比重较多的,何况主要是我一个人参与的项目,自然我那微薄的人力占了项目成本的大部分。至于后期项目的盈利并没有过多考虑,现在的互联网行业本来就是先圈用户后盈利。

质量管理

我在项目的编码过程中,一般一个小功能完成之后都会进行详尽的功能测试。由于功能不负责,产品的质量只要多测试一般问题就不大了。

人力资源管理

项目的初期我就感觉项目不可能成功,但是领导坚持要做,而且我一个做C++的程序员来写android的代码,我当时居然仅有听从领导的意识,没有适当的表达自己的想法。当时怀着一己之心认为可能一个月项目就完成了,完成之后工作依旧,还可以熟练熟练android的开发,谁知一练手就是做了五个月,而到最后项目仍然没有成型。
由于一直对项目持悲观态度,始终认为我自己开发出来的产品我都不想去用,用户怎么可能去用。长期自己独自做,兼任了产品经理、美工、码农的工作,做的过程中还要担心着需求的变更,在项目进行到中后期的时候, 缺少激励因素,对工作激情不够,这也影响了项目的进度。

沟通管理

在项目的开发过程中基本能保持没完成一个关键功能跟领导汇报一次项目进展。在项目中沟通方面存在不少的问题,尤其以前期严重。前期由于跟领导的需求不一致,导致做了很多的重复性劳动。领导对项目的需求本来就不够明确,一旦有好的想法就加到项目中,这也导致了跟领导的沟通困难,因为这样的沟通效率太低。

风险管理

项目可研阶段对技术可行性分析研究不够,导致了后期在对彩信进行分类处理的时候遇到了技术上的难题。当然前期的可研阶段做充分的技术可行性研究也是不太现实了,因为本来技术对公司而言就是未知的。最好的办法就是对自己不熟悉而且短期内不能实现盈利的项目不参与。

文档管理

由于项目管理中要求每个阶段都有输出的文档,这么繁琐的文档我相信中国找不出几家软件公司能够办到,能办到的估计都已经死掉了。此项目中仅有范围定义的思维导图、我在编写某个具体的功能代码前绘制的流程图,应该说文档及其少。但我目前不认为这有什么不对,我还是坚信某些情况下看代码比看文档要来的更快,特别是看我这种android菜鸟写的代码。更何况此项目的需求在不断随着领导的想法而变化,文档都没法写,写文档的成本太高。

我的一些反思:

在项目中我也存在不少问题。

  1. 项目开始阶段应该向领导表达清楚自己的想法,阐明自己对项目的看法。
  2. 对项目的进度没有很好的把握。
  3. 对技术较为熟悉之后可以适当的学习后期开发功能需要技术,比如一直没有研究彩信的技术实现,导致后期在开发彩信功能时遇到技术问题。如果能够提前学习相关技术就能够提前预支项目的风险。

shell中的环境变量

查看环境变量

  1. 通过env命令可以查看所有的环境变量
  2. 通过echo $环境变量名方式来查看单个环境变量

设置环境变量

export命令来设置环境变量

在程序中该如何获取和设置环境变量呢?

通过main函数的第三个参数

通常大家接触比较多的是两个参数的main函数,实际上还有一个包含三个参数的main函数,第三个参数为包含了系统的环境变量的二级指针。
用法如下:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

int main ( int argc, char *argv[], char *arge[])
{
while (*arge)
{
printf("%s\n", *arge);
arge++;
}
return 0;
}

实例将会输出该用户的所有环境变量。

通过外部环境变量environ

该变量定义在“unistd.h”头文件中,定义为:extern char **environ;
用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

extern char **environ;

int main ( int argc, char *argv[] )
{
char **env = environ;
while (*env)
{
printf("%s\n", *env);
env++;
}
return 0;
}

实例将会输出该用户的所有环境变量。

通过系统函数

在C语言的头文件“stdlib.h”中定义了三个和环境变量相关的函数:

1
2
3
char *getenv(const char *name);
int setenv(const char *name, const char *value, int overwrite);
int unsetenv(const char *name);

比较简单,不再举例。

在Linux的头文件sys/mman.h中提供了两个用来分配内存的函数:mmap和munmap,函数定义原型如下:

1
2
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *start, size_t length);

mmap说明

返回值:内存映射后返回虚拟内存的首地址。
参数:
start为指定的映射的首地址,该地址应该没有映射过,如果为0则有系统指定位置。
length为映射的空间大小,真正分配空间大小为(length/pagesize+1)。
prot为映射的权限,分为四种未指定(PROT_NONE)、读(PROT_READ)、写(PROT_WRITE)、执行(PROT_EXEC)。如果为PROT_WRITE,则直接可以PROT_READ。
flags:映射方式,分为内存映射和文件映射。内存映射:匿名映射。当值为文件映射是后面两个参数才有效。常用的值有:MAP_ANONYMOUS、MAP_SHARED、MAP_PRIVATE。
fd:映射的文件描述符。
offset为从文件的偏移位置开始映射。

munmap说明

从start位置开始释放length个字节的内存。

应用举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <sys/mman.h>
#include <stdlib.h>

main()
{
int *p = mmap(NULL,
getpagesize(),
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_SHARED,
0,
0);
munmap(p, getpagesize());
}

其中getpagesize()函数的作用为获取一个页的大小,系统默认为4K。

当linux中的函数内部出错时通常函数会返回-1,并且将错误码保存到全局变量errno中,用来表示错误代码。errno全局变量包含在头文件errno.h文件中。下面给出三种打印错误信息的方法。

perror函数

应用举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main(void)
{
FILE *fp ;
fp = fopen( "/root/noexitfile", "r+" );
if ( NULL == fp )
{
perror("error : ");
}
return 0;
}

输出如下:
Permission denied

strerror函数

strerror函数原型为:char *strerror(int errnum);将参数errnum转换为对应的错误码。
应用举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main(void)
{
FILE *fp ;
fp = fopen( "/root/noexitfile", "r+" );
if ( NULL == fp )
{
printf("%s\n", strerror(errno));
}
return 0;
}

输出如下:
Permission denied

printf中的%m打印

应用举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main(void)
{
FILE *fp ;
fp = fopen( "/root/noexitfile", "r+" );
if ( NULL == fp )
{
printf("%m\n");
}
return 0;
}

输出如下:
Permission denied

0%