Python网络编程 5.2 网络异常及ch.5小结

使用网络连接时可能发生的错误数量相当大(TCP/IP协议相当复杂,每一步都可能出错),但是程序在进行套接字操作的时候抛出的实际异常数量并不多。针对套接字操作而发生的异常如下。
  • OSError:这是socket模块中可能抛出的主要错误,网络传输的所有阶段可能发生的任何问题几乎都会抛出该异常。OSError几乎会再任何套接字调用时都不期而至。例如,如果send()调用使得远程主机发出了一个重置(RST)数据包,那么无论接下来在该套接字上进行哪种套接字操作,都会引发该错误。
  • socket.gaierror:该异常是在getaddrinfo()无法找到提供的名称或服务时被抛出。除了显式地调用getaddrinfo()方法时可能会抛出该异常,如果我们向bind()或connect()这样的调用传入一个主机名而不是IP地址,该异常也会在主机名查询失败时被抛出。如果捕捉到这个异常的话,可以仔细查看异常对象的信息,获取错误编号和错误信息。
  • socket.timeout:有时我们会决定为套接字设定超时参数,而不用永远等待send()或recv()操作的完成。只有在此时,或是我们使用的库设定了套接字超时的参数,才可能会抛出该异常,表示等待操作正常完成的时间已经超过了超时参数的值。
根据使用协议的不同实现,有时候我们只需要处理协议特定的异常,有时候则可能既需要处理协议特定的异常,又需要处理原始套接字错误。在编写网络程序时该如何处理所有可能发生的错误呢?当然这个问题并非只针对网络编程,因为各种各样的Python 程序都需要处理异常。有时我们会将异常封装,提供给其他调用我们API的程序员使用,有时则会中途拦截某些异常,把合适的信息提供给终端用户,这两种情况下使用的方法是不同的。

抛出更具体的异常:
把异常传给使用我们API的用户时,有两种方法。当然,在很多情况下,我们编写的某些模块或程序的唯一用户是我们自己。但是仍然有必要思考一下,在未来,即使是我们自己,也可能会忘记关于该模块的所有内容,因此使用简单明了的方法处理异常是十分有用的。
第一种方法是完全不处理网络异常,而是让调用者负责处理这些异常,这种方法适用于较为底层的网络程序。
第二种方法是将网络错误封装成为我们自己的异常。某些开发者对于程序的实现细节知之甚少,对于他们来说这种方法就比第一种方法简单多了。因为他们的程序现在只需要专门捕捉代码操作中的异常即可,无需了解使用套接字的细节。使用自定义异常的另一个优点是能够在发生网络错误与时构造出更清晰的错误信息,明确地解释导致错误的库操作。假设我们定义编写一个用于在远程机器之间复制文件的程序,如果只使用socket.error的话,滴啊用房就无从得知错误是源机器的连接问题还是目标机器的连接问题,又或是其他问题。在这种情况下如果我们自己定义了与API语义有紧密联系的异常(比如下面的DestinationError或者类似的SourceError),就很有帮助。我们可以使用raise...from语句在异常链中包含原始套接字错误。这样一来,即使API使用者想要深入查看错误信息也没有任何问题。
import socket

class DestinationError(Exception):
    def __str__(self):
        return '%s:%s'% (self.args[0] , self.__cause__.strerror)
try:
    sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    host = sock.connect(('127.0.0.1',8080))
except socket.error as e:
    print('Error----------------------------------')
    raise DestinationError('Error connecting to destination') from e

注意,上面的代码假设DestinationError只封装集成自OSError的异常(比如socket.error),否则的话,如果异常原因包含的文本信息除了strerror之外还有别的属性,那么__str__()函数就更复杂了。不过这个例子至少说明了这一模式的原理,即调用者捕捉DestinationError后,可以通过__cause__来获取它们实际捕捉到的包含丰富语义的异常背后的网络错误。

捕捉与报告网络异常:
要捕捉异常,有两种基本方法:granular异常处理程序与blanket异常处理程序。
  • granular方法是针对每个网络调用,都使用try...except语句,然后在except从句中打印出简洁的错误信息。这种方法对于短小的程序非常适用,但是在大型程序中就显得重复,并且没有给用户提供更多必要的信息。
  • 第二种方法是使用blanket异常处理程序。要使用这种方法,需要重新审视我们的代码,识别出进行特定操作的代码段,比如:“整个程序都用于连接许可证服务器”“这个函数中的所有套接字操作都用于从数据库获取相应”“最后一部分代码都用来进行清理关闭操作”。   然后,外部程序(即收集输入、命令行参数、配置信息,并调用代码段的程序)使用try...except调用这些代码段。最好在我们的代码中抛出自己设计的表示程序终止并为用户打印出错误信息的异常。如
    except:
       FatalError('cannot send replies:{}'.format(e),file = sys.stderr)

    然后,在程序的顶层捕捉抛出的所有FatalError异常,并打印出错误信息。这样,如果有一天我们希望增加一个命令行选项,把严重错误都发送到系统的错误日志,而不是直接打印到屏幕,那么只需要修改一处的代码即可,无需到处修改。
chapter5小结

要把机器信息存放到网络上,就必须先进行相应的转换。无论我们的机器使用的是哪种私有的特定存储机制,转换后的数据都要使用公共并且可重现的表现方式。这样的话其他系统和程序甚至其他的编程语言才能够读取这些数据。

对于文本来说,最重要的问题就是选择一种编码方式,将想要传输的字符转换为字节。这是因为包含8个二进制位的字节是IP网络上的通用传输单元。我们需要格外小心地处理二进制数据,以保证字节顺序能够兼容不同的机器。Python的struct模块就是用来帮助我们解决这个问题的。有时候最好使用JSON或XML来发送数据结构和文档。这两种格式提供了在不同机器之间共享结构化数据的通用方法。

使用TCP/IP流时,我们会米哦按林的一个重要问题就是封帧,就是在长数据流中,如何判定一个特定消息的开始于结束。为了解决这个问题,有许多技术可以选用。由于recv()每次可能只返回传输的部分信息,因此无论使用哪种技术都需要小心地处理。为了识别不同的数据块,可以使用特殊的定界符或模式、定长消息以及分块编码机制来设计数据块。

与我们的代码使用的网络协议一样,套接字也可以抛出各种异常。何时使用try...except从句取决于代码的用户——即为了其他开发者编写程序还是为某终端用户编写工具。除此之外,这个选择也取决于代码的语义。如果从调用者或终端用户的角度来看,某个代码段进行的是同一个较为宏观的操作,那么就可以将整个代码段放在一个try...except从句中。

最后,如果某个操作引发的错误只是暂时的,而调用晚些时候可能会成功,并且我们希望该操作能够自动重试的话,就应将其单独地包含在一个try...except从句中。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值