Linux常用命令(一)

netstat查看端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 参数
-a (all)显示所有选项,默认不显示LISTEN相关
-t (tcp)仅显示tcp相关选项
-u (udp)仅显示udp相关选项
-n 拒绝显示别名,能显示数字的全部转化成数字。将应用程序转为端口显示,即数字格式的地址,如:nfs->2049,ftp->21,因此可以开启两个终端,一一对应一下程序所对应的端口号)
-l 仅列出有在 Listen (监听) 的服務状态
-p 显示建立相关链接的程序名
-r 显示路由信息,路由表
-e 显示扩展信息,例如uid等
-s 按各个协议进行统计
-c 每隔一个固定时间,执行该netstat命令。
提示:LISTEN和LISTENING的状态只有用-a或者-l才能看到
# 查看Linux端口号
$ netstat -anp | grep 80
# 查看服务对应端口
$ netstat -nlp

netstat查看并发连接数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
返回结果示例:
  LAST_ACK 5
  SYN_RECV 30
  ESTABLISHED 1597
  FIN_WAIT1 51
  FIN_WAIT2 504
  TIME_WAIT 1057
状态:描述
  CLOSED:无连接是活动的或正在进行
  LISTEN:服务器在等待进入呼叫
  SYN_RECV:一个连接请求已经到达,等待确认;正在等待处理的请求数
  SYN_SENT:应用已经开始,打开一个连接
  ESTABLISHED:正常数据传输状态
  FIN_WAIT1:应用说它已经完成
  FIN_WAIT2:另一边已同意释放
  ITMED_WAIT:等待所有分组死掉
  CLOSING:两边同时尝试关闭
  TIME_WAIT:另一边已初始化一个释放;表示处理完毕,等待超时结束的请求数。
  LAST_ACK:等待所有分组死掉

lsof查看端口

1
2
3
4
5
# 查看8080端口号对应的进程
$ lsof -i:8080
# 找到被删除的却还被进程占用的文件
$ lsof | grep deleted

ps查看进程

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
# 查看Linux端口号
# ps 命令用于查看当前正在运行的进程
# grep 是搜索
$ ps -ef | grep 80
# 根据进程ID查看进程
$ pe -eLf | grep PID
# 查看Linux进程
$ ps -ef | grep tomcat
# -aux 显示所有状态
$ ps -aux | grep tomcat
# 查看服务进程
$ ps -aux
ps [选项]
-e 显示所有进程,环境变量
-f 全格式
-h 不显示标题
-l 长格式
-w 宽输出
-a 显示终端上地所有进程,包括其他用户地进程
-r 只显示正在运行地进程
-x 显示没有控制终端地进程

nohup后台启动

1
2
3
4
5
6
# 格式:nohup command &
# 后台启动Kibana,会在执行此命令的目录下生成一个nohup.out日志文件
$ nohup kibana &
# 后台启动Logstash,并且指定日志输出到logstash.out文件中
$ nohup logstash -f ${LOGSTASH_HOME}/conf/logstash.conf 2>${LOGSTASH_HOME}/bin/logstash.out 1>${LOGSTASH_HOME}/bin/logstash.out &

Linux性能监控

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
# 查看服务cpu利用
$ top
s - 改变画面更新频率
l - 关闭或开启第一部分第一行 top 信息的表示
t - 关闭或开启第一部分第二行 Tasks 和第三行 Cpus 信息的表示
m - 关闭或开启第一部分第四行 Mem 和 第五行 Swap 信息的表示
N - 以 PID 的大小的顺序排列表示进程列表(第三部分后述)
P - 以 CPU 占用率大小的顺序排列进程列表 (第三部分后述)
M - 以内存占用率大小的顺序排列进程列表 (第三部分后述)
h - 显示帮助
n - 设置在进程列表所显示进程的数量
q - 退出 top
# 查看内存使用情况
$ free
下面是对这些数值的解释:
total:总计物理内存的大小。
used:已使用多大。
free:可用有多少。
Shared:多个进程共享的内存总额。
Buffers/cached:磁盘缓存的大小。
第三行(-/+ buffers/cached):
used:已使用多大。
free:可用有多少。
空闲内存 = free + buffers + cached = total - used
# 显示目前所有文件系统的可用空间及使用情形
$ df -h
# 显示当前目录下所有文件和文件夹的大小
$ ls -lh

tcpdump数据包抓取

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
# tcpdump命令格式
$ tcpdump [-nn] [-i接口] [-w 存储档名] [-c 次数] [-Ae] [-qX] [-r 档案] [所要截取的数据内容]
参数:
-nn:直接以IP及port number显示
-v:输出一个稍微详细的信息,例如在ip包中可以包括ttl和服务类型的信息
-i:后接要监听的网络接口如eth0,wlan0,lo,ppp0
-w:如果你要讲监听所得的封包数据存下来,就用这个参数,后接存档名
-c:监听包个数,若不接会一直监听
-A:封包内容以ASCII码显示,常用语截取www
-q:列出较为尖端的封包信息,每行内容比较精简
-X:可以列出十六进制以及ASCII的封包内容,对于监听封包内容很有用
-r:从后面接的档案封包数据读出来,那个【档案】是已经存在的档案,且这【档案】也是由-w制作出来的
# 抓取包含192.168.1.3的数据包
$ tcpdump -i eth0 -vnn host 192.168.1.3
# 抓取包含192.168.1.3/10网段的数据包
$ tcpdump -i eth0 -vnn net 192.168.1.3/10
# 抓取包含端口22的数据包
$ tcpdump -i eth0 -vnn port 22
# 抓取udp协议的数据包
$ tcpdump -i eth0 -vnn udp
# 抓取ip协议的数据包
$ tcpdump -i eth0 -vnn ip
# 抓取源ip是192.168.1.3数据包
$ tcpdump -i eth0 -vnn src host 192.168.1.3
# 抓取目的ip是211.94.114.21数据包
$ tcpdump -i eth0 -vnn dst host 211.94.114.21
# 抓取源端口是22的数据包
$ tcpdump -i eth0 -vnn src port 22
# 抓取源ip是192.168.1.3且目的端口是22的数据包
$ tcpdump -i eth0 -vnn src host 192.168.1.3 and dst port 22
# 抓取源ip是192.168.1.3或者包含端口是22的数据包
$ tcpdump -i eth0 -vnn src host 192.168.1.3 or port 22
# 抓取源ip是192.168.1.3且端口不是22的数据包
$ tcpdump -i eth0 -vnn src host 192.168.1.3 and not port 22
# 抓取源ip是192.168.1.3且目的端口是22,或源ip是192.168.1.3且目的端口是80的数据包
$ tcpdump -i eth0 -vnn src host 192.168.1.3 and dst port 22 or src host 192.168.1.3 and dst port 80
# 把抓取ip是192.168.1.3的数据包记录存到/Users/ben/Downloads/tcpdump.cap文件中,当抓取100个数据包后就退出程序
$ tcpdump -i eth0 -vnn -w /Users/ben/Downloads/tcpdump.cap -c 100 host 192.168.1.3
# 从/Users/ben/Downloads/tcpdump.cap记录中读取tcp协议的数据包
$ tcpdump -i eth0 -vnn -r /Users/ben/Downloads/tcpdump.cap tcp
# 从/Users/ben/Downloads/tcpdump.cap记录中读取包含192.168.1.3的数据包
$ tcpdump -i eth0 -vnn -r /Users/ben/Downloads/tcpdump.cap host 192.168.1.3
# 抓包结果
00:36:48.658266 IP (tos 0x0, ttl 64, id 15221, offset 0, flags [DF], proto TCP (6), length 40)
10.211.55.4.32903 > 61.135.169.125.443: Flags [.], cksum 0xe7a0 (correct), ack 4785, win 343, length 0
# 抓包结果解释
00:36:48.658266 抓包时间
10.211.55.4.32903 发送端IP以及端口32903
61.135.169.125.443 接收端IP以及端口443

find文件查找相关

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
# find <指定目录> <指定条件> <指定动作>
  - <指定目录>: 所要搜索的目录及其所有子目录。默认为当前目录。
  - <指定条件>: 所要搜索的文件的特征。
  - <指定动作>: 对搜索结果进行特定的处理。
# 参数描述:
! / -not: 非处理
-maxdepth : 搜索层级
-type : 搜索文件的类型,f:文件,l:链接文件,d:文件夹
-name : 文件名,可以使用通配符
-iname : 文件名,忽略文件名大小写
-mtime : 文件的最后modify时间
-atime : 文件的最后access时间
-ctime : 文件的最后create时间
-path : (排除)搜索的文件路径
-prune -o : 配合-path一起使用,排除搜索的路径
-size : 文件的大小
# 搜索当前目录中,所有文件名以my开头的文件 -name(文件名需要带后缀)
$ find . -name 'my*'
# 搜索当前目录中,所有文件名以my开头的文件,并显示它们的详细信息。
$ find . -name 'my*' -ls
# 以文件大小来查找 -size n
$ find . -size +1000000c(在当前目录下查找文件长度大于1 M字节的文件 )
# 在当前目录及所有子目录中查找filename(忽略大小写)
$ find . -iname "Hessian.properties"
# -prune -o用法
# find condition1 -prune -o (condition2 -prune -o condition3 -prune -o) condition4 -print
# 查找当前目录下的properties文件,排除yunyu-stt文件夹
$ find . -path "./yunyu-stt" -prune -o -name "*.properties" -print
# 查找当前目录下的properties文件,排除yunyu-stt和yunyu-task文件夹
$ find . -path "./yunyu-stt" -prune -o -path "./yunyu-task" -prune -o -name "*.properties" -print
# 查找当前目录下的properties文件,排除yunyu-stt和yunyu-task文件夹下的test文件夹
$ find . -path "./yunyu-stt/*/test" -prune -o -path "./yunyu-task/*/test" -prune -o -name "*.properties" -print
# 按照多种条件来查找
# type是file,但不是link
# maxdepth是1
# name是*,但不是gz,zip,pid结尾的文件
# -mtime是表示文件修改时间为大于1天的文件,即距离当前时间2天(48小时)之外的文件
$ find . -maxdepth 1 -type f -name "*" ! -name "*.gz" ! -name "*.zip" ! -name "*.pid" ! -type l -mtime +1

find结合xargs命令批量操作文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 删除当前目录下除了test.txt的其他所有文件
$ find . -type f -not -name test.txt | xargs rm -v
# 默认情况下,find命令每输出一个文件名,后面都会接着输出一个换行符 ('n'),因此我们看到的find的输出都是一行一行的。xargs默认是以空白字符(空格,TAB,换行符)来分割记录的,如果文件名中带有空格这样会被xargs分割开,删除的时候就会找不到该文件了。所以让find在打印出一个文件名之后接着输出一个NULL字符 ('') 而不是换行符,然后再告诉xargs也用NULL字符来作为记录的分隔符。这样就解决了上面的问题。也就是find的-print0和xargs的-0的作用。
$ find . -type f -not -name test.txt -print0 | xargs -0 rm -v
# 查找当前文件下log文件,并且批量替换文件内容martini为middleware
$ find . -name '*.log' | xargs sed -i 's/martini/middleware/g'
# 查找当前目录下的properties文件,排除yunyu-stt和yunyu-task文件夹,并且搜索properties文件中包含127.0.0.1:2181
$ find . -path "./yunyu-stt" -prune -o -path "./yunyu-task" -prune -o -name "*.properties" -print | xargs grep -e "127.0.0.1:2181"
# 查找当前目录下的properties文件,排除yunyu-stt和yunyu-task文件夹下的test文件夹,并且搜索properties文件中包含127.0.0.1:2181
$ find . -path "./yunyu-stt/*/test" -prune -o -path "./yunyu-task/*/test" -prune -o -name "*.properties" -print | xargs grep -e "127.0.0.1:2181"

grep文件内容查找相关

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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
$ grep [options]
[options]主要参数:
-c:只输出匹配行的计数。
-e <范本样式>:指定字符串作为查找文件内容的范本样式。
-I:不区分大小写(只适用于单字符)。
-h:查询多文件时不显示文件名。
-l:查询多文件时只输出包含匹配字符的文件名。
-n:显示匹配行及行号。
-s:不显示不存在或无匹配文本的错误信息。
-v:显示不包含匹配文本的所有行。
-o:只输出文件中匹配到的部分。
-a 不要忽略二进制数据。
-A<显示列数> 除了显示符合范本样式的那一行之外,并显示该行之后的内容。
-b 在显示符合范本样式的那一行之外,并显示该行之前的内容。
-c 计算符合范本样式的列数。
-C<显示列数>或-<显示列数> 除了显示符合范本样式的那一列之外,并显示该列之前后的内容。
-d<进行动作> 当指定要查找的是目录而非文件时,必须使用这项参数,否则grep命令将回报信息并停止动作。
-e<范本样式> 指定字符串作为查找文件内容的范本样式。
-E 将范本样式为延伸的普通表示法来使用,意味着使用能使用扩展正则表达式。
-f<范本文件> 指定范本文件,其内容有一个或多个范本样式,让grep查找符合范本条件的文件内容,格式为每一列的范本样式。
-F 将范本样式视为固定字符串的列表。
-G 将范本样式视为普通的表示法来使用。
-h 在显示符合范本样式的那一列之前,不标示该列所属的文件名称。
-H 在显示符合范本样式的那一列之前,标示该列的文件名称。
-i 忽略字符大小写的差别。
-l 列出文件内容符合指定的范本样式的文件名称。
-L 列出文件内容不符合指定的范本样式的文件名称。
-n 在显示符合范本样式的那一列之前,标示出该列的编号。
-q 不显示任何信息。
-R/-r 此参数的效果和指定“-d recurse”参数相同。
-s 不显示错误信息。
-v 反转查找。
-w 只显示全字符合的列。
-x 只显示全列符合的列。
-y 此参数效果跟“-i”相同。
-o 只输出文件中匹配到的部分。
--exclude-dir 搜索文件内容时,排除指定的文件夹
grep正则表达式元字符集:
- ^ 锚定行的开始 如:'^grep'匹配所有以grep开头的行。
- $ 锚定行的结束 如:'grep$'匹配所有以grep结尾的行。
- . 匹配一个非换行符的字符 如:'gr.p'匹配gr后接一个任意字符,然后是p。
- * 匹配零个或多个先前字符 如:'*grep'匹配所有一个或多个空格后紧跟grep的行。 .*一起用代表任意字符。
- [] 匹配一个指定范围内的字符,如'[Gg]rep'匹配Grep和grep。
- [^] 匹配一个不在指定范围内的字符,如:'[^A-FH-Z]rep'匹配不包含A-R和T-Z的一个字母开头,紧跟rep的行。
- \(..\) 标记匹配字符,如'\(love\)',love被标记为1。
- \ 锚定单词的开始,如:'\匹配包含以grep开头的单词的行。
- \> 锚定单词的结束,如'grep\>'匹配包含以grep结尾的单词的行。
- x\{m\} 重复字符x,m次,如:'0\{5\}'匹配包含5个o的行。
- x\{m,\} 重复字符x,至少m次,如:'o\{5,\}'匹配至少有5个o的行。
- x\{m,n\}重复字符x,至少m次,不多于n次,如:'o\{5,10\}'匹配5--10个o的行。
- \w 匹配文字和数字字符,也就是[A-Za-z0-9],如:'G\w*p'匹配以G后跟零个或多个文字或数字字符,然后是p。
- \b 单词锁定符,如: '\bgrep\b'只匹配grep。
关于匹配的实例:
# 统计所有以“48”字符开头的行有多少
$ grep -c "48" test.txt
# 不区分大小写查找“May”所有的行)
$ grep -i "May" test.txt
# 显示行号;显示匹配字符“48”的行及行号,相同于 nl test.txt |grep 48)
$ grep -n "48" test.txt
# 显示输出没有字符“48”所有的行)
$ grep -v "48" test.txt
# 显示输出字符“471”所在的行)
$ grep "471" test.txt
# 显示输出以字符“48”开头,并在字符“48”后是一个tab键所在的行
$ grep "48;" test.txt
# 显示输出以字符“48”开头,第三个字符是“3”或是“4”的所有的行)
$ grep "48[34]" test.txt
# 显示输出行首不是字符“48”的行)
$ grep "^[^48]" test.txt
# 设置大小写查找:显示输出第一个字符以“M”或“m”开头,以字符“ay”结束的行)
$ grep "[Mm]ay" test.txt
# 显示输出第一个字符是“K”,第二、三、四是任意字符,第五个字符是“D”所在的行)
$ grep "K…D" test.txt
# 显示输出第一个字符的范围是“A-D”,第二个字符是“9”,第三个字符的是“D”的所有的行
$ grep "[A-Z][9]D" test.txt
# 显示第一个字符是3或5,第二三个字符是任意,以1998结尾的所有行
$ grep "[35]..1998" test.txt
# 模式出现几率查找:显示输出字符“4”至少重复出现两次的所有行
$ grep "4\{2,\}" test.txt
# 模式出现几率查找:显示输出字符“9”至少重复出现三次的所有行
$ grep "9\{3,\}" test.txt
# 模式出现几率查找:显示输出字符“9”重复出现的次数在一定范围内,重复出现2次或3次所有行
$ grep "9\{2,3\}" test.txt
# 显示输出空行的行号
$ grep -n "^$" test.txt
# 如果要查询目录列表中的目录 同:ls -d *
$ ls -l |grep "^d"
# 在一个目录中查询不包含目录的所有文件
$ ls -l |grep "^d[d]"
# 查询其他用户和用户组成员有可执行权限的目录集合
$ ls -l |grep "^d…..x..x"
# 读出logcat.log文件的内容,通过管道转发给grep作为输入内容,过滤包含"Displayed"的行,将输出内容再作为输入能过管道转发给下一个grep
$ cat logcat.log | grep -n 'Displayed' | grep ms
# 匹配testgrep文件所有wirelessqa的行
$ grep -e wirelessqa testgrep
# '^wirelessqa'匹配testgrep文件所有以wirelessqa开头的行
$ grep -e ^[wirelessqa] testgrep
# 'wirelessqa$'匹配所有以wirelessqa结尾的行
$ grep -e [wirelessqa]$ testgrep
# .*一起用代表任意字符,'.*qa'匹配任何词以qa结尾的行
$ grep -e .*qa testgrep
# '\<wire'匹配包含以wire开头的单词的行
$ grep -e '\<wire' testgrep
# 'blog\>'匹配包含以blog结尾的单词的行
$ grep -e 'blog\>' testgrep
# 匹配aaa文件中'blog.csdn.net'的行的上下5行内容
$ grep -5 -e blog.csdn.net aaa
# 显示/Users/ben目录下的文件(不含子目录)包含blog的行
$ grep blog /Users/ben
# 显示/Users/ben目录下的文件(包含子目录)包含blog的行
$ grep -r blog /Users/ben
# 结合sort和uniq一起使用可以排序并去重
# 这里使用了正则,"."是匹配一个非换行符的字符
$ cat adlog_kafka_20161204.log | grep -o 'logs.....' | sort | uniq
# 统计各行在文件中出现的次数
$ sort file.txt | uniq -c
# 在文件中找出重复的行
$ sort file.txt | uniq -d
# 搜索当前文件夹下的匹配127.0.0.1:2181的内容,排除yunyu-stt文件夹下的文件
$ grep -r "127.0.0.1:2181" . --exclude-dir="./yunyu-stt"
# 或操作
# 找出文件(filename)中包含123或者包含abc的行
$ grep -E '123|abc' filename
# 用egrep同样可以实现
$ egrep '123|abc' filename
# awk 的实现方式
$ awk '/123|abc/' filename
# 与操作
# 显示既匹配 pattern1 又匹配 pattern2 的行
$ grep pattern1 files | grep pattern2
# 其他操作
# 不区分大小写地搜索。默认情况区分大小写
$ grep -i pattern files
# 只列出匹配的文件名
$ grep -l pattern files
# 列出不匹配的文件名
$ grep -L pattern files
# 只匹配整个单词,而不是字符串的一部分(如匹配‘magic’,而不是‘magical’)
$ grep -w pattern files
# 匹配的上下文分别显示[number]行
$ grep -C number pattern files

sort排序用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# sort参数
-f : 忽略大小写的差异,例如 A 与 a 视为编码相同
-b : 忽略最前面的空格符部分
-M : 以月份的名字来排序,例如 JAN, DEC 等等的排序方法
-n : 使用『纯数字』进行排序(默认是以文字型态来排序的)
-r : 反向排序
-u : 就是 uniq ,相同的数据中,仅出现一行代表
-t : 分隔符,默认是用 [tab] 键来分隔
-k : 以那个区间 (field) 来进行排序的意思
# 在test.txt文本中匹配不是shenzhen的行,获取第3列和第4列的内容,然后按照第4列进行排序
$ cat test.txt | grep -v shenzhen | awk '{print $4, $3}' | sort
20 shanghai
28 beijing
28 beijing
30 shanghai
# 在test.txt文本中匹配不是shenzhen的行,获取第3列和第4列的内容,这里指定输出后的第2列进行排序,即awk的$3
$ cat test.txt | grep -v shenzhen | awk '{print $4, $3}' | sort -k 2
28 beijing
28 beijing
20 shanghai
30 shanghai

uniq去重用法

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
# uniq参数
-i : 忽略大小写字符的不同
-c : 列出重复出现的次数
-u : 只显示不重复行
-d : 仅显示重复行
# 去重后的结果
$ cat test.txt | grep -v shenzhen | awk '{print $4, $3}' | sort | uniq
20 shanghai
28 beijing
30 shanghai
# 列出重复出现的次数
$ cat test.txt | grep -v shenzhen | awk '{print $4, $3}' | sort | uniq -c
1 20 shanghai
2 28 beijing
1 30 shanghai
# 只显示不重复行
$ cat test.txt | grep -v shenzhen | awk '{print $4, $3}' | sort | uniq -u
20 shanghai
30 shanghai
# 仅显示重复行
$ cat test.txt | grep -v shenzhen | awk '{print $4, $3}' | sort | uniq -d
28 beijing

sed在线编辑器

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
sed [-nefr] [动作]
# 选项与参数:
-n : 使用安静(silent)模式。在一般 sed 的用法中,所有来自 STDIN 的数据一般都会被列出到终端上。但如果加上 -n 参数后,则只有经过sed 特殊处理的那一行(或者动作)才会被列出来。
-e : 直接在命令列模式上进行 sed 的动作编辑;
-f : 直接将 sed 的动作写在一个文件内, -f filename 则可以运行 filename 内的 sed 动作;
-r : sed 的动作支持的是延伸型正规表示法的语法。(默认是基础正规表示法语法)
-i : 直接修改读取的文件内容,而不是输出到终端。
# 动作说明:[n1[,n2]]function
# n1, n2 : 不见得会存在,一般代表『选择进行动作的行数』,举例来说,如果我的动作是需要在 10 到 20 行之间进行的,则『 10,20[动作行为] 』
# function:
a : 新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)~
c : 取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行!
d : 删除,因为是删除啊,所以 d 后面通常不接任何咚咚;
i : 插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行);
p : 列印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运行~
s : 取代,可以直接进行取代的工作哩!通常这个 s 的动作可以搭配正规表示法!例如 1,20s/old/new/g 就是啦!
# 列出test.txt文件的内容,并且显示将2-5行的结果
$ nl ~/Downloads/test.txt | sed -n '2,5p'
# 列出test.txt文件的内容,并且显示将2-5行删除后的结果
$ nl ~/Downloads/test.txt | sed '2,5d'
# 列出test.txt文件的内容,并且显示在第2行之前插入新的记录后的结果
$ nl ~/Downloads/test.txt | sed '2i 111'
# 搜索test.txt文件内容匹配beijing的,并且显示匹配的结果
$ nl ~/Downloads/test.txt | sed -n '/beijing/p'
# 搜索test.txt文件内容匹配beijing的,并且显示替换匹配的结果
$ nl ~/Downloads/test.txt | sed -n '/beijing/{s/beijing/shanghai/;p}'
# sed 's/要被取代的字串/新的字串/g'
# 搜索test.txt文件内容匹配beijing的,并且显示替换匹配的结果
$ nl ~/Downloads/test.txt | sed -n 's/beijing/shanghai/gp'
# 替换源文件的内容
$ sed -i 's/beijing/shanghai/g' ~/Downloads/test.txt
# 替换Downloads目录下所有文件,匹配内容beijing,替换为shanghai
$ sed -i 's/beijing/shanghai/g' `grep -rn "beijing" ~/Downloads/`

sed结合grep命令批量替换文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
# 将当前目录(包括子目录)中所有以test开头的txt文件中的zzzz字符串替换为oooo字符串
sed -i 's/zzzz/oooo/g' `grep zzzz -rl --include="test_*.txt" ./`
# sed参数
-i : 表示操作的是文件,``括起来的grep命令,表示将grep命令的的结果作为操作文件
s/zzzz/oooo/ : 表示查找zzzz并替换为oooo
g : 表示一行中有多个zzzz的时候,都替换,而不是仅替换第一个
# grep参数
-r : 表示查找所有子目录
-l : 表示仅列出符合条件的文件名,用来传给sed命令做操作
--include="test_*.txt" : 表示仅查找test开头的txt文件
./ : 表示要查找的根目录为当前目录

du定位大文件

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
# 查看当前目录下的所有文件大小(不包括下级目录,x参数会去除掉mount上去的目录)
# 如果有子文件夹,那就进去继续执行'du -shx *'一级一级地找
$ du -shx *
# 如果有文件被删除,却被某进程占用,并且还在写
# 找到被删除的却还被进程占用的文件,进程也被列出,把相关的进程重启一遍,空间就被释放了。
$ lsof | grep deleted
# 参数
# -b : 以Byte单位输出
# -k : 以KB为单位输出
# -m : 以MB为单位输出
# -h : 以K,M,G为单位显示
# -s : 仅显示总计,只列出最后加总的值
# 找出大于2000m的文件夹
$ du -m ~/Downloads | awk '$1 > 2000'
3048 /Users/yunyu/Downloads/develop
7966 /Users/yunyu/Downloads/install
17671 /Users/yunyu/Downloads/yunyu
31673 /Users/yunyu/Downloads
# 以可阅读的单位显示某文件夹的大小
$ du -sh ~/Downloads
31G /Users/yunyu/Downloads
# 以可阅读的单位显示某文件的大小
$ du -h command.log
52K command.log
# 统计多个文件的大小总和
$ du -ch command.log command_1.log
52K command.log
4.0K command_1.log
56K total
# 找出大于2000m的文件夹,并且按照文件大小排序
du -m ~/Downloads | awk '$1 > 2000' | sort -nr | more
31673 /Users/yunyu/Downloads
17671 /Users/yunyu/Downloads/yunyu
7966 /Users/yunyu/Downloads/install
3048 /Users/yunyu/Downloads/develop

split拆分合并文件

1
2
3
4
5
# 将大文件bigfile.tar按照每100m拆分成多个小文件
# 拆分后的小文件命名类似:split-bigfile.aa, split-bigfile.ab, split-bigfile.ac等等
split -b 100m bigfile.tar split-bigfile.
# 将多个小文件split-bigfile.*合并成一个大文件bigfile.tar
cat split-bigfile.* > bigfile.tar

chown、useradd、groupadd、userdel、usermod、passwd、groupdel(新建用户、用户组,给用户分配权限)

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
# 添加新的账号
$ useradd 选项 用户名
# 删除账号
$ userdel 选项 用户名
# 修改帐号
$ usermod 选项 用户名
# 用户密码的管理
$ passwd 选项 用户名
Old password:******
New password:*******
Re-enter new password:*******
# 设置密码为空
$ passwd -d 用户名
# 添加新的组
$ groupadd 选项 用户组
# 删除组
$ groupdel 用户组
# 修改组
$ groupmod 选项 用户组
# 修改用户所在组(把某个用户改为新的用户组,这样会离开其他用户组)
$ usermod -g 用户组 用户名
# 修改用户所在组(-a代表append,给某个用户添加一个新的组,而不必离开 其他用户组)
$ usermod -a -G 用户组 用户名
# 分配权限(修改某个目录的所属用户和用户组)
$ chown -R hadoop:hadoop /usr/hadoop/
# Linux中查看所有系统用户命令
$ cat /etc/passwd
# Linux中查看所有组用户
$ cat /etc/group
# 查看当前用户所属组
$ groups
# 查看root用户所属组
$ groups root
# 查看当前用户
$ whoami
# 添加用户到用户组
$ gpasswd -a 用户名 用户组
# 从用户组中删除用户
$ gpasswd -d 用户名 用户组

chmod

方式一:用包含字母和操作符表达式的文字设定法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
其语法格式为:chmod [who] [opt] [mode] 文件/目录名
# who表示对象,是以下字母中的一个或组合:
u:表示文件所有者
g:表示同组用户
o:表示其它用户
a:表示所有用户
# opt则是代表操作:
+:添加某个权限
-:取消某个权限
=:赋予给定的权限,并取消原有的权限
# mode则代表权限:
r:可读
w:可写
x:可执行 表示只有当该档案是个子目录或者该档案已经被设定过为可执行
# 为同组用户增加对文件test.txt的读写权限
$ chmod g+rw test.txt
# 给test.sh执行权限
$ chmod +x test.sh

方式二:用数字设定法

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
其语法格式为:chmod [mode] 文件名 或者 chmod UPO 文件名
UPO分别表示User、Group、及Other的权限
关键是mode的取值,其实很简单,我们将rwx看成二进制数,如果有则有1表示,没有则有0表示。
那么rwx r-x r-- 则可以表示成为:111 101 100 再将其每三位转换成为一个十进制数,就是754。
可读 : w=4
可写 : r=2
可执行 : x=1
例如,我们想让test.txt这个文件的权限为:
自己 同组用户 其他用户
可读 是 是 是
可写 是 是
可执行
那么,我们先根据上表得到权限串为:rw-rw-r--,那么转换成二进制数就是110 110 100,
再每三位转换成为一个十进制数,就得到664。
因此我们执行命令:chmod 664 a.txt
例如:
-rw------- (600) -- 只有属主有读写权限
-rw-r--r-- (644) -- 只有属主有读写权限;而属组用户和其他用户只有读权限
-rwx------ (700) -- 只有属主有读、写、执行权限
-rwxrwx--- (770) -- 只有属主和属组用户有读、写、执行权限
-rwxrwxrwx (777) -- 所有用户都有读、写、执行权限
# 给test.sh所有用户读、写、执行权限
$ chmod 777 test.sh

查看用户登录信息

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
$ w
$ w username
18:40 up 11 days, 21:35, 5 users, load averages: 3.35 2.94 2.92
USER TTY FROM LOGIN@ IDLE WHAT
ben console - 16 816 11days -
ben s000 - 16 816 - w
ben s001 - 14:10 12 -bash
ben s003 - 21 816 13 -bash
ben s004 - 16:28 2 -bash
# 输出内容
USER :登陆的用户名;
TTY :登陆终端;
FROM :从哪个IP地址登录;
LOGIN@ :登陆时间;
IDLE :用户闲置时间;
JCPU :指的是和该终端连接的所有进程占用的时间。这个时间里并不包括过去的后台作业时间,但却包括当前正在运行的后台作业所占用的时间;
PCPU :是指当前进程所占用的时间;
WHAT :当前正在运行的命令;
$ who
ben console Aug 16 21:05
ben ttys000 Aug 16 23:26
ben ttys001 Aug 28 14:10
ben ttys003 Aug 21 13:25
ben ttys004 Aug 28 16:28
# 输出内容
- 用户名
- 登录终端
- 登录时间(登录来源IP地址)
# 查看当前登录和过去登录的用户信息
# 注释:last命令默认读取/var/log/wtmp文件数据
$ last
ben ttys005 Sun Aug 28 17:34 - 17:53 (00:19)
ben ttys005 Sun Aug 28 17:21 - 17:29 (00:07)
ben ttys004 Sun Aug 28 16:28 still logged in
ben ttys001 Sun Aug 28 14:10 still logged in
ben ttys002 Wed Aug 24 23:54 - 18:25 (3+18:31)
# 输出内容
- 用户名
- 登录终端
- 登录IP
- 登录时间
- 退出时间(在线时间)
# 查看所有用户最后一次登录时间(Ubuntu好用,Mac不好用,Mac下没有/var/log/lastlog文件)
# 注释:lastlog命令默认读取/var/log/lastlog文件内容
$ lastlog
# 输出内容
- 用户名
- 登录终端
- 登录IP
- 最后一次登录时间

alias别名(很有用,便于记录常用命令)

1
2
3
4
5
6
7
8
# 查看当前设置了多少别名,直接输入alias
$ alias
# 添加别名,直接输入alias '别名'='命令'(注意等号之间不能有空格)
$ alias cdmanage='cd /Users/ben/workspace/manage/target'
# 删除一个别名
$ unalias cdmanage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 永久保存alias的方式(本人是Mac操作系统)
# 进入用户根目录
$ cd /Users/ben
$ vi .bash_profile
# 编辑文件如下并保存
alias cdben="cd /Users/ben"
alias cdcore="cd /Users/ben/workspace/core"
alias cdmanage="cd /Users/ben/workspace/manage"
alias cdservice="cd /Users/ben/workspace/service"
alias cdmongo2="cd /Users/ben/dev/mongodb-osx-x86_64-2.6.9"
alias cdmongo3="cd /Users/ben/dev/mongodb-osx-x86_64-3.0.2"
alias cdredis="cd /Users/ben/dev/redis-3.0.0"
alias cdsearch01="cd /Users/ben/dev/search01"
# 使上面的别名立即生效
$ source .bash_profile

Linux中修改IP地址

1
2
3
4
5
6
7
8
第一步:
# 修改/etc/sysconfig/network-scripts/ifcfg-eth0配置文件
IPADDR=192.168.0.248
GATEWAY=192.168.0.1
DNS1=192.168.0.1
第二步:
# 修改/etc/hosts文件
192.168.0.248 hostname

Linux中修改主机名

1
2
3
4
5
6
7
第一步:
$ hostname oratest
第二步:
# 修改/etc/sysconfig/network中的hostname
第三步:
# 修改/etc/hosts文件
记得重启!!!

tar压缩/解压缩

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
# tar命令详解
-c: 建立压缩档案
-x:解压
-t:查看内容
-r:向压缩归档文件末尾追加文件
-u:更新原压缩包中的文件
# 这五个是独立的命令,压缩解压都要用到其中一个,可以和别的命令连用但只能用其中一个。
# 下面的参数是根据需要在压缩或解压档案时可选的。
-z:有gzip属性的
-j:有bz2属性的
-Z:有compress属性的
-v:显示所有过程
-O:将文件解开到标准输出
参数-f是必须的
-f: 使用档案名字,切记,这个参数是最后一个参数,后面只能接档案名。
# 压缩
# 这条命令是将所有.jpg的文件打成一个名为all.tar的包。-c是表示产生新的包,-f指定包的文件名。
$ tar -cf all.tar *.jpg
# 将目录里所有jpg文件打包成jpg.tar后,并且将其用gzip压缩,生成一个gzip压缩过的包,命名为jpg.tar.gz
$ tar –czf jpg.tar.gz *.jpg
# 这条命令是将所有.gif的文件增加到all.tar的包里面去。-r是表示增加文件的意思。
$ tar -rf all.tar *.gif
# 这条命令是更新原来tar包all.tar中logo.gif文件,-u是表示更新文件的意思。
$ tar -uf all.tar logo.gif
# 查看
# 这条命令是在不解压的情况下查看all.tar包中所有文件,-t是列出文件的意思
$ tar -tf all.tar
# 解压
# 这条命令是解出all.tar包中所有文件,-x是解开的意思
$ tar -xf all.tar
# 解压tar包
$ tar –xvf file.tar
# 解压tar.gz
$ tar -xzvf file.tar.gz
# 解压 tar.bz2
$ tar -xjvf file.tar.bz2
# 解压tar.Z
$ tar –xZvf file.tar.Z
# 解压到指定的目录,如果目录不存在就创建
# 将text.tar.gz 解压到 /home/app/test/ (绝对路径)下
$ tar -zxvf ./text.tar.gz -C /home/app/test/
# 解压rar
$ unrar e file.rar
# 压缩zip
$ zip aa.zip aa.sh
# 将某一个目录下的文件压缩成一个zip包
$ zip folder.zip folder/*
# 解压zip
$ unzip file.zip
# 解压zip到指定目录
# -n : 如果已有相同的文件存在,要求unzip命令不覆盖原先的文件
# -o : 如果已有相同的文件存在,要求unzip命令覆盖原先的文
$ unzip -n test.zip -d /tmp
$ unzip -o test.zip -d /tmp
# 总结
1、*.tar 用 tar –xvf 解压
2、*.gz 用 gzip -d或者gunzip 解压
3、*.tar.gz和*.tgz 用 tar –xzf 解压
4、*.bz2 用 bzip2 -d或者用bunzip2 解压
5、*.tar.bz2用tar –xjf 解压
6、*.Z 用 uncompress 解压
7、*.tar.Z 用tar –xZf 解压
8、*.rar 用 unrar e解压
9、*.zip 用 unzip 解压

软链接

1
2
3
4
5
6
7
8
# 创建软链接
# ln -s a b 中的 a 就是源文件,b是链接文件名,其作用是当进入b目录,实际上是链接进入了a目录
# 值得注意的是执行命令的时候,应该是a目录已经建立,目录b没有建立。我最开始操作的是也把b目录给建立了,结果就不对了
# 如下面的示例,当我们执行命令cd /usr/local/java/的时候,实际上是进入了/software/java/
$ ln -s /software/java /usr/local/java
# 删除软链接(注意不是rm -rf b/)
rm -rf b

其他常用命令

1
2
3
4
5
6
7
8
9
10
11
# 创建文件夹命令
mkdir test
# 创建多级目录的命令
mkdir -p /software/temp/nginx
# 删除文件夹命令
rm -rf test
# 复制文件夹命令
cp -rf /home/root/Downloads/* /home/root/tools

MySQL数据库的备份与还原

1
2
3
4
5
6
7
8
9
# 备份
# root为用户名,user为要备份的数据库,自动会备份到/home/ben/文件目录下
$ mysqldump -u root -p user > /home/ben/user20160101.sql
# 压缩备份
$ mysqldump -u root -p user | gzip > user20160101.sql.gz
# 还原
$ mysql -u root -p user < /home/ben/user20160101.sql

参考文章:

Docker实战(六)Docker安装Redis

初次使用Docker安装各种环境,果然是一堆坑啊,坑,坑,坑,坑死我了。。

大概步骤:
  1. 上传Redis到宿主机,或者在宿主机中下载
  2. 编写Dockerfile构建镜像
  3. 编写supervisor配置文件
  4. build和run
Redis安装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 下载安装Redis
$ wget http://download.redis.io/releases/redis-3.0.5.tar.gz
$ tar xzf redis-3.0.5.tar.gz
$ cd redis-3.0.5
$ make
# make完成之后,可以运行make test来验证
$ make test
# 启动redis server,使用默认的redis.conf配置
$ cd src
$ ./redis-server ../redis.conf
# 启动redis client来连接server,登录密码可以参考redis.conf配置
$ cd src
$ ./redis-cli
# 参考:
http://redis.io/download
# 如果以上操作都没问题,就说明redis已经安装和启动成功了
Dockerfile文件
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
############################################
# version : birdben/redis:v1
# desc : 当前版本安装的redis
############################################
# 设置继承自我们创建的 tools 镜像
FROM birdben/tools:v1
# 下面是一些创建者的基本信息
MAINTAINER birdben (191654006@163.com)
# 设置环境变量,所有操作都是非交互式的
ENV DEBIAN_FRONTEND noninteractive
# 添加 supervisord 的配置文件,并复制配置文件到对应目录下面。(supervisord.conf文件和Dockerfile文件在同一路径)
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# 设置 redis 的环境变量,若读者有其他的环境变量需要设置,也可以在这里添加。
ENV REDIS_HOME /software/redis-3.0.0
ENV LC_ALL C
# 复制 redis-3.0.0 文件到镜像中(redis-3.0.0文件夹要和Dockerfile文件在同一路径)
ADD redis-3.0.0 /software/redis-3.0.0
# 挂载/redis目录
VOLUME ["/redis"]
# 容器需要开放Redis 6379端口
EXPOSE 6379
# 执行supervisord来同时执行多个命令,使用 supervisord 的可执行路径启动服务。
CMD ["/usr/bin/supervisord"]
Dockerfile源文件链接:

https://github.com/birdben/birdDocker/blob/master/redis/Dockerfile

supervisor配置文件内容
1
2
3
4
5
6
7
8
9
10
11
12
13
# 配置文件包含目录和进程
# 第一段 supervsord 配置软件本身,使用 nodaemon 参数来运行。
# 第二段包含要控制的 2 个服务。每一段包含一个服务的目录和启动这个服务的命令。
[supervisord]
nodaemon=true
[program:sshd]
command=/usr/sbin/sshd -D
[program:redis]
# 注意这里指定的redis.conf文件路径,必须是绝对路径
command=/software/redis-3.0.0/src/redis-server /redis/redis.conf
控制台终端
1
2
3
4
# 构建镜像
$ docker build -t="birdben/redis:v1" .
# 执行已经构件好的镜像
$ docker run -p 9999:22 -p 6379:6379 -t -i -v /docker/redis:/redis "birdben/redis:v1"
遇到的问题和解决方法
1
2
3
4
# supervisor配置文件内容
# 注意这里指定的redis.conf文件路径,必须是绝对路径
# 好用 command=/software/redis-3.0.0/src/redis-server /redis/redis.conf
# 不好用 command=/software/redis-3.0.0/src/redis-server ../redis.conf

参考文章:

Docker实战(五)Docker安装Tomcat

这里安装的Tomcat继承了之前JDK7的Docker镜像,因为运行Tomcat需要依赖JDK。

大概步骤:
  1. 上传tomcat7到宿主机
  2. 编写Dockerfile构建镜像
  3. 编写supervisor配置文件
  4. build和run
1
2
3
4
5
# 方式一:可以通过ssh上传指定版本的tomcat(这里选择第一种)
# 1. 上传tomcat7到宿主机
# 2. 将tomcat7都解压到指定的目录下(和Dockerfile文件同目录)
# 方式二:从官网或者镜像网站下载tomcat7
Dockerfile文件
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
############################################
# version : birdben/tomcat7:v1
# desc : 当前版本安装的tomcat7
############################################
# 设置继承自我们创建的 jdk7 镜像
FROM birdben/jdk7:v1
# 下面是一些创建者的基本信息
MAINTAINER birdben (191654006@163.com)
# 设置环境变量,所有操作都是非交互式的
ENV DEBIAN_FRONTEND noninteractive
# 添加 supervisord 的配置文件,并复制配置文件到对应目录下面。(supervisord.conf文件和Dockerfile文件在同一路径)
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# 设置 tomcat 的环境变量,若读者有其他的环境变量需要设置,也可以在这里添加。
ENV CATALINA_HOME /software/tomcat7
# 复制 apache-tomcat-7.0.55 文件到镜像中(apache-tomcat-7.0.55 文件夹要和Dockerfile文件在同一路径)
ADD apache-tomcat-7.0.55 /software/tomcat7
# 容器需要开放Tomcat 8080端口
EXPOSE 8080
# 执行supervisord来同时执行多个命令,使用 supervisord 的可执行路径启动服务。
CMD ["/usr/bin/supervisord"]
Dockerfile源文件链接:

https://github.com/birdben/birdDocker/blob/master/tomcat7/Dockerfile

supervisor配置文件内容
1
2
3
4
5
6
7
8
9
10
11
12
# 配置文件包含目录和进程
# 第一段 supervsord 配置软件本身,使用 nodaemon 参数来运行。
# 第二段包含要控制的 2 个服务。每一段包含一个服务的目录和启动这个服务的命令。
[supervisord]
nodaemon=true
[program:sshd]
command=/usr/sbin/sshd -D
[program:tomcat]
command=/bin/bash -c "exec ${CATALINA_HOME}/bin/catalina.sh run"
控制台终端
1
2
3
4
# 构建镜像
docker build -t="birdben/tomcat7:v1" .
# 执行已经构件好的镜像
docker run -p 9999:22 -p 8080:8080 -t -i birdben/tomcat7:v1
浏览器访问
1
http://10.211.55.4:8080/

参考文章:

Docker实战(四)Docker安装JDK

安装JDK7和JDK8基本没有区别,只是Dockerfile有所不同,但是他们都继承了之前tools的Docker镜像,下面给出了JDK7和JDK8的Dockerfile源文件。

大概步骤:
  1. 上传jdk7到宿主机
  2. 编写Dockerfile构建镜像
  3. 编写supervisor配置文件
  4. build和run
1
2
3
4
5
# 方式一:可以通过ssh上传指定版本的jdk(这里选择第一种)
# 1. 上传jdk7到宿主机
# 2. 将jdk7都解压到指定的目录下(和Dockerfile文件同目录)
# 方式二:从官网或者镜像网站下载jdk7
Dockerfile文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
############################################
# version : birdben/jdk7:v1
# desc : 当前版本安装的jdk7
############################################
# 设置继承自我们创建的 tools 镜像
FROM birdben/tools:v1
# 下面是一些创建者的基本信息
MAINTAINER birdben (191654006@163.com)
# 设置环境变量,所有操作都是非交互式的
ENV DEBIAN_FRONTEND noninteractive
# 添加 supervisord 的配置文件,并复制配置文件到对应目录下面。(supervisord.conf文件和Dockerfile文件在同一路径)
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# 设置 jdk 的环境变量,若读者有其他的环境变量需要设置,也可以在这里添加。
ENV JAVA_HOME /software/jdk7
# 复制 jdk1.7.0_71 文件到镜像中(jdk1.7.0_71 文件夹要和Dockerfile文件在同一路径)
ADD jdk1.7.0_71 /software/jdk7
# 执行supervisord来同时执行多个命令,使用 supervisord 的可执行路径启动服务。
CMD ["/usr/bin/supervisord"]
Dockerfile源文件链接:

https://github.com/birdben/birdDocker/blob/master/jdk7/Dockerfile
https://github.com/birdben/birdDocker/blob/master/jdk8/Dockerfile

supervisor配置文件内容
1
2
3
4
5
6
7
8
9
# 配置文件包含目录和进程
# 第一段 supervsord 配置软件本身,使用 nodaemon 参数来运行。
# 第二段包含要控制的 2 个服务。每一段包含一个服务的目录和启动这个服务的命令。
[supervisord]
nodaemon=true
[program:sshd]
command=/usr/sbin/sshd -D
控制台终端
1
2
3
4
# 构建镜像
docker build -t="birdben/jdk7:v1" .
# 执行已经构件好的镜像
docker run -p 9999:22 -t -i birdben/jdk7:v1

Docker实战(三)Docker安装ssh,supervisor等基础工具

需要提前下载好官方的ubuntu镜像,我这里使用的是ubuntu:14.04版本,这里安装了一些基础的工具ssh,curl,wget,vim等等,包括后续的Docker镜像需要启动多个服务,所以提前先装好supervisor。

Dockerfile文件
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
############################################
# version : birdben/tools:v1
# desc : 当前版本安装的ssh,wget,curl,supervisor
############################################
# 设置继承自ubuntu官方镜像
FROM ubuntu:14.04
# 下面是一些创建者的基本信息
MAINTAINER birdben (191654006@163.com)
# 注意这里要更改系统的时区设置,因为在 web 应用中经常会用到时区这个系统变量,默认的 ubuntu 会让你的应用程序发生不可思议的效果哦
ENV DEBIAN_FRONTEND noninteractive
# 清空ubuntu更新包
RUN sudo rm -rf /var/lib/apt/lists/*
# 一次性安装vim,wget,curl,ssh server等必备软件
# RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe"> /etc/apt/sources.list
RUN sudo apt-get update
RUN sudo apt-get install -y vim wget curl openssh-server sudo
RUN sudo mkdir -p /var/run/sshd
# 将sshd的UsePAM参数设置成no
RUN sed -i 's/UsePAM yes/UsePAM no/g' /etc/ssh/sshd_config
# 添加测试用户admin,密码admin,并且将此用户添加到sudoers里
RUN useradd admin
RUN echo "admin:admin" | chpasswd
RUN echo "admin ALL=(ALL) ALL" >> /etc/sudoers
# 把admin用户的shell改成bash,否则SSH登录Ubuntu服务器,命令行不显示用户名和目录
RUN usermod -s /bin/bash admin
# 安装supervisor工具
RUN sudo apt-get install -y supervisor
RUN sudo mkdir -p /var/log/supervisor
# 添加 supervisord 的配置文件,并复制配置文件到对应目录下面。(supervisord.conf文件和Dockerfile文件在同一路径)
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# 容器需要开放SSH 22端口
EXPOSE 22
# 执行supervisord来同时执行多个命令,使用 supervisord 的可执行路径启动服务。
CMD ["/usr/bin/supervisord"]
Dockerfile源文件链接:
supervisor配置文件内容
1
2
3
4
5
6
7
8
9
# 配置文件包含目录和进程
# 第一段 supervsord 配置软件本身,使用 nodaemon 参数来运行。
# 第二段包含要控制的 2 个服务。每一段包含一个服务的目录和启动这个服务的命令。
[supervisord]
nodaemon=true
[program:sshd]
command=/usr/sbin/sshd -D
supervisor源文件链接:
控制台终端
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
# 构建镜像
$ docker build -t="birdben/tools:v1" .
# 运行已经构件好的镜像,因为我使用的ubuntu虚拟机安装的Docker,而我的虚拟机也安装了ssh服务,所以这里指定了宿主机的端口为9999对应Docker容器的22端口
$ docker run -p 9999:22 -t -i "birdben/tools:v1"
# 此时查看宿主机的9999端口,已经处于监听状态:
$ netstat -aunpt (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 127.0.1.1:53 0.0.0.0:* LISTEN - tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN - tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN - tcp6 0 0 :::9999 :::* LISTEN - tcp6 0 0 :::22 :::* LISTEN -
# 再查看一下宿主机的IP地址,我这里的IP地址是10.211.55.4
$ ifconfig
# 此时可以通过ssh远程连接Docker容器了
$ ssh root@10.211.55.4 -p 9999
# 输入密码应该就可以连接到Docker容器了
# 如果遇到下面的问题,这是Linux重装或则openssh-server重装引起的,执行以下命令即可
$ ssh-keygen -R 10.211.55.4
# 如果上述方式不好用,进入此目录,删除的10.211.55.4相关rsa的信息即可
$ vi ~/.ssh/known_hosts
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
8c:4b:88:88:53:4a:b1:f0:e2:da:9a:dc:aa:67:46:df.
Please contact your system administrator.
Add correct host key in /Users/ben/.ssh/known_hosts to get rid of this message.
Offending RSA key in /Users/ben/.ssh/known_hosts:18
RSA host key for [10.211.55.4]:9999 has changed and you have requested strict checking.
Host key verification failed.
遇到的问题和解决办法
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
Q:ssh登录后,命令行不显示用户名和目录
A:把用户的shell改成bash,否则SSH登录Ubuntu服务器,命令行不显示用户名和目录
RUN usermod -s /bin/bash admin
参考:
http://bbs.csdn.net/topics/390188284
Q:ssh创建admin登录用户,不使用root登录
A:这里使用ssh不建议直接使用root用户登录,建议创建一个新的用户例如admin登录
RUN useradd admin
RUN echo "admin:admin" | chpasswd
RUN echo "admin ALL=(ALL) ALL" >> /etc/sudoers
参考:
http://blog.csdn.net/kongxx/article/details/38395305
http://blog.csdn.net/kongxx/article/details/38412119
Q:如何修改ssh服务相关配置
A:可以直接修改sshd_config配置文件
vi /etc/ssh/sshd_config
需要修改如下
# 设置不允许root用户登录
PermitRootLogin yes
# 利用 PAM 管理使用者认证有很多好处,可以记录与管理。
# 所以这里我们建议你使用 UsePAM 且 ChallengeResponseAuthentication 设定为 no,但是我们这里为了简单设置为密码认证,ChallengeResponseAuthentication设定为yes,UsePAM设置为no
ChallengeResponseAuthentication yes
UsePAM no
参考:
http://my.oschina.net/fsmwhx/blog/143354
http://www.cnblogs.com/ggjucheng/archive/2012/08/19/2646032.html

参考文章:

Docker实战(二)制作自己的Docker镜像

制作自己的Docker镜像主要有如下两种方式:

1.使用docker commit 命令来创建镜像

  1. 通过docker run命令启动容器
  2. 修改docker镜像内容
  3. docker commit提交修改的镜像
  4. docker run新的镜像

2.使用 Dockerfile 来创建镜像

使用 docker commit 来扩展一个镜像比较简单,但是不方便在一个团队中分享。我们可以使用 docker build 来创建一个新的镜像。为此,首先需要创建一个 Dockerfile,包含一些如何创建镜像的指令。

Dockerfile 基本的语法

  • 使用#来注释
  • FROM 指令告诉 Docker 使用哪个镜像作为基础
  • 接着是维护者的信息
  • RUN开头的指令会在创建中运行,比如安装一个软件包,在这里使用 apt-get 来安装了一些软件

构建镜像的步骤

1.新建一个目录和一个 Dockerfile

1
2
3
$ mkdir new_folder
$ cd new_folder
$ touch Dockerfile

2.编写Dockerfile,Dockerfile中每一条指令都创建镜像的一层,例如:

1
2
3
4
5
6
7
# 这里是注释
# 设置继承自哪个镜像
FROM ubuntu:14.04
# 下面是一些创建者的基本信息
MAINTAINER birdben (191654006@163.com)
# 在终端需要执行的命令
RUN apt-get install -y openssh-server RUN mkdir -p /var/run/sshd

3.编写完成 Dockerfile 后可以使用 docker build 来生成镜像。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ sudo docker build -t="birdben/ubuntu:v1" .
# 下面是一堆构建日志信息
############
我是日志
############
# 参数:
# -t 标记来添加 tag,指定新的镜像的用户和镜像名称信息。
# “.” 是 Dockerfile 所在的路径(当前目录),也可以替换为一个具体的 Dockerfile 的路径。
# 以交互方式运行docker
$ docker run -it birdben/ubuntu:v1 /bin/bash
# 运行docker时指定配置
$ sudo docker run -d -p 10.211.55.4:9999:22 ubuntu:tools '/usr/sbin/sshd' -D
# 参数:
# -i:表示以“交互模式”运行容器,-i 则让容器的标准输入保持打开
# -t:表示容器启动后会进入其命令行,-t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上
# -v:表示需要将本地哪个目录挂载到容器中,格式:-v <宿主机目录>:<容器目录>,-v 标记来创建一个数据卷并挂载到容器里。在一次 run 中多次使用可以挂载多个数据卷。
# -p:指定对外80端口
# 不一定要使用“镜像 ID”,也可以使用“仓库名:标签名”

Docker网络

Docker的网络功能相对简单,没有过多复杂的配置,Docker默认使用birdge桥接方式与容器通信,启动Docker后,宿主机上会产生docker0这样一个虚拟网络接口, docker0不是一个普通的网络接口, 它是一个虚拟的以太网桥,可以为绑定到docker0上面的网络接口自动转发数据包,这样可以使容器与宿主机之间相互通信。每次Docker创建一个容器,会产生一对虚拟接口,在宿主机上执行ifconfig,会发现多了一个类似veth**这样的网络接口,它会绑定到docker0上,由于所有容器都绑定到docker0上,容器之间也就可以通信。

在宿主机上执行ifconfig,会看到docker0这个网络接口, 启动一个container,再次执行ifconfig, 会有一个类似veth**的interface,每个container的缺省路由是宿主机上docker0的ip,在container中执行netstat -r可以看到如下图所示内容:
container路由

在容器中使用netstat -r命令查看容器的IP地址

容器中的默认网关跟docker0的地址是一样的:

在宿主机中使用ifconfig查看docker0的IP地址

docker0
当容器退出之后,veth*虚拟接口也会被销毁。

参考文章:

Docker实战(一)Docker基础命令

下面简单介绍一下Docker常用的一些基础命令

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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# 在ubuntu中安装docker
$ sudo apt-get install docker.io
# 查看docker的版本信息
$ docker version
# 查看安装docker的信息
$ docker info
# 查看本机Docker中存在哪些镜像
$ docker images
# 检索image
$ docker search ubuntu:14.04
# 在docker中获取ubuntu镜像
$ docker pull ubuntu:14.04
# 显示一个镜像的历史
$ docker history birdben/ubuntu:v1
# 列出一个容器里面被改变的文件或者目
$ docker diff $CONTAINER_ID
# 从一个容器中取日志
$ docker logs $CONTAINER_ID
# 实时获取容器中取日志
$ docker logs -f $CONTAINER_ID
# 从一个容器中取出最后的100条日志
$ docker logs --tail=100 $CONTAINER_ID
# 显示一个运行的容器里面的进程信息
$ docker top $CONTAINER_ID
# 从容器里面拷贝文件/目录到本地一个路径
$ docker cp $CONTAINER_ID:/container_path to_path
# Docker attach可以attach到一个已经运行的容器的stdin,然后进行命令执行的动作。但是需要注意的是,如果从这个stdin中exit,会导致容器的停止。
$ docker attach $CONTAINER_ID
# Docker exec也可以到一个已经运行的容器的stdin,然后进行命令执行的动作。而且也不会像attach方式因为退出,导致整个容器退出。
# docker exec -it birdben/ubuntu:v1 /bin/sh
$ docker exec -参数 $CONTAINER_ID /bin/sh
# 列出当前所有正在运行的容器
$ docker ps
# 列出所有的容器
$ docker ps -a
# 列出最近一次启动的容器
$ docker ps -l
# 查看容器的相关信息
$ docker inspect $CONTAINER_ID
# 查看容器的性能信息
$ docker stats $CONTAINER_ID
# 显示容器IP地址和端口号,如果输出是空的说明没有配置IP地址(不同的Docker容器可以通过此IP地址互相访问)
$ docker inspect --format='{{.NetworkSettings.IPAddress}}' $CONTAINER_ID
# 保存对容器的修改
$ docker commit -m "Added ssh from ubuntu14.04" -a "birdben" 6s56d43f627f3 birdben/ubuntu:v1
# 参数:
# -m参数用来来指定提交的说明信息;
# -a可以指定用户信息的;
# 6s56d43f627f3代表的时容器的id;
# birdben/ubuntu:v1指定目标镜像的用户名、仓库名和 tag 信息。
# 构建一个容器
$ docker build -t="birdben/ubuntu:v1" .
# 参数:
# -t为构建的镜像制定一个标签,便于记忆/索引等
# . 指定Dockerfile文件在当前目录下,也可以替换为一个具体的 Dockerfile 的路径。
# 在docker中运行ubuntu镜像
$ docker run <相关参数> <镜像 ID> <初始命令>
# 守护模式启动
$ docker run -d ubuntu:14.04
# 交互模式启动
$ docker run -it ubuntu:14.04 /bin/bash
# 指定端口号启动
$ docker run -p 80:80 birdben/ubuntu:v1
# 指定配置启动
$ sudo docker run -d -p 10.211.55.4:9999:22 birdben/ubuntu:v1 '/usr/sbin/sshd' -D
# 参数:
# -d:表示以“守护模式”执行,日志不会出现在输出终端上。
# -i:表示以“交互模式”运行容器,-i 则让容器的标准输入保持打开
# -t:表示容器启动后会进入其命令行,-t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上
# -v:表示需要将本地哪个目录挂载到容器中,格式:-v <宿主机目录>:<容器目录>,-v 标记来创建一个数据卷并挂载到容器里。在一次 run 中多次使用可以挂载多个数据卷。
# -p:表示宿主机与容器的端口映射,此时将容器内部的 22 端口映射为宿主机的 9999 端口,这样就向外界暴露了 9999 端口,可通过 Docker 网桥来访问容器内部的 22 端口了。
# 注意:这里使用的是宿主机的 IP 地址:10.211.55.4,与对外暴露的端口号 9999,它映射容器内部的端口号 22。ssh外部需要访问:ssh root@10.211.55.4 -p 9999
# 不一定要使用“镜像 ID”,也可以使用“仓库名:标签名”
# start 启动容器
$ docker start 117843ade696117843ade696
# stop 停止正在运行的容器
$ docker stop 117843ade696117843ade696
# restart 重启容器
$ docker restart 117843ade696117843ade696
# rm 删除容器
$ docker rm 117843ade696117843ade696
# rmi 删除镜像
$ docker rmi ed9c93747fe1Deleted
# 登录Docker Hub中心
$ docker login
# 发布上传image(push)
$ docker push birdben/ubuntu:v1
# 查看docker的所有网络
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
27822b9fb5c5 bridge bridge local
08b6d63e27d2 host host local
dd0874e0e097 none null local
bacc9a64bb83 test_default bridge local
# 查看某个容器的网络信息
$ docker network inspect $CONTAINER_ID
[
{
"Name": "test_default",
"Id": "bacc9a64bb8323b2e53b1c85b4643061d38699227492f9174855202b6900252a",
"Created": "2017-04-21T10:29:37.26843596Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
# 查看指定容器的9200端口映射情况
$ docker port $CONTAINER_ID 9200
0.0.0.0:9200
# 查看指定容器的端口映射情况
$ docker inspect --format '{{.NetworkSettings.Ports}}' $CONTAINER_ID
# 查看指定镜像的环境变量
$ docker run $IMAGE_ID env
HOSTNAME=d2c948316235
ES_VERSION=2.4.4
ES_HOME=/usr/share/elasticsearch
PATH=/usr/share/elasticsearch/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
JAVA_HOME=/usr/lib/jvm/java-7-openjdk-amd64/jre
SHLVL=0
HOME=/root
GOSU_VERSION=1.7

当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:

  • 检查本地是否存在指定的镜像,不存在就从公有仓库下载
  • 利用镜像创建并启动一个容器
  • 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
  • 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
  • 从地址池配置一个 ip 地址给容器
  • 执行用户指定的应用程序
  • 执行完毕后容器被终止

参考文章:

移动端请求URL的加密/解密

移动端请求URL的加密方式

最近一直在研究如何爬取手机客户端请求的数据信息,发现很多手机客户端在请求服务端数据的时候,对请求的URL都做了一些加密处理,所以自己就私下里研究了一下URL的一些加密算法

下面是一个比较基本的URL加密处理方式:

  • 1.服务端和客户端约定好一个公约KEY
  • 2.选择一种加密方式MD5,SHA等等(这里重点介绍Mac算法)
  • 3.选择要做加密处理的参数(一般是静态参数 + 动态参数的方式,比如:loginToken登录秘钥 + timestamp时间戳)
  • 4.客户端根据公约KEY使用Mac算法,对指定的参数(loginToken + timestamp)进行加密处理生成秘钥secret,把secret当做参数传递给服务端
  • 5.服务端会也会根据公约KEY使用Mac算法,对指定的参数做同样的加密处理,并且把结果和secret参数进行比较,来判断这个请求是否是正确的

参数:

loginToken : 8d940b09872346a**0f9844c70d51

timestamp : 1447327804725

客户端加密处理的秘钥:

secret : 8d940b098773464796b**844c70d516447327804725

MAC算法(这里的MAC可不是苹果笔记本。。)

MAC算法结合了MD5和SHA算法的优势,并加入密钥的支持,是一种更为安全的消息摘要算法。

MAC(Message Authentication Code,消息认证码算法)是含有密钥的散列函数算法,兼容了MD和SHA算法的特性,并在此基础上加入了密钥。

MAC算法主要集合了MD和SHA两大系列消息摘要算法。MD系列的算法有HmacMD2、HmacMD4、HmacMD5三种算法;SHA系列的算法有HmacSHA1、HmacSHA224、HmacSHA256、HmacSHA384.HmacSHA512五种算法。

经过MAC算法得到的摘要值也可以使用十六进制编码表示,其摘要值长度与参与实现的摘要值长度相同。例如,HmacSHA1算法得到的摘要长度就是SHA1算法得到的摘要长度,都是160位二进制,换算成十六进制编码为40位。

模型分析

甲乙双方进行数据交换可以采取如下流程完成

    1. 甲方向乙方公布摘要算法(就是指定要使用的摘要算法名)
    1. 甲乙双方按照约定构造密钥,双方拥有相同的密钥(一般是一方构造密钥后通知另外一方,此过程不需要通过程序实现,就是双方约定个字符串,但是这个字符串可不是随便设定的,也是通过相关算法获取的)
    1. 甲方使用密钥对消息做摘要处理,然后将消息和生成的摘要消息一同发送给乙方
    1. 乙方收到消息后,使用甲方已经公布的摘要算法+约定好的密钥 对收到的消息进行摘要处理。然后比对自己的摘要消息和甲方发过来的摘要消息。甄别消息是否是甲方发送过来的

MAC用于消息认证

消息认证码

密码学中,通信实体双方使用的一种验证机制,保证消息数据完整性的一种工具。

安全性依赖于Hash函数,故也称带密钥的Hash函数。

消息认证码是基于密钥和消息摘要【hash】所获得的一个值,目的是用于验证消息的完整性,确认数据在传送和存储过程中未受到主动攻击

下面是使用HmacSHA1进行加密的具体代码

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
/**
* 初始化HmacSHA1密钥
*
* @return byte[] 密钥
* @throws Exception
*/
public static byte[] initHmacSHAKey() throws Exception {
// 初始化KeyGenerator
KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacSHA1");
// 产生密钥
SecretKey secretKey = keyGenerator.generateKey();
// 获得密钥
return secretKey.getEncoded();
}
/**
* HmacSHA1消息摘要
*
* @param data 待做摘要处理的数据
* @param key 密钥
* @return byte[] 消息摘要
* @throws Exception
*/
public static byte[] encodeHmacSHA(byte[] data, byte[] key)
throws Exception {
// 还原密钥
SecretKey secretKey = new SecretKeySpec(key, "HmacSHA1");
// 实例化Mac
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
// 初始化Mac
mac.init(secretKey);
// 执行消息摘要
return mac.doFinal(data);
}

参考文章:

Akka Helloworld

功能描述:
实现通过一个actior发送消息到另一个actor然后将处理结果返回,感觉很简单类似两个类的方法调用,但是这里实际上的处理时异步的并非同步的调用处理,这里神奇的地方就在于AKKA的内部机制了后续再做深入研究。

HelloWorld.java

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
package com.birdben.akka.helloworld;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.actor.UntypedActor;
public class HelloWorld extends UntypedActor {
@Override
public void preStart() {
// create the greeter actor
final ActorRef greeter =
getContext().actorOf(Props.create(Greeter.class), "greeter");//创建greeter actor实例
// tell it to perform the greeting
greeter.tell(Greeter.Msg.GREET, getSelf());//通过tell方法给greeter actor 发送一条消息
}
@Override
public void onReceive(Object msg) {
if (msg == Greeter.Msg.DONE) {
// when the greeter is done, stop this actor and with it the application
getContext().stop(getSelf());
} else unhandled(msg);
}
}

Greeter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.birdben.akka.helloworld;
import akka.actor.UntypedActor;
public class Greeter extends UntypedActor {
public static enum Msg {
GREET, DONE;
}
@Override
public void onReceive(Object msg) {
if (msg == Msg.GREET) {
System.out.println("Hello World!");
getSender().tell(Msg.DONE, getSelf());
} else unhandled(msg);
}
}

运行HelloWorld类,akka提供了一个主actor类,可以通过这个类直接执行以上的方法,在IDEA中点击Edit Configurations(配置tomcat的按钮),新建一个Application,在Main.class 选项中选择akka.Main,然后在program arguments 中输入 com.birdben.akka.helloworld.HelloWorld,点击 apply 然后运行结果如下:

1
2
3
4
5
6
7
com.birdben.akka.helloworld.HelloWorld
Connected to the target VM, address: '127.0.0.1:50084', transport: 'socket'
Hello World!
[INFO] [11/11/2015 01:40:59.782] [Main-akka.actor.default-dispatcher-3] [akka://Main/user/app-terminator] application supervisor has terminated, shutting down
Disconnected from the target VM, address: '127.0.0.1:50084', transport: 'socket'
Process finished with exit code 0

从运行结果可以看出,HelloWorld actor正确的调用了 Greeter actor 因为输出了 Hello World!从Infor日志可以看出 HelloWorld actor 正常接收到了 Greeter actor 的返回停止了当前actor。

参考文章:

Akka工作原理

Akka

Akka中的角色

  • ProducerActor(StudentActor)
  • ConsumerActor(TeacherActor,onReceive方法接收消息)
  • ActorRef(tell方法,发送消息给MessageDispatcher消息派发器)
  • ActorSystem(actorOf方法,创建ActorRef,ActorRef就是ConsumerActor的Proxy)
  • MailBox
  • Dispatcher
  • Message

Akka工作流程

  1. ProducerActor(StudentActor)创建了一个叫ActorSystem的东西。
  2. 他通过ActorSystem来创建了一个叫ActorRef的对象。QuoteRequest消息就是发送给ActorRef的(它是TeacherActor的一个代理)
  3. ActorRef将消息发送给Dispatcher
  4. Dispatcher将消息投递到目标Actor的邮箱中。
  5. 随后Dispatcher将Mailbox扔给一个线程去执行(这点下节会重点讲到)
  6. MailBox将消息出队并最终将其委托给真实的Teacher Actor的接收方法去处理。

创建ActorSystem

ActorSystem是进入到Actor的世界的一扇大门。通过它你可以创建或中止Actor。甚至还可以把整个Actor环境给关闭掉。

另一方面来说,Actor是一个分层的结构,ActorSystem之于Actor有点类似于java.lang.Object或者scala.Any的角色——也就是说,它是所有Actor的根对象。当你通过ActorSystem的actorOf方法创建了一个Actor时,你其实创建的是ActorSystem下面的一个Actor。

ActorSystem

创建ActorRef(ConsumerActor(TeacherActor)的Proxy)

ActorRef myActor = system.actorOf(Props.create(MyUntypedActor.class), “myactor”);

actorOf是ActorSystem中创建Actor的方法。但是正如你所看到的,它并不会返回我们所需要的TeacherActor。它返回的是一个ActorRef。

这个ActorRef扮演了真实的Actor的一个代理的角色。客户端并不会直接和Actor通信。这也正是Actor模型中避免直接访问TeacherActor中任何的自定义/私有方法或者变量的一种方式。

再重复一遍,消息只会发送给ActorRef,最终才会到达真正的Actor。你是绝对无法直接和Actor进行通信的。如果你真的找到了什么拙劣的方式来直接通信,大家会恨你入骨的。

将消息发送给代理

还是只有一行代码。你只需告诉说把QuoteRequest消息发送到ActorRef就好了。Actor中的这个告诉的方式就是一个!号。(ActorRef中确实也有一个tell方法,不过它只是把这个调用委托给了!号)

分发器及邮箱

ActorRef把消息处理功能委托给了Dispatcher。实际上,当我们创建ActorSystem和ActorRef的时候,就已经创建了一个Dispatcher和MailBox了。我们来看下它们是干什么的。

邮箱

每个Actor都有一个MailBox(后面会介绍一种特殊的情况)。在我们这个比喻当中,每个老师也有一个邮箱。老师得去检查邮箱并处理消息。在Actor的世界中,则是另一种形式——邮箱一有机会就会要求Actor去完成自己的任务。

同样的,邮箱里也有一个队列来以FIFO的方式来存储并处理消息——它和实际的邮箱还有点不同,真实的邮箱新的信总是在最上面的。

分发器

Dispatcher会完成一些很酷的事。从它的角度来看,它只是从ActorRef中取出一条消息然后将它传给了MailBox。但是,在这后面发生了一件不可意义的事情:

Dispatcher会封装一个ExecutorService(ForkJoinPoll或者ThreadPoolExecutor)。它把MailBox扔到ExecutorService中去运行。

是的。我们看到MailBox中包含了队列里面的消息。由于Executor得去执行MailBox,所以它得是一个Thread类型。是的没错。MailBox的声明及构造器就是这样的。

ConsumerActor(TeacherActor)

当MailBox的run方法运行的时候,它会从队列中取出一条消息,然后将它传给Actor去处理。

当你把消息传给ActorRef的时候,最终调用的实际是目标Actor里面的一个receive方法。

TeacherActor只是一个很简单的类,它有一个名言的列表,而receive方法很明显就是用来处理消息的。

TeacherActor的receive方法的模式匹配只会匹配一种消息——QuoteRequest (事实上,模式匹配中最好匹配下默认的情况,不过这个就说来话长了)

receive方法做的就是

  1. 匹配QuoteRequest的模式
  2. 从名言列表中随机选取一条
  3. 构造出一个QuoteResponse
  4. 将QuoteResponse打印到控制台上

参考文章: