上节内容我们介绍的是关于数据库方面的Python编程知识,记住数据库操作的几个关键步骤便可。Python是一个很强大的网络编程工具,Python内有狠毒针对常见的网络协议的库,对网络协议的各个层次进行了抽象封装。其次,Python很擅长处理字节流的各种模式,使用Python可以很容易地写出处理各种协议格式的代码,有可能这些协议格式现在还不存在在处理代码。在这节的内容里面,我会介绍一些Python标准课中可用的一些网络模块,然后具体看看它们的一些用法,接着是能同时处理多个连接的方法和使用。
在网络编程中一个最基本的组件就是套接字,也就是我们称的socket。套接字基本上是两个端点的程序之间的“信息通道”。程序可能分布在不同的计算机上,但是通过网络连接,通过套接字相互发送信息。在Python中大多都隐藏了socket模块的基本细节,不直接和套接字交互。套接字包括两个:服务器套接字和客户机套接字。在创建一个服务器套接字以后,让它等待连接,这样它就在某个网络地址处监听,直到有客户端套接字连接,连接完成后,两者就可以进行交互了。
一个套接字就是socket模块中的socket类的一个实例。它的实例化需要三个参数:
对于一个普通的套接字而言,不需要提供任何参数。套接字有两个方法:send和recv,用于传输数据,可以使用字符串参数调用send以发送数据,用一个所需的字节数做参数调用recv来接受数据。如果不能确定用哪个数字比较好,1024是一个不错的选择。来看看一些具体的列子:
一个小型的服务器:
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')
c.close()
一个小型的客户机:
import socket
s=socket.socket
host=socket.gethostname()
port=1234
s.connect(host,port)
print s.recv(1024)
服务器套接字使用bind方法后,再调用listen方法去监听某个特定的地址。客户端套接字使用connect方法连接到服务器,在connect方法中使用的地址与服务器在bind方法中的地址相同。服务器套接字开始监听后,就可以接受来自客户端的连接。这个步骤使用accept方法来完成。这个方法会阻塞直到客户端连接,然后返回一个格式为(client,address)的元组。这个大过程往往都是在一个无限循环中进行的。
在能使用的各种网络函数库中,功能最强大最丰富可能就是urllib和urllib2了。通过它们在网络上访问文件就好像访问本地电脑上的文件一样,通过一个简单的函数调用,几乎可以把任何URL所指向的东西作为程序输入。这两个没模块的功能都差不多,但urllib2更好一些。
可以像打开本地文件一样打开远程文件,不同之处是只能使用只读模式。使用来自urllib模块的urlopen,而不是简单的open。
from urllib import urlopen
webpage=urlopen('http://www.baidu.com')
urlopen返回的类文件对象支持close、read、readline和readlines方法,当然也支持迭代。
函数urlopen返回一个能从中读取数据的类文件对象。假如你希望urllib为你下载文件并在本地文件中存储一个文件的副本,那么可以使用urlretrieve。urlretrieve返回一个元组(filename,headers)而不是类文件对象。filename是本地文件的名字,headers包含一些远程文件的信息。
urlretrieve('http://www.baidu.com','C:\\python_webpage.html')
这个语句获取百度搜索引擎的主页并把存储在C:\\python_webpage.html中。如果没有指定文件名,文件就会被放在临时地位置,用open函数可以打开它,但如果完成了对它的操作,就可以删除它以节省空间。这时可以调用urlcleanup函数,不需要提供参数,该函数负责清理工作。
对于基础的套接字的服务器的实现并不是很难,但是如果你想再获取一些新的功能,就需要SocketServer模块中的一些服务框架来为你服务:BasedHTTPServer、SimpleHTTPServer、CGIHTTPServer、SimpleXMLRPCServer和DocXMLRPCServer,所有这些服务器框架都为基础服务器增加了特定的功能。
SocketServer包含了4个基本的类:针对TCP流式套接字的TCPServer,针对UDP数据报套接字的UDPServer,以及针对性不强的UnixStreamServer和UnixDatagramServer,一般情况下很少用到后三个。来看看具体的例子:
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')
server=TCPServer(('', 52763), Handler)
server.serve_forever()
BaseRequestHandler类把所有的操作都放到了处理器的一个叫做handle的方法中,这个方法会被服务器调用。然后这个方法就会访问属性self.request中的客户端套接字。如果使用的是流,那么可以使用StreamRequestHandler类,创建了其他两个新属性:self.rfile和self.wfile。然后就能使用这类文件对象和客户机进行通信。
前面讨论的服务器解决方案都是同步的,也就是一次只能连接一个客户机并处理它的请求。假如每个请求只是花费很少的时间,一个完整的聊天会话,那么同时能处理多个连接就很重要。
有三种方法能实现这个目的:分叉(forking)、线程(threading)、异步I/O。通过对SocketServer服务器使用混入类,派生进程和线程很容易处理。
如果是handle方法要花费很长时间的话,那么分叉和线程的行为就有很有。下面展示如何使用线程和分叉。
分叉操作:
from SocketServer import TCPServer,ForkingMixIn,StreamRequestHandler
class Server(ForkingMixIn,TCPServer):
pass
class Handler(StreamRequestHandler):
def handle(self):
addr=self.request.getpeername()
print 'Got connection form', addr
self.wfile.write('Thank you for connecting')
server=Server(('',52763),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 connect from', addr
self.wfile.write('Thank you for connecting')
server=Server(('',52763), Handler)
server.serve_forever()
当一个服务器和一个客户端通信时,来自客户端的数据可能是不连续的。如果使用分叉或者线程处理,那就不是问题。当一个程序在等待数据,另一个并行的程序可以继续处理它们自己的客户端。另外的处理方法是只处理在给定时间内真正要进行通信的客户端。不需要一直监听,只要监听一会儿,然后把它放到其他客户端的后面。
这是asyncore/asynchat框架和Twisted框架采用的方法,这种功能的基础是select函数,如果poll函数可用,那也可以是它,这两个函数都来自与select模块。这两个函数中,poll的伸缩性更好,但它只能在UNIX系统中使用。
select函数需要3个序列作为它的必选参数,此外还有一个可选的以秒为单位的超时间作为第四个参数,这些序列是文件描述符整数,也就是我们等待的连接。
下面来看一个使用了select的简单服务器:
import socket,select
s=socket.socket()
host=socket.gethostname()
port=1234
s.bind((host,port))
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
else:
try:
data=r.recv(1024)
disconnected=not data
except socket.error:
disconnected=True
if disconnected:
print r.getpeername(), 'disconnected'
inputs.remove(r)
else:
print data
poll方法使用起来比select简单,在调用poll时候,会得到一个poll对象。然后就可以使用poll对象的register方法注册一个文件描述符。注册后可以调用unregister方法移除注册的对象。
使用poll的简单服务器:
import socket,select
s=socket.socket()
host=socket.gethostname()
port=1234
s.bind((host,port))
fdmap={s.fileno(): s}
s.listen(5)
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.POLLLIN:
data=fdmap[fd].recv(1024)
if not data:
print fdmap[fd].getpeername(), 'disconnected'
p.unregister(fd)
del fdmap[fd]
else:
print (data)
更多关于select和poll的参考信息可以在官方文档中查看到,在这里不做赘述。好了,关于网络编程的部分问题先介绍到这里,这是第一部分,接着下一章节还会介绍关于网络编程的问题--Twisted。敬请期待!