ycycoco 发表于 2018-8-6 08:49:46

Python Day8

异常处理

什么是异常
  异常就是程序运行时发生错误的信号(在程序出现错误时,则会产生一个异常,若程序没有处理它,则会抛出该异常,程序的运行也随之终止)
  在python中,错误触发的异常如下


常见异常类型
  http://www.cnblogs.com/linhaifeng/articles/6232220.html
  I:语法错误应该在程序运行前修正
  

if 1 >2  print('xxxxx')
  

  II:逻辑错误
  

x #NameError  

  
l=[]
  
l #IndexError
  

  
class Foo:
  pass
  
Foo.x #AttributeError:
  

  
k={'x':1}
  
k['y'] #KeyError
  

  
1/0 #ZeroDivisionError
  

  
for i in 3: #TypeError:
  pass
  

  
age=input('>>: ') #此时输入非数字
  
age=int(age)#ValueError
  

如何处理异常
  为了保证程序的健壮性与容错性,即在遇到错误时程序不会崩溃,我们需要对异常进行处理

如果错误发生的条件是可预知的
  我们需要用if进行处理:在错误发生之前进行预防
  

age=input('>>: ').strip()  if age.isdigit(): #只有在age为字符串形式的整数时,下列代码才不会出错,该条件是可预知的
  age=int(age)
  

如果错误发生的条件是不可预知的
  如果错误发生的条件是不可预知的,则需要用到try...except:在错误发生之后进行处理
  基本语法为
  

try:  被检测的代码块
  
except 异常类型:
  try中一旦检测到异常,就执行这个位置的逻辑
  

  注意:只能捕捉一个异常
  举例1:
  

print('====>start<=====')  

  
try:
  l=[]
  print(l)#此时发生异常
  print('====>1')
  print('====>2')
  print('====>3')
  
except IndexError:#匹配异常类型
  pass#可以写处理代码
  

  
print('====>end<=======')
  
不会影响程序的整体运行
  

  举例2:
  

print('====>start<=====')  
try:
  l=[]
  print(l)
  print('====>1')
  print('====>2')
  print('====>3')
  
except IndexError as e: #赋值给一个变量,变量里是异常的值
  print('===>',e)#打印异常的值
  

  
print('====>end<=======')
  

  1 异常类只能用来处理指定的异常情况,如果非指定异常则无法处理。
  

s1 = 'hello'  
try:
  int(s1)
  
except IndexError as e: #未捕获到异常,程序直接报错,因为上面的错误类型不是IndexError
  print(e)
  

  2 多分支
  多次匹配,如同if..elif
  

s1 = 'hello'  
try:
  int(s1)
  
except IndexError as e:
  print(e)
  
except KeyError as e:
  print(e)
  
except ValueError as e:
  print(e)
  

  3 万能异常Exception
  万能异常就是所有异常都可以匹配,类似if..else,使用逻辑也类似,不可以随随便便只使用Exception
  万能异常与多分支配合
  

print('====>start<=====')  
try:
  print('====>1')
  d={}
  d['k']
  print('====>2')
  print('====>3')
  
except IndexError: #如果是IndexError的话那么可以定义IndexError处理逻辑
  pass
  
except KeyError:   #如果是KeyError那么可以定义KeyError的处理逻辑
  pass
  
except Exception as e: #上面两个都没匹配上的话匹配万能异常Exception
  print('万能异常--->',e)
  

  
print('====>end<=======')
  

  4 有没有异常都触发finally和else
  

print('====>start<=====')  
try:
  l=[]
  print(l) #这里触发了异常
  
except IndexError:
  pass
  
except KeyError:
  pass
  
except Exception as e:
  print('万能异常--->',e)
  
else:      #触发条件是“没有异常发生的时候触发”,这个else很多地方都能用
  print('没有异常发生的时候触发')
  
finally:   #触发条件是“有没有异常都触发”
  print('有没有异常都触发')
  

  
print('====>end<=======')
  

  结果
  

====>start<=====  
有没有异常都触发
  
====>end<=======
  

  finally的用途
  举例:
  

try:  conn=connect('1.1.1.1',3306)
  conn.execute('select * from db1.t1')
  
finally:
  conn.close()
  

  上面的代码模拟连接mysql,此时可能报延长,但这样操作有一个报不报异常都需要进行的步骤
  这就是回收系统资源,此时finally就派上用场了
  5 主动触发异常raise和断言
  

ip_list=[  '1.1.1.1:8080',
  '1.1.1.2:8081',
  '1.1.1.3:8082',
  
]
  

  
判断上面的列表是否为空
  
方法1:
  
if len(ip_list) == 0:
  raise TypeError #如果上面的条件成立主动触发异常结束程序
  

  
方法2:
  
assert len(ip_list) > 0 #断言列表里面数量大于0,如果断言成功则接着执行下面的代码,如果断言失败则报异常,这样写比raise更简洁
  

  
print('从ip_list取出ip地址,验证可用性')
  

  6 自定义异常
  

class MyException(BaseException):  def __init__(self,msg):
  self.msg=msg
  

  def __str__(self):
  return '<%s>' %self.msg
  

  
try:
  raise MyException('类型错误')
  
except MyException as e:
  print(e)
  

  #异常的值:print(obj)
  #'错误类型'这几个字就是提示的错误的值

总结try..except
  1:把错误处理和真正的工作分开来
  2:代码更易组织,更清晰,复杂的工作任务更容易实现;
  3:毫无疑问,更安全了,不至于由于一些小的疏忽而使程序意外崩溃了;

基于tcp协议的简单套接字通信

socket是什么
  Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
  在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
  所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

套接字工作流程


服务端套接字函数
  服务端套接字函数
  

s.bind()    绑定(主机,端口号)到套接字  
s.listen(backlog)开始TCP监听,backlog指操作系统可以挂起的最大连接数量,该值至少为1,大部分应用程序设为5就可以了
  
s.accept()被动接受TCP客户的连接,(阻塞式)等待连接的到来
  
接受TCP连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据,服务器必须通过它与客户通信,address是连接客户端的地址。
  

  客户端套接字(socket)函数
  

s.connect(address)   主动初始化TCP服务器连接,一般address的格式为元组(hostname,port)  
s.connect_ex()connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
  

  公共用途的套接字函数
  

s.recv(bufsize[,flag])            接收TCP数据,bufsize指定要接收的最大数据量,flag通常可以忽略  
s.send()            发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
  
s.close()         关闭套接字
  

举例1
  socket通信流程与打电话流程类似
  服务端:
  

import socket  

  
#1、买手机(获取tcp/ip套接字)
  
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp协议
  
#socket.AF_INET 基于网络通信   #socket.SOCK_STREAM 流式套接字
  

  
#2、绑定手机
  
phone.bind(('127.0.0.1',8081)) #0-65535
  

  
#3、开机
  
phone.listen(5) #这个5指的的是tcp半连接池的大小
  

  
#4、等待电话连接
  
print('starting...')
  
conn,client_addr=phone.accept() #(conn,client_addr)
  
#phone.accept()中是链接对象(套接字对象)和客户端的ip和端口
  
print(conn,client_addr)
  

  
#5、收\发消息
  
data=conn.recv(1024) #1024bytes
  

  
conn.send(data.upper())
  

  
#6、挂电话连接
  
conn.close()
  

  
#7、关机
  
phone.close()
  

  客户端:
  

import socket  

  
#1、买手机
  
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp协议
  

  
#2、拨电话
  
phone.connect(('127.0.0.1',8081)) #0-65535
  

  
#3、发收消息
  
phone.send('hello'.encode('utf-8'))
  

  
data=phone.recv(1024)
  
print(data)
  

  
#4、挂电话
  
phone.close()
  

举例2 加上通信循环与链接循环
  

import socket  

  
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  
phone.bind(('127.0.0.1',8080))
  
phone.listen(5)
  

  
print('starting...')
  

  
while True: #链接的循环
  conn,client_addr=phone.accept() #(conn,client_addr) (链接对象(套接字对象),客户端的ip和端口)
  print(client_addr)
  while True: #通信循环
  try:
  data=conn.recv(1024) #bytes
  if not data:break #针对linux系统,linux系统中客户端单方便断开连接,服务端不会报错会一直收空
  print('客户端消息: ',data)
  conn.send(data.upper())
  except ConnectionResetError: #在windows上客户端单方面断开链接会报错,这里将报错处理掉
  break
  conn.close()
  
phone.close()
  

粘包现象

什么是粘包
  以模拟ssh远程执行命令为例
  服务端:
  

from socket import *  
import subprocess
  

  
server=socket(AF_INET,SOCK_STREAM)
  
server.bind(('127.0.0.1',8080))
  
server.listen(5)
  

  
print('starting...')
  

  
while True: #链接的循环
  conn,client_addr=server.accept() #(conn,client_addr) (链接对象(套接字对象),客户端的ip和端口)
  print(client_addr)
  while True: #通信循环
  try:
  cmd=conn.recv(1024) #bytes
  if not cmd:break #针对linux系统,linux系统中客户端单方便断开连接,服务端不会报错会一直收空
  

  obj=subprocess.Popen(cmd.decode('utf-8'), shell=True,
  stdout=subprocess.PIPE,# 创建一个管道,把正确的结果传进这个管道
  stderr=subprocess.PIPE,# 再创建一个管道,把执行错误的结果传进
  )
  stdout=obj.stdout.read() #默认就是bytes,windows平台默认GBK编码
  stderr=obj.stderr.read()
  

  cmd_res = stdout + stderr
  print(len(cmd_res))
  conn.send(cmd_res)
  except ConnectionResetError: #在windows上客户端单方面断开链接会报错,这里将报错处理掉
  break
  conn.close()
  

  
server.close()
  

  客户端:
  

from socket import *  

  
client=socket(AF_INET,SOCK_STREAM) #tcp协议
  
client.connect(('127.0.0.1',8080))
  

  
while True:
  cmd=input('>>: ').strip()
  if not cmd:continue
  client.send(cmd.encode('utf-8'))
  data=client.recv(1024)
  print(data.decode('gbk')) #注意这里的gbk编码,因为windows平台默认编码就是gbk
  

  
client.close()
  

  上述程序是基于tcp的socket,在运行时会发生粘包
  此时客户端执行一个结果比较长的命令,例如tasklist,可以正常得到结果,但会发现返回的结果不完整。
  此时再执行第二个命令,例如dir,就会发现你拿到并不是dir命令的结果,而是上一条tasklist命令的部分结果。
  这是因为,tasklist命令的结果比较长,但客户端只recv(1024), 可结果比1024长呀,那怎么办?
  只好在服务器端的IO缓冲区里把客户端还没收走的暂时存下来,等客户端下次再来收,所以当客户端第2次调用recv(1024)就会首先把上次没收完的数据先收下来,再收dir命令的结果。
  这个现象叫做粘包,就是指两次结果粘到一起了。它的发生主要是因为socket缓冲区导致的

  须知:只有TCP有粘包现象,UDP永远不会粘包
  UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的

解决粘包问题

struct模块
  该模块可以把一个类型,如数字,转成固定长度的bytes
  

import struct  
res=struct.pack('i',1111111111)
  
print(res,len(res))
  
结果:
  
b'\xc75:B' 4
  

还以模拟ssh远程执行命令为例
  思路:
  为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据
  把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节
  服务端:
  

from socket import *  
import subprocess
  
import struct
  
import json
  

  
server=socket(AF_INET,SOCK_STREAM)
  
server.bind(('127.0.0.1',8080))
  
server.listen(5)
  

  
while True:
  conn,client_addr=server.accept()
  print(client_addr)
  

  while True:
  try:
  cmd=conn.recv(8096)
  if not cmd:break #针对linux系统
  

  obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,
  stdout=subprocess.PIPE,#创建一个管道,把正确的结果传进这个管道
  stderr=subprocess.PIPE   #再创建一个管道,把执行错误的结果传进
  )
  stdout=obj.stdout.read()#默认就是bytes,windows平台默认GBK编码
  stderr=obj.stderr.read()
  

  #1、制作报头
  headers = {
  'filepath': 'a.txt',
  'md5': '123sxd123x123',
  'total_size': len(stdout) + len(stderr)
  }
  #为了该报头能传送,需要序列化并且转为bytes
  headers_bytes = json.dumps(headers).encode('utf-8') #序列化并转成bytes,用于传输
  

  #2、先发报头的长度,为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节
  conn.send(struct.pack('i',len(headers_bytes)))
  

  #3、发送报头
  conn.send(headers_bytes)
  

  #4、发送命令的执行结果
  conn.send(stdout)
  conn.send(stderr)
  except ConnectionResetError: #在windows上客户端单方面断开链接会报错,这里将报错处理掉
  break
  conn.close()
  

  
server.close()
  

  客户端:
  

from socket import *  
import struct
  
import json
  

  
client=socket(AF_INET,SOCK_STREAM)
  
client.connect(('127.0.0.1',8080))
  

  
while True:
  cmd=input('>>: ').strip()
  if not cmd:continue
  client.send(cmd.encode('utf-8'))
  

  #1、先接收报头的长度,先收报头4个bytes,得到报头长度的字节格式,并提取报头的长度
  headers_size=struct.unpack('i',client.recv(4))
  

  #2、再收报头
  headers_bytes=client.recv(headers_size)#按照报头长度headers_size,收取报头的bytes格式
  headers_json=headers_bytes.decode('utf-8')
  headers_dic=json.loads(headers_json)
  print('========>',headers_dic)      #查看一下反序列化出来的字典
  total_size=headers_dic['total_size']#我们只需要里面的total_size
  

  #3、再收命令的结果
  recv_size=0
  data=b''
  while recv_size < total_size:
  recv_data=client.recv(1024)
  data+=recv_data
  recv_size+=len(recv_data)
  

  print(data.decode('gbk'))
  

  
client.close()
页: [1]
查看完整版本: Python Day8