内容简介:版权申明:本人仅授权实验楼发布、转载、下载及传播。本实验将讲解自动化运维的概念和基础知识,常用开源软件的使用场景,一个自动化运维系统需要具备哪些功能;通过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通过为开发人员提供在语言、框架和云上的更多的选择,使开发人员可以构建、测试、运行和管理他们的应用。 -
ELKElasticsearch,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自动化批量查单词
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
iOS软件开发揭密
虞斌 / 电子工业出版社 / 2011-5-1 / 79.00元
本书以严密的体系性提供了iPhone和iPad软件开发从入门到专家的系统性知识,并提供来源于真实项目的可重用商业代码。书中的每个实例都是项目经验的提炼,深入浅出地讲解iPhone和iPad软件开发的核心技术要点,基本涵盖了iOS软件开发在真实商业项目中所需要的所有主题,并将实例介绍的技术深度和超值的实用性结合在一起,成为本书的特色。 随书附赠的光盘中包含了书中大量案例的完整工程源代码,可以让......一起来看看 《iOS软件开发揭密》 这本书的介绍吧!