一、异常处理
1.异常介绍
异常是程序运行时发生错误的信号,一旦程序出错,会产生一个异常,若程序没有处理它,则会抛出异常,程序运行也会终止。
异常分为语法错误和逻辑错误。语法错误无法通过python解释器的语法检测,在程序运行之前必须解决。逻辑错误为一般需要使用异常处理的地方。
#语法错误,编译器及时检测,必须处理 for i in range(3) pass
#逻辑错误,运行时才会抛出异常 for i in 3: pass
以下为常见异常
AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x IOError 输入/输出异常;基本上是无法打开文件 ImportError 无法引入模块或包;基本上是路径问题或名称错误 IndentationError 语法错误(的子类) ;代码没有正确对齐 IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5] KeyError 试图访问字典里不存在的键 KeyboardInterrupt Ctrl+C被按下 NameError 使用一个还未被赋予对象的变量 SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了) TypeError 传入对象类型与要求的不符合 UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量, 导致你以为正在访问它 ValueError 传入一个调用者不期望的值,即使值的类型是正确的
2.异常处理
如果错误发生条件是可预知的,需要使用if进行处理,即在错误发生前预防。
age = input("输入年龄:") if age.isdigit(): age = int(age)
如果错误发生条件是不可预知的,需要用try...except,即在错误发生后处理。
try: l = [] print(l[12]) except IndexError: pass
try: l = [] print(l[12]) except IndexError as e: print(e) #输出异常的值
多分支异常处理
try: l = [] print(l[12]) #此处抛出IndexError异常,后面代码不会执行 d ={} d["a"] except IndexError as e: print(e) except KeyError as e: print(e)
万能异常,如果不管程序出现什么类型异常,用同一代码逻辑处理,则需要用万能异常。如果需要为不同异常制定不同处理逻辑,则需要用多分支异常处理。
try: l = [] # print(l[12]) int("asdf") except Exception as e: #万能异常 print(e)
else和finally
try: l = [] # print(l[12]) int("asdf") except KeyError as e: print(e) except Exception as e: #万能异常 print(e) else: print("try内代码没有异常则会执行else中内容") finally: print("无论是否出现异常,都会执行finally中内容,通常用于清理工作")
主动抛出异常
try: age = input("输入年龄:") if not age.isdigit(): raise TypeError("输入年龄类型错误。") except TypeError as e: print(e)
自定义异常
class MyException(BaseException): def __init__(self,msg): self.msg=msg def __str__(self): return self.msg try: raise MyException(‘类型错误‘) except MyException as e: print(e)
异常处理可以将错误处理同真正的工作进行区分,代码更易组织,更安全。
二、网络编程
1、OSI七层和socket
互联网协议按照功能不同分为OSI七层或tcp/ip五层或tcp/ip四层。
加入socket抽象层
socket是应用层和TCP/IP协议族同学的中间软件抽象层,是一组接口。socket已封装TCP/IP协议,用户只需遵循socket规范编程即可。
套接字工作流程:
服务端先初始化socket,与端口绑定,对端口进行监听,调用accept阻塞,等待客户端连接。
客户端初始化socket,连接服务器,如果连接成功,则服务器和客户端直接的连接就建立了。
socket模块介绍
import socket socket.socket(socket_family,socket_type,protocol=0) socket_family 可以为AF_UNIX或AF_INET socket_type 可以为SOCK_STREAM 或 SOCK_DGRAM protocol一般不用填写,默认为0 获取tcp/ip套接字 tcpsocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 获取udp/ip套接字 udpsocket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
服务端套接字函数
s.bind() #绑定(主机端口号) s.listen() #开始监听 s.accept() #被动接收tcp客户端的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() #主动初始化tcp服务器连接 s.connect_ex() #connect()函数扩展版,出错时返回出错码,不抛异常
公共用途的套接字函数。
s.recv() #接收tcp数据 s.send() #发送tcp数据(send在待发送数据量大于已端缓存区剩余空间时,数据丢失,不会发完) s.sendall() #发送完整的TCP数据(本质是循环调用send,sendall数据不会丢失) s.recvfrom() #接收UDP数据 s.sento() #发送udp数据 s.getpeername() #连接到当前套接字的远端地址 s.getsockname() #当前套接字的参数 s.getsockopt() #返回制定套接字的参数 s.setsockopt() #设置制定套接字的参数 s.close()
2.基于tcp的套接字
服务端
import socket serv = socket.socket(socket.AF_INET,socket.SOCK_STREAM) serv.bind(("127.0.0.1",9989)) serv.listen(5) conn,client_addr = serv.accept() print(client_addr) data = conn.recv(1024) conn.send(data.upper()) conn.close() serv.close()
客户端
import socket client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(("127.0.0.1",9989)) client.send("hello".encode("utf-8")) data = client.recv(1024) print(data) client.close()
加入循环和异常处理机制后的改进版本
服务端
import socket serv = socket.socket(socket.AF_INET,socket.SOCK_STREAM) serv.bind(("127.0.0.1",9989)) serv.listen(5) while True: print("waiting for new client...") conn,client_addr = serv.accept() print(client_addr) while True: try: data = conn.recv(1024) if not data:break #linux下客户端断开连接后,服务端会一直接收空数据 print(data) conn.send(data.upper()) except ConnectionResetError: #windows下客户端断开连接会抛出该异常 break conn.close() serv.close()
客户端
import socket client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(("127.0.0.1",9989)) while True: data = input("==>").strip() if not data:continue #如果直接敲回车,data为空,但是操作系统不会发送空数据,程序接收不到服务端数据会卡住。 client.send(data.encode("utf-8")) data = client.recv(1024) print(data) client.close()
3.粘包问题
只有tcp会出现粘包,udp不会。tcp协议是面向流的协议,发送方按照一段一段的字节流发送,接收方应用程序看到的数据是一个整体,不知道消息之间的界限。
会产生粘包问题的例子:
客户端
import socket client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(("127.0.0.1",9989)) while True: cmd = input("==>").strip() if not cmd:continue #如果直接敲回车,data为空,但是操作系统不会发送空数据,程序接收不到服务端数据会卡住。 client.send(cmd.encode("utf-8")) data = client.recv(1024) print(data.decode("gbk")) client.close()
服务端
import socket import subprocess serv = socket.socket(socket.AF_INET,socket.SOCK_STREAM) serv.bind(("127.0.0.1",9989)) serv.listen(5) while True: print("waiting for new client...") conn,client_addr = serv.accept() print(client_addr) while True: try: data = conn.recv(1024) if not data:break #linux下客户端断开连接后,服务端会一直接收空数据 print(data) obj = subprocess.Popen(data.decode("utf-8"),shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) res_out = obj.stdout.read() res_err = obj.stderr.read() data = res_out+res_err conn.send(data) except ConnectionResetError: #windows下客户端断开连接会抛出该异常 break conn.close() serv.close()
当客户端发送的命令执行结果大于1024字节时,多余结果会存放在客户端缓存中,会和下一个命令执行后的结果粘在一起,无法区分。
3.解决粘包问题
发送端每次发送数据前,添加固定长度报头,报头包含字节流长度,接收端接收时,先从缓存中读出定长报头,在取真实数据。
需要使用struct模块,可将一个类型转换成固定长度的bytes
如struct.pack(‘i‘,323342342323) 会转换成四个字节,数字取值范围为 -2147483648 <= number <= 2147483647
用struct.unpack("i",recv_data) 会还原回数字,结果为元组形式。
用struct解决粘包问题
客户端
import socket import struct client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(("127.0.0.1",9989)) while True: cmd = input("==>").strip() if not cmd:continue #如果直接敲回车,data为空,但是操作系统不会发送空数据,程序接收不到服务端数据会卡住。 client.send(cmd.encode("utf-8")) header = client.recv(4) totalsize = struct.unpack(‘i‘,header)[0] recv_size = 0 res = b"" while recv_size < totalsize: data = client.recv(1024) res+=data recv_size +=len(data) print(res.decode("gbk")) client.close()
服务端
import socket import subprocess import struct serv = socket.socket(socket.AF_INET,socket.SOCK_STREAM) serv.bind(("127.0.0.1",9989)) serv.listen(5) while True: print("waiting for new client...") conn,client_addr = serv.accept() print(client_addr) while True: try: data = conn.recv(1024) if not data:break #linux下客户端断开连接后,服务端会一直接收空数据 print(data) obj = subprocess.Popen(data.decode("utf-8"),shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) res_out = obj.stdout.read() res_err = obj.stderr.read() heads = len(res_out)+len(res_err) data = res_out+res_err conn.send(struct.pack(‘i‘,heads)) conn.send(data) except ConnectionResetError: #windows下客户端断开连接会抛出该异常 break conn.close() serv.close()