Python 基础学习16:网络编程
Python 中有很多库实现了常见的网络协议,可以专注于程序逻辑而不必关心底层线路传输的问题。
常用的模块
标准库中有很多的网络模块,常用的有 scocket 和 SocketServer 相关的模块。
socket
网络编程中的一个基本组件是套接字(socket)。套接字本质上是一个信息通道,两端各有一个程序。
套接字分为两类:服务器套接字和客户端套接字。创建服务器套接字后,让它等待连接请求的到来,它将监听某个网络地址(由 IP 和 PORT 组成),直到客户端套接字建立连接,随后服务器和客户端就可以进行通信了。
客户端套接字处理起来比服务器套接字要容易,它只需连接,完成任务后断开连接就可以了,服务器套接字需要随时准备处理客户端连接,有时还须处理多个连接。
套接子是模块 socket 中的 socket 类的实例。_class_ socket.socket(_family=AF_INET_, _type=SOCK_STREAM_, _proto=0_, _fileno=None_)
实例化套接字时最多可以指定三个参数:
- 地址族,默认为
socket.AF_INET(ipv4)socket.AF_INET6是 ipv6 - 流套接字(默认)(
socket.SOCK_STREAM)还是数据包套接字(socket.SOCK_DGRAM) - 协议号通常为零并且可以省略
创建普通套接字时,不用提供任何参数。
服务器套接字先调用 bind 方法,再调用 listen 方法来监听特定的地址。
随后客户端套接字调用 connect 方法,指定服务器套接字 bind 方法指定的地址。
方法 listen 接受一个参数,用于指定最多可以有多少个连接在队列中等待接纳,到达这个数量后将开始拒绝。
服务器套接字开始监听后,就可以接受客户端的连接了,通过调用 accept 方法来完成。这个方法将阻断到客户端连接到来为止,然后返回一个格式为 (client, address) 的元组,其中 client 是一个客户端套接字, address 是客户端的地址。这种模式称为同步网络编程。
服务器处理客户端的连接,然后再次调用 accept 以接着等待新连接的到来,通常是在一个无限循环中完成的。
为了传输数据,套接子提供了两个方法 send 和 recv 。
要发送数据,可调用方法 send 并提供一个 byte 类型的数据。要接收数据,可调用 recv 方法,并指定最多接受多少个字节的数据,1024 是个不错的选择。
服务器套接字示例:
1 | import socket |
客户端套接字示例:
1 | import sys, socket |
模块 urllib 和 urllib2
这两个模块能够通过网络访问文件,urllib2 相比较 urllib而言能实现 HTTP 身份验证或 Cookie ,或者编写扩展来处理自己的协议,对于简单的下载 urllib 足以。
打开远程文件
使用模块 urllib.request 中的 urlopen 方法,将 URL 作为参数,将访问网页文件对象。urlopen 返回的类似于文件的对象支持 close, read, readline 和 readlines 方法,还支持迭代。
示例:
1 | from urllib.request import urlopen |
下载远程文件
要下载文件可以使用 urllib 中的 urlretrieve 方法。该函数不返回类似于文件的对象,而是返回一个格式为 (filename, headers) 的元组,filename 是本地文件的名称,自动创建的,headers 包含远程文件的信息。
urlretrieve 第一个参数指定下载的资源URL,第二个参数可以指定下载的数据存放位置,如果不指定将存放在临时位置,可以调用 urlcleanup 且不提供任何参数就可以帮助清空数据。
示例:
1 | from urllib.request import urlretrieve |
其它模块
Python 标准库中的一些与网络相关的模块:
| 模块 | 描述 |
|---|---|
| asynchat | 包含补充asyncore的功能 |
| asyncore | 异步套接字处理程序 |
| cgi | 基本的CGI支持 |
| Cookie | Cookie对象操作,主要用于服务器 |
| cookielib | 客户端Cookie支持 |
| 电子邮件(包括MIME)支持 | |
| ftplib | FTP客户端模块 |
| gopherlib | Gopher客户端模块 |
| httplib | HTTP 客户端模块 |
| imaplib | IMAP4客户端模块 |
| mailbox | 读取多种邮箱格式 |
| mailcap | 通过mailcap文件访问MIME配置 |
| mhlib | 访问MH邮箱 |
| nntplib | NNTP客户端模块 |
| poplib | POP客户端模块 |
| robotparser | 解析Web服务器robot文件 |
| SimpleXMLRPCServer | 一个简单的XML-RPC服务器 |
| smtpd | SMTP服务器模块 |
| smtplib | SMTP客户端模块 |
| telnetlib | Telnet客户端模块 |
| urlparse | 用于解读URL |
| xmlrpclib | XML-RPC客户端支持 |
SocketServer 及相关的类
模块 SocketServer 是标准库提供的服务器框架基石,这个框架包括 BaseHTTPServer 、 SimpleHTTPServer 、CGIHTTPServer SimpleXMLRPCServer 和 DocXMLRPCServer 等服务器。它们都在基础服务器的基础功能上添加了各种功能:
1 | +------------+ |
SocketServer 包含四个基本的服务器:
TCPServer支持 TCP 套接字流UDPServer支持 UDP 数据包套接字UnixServerUnixDatagramServer
基于 SockerServer 的极简服务器示例:
1 | from socketserver import TCPServer, StreamRequestHandler |
多个连接
前面的服务器解决方法都是同步的:意味着不能同时处理多个客户端的连接请求,只能处理完一个然后再处理第二个。
处理多个连接的主要方式有三种:分叉(forking)、线程化和异步I/O 。
分叉占用的资源较多,且在客户端很多时可伸缩性不佳,而线程化可能带来同步问题。
分叉和线程是什么?
fork 是一个 UNIX 术语,对进程进行 fork ,基本上是赋值它,这样得到的两个进程都将从当前位置开始继续往下执行,且每个进程都有自己的内存副本。原来的进程为父进程,复制的进程为子进程,进程能够判断它们是原始进程还是子进程,因此能够执行不同的操作。
在分叉的服务器中,对于每个客户端连接,都将通过分叉创建一个子进程,父进程继续监听新连接,而子进程负责处理客户端请求。客户端请求结束后,子进程直接退出,由于分叉出来的进程并行运行,因此客户端无需等待。
鉴于分叉占用的资源较多,还有另一种解决方法:线程化。
线程是轻量级的进程(子进程),都位于同一个进程中并共享内存。由于线程共享内存,必须确保它们不会彼此干扰或同时修改同一项数据,否则将引起混乱,这属于同步问题。
使用 SocketServer 实现分叉和线程化
使用 SocketServer 创建分叉或线程化服务器非常简单,仅当 handle 需要很长时间才能执行完毕时,分叉和线程化才能提供帮助。
分叉服务器示例:
1 | from socketserver import TCPServer, ForkingMixIn, StreamRequestHandler |
多个客户端同时连接,此时系统上的进程可以看到 fork 出了多个子进程:
1 | ps -axf -o pid,ppid,comm | grep 72716 |
线程化服务器示例:
1 | from socketserver import TCPServer, StreamRequestHandler, ThreadingMixIn |
查看线程的信息:
1 | pstree -p 75035 |
使用 select 和 poll 实现异步 I/O
当服务器与客户端通信时,来自客户端的数据可能时断时续,可以通过分叉和线程化解决,还有另一种做法是只处理当前正在通信的客户端,无需不断监听,只需监听后将客户端加入队列即可。这是框架 asyncore/asynchat 和 Twisted 采取的方法。这种方法的基石是函数 select 或 poll 。两个函数都位于 select 模块中,其中 poll 可伸缩性高,但只有 UNIX 系统支持。
函数 select 接受三个必不可少的参数和一个可选参数,其中前三个参数为序列,第四个参数为超时时间,单位是秒。
三个序列分别表示需要输入和输出以及发生异常的连接,如果没有指定超时时间,select 将阻断到有文件描述符准备就绪时,如果超时时间为零,将不断轮询。
select 返回三个序列,也就是一个长度为 3 的元组,每个序列都包含相应参数中处于活动状态的文件描述符。返回的第一个序列包含有数据需要读取的所有输入文件描述符。
使用 select 的服务器示例:
1 | import socket, select |
测试客户端:
1 | import sys, socket |
输出效果:
1 | b'Got connection from' ('127.0.0.1', 59308) |
方法 poll 使用起来比 select 容易。
调用 poll 时,会返回一个轮询对象,可使用 register 方法项这个对象注册文件描述符,注册后可以通过 unregister 将其删除。
注册对象后,可调用其方法 poll,将返回一个包含 (fd, event) 元组的列表。fd 为文件描述符,event 是发生的事件,它是一个位掩码,是一个整数,各个位对应不同的事件。
各个事件是用 select 模块中的常量表示的,要检查指定的位是否为1 ,可以使用按位与运算符:if event & select.POLLIN: ...
select 模块中轮询事件常量:
| 事件名 | 描述 |
|---|---|
| POLLIN | 文件描述符中有需要读取的数据 |
| POLLPRI | 文件描述符中有需要读取的紧急数据 |
| POLLOUT | 文件描述符为写入数据做好了准备 |
| POLLERR | 文件描述符出现了错误状态 |
| POLLHUP | 挂起。连接已断开 |
| POLLNVAL | 无效请求。连接未打开 |
使用 poll 的简单服务器示例:
1 | import socket, select |
Twisted
Twisted 是由 Twisted Matrix Laboratories 开发的,是一个事件驱动的 Python 网络框架。
Twisted 是一个功能及其丰富的框架,支持 Web 服务器和客户端、SSH2、
SMTP、POP3、IMAP4、AIM、ICQ、IRC、MSN、Jabber、NNTP、DNS等。
编写 Twisted 服务器
Twisted 采用的是基于事件的方法,只需实现处理如下情形的事件处理程序:
- 客户端发起连接
- 有数据到来
- 客户端断开连接
专用类可在基本类的基础上定义更细致的事件。
事件处理程序是在协议中定义的,还需一个工厂,它能够在新连接到来时创建这样的协议对象。
如果指向创建自定义协议类的实例,可使用 Twisted 自带的工厂 twisted.internet.protocol 中的 Factory 类。
编写自定义协议时,将模块 twisted.internet.protocol 中的 Protocol 作为超类。有新连接时调用事件处理程序 connectionMade ;连接中断时调用 connectionLost, 来自客户端的数据通过处理程序 dataReceived 接收的。
不能使用事件处理策略来向客户端发送数据。需要使用对象 self.transport 来完成,包含一个 write 方法,还有一个 client 属性,包含客户端的地址。
使用 Twisted 创建的简单服务器示例:
1 | from twisted.internet import reactor |
评论· · · · · ·