Python实战(4)-网络编程
本章将通过示例展示如何使用Python来编写以各种方式使用网络(如互联网)的程序。Python提供了强大的网络编程支持,有很多库实现了常见的网络协议以及基于这些协议的抽象层,让你能够专注于程序的逻辑,而无需关心通过线路来传输比特的问题。另外,对于有些协议格式,可能没有处理它们的现成代码,但编写起来也很容易,因为Python很擅长处理字节流中的各种模式(从本书前面介绍的各种处理文本文件的方式中,你可能领教了这一点)。鉴于Python提供的网络工具众多,这里只能简要地介绍它的网络功能。
标准库中有很多网络模块,其他地方也有不少。有些网络模块明显主要是处理网络的,但还有几个其实也是与网络相关的,如处理各种数据编码以便通过网络传输的模块。这里精挑细选了几个模块进行介绍。
模块socket
网络编程中的一个基本组件是套接字(socket)。套接字基本上是一个信息通道,两端各有一个程序。这些程序可能位于(通过网络相连的)不同的计算机上,通过套接字向对方发送信息。在Python中,大多数网络编程都隐藏了模块socket的基本工作原理,不与套接字直接交互。
套接字分为两类:服务器套接字和客户端套接字。创建服务器套接字后,让它等待连接请求的到来。这样,它将在某个网络地址(由IP地址和端口号组成)处监听,直到客户端套接字建立连接。随后,客户端和服务器就能通信了。客户端套接字处理起来通常比服务器端套接字容易些,因为服务器必须准备随时处理客户端连接,还必须处理多个连接;而客户端只需连接,完成任务后再断开连接即可。本章后面将介绍如何使用SocketServer等类和Twisted框架进行服务器端编程。
套接字是模块socket中socket类的实例。实例化套接字时最多可指定三个参数:一个地址族(默认为socket.AF_INET);是流套接字(socket.SOCK_STREAM,默认设置)还是数据报套接字(socket.SOCK_DGRAM);协议(使用默认值0就好)。创建普通套接字时,不用提供任何参数。
服务器套接字先调用方法bind,再调用方法listen来监听特定的地址。然后,客户端套接字就可连接到服务器了,办法是调用方法connect并提供调用方法bind时指定的地址(在服务器端,可使用函数socket.gethostname获取当前机器的主机名)。这里的地址是一个格式为(host, port)的元组,其中host是主机名(如www.example.com),而port是端口号(一个整数)。方法listen接受一个参数——待办任务清单的长度(即最多可有多少个连接在队列中等待接纳,到达这个数量后将开始拒绝连接)。
服务器套接字开始监听后,就可接受客户端连接了,这是使用方法accept来完成的。这个方法将阻断(等待)到客户端连接到来为止,然后返回一个格式为(client,address)的元组,其中client是一个客户端套接字,而address是前面解释过的地址。服务器能以其认为合适的方式处理客户端连接,然后再次调用accept以接着等待新连接到来。这通常是在一个无限循环中完成的。注意这里讨论的服务器编程形式称为阻断(同步)网络编程。后面你将看到非阻断(异步)网络编程示例,以及如何使用线程来同时处理多个客户端。为传输数据,套接字提供了两个方法:send和recv(表示receive)。要发送数据,可调用方法send并提供一个字符串;要接收数据,可调用recv并指定最多接收多少个字节的数据。如果不确定该指定什么数字,1024是个不错的选择。
代码清单14-1和14-2展示了最简单的客户端程序和服务器程序。如果在同一台机器上运行它们(先运行服务器程序),服务器程序将打印一条收到连接请求的消息,然后客户端程序将打印它从服务器那里收到的消息。在服务器还在运行时,可运行多个客户端。在客户端程序中,通过将gethostname调用替换为服务器机器的主机名,可分别在两台通过网络连接的机器上运行这两个程序。
注意可使用的端口号通常受到限制。在Linux或UNIX系统中,需要有管理员权限才能使用1024以下的端口号。这些编号较小的端口是供标准服务使用的。例如,端口80供Web服务器使用。另外,使用Ctrl+C停止服务器后,可能需要等待一段时间才能使用该服务器原来使用的端口(否则,可能出现“地址已被占用”错误消息)。
代码清单14-1 最简单的服务器
import socket
s = socket.socket()
host = socket.gethostname()
port = 1234
s.bind((host, port))
s.listen(5)
while True:
c, addr = s.accept()
print('Got connection from', addr)
c.send('Thank you for connecting'.encode("utf-8"))
c.close()
代码清单14-2 最简单的客户端
import socket
s = socket.socket()
host = socket.gethostname()
port = 1234
s.connect((host, port))
print(s.recv(1024))
server端运行效果:
client端运行效果:
模块urllib和urllib2
在可供使用的网络库中,urllib和urllib2可能是投入产出比最高的两个。它们让你能够通过网络访问文件,就像这些文件位于你的计算机中一样。只需一个简单的函数调用,就几乎可将统一资源定位符(URL)可指向的任何动作作为程序的输入。想想将这种功能与模块re结合起来使用都能做什么吧!你可下载网页、从中提取信息并自动生成研究报告。
模块urllib和urllib2的功能差不多,但urllib2更好一些。对于简单的下载,urllib绰绰有余。如果需要实现HTTP身份验证或Cookie,抑或编写扩展来处理自己的协议,urllib2可能是更好的选择。
打开远程文件
几乎可以像打开本地文件一样打开远程文件,差别是只能使用读取模式,以及使用模块urllib.request中的函数urlopen,而不是open(或file)。
>>> from urllib.request import urlopen
>>> webpage = urlopen('http://www.python.org')
如果连接到了网络,变量webpage将包含一个类似于文件的对象,这个对象与网页http://www.python.org相关联。注意要在没有联网的情况下尝试使用模块urllib,可使用以file:打头的URL访问本地文件,如file:c:\text\somefile.txt(别忘了对反斜杠进行转义)。urlopen返回的类似于文件的对象支持方法close、read、readline和readlines,还支持迭代等。假设要提取刚才所打开网页中链接About的相对URL,可使用正则表达式)。
>>> import re
>>> text = webpage.read()
>>> m = re.search(b'<a href="([^"]+)" .*?>about</a>', text, re.IGNORECASE)
>>> m.group(1)
'/about/'
获取远程文件
函数urlopen返回一个类似于文件的对象,可从中读取数据。如果要让urllib替你下载文件,并将其副本存储在一个本地文件中,可使用urlretrieve。这个函数不返回一个类似于文件的对象,而返回一个格式为(filename, headers)的元组,其中filename是本地文件的名称(由urllib自动创建),而headers包含一些有关远程文件的信息(这里不会介绍headers,如果你想更深入地了解它,请在有关urllib的标准库文档中查找urlretrieve)。如果要给下载的副本指定文件名,可通过第二个参数来提供。
urlretrieve('http://www.python.org', 'C:\\python_webpage.html')
这将获取Python官网的主页,并将其存储到文件C:\python_webpage.html中。如果你没有指定文件名,下载的副本将放在某个临时位置,可使用函数open来打开。但使用完毕后,你可能想将其删除,以免占用磁盘空间。要清空这样的临时文件,可调用函数urlcleanup且不提供任何参数,它将负责替你完成清空工作。
一些实用的函数
除了通过URL读取和下载文件外,urllib还提供了一些用于操作URL的函数,如下所示(这里假设你对URL和CGI略知一二)。
❑ quote(string[, safe]):返回一个字符串,其中所有的特殊字符(在URL中有特殊意义的字符)都已替换为对URL友好的版本(如将~替换为%7E)。如果要将包含特殊字符的字符串用作URL,这很有用。参数safe是一个字符串(默认为’/'),包含不应像这样对其进行编码的字符。
❑ quote_plus(string[, safe]):类似于quote,但也将空格替换为加号。
❑ unquote(string):与quote相反。❑ unquote_plus(string):与quote_plus相反。urlencode(query[, doseq]):将映射(如字典)或由包含两个元素的元组(形如(key, value))组成的序列转换为“使用URL编码的”字符串。这样的字符串可用于CGI查询中(详细信息请参阅Python文档)。