上一个章节我们说的是套接字名和DNS。这篇文章我们主要解决下面问题。

我们在两台主机之间建立与关闭TCP流连接以及UDP数据报连接后。我们应该怎么准备我们需要传输的数据,该怎么对数据进行编码与格式化。

先说字节和字符串。

套接字直接将字节暴露了出来,对程序员还是应用程序来说都是可见的。

字节的特性:位(bit)是信息的最小单元。每位可以是0或者1。当然在我这个EE的人来说,用高低电平来实现就可以了。

8位组成了1字节。

如果把这些数字作为列表传入bytes(),就可以将其转换为字节字符串。通过遍历字节字符串,就可以把其转换为原来的形式。

如果想用套接字传输一个符号串,就需要使用某种编码方法。从而给每个符号分配特定的值。

一般流行的就是ASCII编码方式,现在Python3之后,Python把字符串看作是由Unicode字符组成的序列。就和Python的数据结构一样,我们不需要考虑字符串的内部实现。

编码方式也分为两大类,单字节编码和多字节编码,这里就不再详细说了。

下面说一下二进制数和网络字节顺序。如果只想通过网络发送文本,那么只需要考虑,编码与封帧问题(这个会在后面说)。

如果使用更紧凑的格式来表示数据,我们编写的Python代码就要注意另外一个问题,网络字节顺序。

一般解决方法有两种,大端法和小端法。这里可以用Python的struct模块来具体看一下具体区别,这里不再详细说明了。

一般来说,会有下面的建议:利用struct模块生成用于网络传输的二进制数据时,接收方收到后用struct模块解码。

如果要自己控制网络传输的数据格式的话,在选择网络字节顺序时使用!前缀。

如果其他人设计了协议并使用小端法,那么我们必须使用<。

使用struct的时候一定要进行测试。

下面说封装与引用,如果使用UDP数据报进行通信,那么协议本身就会使用独立的、可识别的模块进行数据传输。

如果使用TCP进行通信,就要面对封帧问题。即如何分割消息,让接受方可以识别消息的开始与结束。recv()就必须运行多次才能收到完整的数据包。

下面给出一段代码:

import socket

from argparse import ArgumentParser

def server(address):

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

sock.bind(address)

sock.listen(1)

print('Run this script in another window with "-c" to connect')

print('Listening at', sock.getsockname())

sc, sockname = sock.accept()

print('Accepted connection from', sockname)

sc.shutdown(socket.SHUT_WR)

message = b''

while True:

more = sc.recv(8192) # arbitrary value of 8k

if not more: # socket has closed when recv() returns ''

print('Received zero bytes - end of file')

break

print('Received{}bytes'.format(len(more)))

message += more

print('Message:\n')

print(message.decode('ascii'))

sc.close()

sock.close()

def client(address):

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.connect(address)

sock.shutdown(socket.SHUT_RD)

sock.sendall(b'Beautiful is better than ugly.\n')

sock.sendall(b'Explicit is better than implicit.\n')

sock.sendall(b'Simple is better than complex.\n')

sock.close()

if __name__ == '__main__':

parser = ArgumentParser(description='Transmit & receive a data stream')

parser.add_argument('hostname', nargs='?', default='127.0.0.1',

help='IP address or hostname (default:%(default)s)')

parser.add_argument('-c', action='store_true', help='run as the client')

parser.add_argument('-p', type=int, metavar='port', default=1060,

help='TCP port number (default:%(default)s)')

args = parser.parse_args()

function = client if args.c else server

function((args.hostname, args.p))

上面这个算是模式一。避免了在另一方向上使用套接字,一旦客户端和服务器不再进行某一方向的通信时就直接关闭。

模式二是模式一的变体,即在两个方向上都通过流发送信息。等待一遍信息发送完,然后另一方发送数据,不能同时发送,否则会发生死锁。

模式三更加好理解,就是用自己设计的recv()来循环接收消息。

模式四是通过某些方法,使用特殊字符来划分消息的边界。

模式五是每个消息前面加上长度作为前缀,这样就无需分析直接就可以发送二进制数据块。

如果我们想利用模式五的简洁高效,又无法事先知道消息的长度,我们可以使用模式六,使用模式六时,我们并非只发送单个消息,而是会多发送多个数据块,并且将每个数据块前面加上数据块长度作为前缀。到达尾部时,发送方可以发送一个与接收方事先约定好的信号,告知接收方,所有数据块已经发送完毕。

下面这段代码就是模式六的例子:

import socket, struct

from argparse import ArgumentParser

header_struct = struct.Struct('!I') # messages up to 2**32 - 1 in length

def recvall(sock, length):

blocks = []

while length:

block = sock.recv(length)

if not block:

raise EOFError('socket closed with{}bytes left'

' in this block'.format(length))

length -= len(block)

blocks.append(block)

return b''.join(blocks)

def get_block(sock):

data = recvall(sock, header_struct.size)

(block_length,) = header_struct.unpack(data)

return recvall(sock, block_length)

def put_block(sock, message):

block_length = len(message)

sock.send(header_struct.pack(block_length))

sock.send(message)

def server(address):

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

sock.bind(address)

sock.listen(1)

print('Run this script in another window with "-c" to connect')

print('Listening at', sock.getsockname())

sc, sockname = sock.accept()

print('Accepted connection from', sockname)

sc.shutdown(socket.SHUT_WR)

while True:

block = get_block(sc)

if not block:

break

print('Block says:', repr(block))

sc.close()

sock.close()

def client(address):

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.connect(address)

sock.shutdown(socket.SHUT_RD)

put_block(sock, b'Beautiful is better than ugly.')

put_block(sock, b'Explicit is better than implicit.')

put_block(sock, b'Simple is better than complex.')

put_block(sock, b'')

sock.close()

if __name__ == '__main__':

parser = ArgumentParser(description='Transmit & receive blocks over TCP')

parser.add_argument('hostname', nargs='?', default='127.0.0.1',

help='IP address or hostname (default:%(default)s)')

parser.add_argument('-c', action='store_true', help='run as the client')

parser.add_argument('-p', type=int, metavar='port', default=1060,

help='TCP port number (default:%(default)s)')

args = parser.parse_args()

function = client if args.c else server

function((args.hostname, args.p))

编写代码时需要注意的就是,尽管4B的数据量很小,很难想像recv()不会一次性返回所有4字节数据,但是,只有仔细的在一个循环中调用recv(),这个代码才是正确的。这样才能不断接受数据,直到所有的4B数据全被接受为止。

下面说的是pickle自定义定界符的格式。这个pickle是Python原生的标准库。它是将Python数据结构的内容存储起来,以供在另一台机器上重组该数据结构。

>>> import pickle

>>> pickle.dumps([5, 6, 7])

b'\x80\x03]q\x00(K\x05K\x06K\x07e.'

上面最后是以字符串末尾的.字符结束的,它用于标记一个pickle的结束。遇到.后加载器将停止读取,并立刻返回数据。加上其他数据,也会直接被忽略。

>>> pickle.loads(b'\x80\x03]q\x00(K\x05K\x06K\x07e.blahblahblah')

[5, 6, 7]

实际当中是不能使用load()的。可以使用如下的方式:

>>> from io import BytesIO

>>> f = BytesIO(b'\x80\x03]q\x00(K\x05K\x06K\x07e.blahblahblah')

>>> pickle.load(f)

[5, 6, 7]

>>> f.tell()

14

>>> f.read()

b'blahblahblah'

当然还有另外一种方案,就是可以创建一个协议,协议唯一的内容就是在两个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提供的流终止信息,自动探测每个压缩流的结尾。

下面说一下网络错误和异常以及处理方法。套接字发生的几种常见的异常如下。OSError:这是socket模块的可能抛出的主要错误。网络传输的所有阶段可能发生的任何问题几乎都会抛出该异常。

socket.gaierror:该异常在getaddrinfo()无法找到提供的名称或服务时被抛出。

socket.timeout:有时我们会决定为套接字设定超时参数,而不希望永远等待send()或者recv()的操作的完成。只有使用的库设置了套接字超时参数时,才会抛出这个异常。证明等待操作正常完成的时间已经超过了超时参数的值。

当然,使用Python提供的基于套接字的高层协议时,我们可以在代码中直接处理原始套接字错误,然后将他们转换为协议特定的错误类型。

举个例子,httplib认为自己相对底层,因此在连接到未知主机名时,能看到底层套接字错误。

但是urllib2就把相同的错误隐藏起来,并抛出一个URLError。这可能是urllib2认为自己是一个用于将URL解析为文档的干净且中性的系统,所以希望保持相应的语义。

我们在网络错误处理的时候,我们会将异常封装,提供给其他调用API的程序员使用,有时会中途拦截某些异常,把合适的信息提供给终端用户。这两种情况下使用的方法是不同的。

给传递使用我们API的用户时,有两种方法。

第一种就是不作处理,调用者负责处理异常,他们捕捉异常,直接把异常输出至报告。

另一种方法就是把网络错误封装为我们自己的异常。这样会对开发者更加友好。

然后我们说捕捉和报告网络异常。

捕捉异常有两种方法,granular异常处理程序与blanket异常处理程序。

granular方法就是针对每个网络调用,都使用try...except语句,然后在except从句中打印出简洁的错误信息。

另一种方法是blanket异常处理程序。要使用这个方法,需要重新审视我们的代码,识别出进行特定操作的代码段。整个程序都用于连接许可证服务器

这个函数中的所有套接字操作都用于从数据库获取响应

最后一部分代码都用来进行清理与关闭操作

然后外部程序使用try...except语句调用这些代码段。然后在程序的顶层捕捉抛出的所有FatalError异常,并打印出错误信息。而且可以增加一个命令行选项,可以把严重错误发送到系统的错误日志里面去。

到这里,关于Python网络编程中的网络数据和网络错误的文章就结束了。

谢谢大家关注。如果觉得有用就收藏或者点赞吧,每次总是收藏总是比赞多,哈哈,不过大家随心就好。

再次谢谢大家,希望大家在新的一年里万事胜意。

python网络通信传输的数据类型_Python网络编程中的网络数据和网络错误。相关推荐

  1. python网络通信传输的数据类型_Python Socket 编程详细介绍(转)

    Python 提供了两个基本的 socket 模块: Socket 它提供了标准的BSD Socket API. SocketServer 它提供了服务器重心,可以简化网络服务器的开发. 下面讲解下 ...

  2. python的功能模块_Python的功能模块[1] - struct - struct 在网络编程中的使用

    struct模块/ struct Module 在网络编程中,利用 socket 进行通信时,常常会用到 struct 模块,在网络通信中,大多数传递的数据以二进制流(binary data)存在.传 ...

  3. python 网络编程是什么_什么是网络编程-Python 网络编程-嗨客网

    Python网络编程网络编程教程 网络编程的本质是两个设备之间的数据交换,当然,在计算机网络中,设备主要指计算机.数据传递本身没有多大的难度,不就是把一个设备中的数据发送给两外一个设备,然后接受另外一 ...

  4. python网络编程中,Cisco packet tracer 中两个交换机和一个路由器的配置

    python网络编程中,Cisco packet tracer 中两个交换机和一个路由器的配置 原理图如下所示: 配置PC6–PC11的ip地址和子网掩码: 配置路由器的左半部分的ip地址和子网掩码: ...

  5. Python核心编程(第3版)第2章网络编程中关于tcp/udp服务器和客户端实现代码的运行出错的修正

    在Python核心编程(第3版)第2章网络编程中, 关于tcp/udp服务器和客户端实现代码的运行会出现 ['str' does not support the buffer interface]之类 ...

  6. Python30 网络编程通讯协议,1.学习网络编程的目的 2.什么是互联网 3.c/s结构 4.通讯基本要素 5.OSI模型...

    今日内容: 网络通讯协议 1.学习网络编程的目的 2.什么是互联网 3.c/s结构 4.通讯基本要素 5.OSI模型 思维路线 目的是要链接互联网中的其他计算机 物理层 用物理介质链接其他计算机 数据 ...

  7. 网络编程中使用float型数据要注意

    2019独角兽企业重金招聘Python工程师标准>>> 在网络编程中使用float型数据要特别注意,因为各个机器对浮点数的表示极有可能会不一样,比如在gsoap中,当在客户机和服务器 ...

  8. 网络编程中的关键问题总结

    网络编程中的关键问题总结 总结下网络编程中关键的细节问题,包含连接建立.连接断开.消息到达.发送消息等等: 连接建立 包括服务端接受 (accept) 新连接和客户端成功发起 (connect) 连接 ...

  9. 关于网络编程中MTU、TCP、UDP优化配置的一些总结

    首先要看TCP/IP协议,涉及到四层:链路层,网络层,传输层,应用层.  其中以太网(Ethernet)的数据帧在链路层 IP包在网络层 TCP或UDP包在传输层 TCP或UDP中的数据(Data)在 ...

最新文章

  1. 如何每天自动备份 SourceSafe (转)
  2. 阿里云马劲:保证云产品持续拥有稳定性的实践和思考\n
  3. 再也不怕别人动电脑了!用Python实时监控
  4. HDU-5023 线段树染色问题+延时标记
  5. python print sep,Python3.x语句print(1,2,3,sep=’:’)的输出结果为()。
  6. 无人机航测流程—市县城镇开发边界内1:500地形图
  7. linux版本的火狐浏览器,火狐浏览器Linux版本
  8. YDOOK: ANSYS Maxwell 19 教程21:Maxwell 2D 直流传导电场 求解设置 分析设置
  9. Java多线程之定时任务 以及 SpringBoot多线程实现定时任务以及分享动态实现定时任务
  10. Flyme应用中心应用认领
  11. pg数据库自动备份记录
  12. Django邮件应用--QQ邮箱、网易邮箱(一)
  13. PLSQL13,登录进去的时候有个弹窗,选择后同时勾选了dont show this message again,下次不再显示了,但是又想让他继续提示怎么办?
  14. 前端页面与form表单提交:代码分享
  15. 《Scanner的hasNext、hasNextInt用法》
  16. 【软件分享】免费多线程下载神器,可完全替代IDM(支持MacWindows)
  17. 解惑:NFC手机怎样轻松读取银行卡信息?
  18. PL/SQL Developer 登录报错(ORA-12547)解决方案
  19. 计算机网络网络适配器的作用是什么原因,网络适配器的作用_网络适配器删了怎么办...
  20. Python网络爬虫实战项目代码大全(长期更新,欢迎补充)

热门文章

  1. C++11 修复了双重检查锁定问题(转)
  2. 杭电1285确定比赛名次
  3. 排列组合算法之一: 01转换法_java改变后的c++版
  4. 基于人人网的简单爬虫(一)——正则表达式
  5. Spring Tool Suite 错误: Server Tomcat v8.0 Server at localhost was unable to start within 45 seconds.
  6. 随想录(以师带徒的原则)
  7. 一步一步写算法(之克鲁斯卡尔算法 下)
  8. 用汇编的眼光看C++ (之x86汇编)
  9. java求最大值时i的值_java 输入一组数组,求最大值。
  10. linux怎么创建牡蛎_Linux文件也有快捷方式?有的,你会用吗?