天天看点

python之网络编程

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,如需转载请自行联系原作者