Python学习笔记八(异常处理和网络编程)

Posted <<<<

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python学习笔记八(异常处理和网络编程)相关的知识,希望对你有一定的参考价值。

一、异常处理

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()

 

以上是关于Python学习笔记八(异常处理和网络编程)的主要内容,如果未能解决你的问题,请参考以下文章

Python 2.7 学习笔记 异常处理

新手学Python之学习官网教程(八:Errors and Exceptions)

python学习笔记8:异常处理

python学习笔记——多进程

Day3: Python学习笔记之计算机基础——网络片

python学习笔记(十五)-异常处理