内容简介:版权申明:本人仅授权实验楼发布、转载、下载及传播。本实验将讲解自动化运维的概念和基础知识,常用开源软件的使用场景,一个自动化运维系统需要具备哪些功能;通过Pexpect库实现自动监控服务器的负载、磁盘、内存、CPU、网络接口(流量)、端口等。本课程难度为初级,适合想了解自动化运维或具有Python基础的用户,使用Python开发自动化运维系统应该从何入手。
版权申明:本人仅授权实验楼发布、转载、下载及传播。
一、实验介绍
1.1 实验内容
本实验将讲解自动化运维的概念和基础知识,常用开源软件的使用场景,一个自动化运维系统需要具备哪些功能;通过Pexpect库实现自动监控服务器的负载、磁盘、内存、CPU、网络接口(流量)、端口等。
1.2 实验知识点
自动化监控
1.3 实验环境
- Python2.7
- Xfce终端
- Pexpect模块
1.4 适合人群
本课程难度为初级,适合想了解自动化运维或具有 Python 基础的用户,使用Python开发自动化运维系统应该从何入手。
1.5 代码获取
你可以通过下面命令将代码下载到实验楼环境中,作为参照对比进行学习。
$ wget https://shiyanlou1.oss-cn-shanghai.aliyuncs.com/code/monitor.py
二、实验原理
Pexpect库的核心组件
-
spawn类
spawn是pexpect的主要类接口,功能是启动和控制子应用程序,以下是它的构造函数定义:class pexpect.spawn(command, args=[], timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None, ignore_sighup=False, echo=True, preexec_fn=None, encoding=None, codec_errors='strict', dimensions=None)
-
sendline
发送需要执行的命令,如输入password -
expect
期望得到的输出结果,可以通过列表传递多个值,如["$", "#"]
-
pexpect.EOF
、pexpect.TIMEOUT
换行符,连接超时报错
三、开发准备
打开Xfce终端,进入 /home/shiyanlou
目录,创建 monitor.py
文件
$ touch monitor.py
安装 pexpect
模块
$ sudo pip install pexpect==4.6.0
四、实验步骤
4.1 什么是自动化运维
自动化运维是指将IT运维中日常的、大量的重复性工作自动化,把过去的手工执行转为自动化操作。 自动化是IT运维工作的升华,自动化运维不单纯是一个维护过程,更是一个管理的提升过程,是IT运维的最高层次,也是未来的发展趋势。
4.2 开源自动化运维工具
-
Jekins
一个具有许多插件的自动化服务器。用于构建,测试和自动化部署应用程序。通常Jenkins用作软件开发的CI/CD工具。Jenkins 的作业(构建)可以由各种触发器启动。例如提交代码到版本控制系统,按计划事件,通过访问特定URL构建或者在完成其它构建之后进行触发。 -
Ansible
集合了众多运维工具(puppet、chef、func、fabric)的优点,实现了批量系统配置、批量程序部署、批量运行命令等功能。 -
SaltStack
基于Python开发的一套C/S架构配置管理工具,简单易部署,同时支持服务器/客户端 和无代理模式。在后一种情况下,Salt 使用SSH连接到受管理的节点/虚拟机。Salt 使用以Python编写的执行模块,其中包含函数以定义配置任务。 -
Nagios
网络监控工具,能有效监控Windows、 Linux 和Unix的主机状态,交换机、路由器等网络设备,并发送告警信息。 -
Zabbix
是一个为应用服务,网络服务和硬件监控提供的解决方案。Zabbix 将收集的数据存储在关系数据库中,如MySQL,PostgreSQL等。Zabbix 允许你监控简单的服务,如HTTP服务。Zabbix agent端可以安装在Windows和 类Unix服务器上,用来检视系统参数,如CPU负载、内存和磁盘利用率等。另外,agent可用于监视标准服务和自定义应用程序。Zabbix也支持通过SNMP、SSH等方式,无需在要监视的服务器上安装代理。 -
Kubernets
简称k8s,是来自 Google 云平台的开源容器集群管理系统,功能包括自动化容器的部署,调度和节点集群间扩展,支持 Docker 和Rocket。 -
OpenShift
由RedHat推出的一款面向开源开发人员开放的平台即服务(PaaS)。 OpenShift通过为开发人员提供在语言、框架和云上的更多的选择,使开发人员可以构建、测试、运行和管理他们的应用。 -
ELK
Elasticsearch,Logstash,Kibana软件的组合,它是用于记录,日志分析,日志搜索和可视化的完整工具。Elasticsearch是基于Apache Lucene的搜索工具。Logstash是用于收集,解析和存储日志的工具,可以通过Elasticsearch对其进行索引。
4.3 自动化运维系统的功能
具体应包括哪些功能没有统一的标准,视每个公司业务和开发需求而定。大体上,一个成熟的自动化运维系统功能应包含如下五大功能模块:
(CMDB)配置管理 集中监控报警 (ITSM)流程管理 日志分析与处理 持续集成与发布
推荐几个Github上不错的自动化运维平台:
- OpsManage 代码及应用部署CI/CD、资产管理CMDB、计划任务管理平台、 SQL 审核|回滚、任务调度、站内WIKI
- adminset CMDB、CD、DevOps、资产管理、任务编排、持续交付、系统监控、运维管理、配置管理
- jumpserver 全球首款完全开源的堡垒机,是符合 4A 的专业运维审计系统, 官网地址
4.4 Pexpect实现自动化监控服务器
Pexpect 是一个用来启动子程序并对其进行自动控制的纯 Python 模块。 Pexpect 可以用来和像 ssh、ftp、passwd、telnet 等命令行程序进行自动交互。通过Pexpect实现连接到服务器,并执行指定命令,如 cat /proc/cpuinfo
,通过正则表达式过滤或字符串切片得到需要的CPU型号、核数、主频等信息,其它监控项类似。
4.4.1 SSH登录执行命令
函数通过执行 ssh -l user host command
,使用 user
用户登录到 host
,获取 command
命令的返回结果
def ssh_command(host, user, password, command): """ SSH登录执行命令 :param host: <str> 主机IP :param user: <str> 用户名 :param password: <str> 登录密码 :param command: <str> bash命令 :return: pexpect的spawn对象 """ ssh = pexpect.spawn('ssh -l {} {} {}'.format(user, host, command)) # 登录口令 i = ssh.expect(['password:', 'continue connecting (yes/no)?'], timeout=30) if i == 0: ssh.sendline(password) if i == 1: ssh.sendline('yes') ssh.expect('[p,P]assword: ') # Password/password ssh.sendline(password) index = ssh.expect(["$", "#", pexpect.EOF, pexpect.TIMEOUT]) # 此处注意,root用户登录符号为#,普通用户为$ if index != 0: print("登录失败!报错内容:{};{}".format(ssh.before, ssh.after)) return False return ssh
4.4.2 内存监控
执行 cat /proc/meminfo
,使用 r"(\d+) kB"
正则匹配内存信息
def memory(): """内存监控""" ssh = ssh_command("192.168.1.1", "username", "password", "cat /proc/meminfo") ssh.expect(pexpect.EOF) # 命令执行完毕 ssh.close() # 关闭连接进程 data = re.findall(b"(\d+) kB", ssh.before) MemTotal = int(data[0]) / 1024 # 除以1024得到MB MemFree = int(data[1]) / 1024 Buffers = int(data[2]) / 1024 Cached = int(data[3]) / 1024 SwapCached = int(data[4]) / 1024 SwapTotal = int(data[13]) / 1024 SwapFree = int(data[14]) / 1024 print("*******************内存监控 {}******************".format(datetime.today().strftime("%Y-%m-%d %H:%M:%S"))) print("总内存: {} MB".format(MemTotal)) print("空闲内存: {} MB".format(MemFree)) print("给文件的缓冲大小: {} MB".format(Buffers)) print("高速缓冲存储器使用的大小: {} MB".format(Cached)) print("被高速缓冲存储用的交换空间大小: {} MB".format(SwapCached)) print("给文件的缓冲大小: {} MB".format(Buffers)) if int(SwapTotal) == 0: print("交换内存总共为:0") else: print("交换内存利用率: {0:.4}%".format((SwapTotal - SwapFree) / float(SwapTotal) * 100)) print("内存利用率: {0:.4}%".format((MemTotal - MemFree) / float(MemTotal) * 100))
效果图如下
4.4.3 负载监控
执行 cat /proc/loadavg
,使用字符串切片获取结果
def load(): """监控负载""" ssh = ssh_command("192.168.1.1", "username", "password", "cat /proc/loadavg") ssh.expect(pexpect.EOF) ssh.close() loadavg = ssh.before.strip().split() print("*******************负载均衡监控 {}******************".format(datetime.today().strftime("%Y-%m-%d %H:%M:%S"))) print("系统5分钟前的平均负载: {}".format(loadavg[0])) print("系统10分钟前的平均负载: {}".format(loadavg[1])) print("系统15分钟前的平均负载: {}".format(loadavg[2])) print("分子是正在运行的进程数,分母为总进程数: {}".format(loadavg[3])) print("最近运行的进程ID: {}".format(loadavg[4]))
效果图如下
4.4.4 磁盘空间监控
执行 df -h
,使用字符串切片获取结果
def disk(): """磁盘空间监控""" ssh = ssh_command("192.168.1.1", "username", "password", "df -h") ssh.expect(pexpect.EOF) ssh.close() data = ssh.before.strip().split('\n') disklists = [] for disk in data: disklists.append(disk.strip().split()) print("*******************磁盘空间 {}******************".format(datetime.today().strftime("%Y-%m-%d %H:%M:%S"))) for i in disklists[1:]: print("\t文件系统: {}".format(i[0])) print("\t容量: {}".format(i[1])) print("\t已用: {}".format(i[2])) print("\t可用: {}".format(i[3])) print("\t已用%挂载点: {}".format(i[4]))
效果图如下
4.4.5 网卡流量监控
执行 cat /proc/net/dev
,使用字符串切片获取结果
def ionetwork(): """获取网络接口的输入和输出""" ssh = ssh_command("192.168.1.1", "username", "password", "cat /proc/net/dev") ssh.expect(pexpect.EOF) ssh.close() li = ssh.before.strip().split('\n') print("*******************网络接口的输入和输出监控 {}******************".format(datetime.today().strftime("%Y-%m-%d %H:%M:%S"))) net = {} for line in li[2:]: net_io = {} line = line.split(":") eth_name = line[0].strip() net_io['Receive'] = round(float(line[1].split()[0]) / (1024.0 * 1024.0), 2) # bytes / 1024 / 1024 得到MB net_io['Transmit'] = round(float(line[1].split()[8]) / (1024.0 * 1024.0), 2) net[eth_name] = net_io for k, v in net.items(): print("接口{}: 接收 {}MB 传输 {}MB".format(k, v.get("Receive"), v.get("Transmit")))
效果图如下
4.4.6 活动端口监控
执行 cat /proc/net/dev
,直接输出结果
def com(): """端口监控""" ssh = ssh_command("192.168.91.58", "support", "splinux", "netstat -tpln") ssh.expect(pexpect.EOF) ssh.close() print("*******************端口监控 {}******************".format(datetime.today().strftime("%Y-%m-%d %H:%M:%S"))) print(ssh.before)
效果图如下
4.4.7 获取CPU信息
执行 cat /proc/cpuinfo
,使用 r'processor.*?(\d+)'
正则匹配CPU信息
def cpu_info(): """CPU信息获取""" ssh = ssh_command("192.168.1.1", "username", "password", "cat /proc/cpuinfo") ssh.expect(pexpect.EOF) ssh.close() cpu_num = re.findall('processor.*?(\d+)', ssh.before)[-1] print("*******************CPU信息 {}******************".format(datetime.today().strftime("%Y-%m-%d %H:%M:%S"))) print("CPU数目: {}".format(str(int(cpu_num) + 1))) li = ssh.before.replace('\t', '').split('\r') CPUinfo, procinfo, nprocs = {}, {}, 0 for line in li: if line.find("processor") > -1: CPUinfo['CPU%s' % nprocs] = procinfo nprocs = nprocs + 1 else: if len(line.split(':')) == 2: procinfo[line.split(':')[0].strip()] = line.split(':')[1].strip() else: procinfo[line.split(':')[0].strip()] = '' for processor in CPUinfo.keys(): print("CPU属于的名字及其编号、标称主频: {}".format(CPUinfo[processor]['model name'])) print("CPU属于其系列中的哪一代的代号: {}".format(CPUinfo[processor]['model'])) print("CPU制造商: {}".format(CPUinfo[processor]['vendor_id'])) print("CPU产品系列代号: {}".format(CPUinfo[processor]['cpu family'])) print("CPU的实际使用主频: {0:.2} GHz".format(float(CPUinfo[processor]['cpu MHz']) / 1024))
效果图如下
4.4.8 获取vmstat信息
执行 vmstat 1 2 | tail -n 1
,直接输出结果
def vmstat(): """内核线程、虚拟内存、磁盘和CPU活动的统计信息""" ssh = ssh_command("192.168.1.1", "username", "password", "vmstat 1 2 | tail -n 1") ssh.expect(pexpect.EOF) ssh.close() vmstat_info = ssh.before.strip().split() processes_waiting = vmstat_info[0] processes_sleep = vmstat_info[1] swpd = int(vmstat_info[2]) / 1024 free = int(vmstat_info[3]) / 1024 buff = int(vmstat_info[4]) / 1024 cache = int(vmstat_info[5]) / 1024 si = int(vmstat_info[6]) / 1024 so = int(vmstat_info[7]) / 1024 io_bi = vmstat_info[8] io_bo = vmstat_info[9] system_interrupt = vmstat_info[10] system_context_switch = vmstat_info[11] cpu_user = vmstat_info[12] cpu_sys = vmstat_info[13] cpu_idle = vmstat_info[14] cpu_wait = vmstat_info[15] st = vmstat_info[16] print("*******************vmstat信息统计 {}******************".format(datetime.today().strftime("%Y-%m-%d %H:%M:%S"))) print("等待运行进程的数量: {}".format(processes_waiting)) print("处于不间断状态的进程: {}".format(processes_sleep)) print("使用虚拟内存(swap)的总量: {} MB".format(swpd)) print("空闲的内存总量: {} MB".format(free)) print("用作缓冲的内存总量: {} MB".format(buff)) print("用作缓存的内存总量: {} MB".format(cache)) print("交换出内存总量 : {} MB".format(si)) print("交换入内存总量 : {} MB".format(so)) print("从一个块设备接收: {}".format(io_bi)) print("发送到块设备: {}".format(io_bo)) print("每秒的中断数: {}".format(system_interrupt)) print("每秒的上下文切换数: {}".format(system_context_switch)) print("用户空间上进程运行的时间百分比: {}".format(cpu_user)) print("内核空间上进程运行的时间百分比: {}".format(cpu_sys)) print("闲置时间百分比: {}".format(cpu_idle)) print("等待IO的时间百分比: {}".format(cpu_wait)) print("从虚拟机偷取的时间百分比: {}".format(st))
效果图如下
将上述功能函数使用使用面向对象的方式实现,通过 While True
循环每分钟执行一轮,完整代码如下:
#!/usr/bin/python3 # -*- coding:utf-8 -*- # __author__ = 'liao gao xiang' import re import time from datetime import datetime import pexpect class Monitor(object): """服务器自动化监控""" def __init__(self): self.host = "192.168.1.1" self.user = "username" self.password = "password" def ssh_command(self, command): """SSH登录执行命令""" ssh = pexpect.spawn('ssh -l {} {} {}'.format(self.user, self.host, command)) # 登录口令 i = ssh.expect(['password:', 'continue connecting (yes/no)?'], timeout=30) if i == 0: ssh.sendline(self.password) if i == 1: ssh.sendline('yes') ssh.expect('[p,P]assword: ') ssh.sendline(self.password) index = ssh.expect(["$", "#", pexpect.EOF, pexpect.TIMEOUT]) # 此处注意,root用户登录符号为#,普通用户为$ if index != 0: print("登录失败!报错内容:{};{}".format(ssh.before, ssh.after)) return False return ssh def memory(self): """内存监控""" ssh = self.ssh_command("cat /proc/meminfo") ssh.expect(pexpect.EOF) data = re.findall(b"(\d+) kB", ssh.before) MemTotal = int(data[0]) / 1024 # 除以1024得到MB MemFree = int(data[1]) / 1024 Buffers = int(data[2]) / 1024 Cached = int(data[3]) / 1024 SwapCached = int(data[4]) / 1024 SwapTotal = int(data[13]) / 1024 SwapFree = int(data[14]) / 1024 print("*******************内存监控 {}******************".format(datetime.today().strftime("%Y-%m-%d %H:%M:%S"))) print("总内存: {} MB".format(MemTotal)) print("空闲内存: {} MB".format(MemFree)) print("给文件的缓冲大小: {} MB".format(Buffers)) print("高速缓冲存储器使用的大小: {} MB".format(Cached)) print("被高速缓冲存储用的交换空间大小: {} MB".format(SwapCached)) print("给文件的缓冲大小: {} MB".format(Buffers)) if int(SwapTotal) == 0: print("交换内存总共为:0") else: print("交换内存利用率: {0:.4}%".format((SwapTotal - SwapFree) / float(SwapTotal) * 100)) print("内存利用率: {0:.4}%".format((MemTotal - MemFree) / float(MemTotal) * 100)) def vmstat(self): """内核线程、虚拟内存、磁盘和CPU活动的统计信息""" ssh = self.ssh_command("vmstat 1 2 | tail -n 1") ssh.expect(pexpect.EOF) vmstat_info = ssh.before.strip().split() processes_waiting = vmstat_info[0] processes_sleep = vmstat_info[1] swpd = int(vmstat_info[2]) / 1024 free = int(vmstat_info[3]) / 1024 buff = int(vmstat_info[4]) / 1024 cache = int(vmstat_info[5]) / 1024 si = int(vmstat_info[6]) / 1024 so = int(vmstat_info[7]) / 1024 io_bi = vmstat_info[8] io_bo = vmstat_info[9] system_interrupt = vmstat_info[10] system_context_switch = vmstat_info[11] cpu_user = vmstat_info[12] cpu_sys = vmstat_info[13] cpu_idle = vmstat_info[14] cpu_wait = vmstat_info[15] st = vmstat_info[16] print("*******************vmstat信息统计 {}******************".format(datetime.today().strftime("%Y-%m-%d %H:%M:%S"))) print("等待运行进程的数量: {}".format(processes_waiting)) print("处于不间断状态的进程: {}".format(processes_sleep)) print("使用虚拟内存(swap)的总量: {} MB".format(swpd)) print("空闲的内存总量: {} MB".format(free)) print("用作缓冲的内存总量: {} MB".format(buff)) print("用作缓存的内存总量: {} MB".format(cache)) print("交换出内存总量 : {} MB".format(si)) print("交换入内存总量 : {} MB".format(so)) print("从一个块设备接收: {}".format(io_bi)) print("发送到块设备: {}".format(io_bo)) print("每秒的中断数: {}".format(system_interrupt)) print("每秒的上下文切换数: {}".format(system_context_switch)) print("用户空间上进程运行的时间百分比: {}".format(cpu_user)) print("内核空间上进程运行的时间百分比: {}".format(cpu_sys)) print("闲置时间百分比: {}".format(cpu_idle)) print("等待IO的时间百分比: {}".format(cpu_wait)) print("从虚拟机偷取的时间百分比: {}".format(st)) def cpu_info(self): """CPU信息获取""" ssh = self.ssh_command("cat /proc/cpuinfo") ssh.expect(pexpect.EOF) cpu_num = re.findall(r'processor.*?(\d+)', ssh.before)[-1] print("*******************CPU信息 {}******************".format(datetime.today().strftime("%Y-%m-%d %H:%M:%S"))) print("CPU数目: {}".format(str(int(cpu_num) + 1))) li = ssh.before.replace('\t', '').split('\r') CPUinfo, procinfo, nprocs = {}, {}, 0 for line in li: if line.find("processor") > -1: CPUinfo['CPU%s' % nprocs] = procinfo nprocs = nprocs + 1 else: if len(line.split(':')) == 2: procinfo[line.split(':')[0].strip()] = line.split(':')[1].strip() else: procinfo[line.split(':')[0].strip()] = '' for processor in CPUinfo.keys(): print("CPU属于的名字及其编号、标称主频: {}".format(CPUinfo[processor]['model name'])) print("CPU属于其系列中的哪一代的代号: {}".format(CPUinfo[processor]['model'])) print("CPU制造商: {}".format(CPUinfo[processor]['vendor_id'])) print("CPU产品系列代号: {}".format(CPUinfo[processor]['cpu family'])) print("CPU的实际使用主频: {0:.2} GHz".format(float(CPUinfo[processor]['cpu MHz']) / 1024)) def load(self): """监控负载""" ssh = self.ssh_command("cat /proc/loadavg") ssh.expect(pexpect.EOF) loadavg = ssh.before.strip().split() print("*******************负载均衡监控 {}******************".format(datetime.today().strftime("%Y-%m-%d %H:%M:%S"))) print("系统5分钟前的平均负载: {}".format(loadavg[0])) print("系统10分钟前的平均负载: {}".format(loadavg[1])) print("系统15分钟前的平均负载: {}".format(loadavg[2])) print("分子是正在运行的进程数,分母为总进程数: {}".format(loadavg[3])) print("最近运行的进程ID: {}".format(loadavg[4])) def ionetwork(self): """获取网络接口的输入和输出""" ssh = self.ssh_command("cat /proc/net/dev") ssh.expect(pexpect.EOF) li = ssh.before.strip().split('\n') print("*******************网络接口的输入和输出监控 {}******************".format(datetime.today().strftime("%Y-%m-%d %H:%M:%S"))) net = {} for line in li[2:]: net_io = {} line = line.split(":") eth_name = line[0].strip() net_io['Receive'] = round(float(line[1].split()[0]) / (1024.0 * 1024.0), 2) # bytes / 1024 / 1024 得到MB net_io['Transmit'] = round(float(line[1].split()[8]) / (1024.0 * 1024.0), 2) net[eth_name] = net_io for k, v in net.items(): print("接口{}: 接收 {}MB 传输 {}MB".format(k, v.get("Receive"), v.get("Transmit"))) def disk(self): """磁盘空间监控""" ssh = self.ssh_command("df -h") ssh.expect(pexpect.EOF) data = ssh.before.strip().split('\n') disklists = [] for disk in data: disklists.append(disk.strip().split()) print("*******************磁盘空间 {}******************".format(datetime.today().strftime("%Y-%m-%d %H:%M:%S"))) for i in disklists[1:]: print("\t文件系统: {}".format(i[0])) print("\t容量: {}".format(i[1])) print("\t已用: {}".format(i[2])) print("\t可用: {}".format(i[3])) print("\t已用%挂载点: {}".format(i[4])) def com(self): """端口监控""" ssh = self.ssh_command("netstat -tpln") ssh.expect(pexpect.EOF) print("*******************端口监控 {}******************".format(datetime.today().strftime("%Y-%m-%d %H:%M:%S"))) print(ssh.before) if __name__ == '__main__': m = Monitor() while True: m.memory() m.vmstat() m.cpu_info() m.load() m.ionetwork() m.disk() m.com() time.sleep(60)
五、实验总结
本实验讲解了自动化运维的基本概念,常用的开源 工具 及使用场景,阐述了一个成熟的自动化运维系统应该具有哪些功能,通过自动化服务器监控脚本定时获取服务器信息,模拟自动化运维的实现过程。开发自动化运维平台是一个持续的过程,我们可以借助开源软件简化工作,结合公司具体业务实现目标。
六、课后习题
监控服务器当前的在线用户数,每分钟返回一次结果
七、参考链接
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 某小公司自动化智能监控平台的实践
- 用深度学习DIY自动化监控系统
- 从列表搜索到自动化测试监控的碎碎念
- 使用 Monit 替代 Supervisor 自动化管理和监控服务小结
- Kong 发布 Kong Brain 和 Kong Immunity,可进行智能自动化和适应性监控
- Java自动化——使用Selenium+POI实现Excel自动化批量查单词
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
精通Windows应用开发
[美] Jesse Liberty Philip Japikse Jon Galloway / 苏宝龙 / 人民邮电出版社 / 59.00元
Windows 8.1的出现不仅提供了跨设备的用户体验,也提供了跨设备的开发体验。本书着眼于实际项目中所需要的特性,以及现有C#编程知识的运用,对如何最大限度地利用Metro、WinRT和Windows 8进行了讲解,内容详尽,注重理论学习与实践开发的配合。 Windows 8.1和WinRT的作用及其特殊性 如何使用先进特性创建具有沉浸感和吸引力的Windows 8.1应用 如......一起来看看 《精通Windows应用开发》 这本书的介绍吧!