Python 网络编程
小学期学了一门课叫做"Web框架技术",开心的选了,以为要讲 Web 框架,听了课才明白,讲的是 Web 开发,作业是用框架写个个人网站。
我: …
默默拿出自己从图书馆借的书《Python网络编程》,从头开始学习一些 web 开发基础的东西:UDP,TCP,HTTP客户端和服务端,SSL/TLS,以及服务器架构。
虚假的 WEB 框架技术: 使用 vue + django 完成一个个人网站
真正的 WEB 框架技术:学习手撕服务器程序(希望能做到吧)
- 联动一下 Django 源码的学习的系列
- 大三感觉比较忙,希望不要鸽
Intro: 从一个简单的API调用说起
调用 API 是我们再熟悉不过的东西了,就举个例子,调取百度地图 API 来获取经纬度。
1 | import requests |
我们很熟练的用 requests 库完成了 get 操作,似乎已经足够了,我们并不需要了解具体的实现过程,只需要关心数据正确拿到了就行。
等等,那我们还怎么去更深入的学习?
协议的使用
request 库确实方便,但它隐藏了很多的实现细节。我们拨开一层看一看。现在我们自己构造 URL 和连接,也就是说使用 HTTP(Hypertext Transfer Protocol)协议来完成这一任务
1 | import http.client |
首先我们请求连接一个特定的主机,然后手动构造一个带有参数的 GET 查询,最后从 HTTP 连接读取结果。
原始网络会话
你可能会觉得,前面的还是有点高级了。HTTP 协议并非是通过空气来在两台机器中传输数据的,HTTP 协议需要使用一些更加简单抽象的东西来完成操作。现代操作系统中提供了使用 TCP 协议在 IP 网络中的不同程序进行纯文本网络会话的功能,而 HTTP 协议正是使用了这一功能。换句话说,HTTP 协议精准描述了两台主机之间通过 TCP 传输的信息格式。
修改前面的代码,使用更加底层的方法来实现我们的 get 请求。
1 | # coding: utf-8 |
我们使用主机操作系统提供的原始 socket() 函数来支持 IP 网络的通信,我们将在后面更加详细的了解 socket。
ps: 注意几个细节:
- 使用 sendall() 函数会出现 400(Bad Request) 的错误,这时可以采用逐条 send 的方法发过去,记得一定要有一个空的行(“\n\r”)
- 请求头中指定允许的压缩方式的话如果你不进行解压将会有字符无法被 utf8 解码,平常这些操作都是浏览器完成的,你都没碰到过。所以现在没有浏览器自动帮你还原数据,最好还是不要指定允许的压缩方式了(Accept-Encoding 头字段)
你说这还不够原始?好吧,拿出你的《计算机网络》…
层层深入
socket 并非是这个操作的最底层协议,套接字(socket)其实也是抽象与更加底层的协议,只不过这些协议由操作系统来管理,而非Python。拿出你的《操作系统》
- 在 socket() API 之下有:
- 传输控制协议(TCP),该层通过发送(也有可能重发),接收以及重排数据包,支持由字节流组成的双向网络会话。
- 网际协议(IP),该层处理不同计算机之间数据包的发送。
- 最底层,“链路层”,该层负责在直接相连的计算机之间发送物理信息,由网络硬件设备组成,如以太网端口和无线网卡。
一些简单的基础知识我们忽略掉,比如说 python 编码和解码,什么是 ip 地址。
我们首先要对网络传输有一个大致的理解:
数据包
网络设备之间共享的基本单元是数据表(packet)。数据包就像流通的货币,只要需要就可以交换。一个数据包是一串长度在几字节到几千字节之间的字符串,他们是网络传输的基本单元
数据包在物理层面只有2个属性,包含的字节数以及目标传输地址。物理数据包的地址一般是一个唯一标识符,它标识在计算机传输数据包的过程中,插入同一以太网的其他网卡或无线信道。网卡负责发送并接收这样的数据包,操作系统并不关心信号,网线等等细节。
由于 IP 支持的数据包极大,最大可以至 64KB,但是构建于 IP 网络之上的设备通常并不支持这么大的数据包,所以往往采用分组的方法。例如,以太网只支持 1500B 的数据包,因此网络数据包中包含一个表示“不分组(DF, Don’t Fragment)”的标记,在源计算机与目标计算机之间的某条物理网络无法容纳这么大的数据包的时候,发送者可以通过这个标记来表示是否分组:
- 如果没有设置 DF 标记,那么表示允许分组,当数据包超过上限的时候,网关能够将其分为多个小的数据包,并进行标记,接收方在收到后会重组为大的数据包
- 如果设置了 DF 标记,此时不允许分组,网络容纳不下的超出部分将会直接被丢弃,并发回一个错误信息。错误信息是由一种特殊的信号数据包表示的,这种数据包叫做 Internet 控制报文协议(ICMP, Internet Control Message Protocol)数据包。发送方在收到信息错误后会尝试将信息分割为较小的数据包然后重发。
DF 的设置由操作系统来完成,粗略的说,系统通常使用的逻辑是:如果正在进行一个由网络间传输的独立数据组成的 UDP 会话,操作系统不会设置 DF,如果是 TCP 会话,而 TCP 会话是由可能多达成百上千的数据包组成的长数据流,那么操作系统会设置 DF 标记,这样操作系统可以选择正确的数据包大小,使得 TCP 会话顺畅进行。否则,数据包将会在中途不断被分组,从而使得会话较为低效。
路由
一旦应用程序请求操作系统向某一特定的 IP 地址发送数据,操作系统就要决定如何使用该机器连接的某一物理网络来传输数据。这一决定(根据目的 IP 地址选择将 IP 数据包发往何处)就叫做路由(routing)
我们编写的大部分代码,都会运行在网络边缘,会有一个网络接口将程序与互联网相连,对于运行这些程序的机器来说,路由的决定就很简单。
- 如果 IP 地址是 127.*,*,*,那么操作系统知道数据包的目的地址是本机上运行的另一个应用程序,这个数据包甚至不会被送给物理网络设备,而是直接通过操作系统的内部数据复制转交给另一个程序。
- 如果目的 IP 地址与本机处于同一子网,那么可以通过简单的查找本地以太网段,无线信道或者是其他任何网络信息来找到目标主机,然后就可以将数据发给本地连接的服务器。
- 否则计算机将数据包转发给一台网关机器(gateway machine),这台机器将本地子网连接至互联网,然后在决定将该数据包发往何处。
对于组成互联网骨干网络的专用设备来说,路由决定就会复杂很多。
由 IP + 子网掩码来表示子网。
关于子网掩码的数字表示:
- 127.0.0.0/8: 此模式描述预留给本机的 IP 地址段,该模式下前8位也是1字节,必须与127匹配,余下的24位可以是任何值
- 192.168.0.0/16:匹配 192.168私有地址段的任何 IP,后16位可以是任意值
- 192.168.5.0/24:这里明确了一个独立的子网,这可能是互联网上最常见的子网掩码,前3个字节被明确指出,用来匹配 ip。允许有8位(最后一个字节)不同,共256个不同的地址。通常说 .0 表示子网名,.255 作为广播数据包的目标地址,广播数据包会被发到子网内网中所有主机,.1 通常用于连接外网的网关。