-
kibana4打包和安装
对于Kibana4,官方只提供了一个打包好的JAVA的包(现在已经是用nodejs了,使用很方便,修改源码也很方便,所以后面这些话忽略吧). 如果想自己修改一些代码添加一些自定义功能, impossible. 至少我还是希望能像Kibana3一样,就是一普通的hmlt静态网站,放在nginx下面跑. 可以添加一些自己的panel. 好吧, 虽然Kibana4好像已经不需要添加什么panel了,但改改css, html总行吧. 而且还可以利用nginx做一些权限控制什么的.
虽然github有源码了,但做为一个新手, 对于grunt这些东西只是有最最最基本的一些了解,还是折腾了一会才搞定. 纪录一下.
以下操作都是基于8cef836c61b5749164156e19accb2a0881e902e4这个commit
-
从github下载kibana4代码.
git clone git@github.com:elasticsearch/kibana.git
-
默认你已经有node了, 如果没有, 用apt-get 或者 yum 或者brew等工具装上. 然后先把bower grunt装好, 接下来就要用.
npm install -g bower grunt-cli
-
进入kibana目录. 我没有在master分支, 我是切换到了v4.0.0-beta2分支上面.
cd kibana git checkout v4.0.0-beta2
-
安装需要的npm包.
其实, 我不确定是不是所有的包都要装, 我只是要用grunt build一下.甚至只是生成css文件而已.
这里要注意, 如果是用官方源, 而你身在大陆的话, 那就等死吧. 淘宝源在此npm install
-
bower install
安装需要的js css包. 从github下载. 对于大陆电信30M用户来说, 也是极其痛苦, 一个400K的包死活就是下载不下来.
osx系统的bower cache文件夹好像在这里/private/var/folders/j9/37cyszz92cg1xkfc46cl5w5r0000gn/T/yourusername/bower 浏览器明明能下载下来, bower就是死活不行. 没办法, 先用浏览器下载之后放在cache文件夹里面bower install
- grunt
可以看到默认是跑了两个任务.
grunt.registerTask(‘default’, [‘jshint:source’, ‘less’]); less生成css文件. jshint大概就是检查一下JS语法错误吧.
grunt
-
grunt build
其实生成css之后, 把src/kibana放在nginx下面就可以跑了.
build, 就是把一些文件合并压缩一下, 加速用户访问. -
config 配置.
grunt之后, 会在目录下生成一个config文件, 这是一个json文件, 里面是kibana4的一些配置. 里面的kibanaIndex需要改成kibana_index这种格式. 这个生成的原始配置文件应该是nodejs用的, nodejs应该会转成kibana_index这种格式, 但我们现在不用Nodejs做后端服务器了, 所以需要用人改一下.
{ "request_timeout": 60, "kibana_index": ".kibana", "bundled_plugin_ids": [ "plugins/dashboard/index", "plugins/discover/index", "plugins/settings/index", "plugins/table_vis/index", "plugins/vis_types/index", "plugins/visualize/index" ], "default_app_id": "discover", "host": "0.0.0.0", "shard_timeout": 30000, "plugins": [ "plugins/dashboard/index", "plugins/discover/index", "plugins/settings/index", "plugins/table_vis/index", "plugins/vis_types/index", "plugins/visualize/index" ], "port": 5601, "verifySSL": false }
-
还差一点点了.
到这里…居然还不行…
打开components/config/config.js, 可以看到:var configFile = JSON.parse(require('text!config')); configFile.elasticsearch = (function () { var a = document.createElement('a'); a.href = 'elasticsearch'; return a.href; }());
最重要的elasticsearch的配置, 不是放在配置文件里, 而是写在代码里面了. 这里改成真正的elasticsearch的地址就可以了.
- k4要求ES版本至少也是1.4. 但其实1.3版本的也可以正常使用. 我们就是1.3. 暂时也没打算升级. 限制的代码在index.js里面, 改成
constant('minimumElasticsearchVersion', '1.3.0')
就OK了.
OVER.
补充 (曾经的某个版本下还需要):
-
script_fileds
安全起见, 我们的ES禁用script.
但是k4有些请求, 至少在找index pattern的时候, 使用了script_fileds.
我简单粗暴的把components/courier/data_source/_abstract.js里面带script_fileds的代码注释了…
目前还没有发现副作用, 先这样用吧. -
要设置一个默认index pattern
新建index pattern之后, 要设置一个为默认. 否则k4有些2去找名叫.kibana这个index pattern, 当然找不到, 就报错了.
beta2还没这个要求.
我也不是完全确认就是这个原因造成的, 但的确设置一个默认之后就好了.
-
-
tcp/ip协议学习 第四章 ARP:地址解析协议
ARP干嘛的
曾经有段时间, 六七年前了吧. 本科的时候, 流行了一阵子ARP病毒攻击, 导致整个局域网都不能上网了. 当时只听说这个东西防不住, 只要有一个人中毒, 就导致所有人上不了网. 现在也终于知道这是怎么回事了, 也能手工让某个同学上不了网了, 咳咳.
大家应该也都知道ARP是干嘛的, 我再啰嗦一下.. 比如我访问了百度, 百度回了包给我, 百度只知道我的IP是什么,不知道我的MAC地址. 这个包到网关的时候, IP这一层再把数据交给下一层的链路层, 链路层不知道IP是什么东西的, 它只认MAC地址. 所以就需要把IP转成MAC地址, ARP请求就是做这个的.
就是说, 我可以通过这个协议广播问一下所有机器 , 谁的IP是XXX.XXX.XXX.XXX, 请把你的MAC地址告诉我. 这个IP是XXX.XXX.XXX.XXX的机器收到请求之后, 就会告诉我, XXX.XXX.XXX.XXX的MAC地址是啥啥啥.
不幸这个是基于互相信任的, 理论上大家都会相信别人说的是正确的. 但是, 我可以撒谎说, XXX.XXX.XXX.XXX是我, 我的MAC是啥啥啥. 然后本来应该到 XXX.XXX.XXX.XXX那里的数据包就到我这里来了. XXX.XXX.XXX.XXX不仅仅是断网了, 我还能窃听他的数据.
协议格式
要想伪造, 啊,不, 发送一个ARP请求或者应答, 一定要了解协议格式. 从RFC文件抄了一份.
To communicate mappings from <protocol, address> pairs to 48.bit Ethernet addresses, a packet format that embodies the Address Resolution protocol is needed. The format of the packet follows. Ethernet transmission layer (not necessarily accessible to the user): 48.bit: Ethernet address of destination 48.bit: Ethernet address of sender 16.bit: Protocol type = ether_type$ADDRESS_RESOLUTION Ethernet packet data: 16.bit: (ar$hrd) Hardware address space (e.g., Ethernet, Packet Radio Net.) 16.bit: (ar$pro) Protocol address space. For Ethernet hardware, this is from the set of type fields ether_typ$<protocol>. 8.bit: (ar$hln) byte length of each hardware address 8.bit: (ar$pln) byte length of each protocol address 16.bit: (ar$op) opcode (ares_op$REQUEST | ares_op$REPLY) nbytes: (ar$sha) Hardware address of sender of this packet, n from the ar$hln field. mbytes: (ar$spa) Protocol address of sender of this packet, m from the ar$pln field. nbytes: (ar$tha) Hardware address of target of this packet (if known). mbytes: (ar$tpa) Protocol address of target.
这个还是TCP/IP协议详解书里面的图好看一些, 且等我截个图来, 呃,截歪了.
其实里面有些值, 像”硬件地址长度”什么的, 是个变量, 后面的”发送端以太网地址”就是根据这个而变化 .就我们目前的应用和环境来说, 拿这个图就可以啦.
这里的长度单位是字节, 不是上一篇IP协议里面的bit了.详细解释我也copy一下吧. 也是出自TCP/IP协议详解一书.
以太网报头中的前两个字段是以太网的源地址和目的地址。目的地址为全 1的特殊地址是 广播地址。电缆上的所有以太网接口都要接收广播的数据帧。
两个字节长的以太网帧类型表示后面数据的类型。对于 ARP请求或应答来说,该字段的 值为0x0806。
形容词hardware(硬件)和protocol(协议)用来描述 ARP分组中的各个字段。例如,一个 ARP 请求分组询问协议地址(这里是 IP地址)对应的硬件地址(这里是以太网地址)。
硬件类型字段表示硬件地址的类型。它的值为 1即表示以太网地址。协议类型字段表示要 映射的协议地址类型。它的值为 0x0800即表示 IP地址。它的值与包含 IP数据报的以太网数据 帧中的类型字段的值相同,这是有意设计的(参见图 2-1)。
接下来的两个 1字节的字段,硬件地址长度和协议地址长度分别指出硬件地址和协议地址 的长度,以字节为单位。对于以太网上 IP地址的 ARP请求或应答来说,它们的值分别为 6和4。 操作字段指出四种操作类型,它们是 ARP请求(值
(值为3)和RARP应答(值为4)(我们在第5章讨论RARP)。这个字段必需的,因为 ARP请求 和ARP应答的帧类型字段值是相同的。
接下来的四个字段是发送端的硬件地址(在本例中是以太网地址)、发送端的协议地址 (IP地址)、目的端的硬件地址和目的端的协议地址。注意,这里有一些重复信息:在以太网的数据帧报头中和 ARP请求数据帧中都有发送端的硬件地址。
对于一个 ARP请求来说,除目的端硬件地址外的所有其他的字段都有填充值。当系统收到一份目的端为本机的 ARP请求报文后,它就把硬件地址填进去,然后用两个目的端地址分 别替换两个发送端地址,并把操作字段置为 2,最后把它发送回去。
来段程序吧
其它也没啥好说的了, 先来段程序吧.
广播一个ARP请求, 问一下网关的MAC地址是多少. 运行环境是ubuntu12.04. osx上面是不行的. 据说windows也不行. 搜索了好久, 也不知道mac上面怎么搞.
#!/usr/bin/env python # -*- coding: utf-8 -*- ''' 广播一个ARP请求, 问一下网关的MAC地址是多少. 运行环境是ubuntu12.04. osx上面是不行的. 据说windows也不行. 搜索了好久, 也不知道mac上面怎么搞. ''' import socket import struct def main(): st = struct.Struct('!6s 6s h h h b b h 6s 4s 6s 4s') GATEWAY = '192.168.1.1' MYIP = '192.168.1.8' MYMAC = '20:c9:d0:88:96:3f' dst_ethernet_addr = ''.join( [chr (int(e, 16)) for e in 'FF:FF:FF:FF:FF:FF'.split(':')]) protocol_type = 0x0806 hw_addr_space = 1 protocol_addr_space = 0x800 hw_addr_length = 6 protocol_addr_length = 4 op = 1 my_mac = ''.join([chr(int(e, 16)) for e in MYMAC.split(':')]) my_ip = socket.inet_aton(MYIP) target_hw_addr = ''.join( [chr (int(e, 16)) for e in '00:00:00:00:00:00'.split(':')]) des_ip = socket.inet_aton(GATEWAY) data = ( dst_ethernet_addr, my_mac, protocol_type, hw_addr_space, protocol_addr_space, hw_addr_length, protocol_addr_length, op, my_mac, my_ip, target_hw_addr, des_ip, ) packed_data = st.pack(*data) s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.SOCK_RAW) s.bind(('eth0', socket.SOCK_RAW)) # 下面这样也行, 不知道区别. #http://sock-raw.org/papers/sock_raw 这个应该可以参考 #s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW) #s.bind(('eth0',0)) r = s.send(packed_data) print r return if __name__ == '__main__': main()
抓包结果, 这里已经忽略了以太网首部的14个字节. 是从上图中的”硬件类型”开始的.
% sudo tcpdump -nn -vvv -x -c1 arp tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes 19:28:43.209235 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 192.168.1.1 tell 192.168.1.8, length 28 0x0000: 0001 0800 0604 0001 20c9 d088 963f c0a8 0x0010: 0108 0000 0000 0000 c0a8 0101 1 packet captured 1 packet received by filter 0 packets dropped by kernel
ARP攻击
上面的代码简单改一下就可以做ARP攻击了.
对网关做一个ARP应答.
op改为2, 代表ARP应答. 然后把 “以太网源地址”和”发送端以太网地址”都写自己的. IP地址写要攻击的人. OVER -
tcp/ip协议学习 第三章 IP协议
IP的头文件还是先贴一下, 总是记不住.
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Version| IHL |Type of Service| Total Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Identification |Flags| Fragment Offset | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time to Live | Protocol | Header Checksum | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Options | Padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
网络和子网划分
不太明白ABCD四类地址划分的意义,除了在路由选择的匹配中会配合子网划分用到.
路由表, 路由选择
以前本科的时候, IP配置好之后, 学校内网是可以访问的. 如果想访问外网, 需要拨号.但拨号之后内网就不方便上了.
学校网站有个小程序, 可以傻瓜式的修改系统路由. 拨号之后内网外网一起上.
当时不明白这是个什么东西, 其实现在也想不太明白怎么回事, 也没条件回学校试验下了. 但至少现在也算大概明白路由表以及路由选择了.
之前为了学习, 拿公司的网络实验了一把. osx下面的双网卡路由配置
来段程序吧
python
用程序发送raw IP包的时候, Total Length 和 Checksum 会由内核自动生成. 至于为啥,可能看到tcpip协议详解下(实现)的时候就知道了吧.
Checksum 算法在模拟后面协议的时候就要自己实现了.ICMP、IGMP、UDP和TCP都采用相同的检验和算法.
有个不同点是: 首部检验和字段是根据IP头计算的检验和码。它不对body进行计算。 ICMP、 IGMP、UDP和TCP的checksum覆盖首部和数据。
#!/usr/bin/env python # -*- coding: utf-8 -*- '''发送一个裸的IP包, 20字节的IP头, 后面跟一个随便写的字符串. 还不知道IP包的ID应该根据什么生成, 就随便写了一个54321 IP头里面: IP包总长度属性和checksum属性都是内核自动生成的. 协议是用的socket.IPPROTO_TCP,也就是6.但没什么用,IP包里面就随便的字符串,不是按TCP协议来的. ''' import socket from struct import pack import sys def main(): try: s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) except socket.error as msg: print 'Socket could not be created. Error Code : ' + str(msg[0]) + ' Message ' + msg[1] return packet = '' source_ip = '127.0.0.1' dest_ip = '127.0.0.1' # ip header fields ip_ver = 4 ip_ihl = 5 ip_tos = 0 ip_tot_len = 0 # kernel will fill the correct total length ip_id = 54321 ip_frag_off = 0 ip_ttl = 32 ip_proto = socket.IPPROTO_TCP # no use in this case ip_checksum = 0 # kernel will fill the correct checksum ip_saddr = socket.inet_aton(source_ip) ip_daddr = socket.inet_aton(dest_ip) ip_ihl_ver = (ip_ver << 4) + ip_ihl # the ! in the pack format string means network order ip_header = pack( '!BBHHHBBH4s4s', ip_ihl_ver, ip_tos, ip_tot_len, ip_id, ip_frag_off, ip_ttl, ip_proto, ip_checksum, ip_saddr, ip_daddr) user_data = sys.argv[1] if sys.argv[1:] else '0123456789' packet = ip_header + user_data # the port specified has no effect r = s.sendto(packet, (dest_ip, 0)) # result is the length of packet sent out print r if __name__ == '__main__': main() ip_check = 1231 # kernel will fill the correct checksum
抓包. 运行环境, ubuntu12.04
% sudo tcpdump -i lo -x -vvv -t -c 1 ✭ tcpdump: listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes IP (tos 0x0, ttl 32, id 54321, offset 0, flags [none], proto TCP (6), length 30) localhost.12337 > localhost.12851: tcp 10 [bad hdr length 0 - too short, < 20] 0x0000: 4500 001e d431 0000 2006 c8a6 7f00 0001 0x0010: 7f00 0001 3031 3233 3435 3637 3839 1 packet captured 2 packets received by filter 0 packets dropped by kernel
tcp 10 [bad hdr length 0 - too short, < 20] 是说IP body里面的字节数太少, 只有10, 小于TCP header应有的20字节.
-
1字节, 45, 4代表Version, 5代表Header Length, 单位是4Byte.
- 2字节, 00, OTS(Type of Service), 代表此IP包的最小时延,最大吞吐量,最高可靠性, 最小费用等特征. 比如用于FTP的控制的IP包, 应该是最小时延, 用于FTP的数据的IP包, 则应该是最大吞吐量.书中提到:
现在大多数的TCP/IP实现都不支持TOS特性,但是自4.3BSD Reno以后的新版系统都对它 进行了设置。另外,新的路由协议如 OSPF和IS-IS都能根据这些字段的值进行路由决策。
-
3,4字节, 001e, Total Length. 0x1e = 30; 发送的01234567890+20字节的IP Header.
因为一些数据链路(如以太网)需要填充一些数据以达到最小长度。尽管以太网的最小帧长为 46字节,但是IP数据可能会更短,像我们就是30。如果没有总长度字段,那么IP层就不知道46字节中有多少是IP数据报的内容。所以IP包总长度这个字段是一定需要的. -
5,6字节, Identification. 0xd431=54321.
-
7,8字节, 0000.
-
9字节, 20. TTL. 路由转发此IP包的时候就把这个数值减1, 减到0的时候就直接丢弃, 并发送ICMP通知主机. trouceroute即利用了这个.
-
10字节, 06, 代表TCP协议.
-
11,12字节 c8a6. Checksum.
-
13-16字节, 7f00 0001. Source Address. 代表127.0.0.1
- 17-20字节, 7f00 0001. Destination Address. 代表127.0.0.1
Checksum是内核帮我们算好的, 但以后的TCP等就要自己算了, 这里先看一下算法.
首先把检验和字段置为 0。然后,对首部中每个 16 bit 进行二进制反码求和(整个首部看成是由一串 16 bit的字组成)
def checksum(ip_header): ip_header += chr(0x00)*(len(ip_header)%2) r = 0 while ip_header: # 因为ip_header已经是网络序了, 所以这里用!H. 否则需要用H r += unpack('H',ip_header[:2])[0] ip_header = ip_header[2:] if r > 0xffff: r = r&0xffff + (r>>16); return ~r
-
-
OSX下面的双网卡路由配置
曾经的公司网络
公司网络现在已经好多了, 以前的时候挺奇葩的:无线网络可以连外网, 但不能连公办网络, 有线的正好反过来.
想工作的时候吧,就把无线停了,或者把有线的优先级设高. 遇到问题想google一下呢,就要再反过来把有线停了,无线打开.
有同事说osx设置好有线和无线的优先级,不会有这个问题,系统会自己尝试,测试下来不可以.
当前配置
这种配置下, 只能走有线到办公网络. 不能通过wifi连外网.
无线网卡
% ifconfig en0 en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500 ether 20:c9:d0:88:96:4f inet6 fe80::22c9:d0ff:fe88:963f%en0 prefixlen 64 scopeid 0x5 inet 172.16.27.226 netmask 0xfffff800 broadcast 172.16.31.255 nd6 options=1<PERFORMNUD> media: autoselect status: active
有线网卡
% ifconfig en2 en2: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500 options=4<VLAN_MTU> ether 00:8a:8d:8a:12:2a inet6 fe80::28a:8dff:fe8a:121a%en2 prefixlen 64 scopeid 0x4 inet 172.16.142.76 netmask 0xffffff00 broadcast 172.16.142.255 nd6 options=1<PERFORMNUD> media: autoselect (100baseTX <full-duplex>) status: active
路由表 osx下面要用netstat查路由表
% netstat -rl -f inet Routing tables Internet: Destination Gateway Flags Refs Use Mtu Netif Expire default 172.16.142.251 UGSc 31 0 1500 en2 default 172.16.24.1 UGScI 3 0 1500 en0 127 localhost UCS 0 0 16384 lo0 localhost localhost UH 8 17513401 16384 lo0 169.254 link#4 UCS 0 0 1500 en2 172.16.24/21 link#5 UCS 8 0 1500 en0 172.16.24.1 0:1a:e3:5b:f:d7 UHLWIir 4 43 1500 en0 1119 172.16.142.251 0:1a:e3:5b:1c:44 UHLWIir 32 14 1500 en2 1200
先看一下系统是怎么找一个IP包应该走哪个路由的:
IP路由选择主要完成以下这些功能: 1)搜索路由表,寻找能与目的IP地址完全匹配的表目(网络号和主机号都要匹配)。如果 找到,则把报文发送给该表目指定的下一站路由器或直接连接的网络接口(取决于标 志字段的值)。
2)搜索路由表,寻找能与目的网络号相匹配的表目。如果找到,则把报文发送给该表目 指定的下一站路由器或直接连接的网络接口(取决于标志字段的值)。目的网络上的所有主机都可以通过这个表目来处置。例如,一个以太网上的所有主机都是通过这种表目进行寻径的。这种搜索网络的匹配方法必须考虑可能的子网掩码。关于这一点我们在下一节中进行讨论。
3)搜索路由表,寻找标为“默认(default)”的表目。如果找到,则把报文发送给该表目指定的下一站路由器。
from TCP/IP 协议卷1 3.3节
可以看到我这里路由默认走的是有线的, 毕竟在上班, 办公环境要优先, 然后是wifi. 但实际上路由表搜索下来, 除了172.16.24, 其他是不会走到wifi的. 就是说wifi其实是废的.
添加路由
思路就是:
-
把wifi设置成优先的default路由
-
配置内网的网段走有线的网关
-
除了有线的, 自然就是走wifi default了
走起.
-
网络偏好设置里面, 把wifi调整为最高优先级
-
添加路由
route add -net 192.168.0.0/16 172.16.142.251 route add -net 10.0.0.0/8 172.16.142.251
就这么简单, 搞定! 来Ping 一下
% ping -c 1 www.baidu.com PING www.a.shifen.com (61.135.169.105): 56 data bytes 64 bytes from 61.135.169.105: icmp_seq=0 ttl=50 time=23.199 ms --- www.a.shifen.com ping statistics --- 1 packets transmitted, 1 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 23.199/23.199/23.199/0.000 ms % ping -c 1 10.8.84.90 64 ↵ PING 10.8.84.90 (10.8.84.90): 56 data bytes 64 bytes from 10.8.84.90: icmp_seq=0 ttl=58 time=1.286 ms --- 10.8.84.90 ping statistics --- 1 packets transmitted, 1 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 1.286/1.286/1.286/0.000 ms
赞, 都是通的.
再来一个内网的域名
% ping es.XXXcorp.com ping: cannot resolve es.XXXcorp.com: Unknown host
擦, 解析不了域名!
更改DNS
域名解析不了嘛, 肯定是dns的问题. 手动更改一下/etc/resolv.conf嘛. 看一下有线的dns配置, 找到有线的dns服务器加进去.
再来一次!
% ping es.XXXcorp.com ping: cannot resolve es.XXXcorp.com: Unknown host
还是不行…
清理一下dns再来一次. 不同版本的osx不一样的命令, 咱保险起见, 都来一次.
dscacheutil -flushcache killall -HUP mDNSResponder
还是不行…
nslookup手工指定server是没问题的.
% nslookup > server 192.168.102.20 Default server: 192.168.102.20 Address: 192.168.102.20#53 > es.XXXcorp.com Server: 192.168.102.20 Address: 192.168.102.20#53 Non-authoritative answer: Name: es.XXXcorp.com Address: 10.8.81.10 >
总之, 一翻测试下来, 才发现
nslookup, host命令是可以的, ping 浏览器都解析不了.然后又一翻搜索, 原来还要到网络偏好设置里面,去那里改dns才行.
nslookup和浏览器是去不同地方找配置的.做如上修改之后, 整个网络都畅通了.
-
-
python里多线程的写法
今天用到python多线程的时候, 发现不知道如何正确的等待所有线程结束后再结束主线程.
其实到最后我才知道这都是杞人忧天, Thread()出来的实例本来就是等到主进程结束后才结束.
官方解释:
daemon A boolean value indicating whether this thread is a daemon thread (True) or not (False). This must be set before start() is called, otherwise RuntimeError is raised. Its initial value is inherited from the creating thread; the main thread is not a daemon thread and therefore all threads created in the main thread default to daemon = False.
The entire Python program exits when no alive non-daemon threads are left.
默认daemon是false, start之后也不会阻塞在那里(废话, 否则要多线程干嘛, 不过总会想到join会阻塞这点). 对于我的需要来说简直完美,其实什么都不用做嘛.
可以用setDaemon(True)改变daemon值. 这样的话, 就需要调用join等待子线程结束了. (好多此一举..)
for t in threads: t.join()
可以把所有要起的线程放到一个队列里面, 然后对每一个线程join. 这样的确是可以实现, 但看起来太丑, 用着实在别扭.
像如下这样, 其实for循环里面, 进程等待在第一个join那里:
#!/usr/bin/env python # -*- coding: utf-8 -*- from threading import Thread import time def target(i): time.sleep(10-i) def main(): threads = [] for i in range(10): t = Thread(target=target,args=(i,)) threads.append(t) for t in threads: t.start() for idx,t in enumerate(threads): print idx t.join() if __name__ == '__main__': main()
multiprocessing.Pool
又用Queue实现了一下, 但还是太丑陋, 封装不好. 然后才搜索到Python其实有个multiprocessing.Pool, 可以很好看的实现这个功能:
#!/usr/bin/env python # -*- coding: utf-8 -*- from multiprocessing import Pool import time def work(): print time.time() time.sleep(4) def main(): pool = Pool(processes=4) for i in range(4): pool.apply_async(work) pool.close() pool.join() if __name__ == '__main__': main()
借鉴官方的Pool, 尝试自己实现一下最简单的Multiprocessing Pool
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
#!/usr/bin/env python # -*- coding: utf-8 -*- from threading import Thread from Queue import Queue import time def hello(msg=None): print time.time(),msg time.sleep(5) class MPool(object): def posttask(self,func): def inner(*args, **kwargs): q = args[0] real_args = args[1:] func(*real_args, **kwargs) q.task_done() return inner def __init__(self): self._queue = Queue() #简单起见没有设置队列上限 def run_task(self, target, *args, **kwargs): task = Thread(target=self.posttask(target), args=(self._queue,)+args, kwargs=kwargs) self._queue.put(task) task.setDaemon(True) task.start() def join(self): self._queue.join() def main(): p = MPool() for i in range(10): p.run_task(hello,i) p.join() if __name__ == '__main__': main()
最后再提一下, 我的初衷只是想等所有线程结束后再退出. 默认就是这样的, join什么的都不用.
-
rabbitmq 学习记录 -- 权限控制
好懂? 不好懂?
看懂之后, 觉得官网上的文档还是写的挺全面的. 但没看懂的时候, 觉得这写的啥啊…看不懂啊..
简单说下权限控制的层次
首先, 权限都是针对virtual host设置的. 毕竟virtual host也是个host.. 当一个用户建立连接的时候, 就先要判断这个用户对这个virtual host有没有权限. 没有的话, 连接也建立不了.
在一个virtual host里面, 对一个资源(原文是resource, 我理解大概就是指的amqp command, 比如说basic_consume), rabbitmq有三个方面的权限控制, 分别是configure, write, read. 对于一个amqp command, 需要对这三个方面(中的一个或者多个)设置相应的权限. 权限的表现形式就是一个正则表达式, 匹配queue或者是exchange的名字
preprocess是我们的一个用户, preprocessed rawevent是两个virtual host.
在下面的命令中, 可以看到一个用户的权限都是针对virtual host来的.
针对一个 virtual host, 按顺序分别列出了configure, write, read的权限对于rawevent这个virtual host, 我们的configure权限是^$, write是^$, read是^rawevent$ , 没错, 就是普通的正则表达式. # rabbitmqctl list_user_permissions preprocess Listing permissions for user "preprocess" ... preprocessed ^$ ^amq\\.default$ ^$ rawevent ^$ ^$ ^rawevent$ ...done
来几个例子
官网文档这个表格里面, 第一列就相当于权限啦, 一共有16个权限可以用.
如果想拥有声明queue的权限, 找到queue.declare这行, 可以看到需要在拥有configure的权限, 权限是一个针对queue的正则表达式.
反过来说, 如果我们的configure权限是 .* , 就代表我们可以声明任何名字的queue
如果我们限制他声明的队列只能以abcd开头, 就给他configure权限是 ^abcd.*$
如果还想要queue.bind的权限, 同样找到queue.bind这一行. 看到需要read write两个权限, read权限是queue的正则, write是exchange的正则.
比如, 某用户要订阅消息, 我们限制用户不能自己给queue取名字,只能用服务器生成的队列名字, 而且只能绑定在amq.fanout这个exchange.
比对这张表格,
声明queue需要:
configure权限应该是^mq.gen.*$绑定到amq.fanout需要:
write是^mq.gen.*$
read是 ^amq.fanout$basic_consume需要:
read是^mq.gen.*$结合起来! 就是:(configure write read的顺序)
^mq.gen.$ ^mq.gen.$ ^(amq.fanout) (mq.gen.*)$ -
rabbitmq 学习记录 -- TTL
queue本身的TTL
注意, 这里说的是queue本身的TTL. 不是说里面的消息
声明一个队列的时候, 可以用x-expires指定队列的TTL值. 过期之后, 这个队列就被删掉了.
不管里面是不是还有消息没有消费
#!/usr/bin/env python # -*- coding: utf-8 -*- ''' the queue exists for only 5 seconds, whether there is messages! ''' import pika import sys def main(): body = ' '.join(sys.argv[1:]) or 'Hello World' connection = pika.BlockingConnection( pika.ConnectionParameters( host='localhost')) channel = connection.channel() channel.queue_declare(queue='ttlhello',arguments={"x-expires":5000}) # ttl 5 second channel.basic_publish(exchange='', routing_key='ttlhello', body=body, ) connection.close() if __name__ == '__main__': main()
跑一下看看效果
# python queueTTL.py ;sleep 1; rabbitmqctl list_queues; sleep 6;rabbitmqctl list_queues Listing queues ... ttlhello 1 Listing queues ...
Per-Queue Message TTL
这个也是队列的属性, 而不是消息的. 队列中的所有消息过了TTL就会被删除.
#!/usr/bin/env python # -*- coding: utf-8 -*- ''' the messages in the queue exist for only 5 seconds ''' import pika import sys def main(): body = ' '.join(sys.argv[1:]) or 'Hello World' connection = pika.BlockingConnection( pika.ConnectionParameters( host='localhost')) channel = connection.channel() channel.queue_declare(queue='ttlmessagehello',arguments={"x-message-ttl":5000}) # ttl 5 second channel.basic_publish(exchange='', routing_key='ttlmessagehello', body=body, ) connection.close() if __name__ == '__main__': main()
跑一下看看效果
# python queueMessageTTL.py; rabbitmqctl list_queues; sleep 6; rabbitmqctl list_queues Listing queues ... ttlmessagehello 1 Listing queues ... ttlmessagehello 0
#Per-Message TTL
发送消息的时候, 也可以给每条消息一个TTL的属性.
messageTTLSend.py
#!/usr/bin/env python # -*- coding: utf-8 -*- import pika import sys def main(): body = ' '.join(sys.argv[1:]) or 'Hello World' connection = pika.BlockingConnection( pika.ConnectionParameters( host='localhost')) channel = connection.channel() channel.queue_declare(queue='hello') channel.basic_publish(exchange='', routing_key='hello', body=body, properties=pika.BasicProperties( expiration="5000" ) ) connection.close() if __name__ == '__main__': main()
跑一下看看效果
# python messageTTLSend.py; rabbitmqctl list_queues; sleep 6; rabbitmqctl list_queues; Listing queues ... hello 1 Listing queues ... hello 0
最后, 如per-message ttl 和 per-queue message ttl不一样, 按小的来.
-
rabbitmq 学习记录 -- ACK和数据持久化
为了数据不丢失, 需要在两个层面上做一些配置. 一个是ACK, 一个是数据持久化.
ACK
如果没有启用的话, 消费者拿走消息的时候, queue就把它删除了.
消费者拿走一条消息之后, 还没有处理完就crash了. 那么这条消息就丢失了. 为了保证消息一定被处理完了才从queue中被删掉, 就要启用Message acknowledgment .
启用之后, queue会在收到ack之后把消息删掉.
在这里没有timeout的概念, 哪怕这个任务执行很久, 不管多久, 会一直等ack. 或者是tcp链接断了, 才会把消息再给另外一个消费者.
ack默认是开启的, 也可以显示显示地关闭
channel.basic_consume(callback, queue=queue_name, no_ack=True)
callbak里面要记得发送ack,否则消息要被一次又一次的处理,然后再次回到队列 … …
def callback(ch, method, properties, body): print " [x] Received %r" % (body,) time.sleep( 10 ) raise SystemExit(1) # message will put back to the original queue ch.basic_ack(delivery_tag = method.delivery_tag) print " [x] Done"
来跑几个例子测试一下
生产者:
#!/usr/bin/env python # -*- coding: utf-8 -*- import pika import sys def main(): body = ' '.join(sys.argv[1:]) or 'Hello World' connection = pika.BlockingConnection( pika.ConnectionParameters( host='localhost')) channel = connection.channel() channel.queue_declare(queue='hello') channel.basic_publish(exchange='', routing_key='hello', body=body, ) connection.close() if __name__ == '__main__': main()
消费者:
#!/usr/bin/env python # -*- coding: utf-8 -*- import pika import time def callback(ch, method, properties, body): print " [x] Received %r" % (body,) time.sleep(10) raise SystemExit(1) ch.basic_ack(delivery_tag = method.delivery_tag) print " [x] Done" def main(): connection = pika.BlockingConnection( pika.ConnectionParameters( host='localhost')) channel = connection.channel() channel.queue_declare(queue='hello') channel.basic_consume(callback, queue='hello', ) channel.start_consuming() if __name__ == '__main__': main()
发送一条消息到队列 , 然后消费. 观察一下状态
# rabbitmqctl list_queues name messages_ready messages_unacknowledged Listing queues ... hello 0 1
等10秒, 再看, 消息没有被消费成功, 再次回到队列中.
# rabbitmqctl list_queues name messages_ready messages_unacknowledged Listing queues ... hello 1 0
REJECT
可以用ACK告诉rabbitmq任务处理完了, 但是如果没有成功的话, 也可以再把消息塞回队列. 就是Negative Acknowledgement. pika对应的方法是basic_reject
但可得注意, 不要搞成死循环了
数据持久化
启用ack之后, 消费者死掉不会丢失数据, 但rabbitmq进程死掉的话, 消息就丢掉了. 为保证数据不丢失, 还需要启动数据持久化. 需要在两个层面上做持久化:
- 队列的持久化
- 消息的持久化
channel.queue_declare(queue='hello', durable=True)
这样就申明了一个持久化的队列, durable的属性是不会变的, 如果之前hello队列已经申明过且不是持久化的, 这个再次申明会失败. 这个队列不会因为rabbitmq重启而丢失, 接下来还要继续做消息的持久化.
channel.basic_publish(exchange='', routing_key="task_queue", body=message, properties=pika.BasicProperties( delivery_mode = 2, # make message persistent ))
Q: 如果在一个非持久化的队列上发送数据时, 指明要持久化, 为发生什么情况?
A: 可以正常发送, 但重启rabbitmq之后, 队列丢失, 当然消息找不到了.
-
rabbitmq 学习记录 -- 基本概念
messag queue嘛, 就是生产者往里扔东西, 消费者取走. 但是要涉及到细节,还是有些多的.
#基本概念 其实就是官网文档的搬运工.
路由模型
先来看看一条消息的生命线, 生产者把消息发送到exchange, 然后根据exchange的类型和routing key(消息发送时的一个参数), 把这条消息路由到不同的队列中去, 图片中是发到了一个列队, 其实也可以到多个. 然后消费都从队列中把消息取走. 和kafka有些不同, rabbitmq里面的一个队列里面一条消息被一个消费者拿走之后, 就不可能再被其他人取到了(不考虑ACK的时候).
queue
队列, 就是存储消息的容器. 有些属性
- Name
- Durable (the queue will survive a broker restart)
- Exclusive (used by only one connection and the queue will be deleted when that connection closes)
- Auto-delete (queue is deleted when last consumer unsubscribes)
- Arguments (some brokers use it to implement additional features like message TTL)
使用queue之前, 需要先声明. 生产者和消费者都可以申明声明. 声明的时候如果队列已经存在了, 也没啥事, 但是如果再次声明的时候, 已经存在的队列参数和当时申明的参数不一样, 是会报错的.
exchange
生产者不和queue接触, 消息全部是通过exchange转到对应的queue的. 每一个队列都和一个或者多个exchange绑定在一起. 声明一个queue的时候, 它已经和default exchange绑定在一起了.
exchange有四种类型, 每种有不同的路由方式~
先看一下exchange的属性吧
- Name
- Durability (exchanges survive broker restart)
- Auto-delete (exchange is deleted when all queues have finished using it)
- Arguments (these are broker-dependent)
说exchange类型之前, 要先知道routing key的概念.
- queue和exchange绑定的时候, 是有一个routing key的, 为了和下面的routing key区别开来, 还是叫做binding key吧.
- 发送一条消息时, 有个参数, 叫routing key
- exchange type, bind key, routing key这三个东西结合在一起, 就决定了这条消息最终被路由到哪个/哪几个queue里面.
-
direct exchange
发送到direct的消息, 会找到和routing key一样的binding key的队列, 发过去. 一般来说, 这种exchange就是为了点对点的发消息, 一个消息就是固定发到一个特定的queue中. 但是一定要用来发到多个queue也是可以的.
#!/usr/bin/env python # -*- coding: utf-8 -*- import pika import sys def main(): body = ' '.join(sys.argv[1:]) or 'Hello World' connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) channel = connection.channel() channel.queue_declare(queue='hello1') channel.queue_bind(exchange='amq.direct', queue='hello1',routing_key="hello") channel.queue_declare(queue='hello2') channel.queue_bind(exchange='amq.direct', queue='hello2',routing_key="hello") channel.basic_publish(exchange='amq.direct', routing_key='hello', body=body) connection.close() if __name__ == '__main__': main()
-
fanout
这种类型的exchange会把消息发到每一个和他绑定的队列, routing/binding key被忽略. 适合用广播(和简单订阅?)
-
topic
比较灵活的路由方式, routing key可以用通配符.
* (star) can substitute for exactly one word.
# (hash) can substitute for zero or more words.直接看图吧
-
headers 这种路由的方式还是很灵活的.
如果需要绑定的不是一个特定的字符串, 而多个属性. 比如一条服务器的消息, 有OS, 有CPU核数, 有内存大小, 希望这些全部都匹配成功时,也可以是有一条匹配成功时,发到你的队列中来. 这个时候用headers exchange就比较方便.
绑定的时候,一个重要的参数是x-match. 如果是all,就是说所有属性匹配成功才发到这个队列. 如果是any,就是任意一个属性匹配成功啦.
这里也有个python的例子 Using pika to create headers exchanges with RabbitMQ in python
-
default 官网上把default exchange单独列了出来, 不过在我看来, default exchange就是一个direct exchange. 只是有些特殊的地方:
- 创建一个queue的时候, 自动绑定到default exchange. binding key就是队列名字
- 一个queue不能和default exchange解除绑定 (这点我不100%确定)
我只是大概翻译一下官网文档,记录一下. 这里对exchange介绍的更加详细. Working with RabbitMQ exchanges 这里面介绍比较详细.
-
访问python中的私有变量
要给实习生培训python, 话说我自己都不怎么会用, 不能误人子弟, 再看看一些python中的概念吧.
看到类以及私有变量时, 想到以前看过文章, 说Python里面有私有函数也能被调用, 就顺手搜索了一下, stackoverflow有个问题就是问这个的. Why are Python’s ‘private’ methods not actually private?
HOW
类里面用两个下划线__打头的, 就是私有变量/函数 (后面会说到其实不是这样的)
照着写一份python代码跑跑看
#!/usr/bin/env python # -*- coding: utf-8 -*- class A(object): def __init__(self): super(A, self).__init__() self.name = "childe" self.__age = 28 def pub_f(self): print 'this is a public function' def __pri_f(self): print 'this is a private function' def main(): a = A() a.pub_f() print a.name try: a._pri_f() except AttributeError as e: print e try: print a.__age except AttributeError as e: print e print dir(a) if __name__ == '__main__': main()
运行结果如下:
this is a public function childe 'A' object has no attribute ‘__pri_f' 'A' object has no attribute '__age' ['_A__age', '_A__pri_f', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'pub_f']
可以看到直接调用私有变量/函数失败了. 但是dir(a)的时候, 有’_A__age’, ‘_A__pri_f’ 这两个东西, 来调用一下.
#!/usr/bin/env python # -*- coding: utf-8 -*- class A(object): def __init__(self): super(A, self).__init__() self.name = "childe" self.__age = 28 def pub_f(self): print 'this is a public function' def __pri_f(self): print 'this is a private function' def main(): a = A() try: a._A__pri_f() except AttributeError as e: print e try: print a._A__age except AttributeError as e: print e if __name__ == '__main__': main()
跑一下, 正常输出了.
this is a private function
28WHY
以上还都是PO主的问题以及他的代码, 他问的也不是how, 而是why. 下面那个回答才让我开了眼界(好吧, 是我太无知了). 这次真的是真的copy代码:
class Foo(object): def __init__(self): self.__baz = 42 def foo(self): print self.__baz class Bar(Foo): def __init__(self): super(Bar, self).__init__() self.__baz = 21 def bar(self): print self.__baz x = Bar() x.foo() x.bar() print x.__dict__
运行结果:
42
21
{‘_Bar__baz’: 21, ‘_Foo__baz’: 42}也就是说, 子类的私有变量与父类里面同名的私有变量共存了.. 以前真的不知道还可以这样.
同时, [Alya] 还回答了这个问题,
The name scrambling is used to ensure that subclasses don’t accidentally override the private methods and attributes of their superclasses. It’s not designed to prevent deliberate access from outside.
为比我E文还差的比我还懒的同学翻译一下, 就是说 私有变量不是为了防止从外部调用, 而是为了避免被子类不小心重写.
鉴于Alya只回答过这一个问题, 所以还是去官网看一下类的文档吧.
看过之后, 有种被调戏的感觉. 官网明确说了,python中并没有私有变量, 一个下划线打头表明大家把它”当做”私有变量对待吧, “建议”不要从外部直接改. 至少两个下划线打头, 并以最多一个下划线结尾的变量, 比如说__spam会被重命名为_classname__spam
嗯, 讲python的时候, 又可以多说一点儿了.
C++
C++里面的私有变量应该也是能访问/修改的. 因为类实例是在栈空间里面的, 类实例里面的变量, 不管是私有还是公有, 都顺序排列在内存中.
#include <iostream> using namespace std; class Person{ public: Person(){ this->age = 28; } private: int age; }; int main(int argc, const char *argv[]) { Person p; cout << *(int*)(&p) << endl; return 0; }
% ./a.out 28
不知道怎么调用私有函数, 但我想也是有办法的吧?