Python学习笔记_第十四章:网络编程
Python的网络编程解决方案:
- Python内有很多针对常见网络协议的库
- 用Python很容易写出处理各种协议格式的代码(Python非常擅长于处理字节流的各种模式)
如果想了解更多Python中的网络编程,推荐:Jphn Goerzen 的 The Foundations of Python Network Programming
少数几个网络设计模块
socket模块
网络编程最基本的就是套接字(socket),它可以说是两个端点程序之间的“信息通道”,Python大部分模块隐藏了套接字的基本细节,不直接和套接字交互。
套接字分:服务器套接字、客户端套接字
可以通过实例化socket模块的socket类来获得一个套接字,需要3个参数,第一个是地址簇:默认为socket.AF_INET,第二个是流:默认为socket.SOCK_STREAM,第三个是使用的协议:默认为0,对于一个普通的套接字不需要任何参数。
获得套接字后可以通过bind方法绑定到一个地址,再调用listen方法去监听,listen方法的唯一参数为服务器未处理连接的长度。
服务器开始监听后就可以通过使用accept方法来接受客户端的连接,这个方法是阻塞的,直到有客户端连接,连接后其返回值为元祖(client, address),服务器处理完一个连接后,再次调用accept来等待下一个连接。
以上描述的服务器编程为阻塞或同步网络编程
套接字对象有两个方法:send和recv
在Linux或UNIX系统中,需要有系统管理员权限才能使用1024以下的端口。这些地域1024的端口被标准服务占用,如:80用于web
一个小型服务器:
import socket
s = socket.socket()
host = socket.gethostname()
print host
prot = 1234
s.bind((host, prot))
s.listen(5)
while True:
c, address = s.accept()
print 'Got connection form', address
c.send('Thank you for connect')
print c.recv(1024)
c.close()
一个小型客户端:
import socket
s = socket.socket()
host = socket.gethostname()
port = 1234
s.connect((host, port))
print s.recv(1024)
s.send('hello server')
在Python库参考文档(http://python.org/doc/lib/module_socket.html)和Gordon McMillan的Socket Programming HOWTO(http://doc.python.org/dev/howto/sockets.html)中找到更多socket模块的内容。
urlib和urllib2模块的内容
在能用的各个网络库中,最强大的就数urlib和urllib2了,通过他们调用网络文件如调本地,几乎可以把任何URL指向的东西作为程序的输入。可以想象下将它们和re模块结合的效果。
这俩的功能相近,但urlib2更好点,如果只是简单的下载urlib就够了,如果需要HTTP验证或cookie,或者要为自己的协议编写扩展,那就urlib2更好了。
1.打开远程文件
>>> from urllib import urlopen
>>> webpage = urlopen('www.baidu.com')
>>> webpage = urlopen('http://www.baidu.com')
>>> webpage
<addinfourl at 88157960L whose fp = <socket._fileobject object at 0x00000000053C2E58>
2.获取远程文件
>>> from urllib import urlretrieve
>>> urlretrieve('http://www.python.org', 'C:\U')
>>> urlretrieve('http://www.python.org', r'C:\Users\hcs\Desktop\python_webpage.html')
('C:\\Users\\hcs\\Desktop\\python_webpage.html', <httplib.HTTPMessage instance at 0x000000000542D708>)
以上,如果不指定保存位置,文件就会保存在零时位置,要清理,可以调用urlcleanup函数。
一些功能
处理读取和下载文件,urllib来提供了些函数操作URL本身:
函数 | 描述 |
---|---|
quote(string[, safe]) | 返回一个字符串,其中所有URL特殊字符都被URL友好的字符代替 |
quote_plus(string[, safe]) | 和quote功能差不多,但用+代替空格 |
unquote(string) | 和quote相反 |
unquote_plus(string) | 和unquote_plus相反 |
urlencode(query[, doseq]) | 将映射或者包含两个元素的元组的序列转换为URL格式的字符串,这种字符串可以用于CGI查询 |
其他模块
标准库中一些和网络相关的模块
模块 | 描述 |
---|---|
asynchat | asyncore的增强版(从更高层次处理异步IO的框架) |
asyncore | …… |
cgi | 通用网关接口 |
Cookie | Cookie对象操作,主要用于服务器 |
cookielib | 客户端cookie支持 |
email消息支持 | |
ftplib | FTP客户端模块 |
gopherlib | gopher客户端模块(Internet提供的采用菜单式驱动的信息查询工具,采用C/S) |
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,所有的这些服务器框架都为基础服务器增加了特定的功能。
SocketServer包括四个基本的类:针对TCP流式套接字的TCPServer;针对UDP数据报套接字的UDPServer;以及针对性不强的UnixStreamServer和UnixDatagramServer。后三个不常用。
前面小型服务器的SocketServer版本:
from SocketServer import TCPServer, StreamRequestHandler
class handler(StreamRequestHandler):
def handle(self):
addr = self.request.getpeername()
print 'Got connection from', addr
self.wfile.write('Thank you for connecting')
print self.rfile.read()
server = TCPServer(('', 1234), handler)
server.serve_forever()
它能和前面小型客户端协同工作
Python参考库(http://python.org/doc/lib/module-SocketServer.html)和John Goerzen的The Foundations of Python Network Programming可以找到关于SocketServer的更多信息。
多个连接
前面的服务器解决方案都是同步的:即一次只能连接一个客户机并处理,为了实现异步可以有三种方法:分叉(forking)、线程(threading)以及异步I/O,分叉在现代UNIX和Linux系统很高效,但是windows不支持分叉。
通过SocketServer服务器使用混入类很容易派生进程和线程。
避免线程和分叉的另外一种方法是转换到Stackless Python(http://stackless.com),一个为了能够在不同的上下文之间快速、方便切换而设计的Python版本。,它支持一个叫做微线程的类线程的并行形式。
使用Socket Server进行分叉和线程处理
使用分叉技术的服务器(windows不支持):
from SocketServer import TCPServer, ForkingMixIn, StreamRequestHandler
class server(ForkingMixIn, TCPServer):pass
class handler(StreamRequestHandler):
def handle(self):
addr = self.request.getpeername()
print 'Got connection from', addr
self.wfile.write('Thank you for connecting')
self.rfile.read()
server = TCPServer(('', 1234), handler)
server.serve_forever()
使用线程处理的服务器
from SocketServer import TCPServer, ThreadingMixIn, StreamRequestHandler
class server(ThreadingMixIn, TCPServer):pass
class handler(StreamRequestHandler):
def handle(self):
addr = self.request.getpeername()
print 'Got connection from', addr
self.wfile.write('Thank you for connecting')
self.rfile.read()
server = TCPServer(('', 1234), handler)
server.serve_forever()
不知道为啥,两种方式实现的效果一样,都不太正常
带有select和poll的异步I/O
这种处理方式只处理在给定的时间内真正要进行通信的客户端,不需要一直监听,只需要监听一会,然后把它放到其他客户端的后面。这个功能的基础是select,如果poll可用,也可以是它,这两个函数都来自select模块。poll只能在UNIX中使用。
select有三个必选的序列参数,还有一个可选的以秒为单位的超时时间作为第四个参数。三个序列为文件描述符整数或者带有可以产生这样整数的fileno方法的对象,这些都是我们等待连接的对象。
三个序列分别用于:输入、输出、异常情况,select函数的输出为三个序列,分别对应这三种输入参数序列的一个活动子集
序列能包含文件对象(在windows中行不通)或者套接字。
简单的select服务器
import socket, select
s = socket.socket()
addr = socket.gethostname()
s.bind((addr, 1234))
s.listen(5)
inputs = [s]
while True:
rs, ws, es = select.select(inputs, [], [])
for r in rs:
if r is s:
c, addr = s.accept()
print 'Got connection from', addr
inputs.append(c)
else:
try:
data = r.recv(1024)
disconnected = not data
except:
disconnected = True
if disconnected:
print r.getpeername(), 'disconnected'
inputs.remove(r)
else:
print data
poll方法返回的元组(fd, event)中event的类型(select模块中的polling时间常量类型)有:
事件名 | 描述 |
---|---|
POLLIN | 读取来自文件描述符的数据 |
POLLPRI | 读取来自文件描述符的紧急数据 |
POLLOUT | 文件描述符已经准备好数据,写入时不会发生阻塞 |
POLLERR | 与文件描述符有关的错误情况 |
POLLHUP | 挂起,连接丢失 |
POLLNVAL | 无效请求,连接没有打开 |
使用poll代替select的代码(只有在UNIX有用)
import select, socket
s = socket.socket()
addr = socket.gethostname()
port = 1234
s.bind((addr, port))
s.listen(5)
fdmap = {s.fileno() : s}
p = select.poll()
p.register(s)
while True:
events = p.poll()
for fd, event in events:
if fd == s.fileno():
c, addr = s.accept()
print('Got connection from', addr)
p.register(c)
fdmap[c.fileno()] = c
elif event & select.POLLIN:
data = fdmap[fd].recv(1024)
if not data:
print(fdmap[fd].getpeername(), 'disconnected')
p.unregister(fd)
del fdmap[fd]
else:
print(data)
在Python库参考文档(http://python.org/doc/lib/module-select.html)中可以找到更多关于select和poll的信息。
Twisted
Twisted是一个事件驱动的Python网络框架,它能和几个常见的GUI工具包(Tk, GTK, Qt, wxWidget)协同工作。它相当丰富,支持web服务器、客户机、SSH2、SMTP、POP3、IMAP4、AIM、ICQ、IRC、MSN、Jabber、NNTP和DNS等等。
下载和安装Twisted
windows直接登录网址http://twistedmatrix.com下载Python版本对应的Windows安装程序就好
其他系统:如果有包管理器(APT、RPM、DPKG、Portage、Fink、MacPorts)那么可以用它们直接安装,如果没有用包管理器,则需要下载档案文件,用tar命令解压缩后运行Distutils脚本:python setup.py install
编写Twisted服务器
Twited使用一个事件甚至多个基于事件的方法,要编写基本的服务器就要实现处理比如:新客户端连接、数据到达以及一个客户端断开连接等事件的事件处理程序。下面是个例子:
from twisted.internet import reactor
from twisted.internet.protocol import Protocol, Factory
class SimpleLogger(Protocol):
def connectionMade(self):
print 'Got connection from', self.transport.client
def connectionLost(self, reason):
print self.transport.client, 'disconnected'
def dataReceived(self, data):
print data
factory = Factory()
factory.protocol = SimpleLogger
reactor.listenTCP(1234, factory)
reactor.run()
上面的服务器程序每行之输出了个字符,为了能实现每次输出一行,twisted.protocols.basic模块中包含一个有用的预定义协议,是LineReceiver,请看下面的例子:
from twisted.internet import reactor
from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver
class SimpleLogger(LineReceiver):
def connectionMade(self):
print 'Got connection from', self.transport.client
def connectionLost(self, reason):
print self.transport.client, 'disconnected'
def dataReceived(self, line):
print line,
factory = Factory()
factory.protocol = SimpleLogger
reactor.listenTCP(1234, factory)
reactor.run()
关于twisted,可以在网站http://twistedmatrix.com上找到