Python第十三章-网络编程
网络编程
一、网络编程基础
python 的网络编程模块主要支持两种Internet协议: TCP 和 UDP.
1.1通信协议
通信协议也叫网络传输协议或简称为传送协议(Communications Protocol),是指计算机通信或网络设备的共同语言。
SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。现在最普及的计算机通信为网络通信,所以“传送协议”一般都指计算机通信的传送协议,如:TCP/IP、NetBEUI、HTTP、FTP等。
然而,传送协议也存在于计算机的其他形式通信,例如:面向对象编程里面对象之间的通信;操作系统内不同程序之间的消息,都需要有一个传送协议,以确保传信双方能够沟通无间。
1.2TCP/IP协议
在Internet中TCP/IP协议是使用最为广泛的通讯协议(互联网上的一种事实的标准)。TCP/IP是英文Transmission Control Protocol/Internet Protocol的缩写,意思是“传输控制协议/网际协议”
TCP/IP 协议是一个工业标准协议套件,专为跨广域网(WAN)的大型互联网络而设计。
TCP/IP 网络体系结构模型就是遵循TCP/IP 协议进行通信的一种分层体系,现今,Internet和Intranet所使用的协议一般都为TCP/IP 协议。
在了解该协议之前,我们必须掌握基于该协议的体系结构层次,而TCP/IP体系结构分为四层。
第 1 层 网络接口层
包括用于协作IP数据在已有网络介质上传输的协议,提供TCP/IP协议的数据结构和实际物理硬件之间的接口。比如地址解析协议(Address Resolution Protocol, ARP )等。
第 2 层 网络层
对应于OSI模型的网络层,主要包含了IP、RIP等相关协议,负责数据的打包、寻址及路由。还包括网间控制报文协议(ICMP)来提供网络诊断信息。
第 3 层 传输层
对应于OSI的传输层,提供了两种端到端的通信服务,分别是TCP和UDP协议。
第 4 层 应用层
对应于OSI的应用层、表达层和会话层,提供了网络与应用之间的对话接口。包含了各种网络应用层协议,比如Http、FTP等应用协议。
附录:OSI 七层参考模型
1.3 IP 地址和端口号
1.3.1 IP 地址
互联网协议地址(英语:Internet Protocol Address,又译为网际协议地址),缩写为IP地址(英语:IP Address)
IP 地址是分配给网络上使用网际协议(英语:Internet Protocol, IP)的设备的数字标签。常见的IP地址分为IPv4与IPv6两大类。
IPV4地址
IP地址由32位二进制数组成,为便于使用,常以XXX.XXX.XXX.XXX形式表现,每组XXX代表小于或等于255的10进制数。例如维基媒体的一个IP地址是208.80.152.2。
地址可分为A、B、C、D、E五大类,其中E类属于特殊保留地址。
IP地址是唯一的。目前IP技术可能使用的IP地址最多可有4,294,967,296个(即232)。骤看可能觉得很难会用尽,但由于早期编码和分配上的问题,使很多区域的编码实际上被空出或不能使用。加上互联网的普及,使大部分家庭都至少有一部电脑,连同公司的电脑,以及连接网络的各种设备都消耗了大量IPv4地址资源。
随着互联网的快速成长,IPv4的42亿个地址的分配最终于2011年2月3日用尽[1][2]。相应的科研组织已研究出128位的IPv6,其IP地址数量最高可达3.402823669 × 1038个,届时每个人家居中的每件电器,每件对象,甚至地球上每一粒沙子都可以拥有自己的IP地址。
在A类、B类、C类IP地址中,如果主机号是全1,那么这个地址为直接广播地址,它是用来使路由器将一个分组以广播形式发送给特定网络上的所有主机。32位全为1的IP地址“255.255.255.255”为受限广播地址("limited broadcast" destination address),用来将一个分组以广播方式发送给本网络中的所有主机,路由器则阻挡该分组通过,将其广播功能限制在本网内部。
IPV6地址
IPv6地址为128位长但通常写作8组每组四个十六进制数的形式。例如:
2001:0db8:85a3:08d3:1319:8a2e:0370:7344
是一个合法的IPv6地址。
IPv4地址可以很容易的转化为IPv6格式。举例来说,如果IPv4的一个地址为135.75.43.52(十六进制为0x874B2B34),它可以被转化为0000:0000:0000:0000:0000:0000:874B:2B34或者::874B:2B34。同时,还可以使用混合符号(IPv4-compatible address),则地址可以为::135.75.43.52。
1.3.2端口
在网络技术中,端口(Port)包括逻辑端口和物理端口两种类型。
物理端口指的是物理存在的端口,如ADSL Modem、集线器、交换机、路由器上用 于连接其他网络设备的接口,如RJ-45端口、SC端口等等。
逻辑端口是指逻辑意义上用于区分服务的端口,如TCP/IP协议中的服务端口,端口号的范围从0到65535,比如用于浏览网页服务的80端口,用于FTP服务的21端口等。由于物理端口和逻辑端口数量较多,为了对端口进行区分,将每个端口进行了编号,这就是端口号。
我们主要研究的是逻辑端口号.我们平时所说的端口号也是指的逻辑端口号
端口是一个软件结构,被客户程序或服务程序用来发送和接收数据,一台服务器有 256*256个端口。 端口号范围: 0 - 65535
0-1023是公认端口号,即已经公认定义或为将要公认定义的软件保留的
1024-65535是并没有公共定义的端口号,用户可以自己定义这些端口的作用。
端口与协议有关:TCP和UDP的端口互不相干
二、TCP编程
什么是 TCP 协议
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接(连接导向)的、可靠的、基于IP的传输层协议。
弥补了IP协议的不足,属于一种较高级的协议,它实现了数据包的有力捆绑,通过排序和重传来确保数据传输的可靠(即数据的准确传输以及完整性)。
排序可以保证数据的读取是按照正确的格式进行,重传则保证了数据能够准确传送到目的地!
使用 TCP 协议通信是, 首先创建 TCP 连接, 主动发起连接的叫客户端, 被动响应连接的叫服务器
比如:
当我们在浏览器中访问新浪主页时,我们自己的计算机就是客户端,浏览器会主动向新浪的服务器发起连接。如果一切顺利,新浪的服务器接受了我们的连接,一个TCP连接就建立起来的,后面的通信就是发送网页内容了。
2.1什么是Socket
Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。
可以把Socket理解成类似插座的东西, 通过Socket就可以发送和接受数据了, 就像插座插上电器之后就可以向外提供电能了.
TCP编程的客户端和服务器端都是通过Socket来完成的.
其实UDP协议通信也是使用的套接字, 和TCP协议稍有差别. TCP是面向连接的套接字, 而UDP是面向无连接的套接字.
套接字的起源可以追溯到20世纪70年底, 他是加利福尼亚大学的伯克利版本 Unix(也成 BSD Unix) 的一部分. 因此, 有时你可能会听过将套接字称为伯克利套接字或 BSD 套接字.
套接字最初是为同一主机上的应用程序锁创建, 使得主机上一个程序(也叫一个进程)与另一个允许的程序进行通信. 这就是所谓的进程间通信(Inter Process Communication IPC)
Socket families
有两种类型的套接字: 基于文件的和面向对象的.
基于文件的套接字:AF_UNIX
AF_UNIX是基于文件的套接字.
因为两个进程允许在同一台计算机上, 所以这些套接字都是基于文件的, 这意味着文件系统支持他们的底层基础结构. 这是能够说的通的, 因为文件系统是一个运行在同一主机上的多个进程之间的共享常量.
AF_UNIX在编程的时候并不是太常用.
基于网络的套接字:AF_INET
AF_INET用于基于网络的Socket. 还有一个地址家族AF_INET6, 用于IPv6.
其实还有一些其他地址家族, 哪些要么是专业的, 过时的, 很少使用的, 要么是仍未实现的.
在所有的地址家族中, AF_INET是使用最广泛的.
因为本章重点讨论网络编程, 所以本章剩余的内容中, 都是将使用AF_INET
socket模块
要创建套接字, 必须使用socket模块下的socket()函数.
他的一般语法如下:
import socket
socket.socket(socket_family, socket_type, protocal=0)
说明:
1, 其中, socket_family是AF_UNIX 或 AF_INET, Socket_type如果是TCP编程是SOCKET_STREAM, 如果是 UDP 编程则使用SOCKET_DGRAM. protocal通常省略, 默认是0
- 返回值就是
Socket对象.Socket对象提供了一些方法来让我们操作这些套接字.
2.2 TCP 客户端编程
客户端代码参考下面的代码:
from socket import *
host = "localhost" # 客户端准备连接的服务器的地址
port = 10000 # 服务器的端口号
address = (host, port) # 服务器的地址
bufSize = 1024 # 客户端缓冲区的大小(单位字节)
tcpCliSock = socket(AF_INET, SOCK_STREAM) # 所有的套接字都使用 socket 函数来创建
tcpCliSock.connect(address) # 客户端去连接服务器
while True:
data = input("> ") # 从键盘读取数据
if not data:
break
# 给服务器发送消息. 由于 send 只能发送字节数据,所以把字符串编码之后再发送
tcpCliSock.send(data.encode("utf-8"))
data = tcpCliSock.recv(bufSize) # 接受服务器发送来的信息
if not data:
break
# 由于通过网络传递过来的其实是字节数据, 解码之后再输出
print(data.decode("utf-8"))
tcpCliSock.close()
2.3 TCP 服务器编程
服务器代码参考下面的代码:
from socket import *
host = "localhost" # 服务器要绑定的主机地址
port = 10000 # 服务器要监听的端口号
address = (host, port)
bufSize = 1024 # 设置服务器的缓冲区大小
tcpSevSock = socket(AF_INET, SOCK_STREAM) # 创建 socket 对象
tcpSevSock.bind(address) # 把 socket 绑定到指定的地址和端口
tcpSevSock.listen() # 开启服务器监听器
print("正在等等客户端连接...")
tcpCliSock, cliAddress = tcpSevSock.accept() # 接受客户端的连接
print("来自:", cliAddress, "的连接...")
while True:
data = tcpCliSock.recv(bufSize) # 接受客户端发来的数据
if not data:
break
# 把接收到字节数据解码
data = data.decode("utf-8")
# 向客户端发送数据. 先把字符串编码, 再发送
tcpCliSock.send(("我是...服务器...你的信息是:" + data).encode("utf-8"))
tcpCliSock.close() # 关闭客户端
tcpSevSock.close() # 关闭服务器
2.4 运行服务器和客户端
执行 TCP 服务器和客户端
现在开始运行服务器和客户端程序, 看看他们是如何工作的.
那么应该先运行客户端还是服务器呢?
当然是先运行服务器, 让服务器先处于等等客户端接入的状态, 这样在客户端申请接入的时候才不会出错.
其实, 服务器是被动端, 客户端是主动端.
三、UDP编程
UDP简介
UDP也叫用户数据报协议
UDP编程相比TCP编程简单了很多.
因为UDP不是面向连接的, 而是面向无连接的.
TCP是面向连接的, 客户端和服务端必须连接之后才能通讯, 就像打电话, 必须先接通才能通话.
UDP是面向无连接的, 一方负责发送数据(客户端), 只要知道对方(接受数据:服务器) 的地址就可以直接发数据了, 但是能不能达到就没有办法保证了.
虽然用UDP传输面向无连接, 数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。 比如局域网的视频同步, 使用 udp 是比较合适的:快, 延迟越小越好
创建UDP的Socket对象
创建方式和TCP的Socket一样的, 只是需要把socket_tpye的值设置为SOCKET_DGRAM
socket(AF_INET, SOCKET_DGRAM)
3.1 UDP客户端编程
参考下面的代码:
from socket import *
host = "localhost" # 对方地址
port = 20000 # 对方端口
address = (host, port)
bufSize = 1024
udpCliSock = socket(AF_INET, SOCK_DGRAM)
while True:
data = input("> ")
if not data:
break
# 把数据发送到指定的 udp 服务器
udpCliSock.sendto(data.encode("utf-8"), address)
udpCliSock.close()
3.2 UDP服务器编程
UDP服务器需要做的事情比较少, 除了等待传入的连接之外, 几乎不需要做其他工作.
参考下面的代码:
from socket import *
host = "localhost" # 服务器要绑定的地址
port = 20000 # 服务器要绑定的端口
address = (host, port)
bufSize = 1024
udpServeSock = socket(AF_INET, SOCK_DGRAM)
udpServeSock.bind(address)
while True:
print("等待有人给我发信息:")
data, cliAddress = udpServeSock.recvfrom(bufSize)
print(cliAddress, "发来的信息是:", data.decode("utf-8"))
udpServeSock.close()
3.3 运行UDP服务器和客户端
仍然需要先执行服务器再执行客户端.
四、socket模块其他属性和函数
在socket模块中, 除了目前熟悉的socket.socket()函数之外, 还提供了更多用于网络应用开发的属性.
| 属性 | 描述 |
|---|---|
| AF_UNIX, AF_INET, AF_INET6, AF_NETLINK, AF_TIPC | python 中支持的套接字地址家族 |
| SOCK_STREAM, SOCK_DGRAM | 套接字类型(TCP=流, UDP=数据包) |
| has_ipv6 | 指示是否支持 IPv6的布尔标记 |
| 异常 | 描述 |
|---|---|
| error | 套接字相关错误 |
| haserror | 主机和地址相关错误 |
| gaierror | 地址相关错误 |
| timeout | 超时时间 |
| 函数 | 描述 |
|---|---|
| socket() | 创建套接字对象 |
| getaddrinfor() | 获取一个五元组序列形式的地址信息 |
| getnameinfo() | 给定一个套接字地址, 返回二元组(主机名, 端口号) |
| getfqdn() | 返回完整域名 |
| gethostname() | 返回当前主机名 |
| gethostbyname() | 将一个主机名, 映射到他的 ip 地址 |
| gethostbyname_ex() | gethostbyname()的扩展版本, 返回主机名, 别名主机集合和 ip 地址列表 |
五、python web 客户端
TCP和UDP是比较低级的协议, 是底层网络通讯协议, 是当今因特网中大部分客户端/服务器协议的核心.
大部分情况我们并不会直接使用TCP, UDP去编程, 而是使用更加高级的协议去编程.
比如 HTTP(超文本传输协议), FTP(文件传输协议)等.
本节内容主要学习使用 HTTP 去访问互联网中的内容.
所以我们先从 HTTP 协议开始讲起, 他是目前互联网上应用最广泛的通信协议.
5.1 HTTP 协议简介
5.1.1 什么是 HTTP 协议
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。
HTTP 是基于 TCP/IP 协议的应用层协议。它不涉及数据包(packet)传输,主要规定了客户端和服务器之间的通信格式,默认使用80端口。
5.1.2 HTTP 协议发展简史
- 最早版本是1991年发布的0.9版。该版本极其简单,只有一个命令GET。
- 1996年5月,HTTP/1.0 版本发布,内容大大增加。
任何格式的内容都可以发送。这使得互联网不仅可以传输文字,还能传输图像、视频、二进制文件。这为互联网的大发展奠定了基础。 - 1997年1月,HTTP/1.1 版本发布,只比 1.0 版本晚了半年。它进一步完善了 HTTP 协议,一直用到了20年后的今天,直到现在还是最流行的版本。
5.1.3 HTTP 协议工作原理
HTTP协议工作于客户端-服务端架构为上, 是一种请求应答式的.
浏览器(或其他客户端)作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。
5.1.4 HTTP协议格式
通信规则规定了客户端发送给服务器的内容格式,也规定了服务器发送给客户端的内容格式。
客户端发送给服务器的格式叫“请求协议”;
服务器发送给客户端的格式叫“响应协议”。
重点学习这两个格式。
5.1.5 请求协议格式
请求行 例:GET /images/logo.gif HTTP/1.1,表示从/images目录下请求logo.gif文件。
请求头 例:Accept-Language: en(很多请求头)
空行 必须的,服务通过这个空行来区别出请求头和请求体
请求体 有时候也叫消息体,是可选的,get请求时无请求体,post请求会有。
浏览器向服务器发送请求时必须依据该格式,否则服务器无法识别。http协议中的请求行中可以有8种请求方法,但是目前为止,通用和大家都在用的只有两种:post请求和get请求。
5.1.6 响应协议格式
状态行;
响应头信息;
空行;
响应体(响应正文)。
5.1.7 GET 请求和 POST 请求的区别
注意区别就是请求数据的传送方式:
1.GET 方法
查询字符串(名称/值对)是在 GET 请求的 URL 中发送的:
/test/demo_form.asp?name1=value1&name2=value2
2.POST 方法
请求数据(名称/值对)是在 POST 请求的 HTTP 消息主体中发送的:
POST /test/demo_form.asp HTTP/1.1
Host: w3schools.com
name1=value1&name2=value2
比较
| 项目 | GET | POST |
|---|---|---|
| 后退按钮/刷新 | 无害 | 数据会被重新提交(浏览器应该告知用户数据会被重新提交)。 |
| 书签 | 可收藏为书签 | 不可收藏为书签 |
| 缓存 | 能被缓存 | 不能缓存 |
| 编码类型 | application/x-www-form-urlencoded | application/x-www-form-urlencoded 或 multipart/form-data。为二进制数据使用多重编码。 |
| 历史 | 参数保留在浏览器历史中。 | 参数不会保存在浏览器历史中。 |
| 对数据长度的限制 | 是的。当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符)。 | 无限制。 |
| 对数据类型的限制 | 只允许 ASCII 字符。 | 没有限制。也允许二进制数据。 |
| 安全性 | 与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。在发送密码或其他敏感信息时绝不要使用 GET ! | POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中。 |
| 可见性 | 数据在 URL 中对所有人都是可见的。 | 数据不会显示在 URL 中。 |
5.2 URL
前面我们用浏览器使用 HTTP 协议去访问网络。 但是有一点大家需要记住, 浏览器只是 web 客户端的一种。
- 可以这么说, 任何一个向 web 服务器发送求来获得应用程序的都是客户端。
- 浏览器作为一个比较特别的客户端,主要用于浏览网页内容并同其他 web 站点交互。
- 而一个更普通的客户端可以完成更多的工作,不仅可以下载数据, 还可以存储、操作数据, 甚至可以将其传送到另外一个地方或者传给另外一个应用。
python 提供的 urllib 模块, 使用它, 就可以编写可以下载或或者访问互联网上信息的简单 web 客户端。
你首先需要做的就是为程序提供一个有效的 web 网址, 这个 web 网站就是一个URL。
我们先了解URL是什么?
5.2.1 URL
URL 是Uniform Resource Locator的缩写, 中文叫:统一资源定位符。
浏览网页需要 URL, 这个 URL 就表示这个网页的地址。 这个地址用来在 web 上定位定位一个文档。
如街道地址一样, URL 地址也有一些结构。URL 使用如下的这种格式:
prot_sch://net_loc/path;params?query#frag
| URL组件 | 描述 |
|---|---|
| pro_sch | 网络协议, 如:http, https |
| net_loc | 服务器所在地 |
| path | 使用/分割的路径 |
| params | 可选参数 |
| ? | 可选, 表示后面是查询参数 |
| query | 可选, 用连接符(&)分割的一系列键值对, 如: user=lisi&pwd=aaa |
| #frag | 可选, 指定文档内特点锚的部分 |
5.3 urllib包和parse模块
urllib是一个package, 这个package包含了几个模块, 这几个模块都是使用url来工作.
urllib.request模块, 用于打开和读取urlurllib.error模块, 包含了urllib.request抛出的一些异常.urllib.parse模块, 解析urlurllib.robotparser模块, 解析robots.txt文件
5.3.1 urllib包
在python2中的模块urllib, urlparse, urllib2, 以及其他内容都整合在了urllib单一包中.
urllib和urllib2的内容整合在了urllib.request模块中urlparse的内容整合在了urllib.parse模块中urllib包还包括其他模块如:response, error, robotparse, 后面再学习.
python支持两种不同的模块来处理url, 一个是parse, 另一个是request.
两种模块的功能不一样, 下面会分别介绍两个模块.
5.3.2 parse模块
parse是包urllib下的模块, 只是用来处理url这个字符串本身, 而不负责使用这个url去联网获取资源.
parse主要提供了三个功能: urlparse(), urlunparse(), urljoin()
1. parse.urlparse()
urlparse()用来将url字符串解析成我们前面说的那些组件.
语法:
urlparse(urlstr, defProSch='', allowFrag=True)
**说明:
**
- 参数1就是
url字符串. 返回值一个ParseResult类型的数据 defProSch默认网络协议.allowFrag表示url中是否允许使用片段.
from urllib.parse import *
o = urlparse('http://www.cwi.nl:80/%7Eguido/Python.html')
print(o)
结果:
ParseResult(scheme='http', netloc='www.cwi.nl:80', path='/%7Eguido/Python.html', params='', query='', fragment='')
2. parse.urlunparse()
是把各个部分组合成url字符串.
from urllib.parse import *
o = urlparse('http://www.cwi.nl:80/%7Eguido/Python.html')
urlstr = urlunparse(o)
print(urlstr)
3. parse.urljoin()
urljoin()实现了url的连接功能.
urljoin(base, new_url, allow_fragments=True)
说明:
取得base的根路径(不包括路径中末端的文件), 然后与new_url连接起来.
from urllib.parse import *
newUrl = urljoin('http://www.cwi.nl/abc/Python.html', 'FAQ.html')
print(newUrl)
5.4 request模块
urllib.request模块提供了许多函数, 可用于从指定URL加载数据, 同时也可以对字符串进行编码解码工作, 以便再URL中以正确的形式显示出来.
request.urlopen()
urlopen(url, data=None[, timeout])
说明:
- 该函数, 打开指定的url并返回类文件对象, 可使用该对象读取返回的数据.
- 对所有HTTP请求, 最常用的是
GET请求, 向服务器发送的请求参数应该是url的一部分. 注意使用到的参数应该是已经经过url编码的(使用parse.urlencode()编码). - 如果是
post请求, 请求的字符串(包括表单数据)应该放在第二个参数data中.
from urllib.request import *
with urlopen("http://www.yztcedu.com") as r:
print(r)
urlopen()的返回值类文件对象
一旦连接成功, urlopen()会返还一个类文件对象, 就像在目标路径下打开了一个可读文件。
| urlopne()类文件对象方法 | 描述 |
|---|---|
| f.read([bytes]) | 从文件中读取所有或bytes个字节 |
| f.readline() | 从f中读取一行 |
| f.readlines() | 从f中读取所有行, 作为列表返回 |
| f.close() | 关闭f的url连接 |
| f.fileno() | 返回f的文件句柄 |
| f.info() | 返回f的mime头文件 |
| f.geturl() | 返回f的真正url |
from urllib.request import *
with urlopen("http://www.yztcedu.com") as r:
for line in r.readlines():
print(line.decode("utf-8"))

