14.1.1 socket模块
在网络编程中德一个基本组件就是套接字。套接字主要是两个程序之间的信息通道。
套接字包括两个:服务器套接字和客户机套接字。创建一个服务器套接字后,让它等待连接。这样它就在某个网络地址处监听。
一个套接字就是一个socket模块中socket类的实例。它的实例化需要3个参数:第一个参数是地址族(默认是socket.AF_INET);第2个参数是流(socket.SOCK_STREAM,默认值)或数据报(socket.SOCK_DGRAM)套接字。第三个参数是使用的协议(默认是0)。
服务器端套接字使用bind方法后,再调用listen方法去监听这个给定的地址。客户端套接字使用connect方法连接到服务器,在connect方法中使用的地址与bind方法中的地址相同。在这种情况下,一个地址就是一个格式为(host,port)的元组,其中host是主机名,port是端口号。listen方法只有一个参数,即服务器未处理的连接的长度。
服务器端套接字开始监听后,它就可以接受客户端的连接。这个步骤使用accept方法来完成。这个方法会阻塞直到客户端连接,然后该方法就返回一个格式为(client,address)的元组,client是一个客户端套接字,address是一个前面解释过的地址。服务器能处理客户端到它满意的程度,然后调用另一个accept方法开始等待下一个连接。
套接字有两个方法: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()
一个小型客户机
s = socket.gethostname()
port = 1234
s.connect((host,port))
print s.recv(1024)
14.1.2 urllib和urllib2模块
在能使用的各种网络工作库中,功能最强大的是urllib和urllib2.它们能让通过网络访问文件,就像那些文件存在于你的电脑上一样。通过一个简单的函数调用,几乎可以把任何URL所指向的东西用做程序的输入。
这两个模块的功能差不多,但urllib2更好一些。如果需要使用http验证或者cookie或者要为自己的协议写扩展程序的话,那么urllib2是好的选择。
1.打开远程文件
可以像打开本地一样打开远程文件,不同之处是可以使用只读模式,使用的是来自urllib模块的uriopen,而不是open
>>>from urllib import urlopen
>>>webpage = urlopen('http://www.python.org')
urlopen返回的类文件对象支持close,read,readline,readlines方法,当然也支持迭代。
假设想要提取在前面打开的python页中“About”链接的URL,那么就可以用正则表达式来实现。
>>>import re
>>>text = webpage.read()
>>>m = re.search('<a href="([^"]+)" .?>about</a>',text,re.IGNORECASE)
>>>m.group(1)
'/about/'
2.获取远程文件
函数urlopen提供一个能从中读取数据的类文件对象。如果希望urllib为你下载文件并在本地文件中存储一个文件的副本,那么可以使用urlretrieve。urlretrieve返回一个元组(filename,headers)而不是类文件对象,filename是本地文件的名字(由urllib自动创建),headers包含一些远程文件的信息,如果想要为下载的副本指定文件名,可以在urlretrieve函数的第2个参数中给出。
urlretrieve('http://www.python.org','C:\\python_webpage.html')
这个语句获取python的主页并把它存储在html中,如果没有指定文件名,文件就会放在临时的位置,用open函数可以打开它,但如果完成了对它的操作,就可以删除它以节省硬盘空间。要清理临时文件,可以调用urlcleanup函数,但不要提供参数,该函数会负责清理工作。
14.2 SocketServer和它的朋友们
SocketServer模块是标准库中很多服务器框架的基础,这些服务器框架包括BaseHTTPServer,SimpleHTTPServer,CGIHTTPServer,SimpleXMLRPCServer和DocXMLRPCServer,所有的这些服务器框架都为基础服务器增加了特定的功能。
SocketServer包含了4个基本的类:针对TCP套接字流的TCPServer;针对UDP数据报套接字的UDPServer;以及针对性不强的UnixStreamServer和UnixDatagramServer。可能不会用到后3个。
为了写一个使用SocketServer框架的服务器,大部分代码会在一个请求处理程序,每当服务器收到一个请求时,就会实例化一个请求处理程序,并且它的各种处理方法会在处理请求时被调用。具体调用哪个方法取决于特定的服务器和使用的处理程序类,这样可以把它们子类化,使得服务器调用自定义的处理程序集。基本的BaseRequestHandler类把所有的操作都放到了处理器的一个叫做handle的方法中,这个方法会被服务器调用。然后这个方法就会访问属性self.request中的客户端套接字。如果使用的是流,那么可以使用StreamRequestHandler类,创建了其他两个新属性,self.rfile(用于读取)和self.wfile(用于写入)然后就能使用这些类文件对象和客户机进行通信。
SocketServer框架中德其他类实现了对HTTP服务器的基本支持,其中包括运行CGI脚本。
一个基于SocketServer的小型服务器
from SocketServer import TCPServer,StreamRequestHandler
class Handler(StreamRequestHandler):
def handle(self):
addr = self.request.getpeername()
self.wfile.write('Thank you for connecting')
server = TCPServer(('',1234),Handler)
server.serve_forever()
14.3 多连接
到目前为止讨论的服务器解决方案都是同步的:即一次只能链接一个客户机并处理它的请求。如果每个请求只是花费很少的时间,比如,一个完整的聊天会话,那么同时能处理多个连接就很重要。
有3种主要的方法能实现这个目的:分叉、线程以及异步I/O。通过对SocketServer服务器使用混入类,派生进程和线程很容易处理。
分叉占用资源,并且如果有太多的客户端时分叉不能很好分叉
线程处理能导致同步问题。
Twisted是一个非常强大的异步网络变成框架。
14.3.1 使用SocketServer进行分叉和线程处理
windows不支持分叉
使用了分叉技术的服务器
from SocketServer import TCPServer,ForkingMinIn,StreamRequestHandler
class Server(ForkingMixIn,TCPServer): pass
server = Server(('',1234),Handler)
一个使用了线程处理的服务器
from SocketServer import TCPServer,ThreadingMixIn,StreamRequestHandler
class Server(ThreadingMixIn,TCPServer): pass
print 'Got connection from',addr
14.3.2 带有select和poll的异步I/O
当一个服务器与一个客户端通信时,来自客户端的数据可能是不连续的。如果使用分叉或线程处理,那就不是问题。当一个程序在等待数据,另一个并行的程序可以继续处理它们自己的客户端。另外的处理方法是只处理在给定时间内真正要进行通信的客户端。不需要一直监听----只要监听一会儿,然后把它放到其他客户端的后面。
这是asyncore/asynchar框架和Twisted框架采用的方法,这种功能的基础是select函数,如果poll函数可用,那也可以是它,这两个函数都来自select模块,这两个函数中,poll的伸缩性更好,但它只能在unix系统中使用(windows不可用)。
select函数需要3个序列作为它的必选参数,此外还有一个可选的以秒为单位的超时时间作为第4个参数。这些序列式文件描述符整数。这些就是我们等待的连接。3个序列用于输入、输出、以及异常情况。如果没有给定超时时间,select会阻塞,直到其中的一个文件描述符已经为行动做好了准备;如果给定了超时时间,select最多阻塞给定的超时时间,如果给定的超时时间是0,那么就给出了一个连续的poll(即不阻塞)。select的返回值是3个序列,每个代表相应参数的一个活动子集。比如返回的第1个序列是一个输入文件描述符的序列,其中有一些可以读取的东西。
序列能包含文件对象(在windows中行不通)或者套接字。服务器是个简单的记录器,它输出来自客户机的所有数据。可以使用Telnet连接它来进行测试。尝试用多个Telnet去连接来验证服务器能同时为多个客户端服务。
使用了select的简单服务器
import socket,select
inputs = [s]
rs.ws,es = select.select(inputs,[],[])
for r in rs:
if r is s:
print 'Got connection from' , addr
inputs.append(c)
else:
try:
data = r.recv(1024)
disconnected = not data
except socket.error:
disconnected = True
if disconnected:
print r.getpeername(),'disconnected'
inputs.remove(r)
print data
poll方法使用起来比select简单。在调用poll时,会得到一个poll对象。然后就可以使用pol对象的register方法注册一个文件描述符。注册后就可以使用unregister方法移除注册的对象。注册了一些对象以后,就可以调用poll方法并得到一个格式列表,其中fd是文件描述符,event则告诉你发生了什么。这是一个位掩码,意思是它是一个整数,这个整数的每个位对应不同的事件。那些不同的事件是select模块的常量。
使用poll的简单服务器
import socket.select
fdmap = {s.fileno(): s}
p = select.poll()
p.register(s)
events = p.poll()
for fd, event in events:
if fd = s.fileno():
c,addr = s.accept()
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]
14.4 Twisted
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):
factory = Factory()
factory.protocol = Simplelogger
reactor.listenTCP(1234,factory)
reactor.run()
如果用telnet 连接到此服务器并进行测试的话,那么每行可能只输出一个字符取决于缓冲或类似的东西。当然可以使用sys.stdout.write来代替print。
一个使用了LineReceiver协议改进的记录服务器
from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver
class SimpleLogger(LineReceiver):
def SimpleLogger(LineReceiver):
def lineReceived(self,line):
print line
factory.protocol = SimpleLogger
本文转自潘阔 51CTO博客,原文链接:http://blog.51cto.com/pankuo/1661448,如需转载请自行联系原作者