上一个章节我们说的是套接字名和DNS。这篇文章我们主要解决下面问题。
我们在两台主机之间建立与关闭TCP流连接以及UDP数据报连接后。我们应该怎么准备我们需要传输的数据,该怎么对数据进行编码与格式化。
套接字直接将字节暴露了出来,对程序员还是应用程序来说都是可见的。
如果把这些数字作为列表传入bytes(),就可以将其转换为字节字符串。通过遍历字节字符串,就可以把其转换为原来的形式。
如果想用套接字传输一个符号串,就需要使用某种编码方法。从而给每个符号分配特定的值。
一般流行的就是ASCII编码方式,现在Python3之后,Python把字符串看作是由Unicode字符组成的序列。就和Python的数据结构一样,我们不需要考虑字符串的内部实现。
编码方式也分为两大类,单字节编码和多字节编码,这里就不再详细说了。
下面说一下二进制数和网络字节顺序。如果只想通过网络发送文本,那么只需要考虑,编码与封帧问题(这个会在后面说)。
如果使用更紧凑的格式来表示数据,我们编写的Python代码就要注意另外一个问题,网络字节顺序。
一般解决方法有两种,大端法和小端法。这里可以用Python的struct模块来具体看一下具体区别,这里不再详细说明了。
一般来说,会有下面的建议:
使用struct的时候一定要进行测试。
下面说封装与引用,如果使用UDP数据报进行通信,那么协议本身就会使用独立的、可识别的模块进行数据传输。
如果使用TCP进行通信,就要面对封帧问题。即如何分割消息,让接受方可以识别消息的开始与结束。recv()就必须运行多次才能收到完整的数据包。
上面这个算是模式一。避免了在另一方向上使用套接字,一旦客户端和服务器不再进行某一方向的通信时就直接关闭。
模式二是模式一的变体,即在两个方向上都通过流发送信息。等待一遍信息发送完,然后另一方发送数据,不能同时发送,否则会发生死锁。
模式三更加好理解,就是用自己设计的recv()来循环接收消息。
模式四是通过某些方法,使用特殊字符来划分消息的边界。
模式五是每个消息前面加上长度作为前缀,这样就无需分析直接就可以发送二进制数据块。
如果我们想利用模式五的简洁高效,又无法事先知道消息的长度,我们可以使用模式六,使用模式六时,我们并非只发送单个消息,而是会多发送多个数据块,并且将每个数据块前面加上数据块长度作为前缀。到达尾部时,发送方可以发送一个与接收方事先约定好的信号,告知接收方,所有数据块已经发送完毕。
下面这段代码就是模式六的例子:
编写代码时需要注意的就是,尽管4B的数据量很小,很难想像recv()不会一次性返回所有4字节数据,但是,只有仔细的在一个循环中调用recv(),这个代码才是正确的。这样才能不断接受数据,直到所有的4B数据全被接受为止。
下面说的是pickle自定义定界符的格式。这个pickle是Python原生的标准库。它是将Python数据结构的内容存储起来,以供在另一台机器上重组该数据结构。
上面最后是以字符串末尾的.字符结束的,它用于标记一个pickle的结束。遇到.后加载器将停止读取,并立刻返回数据。加上其他数据,也会直接被忽略。
实际当中是不能使用load()的。可以使用如下的方式:
当然还有另外一种方案,就是可以创建一个协议,协议唯一的内容就是在两个Python程序员中发送pickle。
如果要设计支持其他编程语言的协议,或者只是希望使用通用标准,这时候就可以使用JSON和XML数据。这两种数据都不支持封帧。因此,在处理网络数据之前,先要使用某种方法提起出完整的文本字符串。
JSON是在两种不同的编程语言之间发送数据的最佳选择之一。Python2.6标准库就提供了对JSON的支持,封装在json模块中。
JSON是通过一个字符串来表示的。而且JSON不仅仅在字符串中支持Unicode字符,如果告诉Python的json模块不需要将输出字符限制在ASCII字符表的话,那就甚至可以在数据中包含Unicode字符的字面值。
但是,对于文档来说,XML格式更为实用。原因在于它的基本结构就是将字符串封装为包含在尖括号中的元素,并为他们打上标签。而且不要只把XML局限在HTTP协议中。
下面我们说一下压缩。数据在网络传输中需要的时间常常多余CPU准备的时间。因此,在发送之前进行压缩是非常值得的。
GUN的zlib是当今互联网最普遍的压缩形式之一。Python标准库提供了对于zlib的支持,能够进行封帧是zlib的一个特点。
一般来说,大多数协议设计者会把压缩数据设置为可选项。并且自行为其设置封帧策略。如果会使用zlib的话,那么各种惯例用法会能够让我们充分利用zlib提供的流终止信息,自动探测每个压缩流的结尾。
下面说一下网络错误和异常以及处理方法。套接字发生的几种常见的异常如下。
当然,使用Python提供的基于套接字的高层协议时,我们可以在代码中直接处理原始套接字错误,然后将他们转换为协议特定的错误类型。
举个例子,httplib认为自己相对底层,因此在连接到未知主机名时,能看到底层套接字错误。
但是urllib2就把相同的错误隐藏起来,并抛出一个URLError。这可能是urllib2认为自己是一个用于将URL解析为文档的干净且中性的系统,所以希望保持相应的语义。
我们在网络错误处理的时候,我们会将异常封装,提供给其他调用API的程序员使用,有时会中途拦截某些异常,把合适的信息提供给终端用户。这两种情况下使用的方法是不同的。
给传递使用我们API的用户时,有两种方法。
第一种就是不作处理,调用者负责处理异常,他们捕捉异常,直接把异常输出至报告。
另一种方法就是把网络错误封装为我们自己的异常。这样会对开发者更加友好。
然后我们说捕捉和报告网络异常。
捕捉异常有两种方法,granular异常处理程序与blanket异常处理程序。
granular方法就是针对每个网络调用,都使用try...except语句,然后在except从句中打印出简洁的错误信息。
另一种方法是blanket异常处理程序。要使用这个方法,需要重新审视我们的代码,识别出进行特定操作的代码段。
然后外部程序使用try...except语句调用这些代码段。然后在程序的顶层捕捉抛出的所有FatalError异常,并打印出错误信息。而且可以增加一个命令行选项,可以把严重错误发送到系统的错误日志里面去。
到这里,关于Python网络编程中的网络数据和网络错误的文章就结束了。
谢谢大家关注。如果觉得有用就收藏或者点赞吧,每次总是收藏总是比赞多,哈哈,不过大家随心就好。
再次谢谢大家,希望大家在新的一年里万事胜意。
自己用python2.7写了一个udp服务在windows接受数据正常,放到linux一直接收不到数据,百度了一下,许多网友也遇到过这个问题,但是没有一个确切的解决方案,下面是我的解决方案,记录一下。
解决问题的关键在与host,我起初写的host是127.0.0.1,在windows正常,在linux接收不到数据,主要原因是linux是多网卡的,127.0.0.1对应的网卡ip跟网络调试助手发数据的目标ip不是同一个ip,所以没数据。改成0.0.0.0就是监听任意ip,接受任意ip发过来的数据,或者本地设定的固定ip,发送给这个IP的数据就能接收到,也可以只监听特定的ip,这样子的话,就只有接受该ip发给你的数据了
版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。