Chinaunix首页 | 论坛 | 博客
  • 博客访问: 38174
  • 博文数量: 22
  • 博客积分: 285
  • 博客等级: 二等列兵
  • 技术积分: 237
  • 用 户 组: 普通用户
  • 注册时间: 2012-12-18 20:02
个人简介

人生最大的悲哀莫过于迷失自我而无法自拔!

文章分类

全部博文(22)

文章存档

2013年(11)

2012年(11)

我的朋友

分类: LINUX

2013-02-01 22:55:41

Python 网络编程


Python内有很多针对常见网络协议的库,在库顶部可以获得抽象层,这样就可以集中精力在程序的逻辑处理上,而不是停留在网络实现的细节中。
Twisted框架----Python中一个丰富、成熟的、用于编写网络应用程序的框架。


1.socket模块:
在网络编程中的一个基本组件就是套接字(socket)。套接字主要是两个程序之间的“信息通道”。
程序可能(通过internet)分布在不同的计算机上,通过套接字相互发送信息。在python中的大部分的网络编程都隐藏了socket模块的基本细节,
并且不直接和socket交互。


套接字包括两个:服务器套接字和客户机套接字。创建一个服务器套接字后,让它等待连接。
这样他就在某个网络地址处(IP地址和一个端口号的组合)监听。
处理客户端套接字通常比处理服务器端套接字容易,因为服务器必须准备随时处理客户端的连接,
同时还要处理多个连接,而客户机只是简单地连接->完成事物->断开连接。


一个套接字就是一个socket模块中的socket类的实例。他的实例化需要3个参数:第一个参数是
地址族(默认是socket.AF_INET);第二个参数是流(socket.SOCK_STREAM,默认值)或数据报(socket.SOCK_DGRAM)
套接字;第三个参数是使用的协议(默认是0,使用默认值即可)。对于一个普通的套接字,不需要提供任何参数。


服务器端套接字使用bind方法后,再调用listen方法去坚挺给定的地址。客户端套接字使用connect方法连接到服务器
在connect方法中使用的地址与bind方法中的地址相同(在服务器端,能够实现很多功能,比如使用函数socket.gethostname
得到当前主机名)。在这种情况下,一个地址就是一个格式为(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()
一个小型的客户机:
import socket


s = socket.socket()


host = socket.gethostname()
port = 1234


s.connect((host,port))
print s.recv(1024)




2.urllib和urllib2模块:
使用urllib模块的urlopen打开远程文件,如下语句中的webpage包含一个链接
到网页的类文件对象。当使用urllib访问本地文件时,使用
file开头的url访问本地文件,比如file:c:\text\somefile.txt
urlopen返回的类文件对象支持close、read、readline和readlines方法,当然也支持迭代。


urlretrieve返回一个元组(filename,headers)而不是类文件对象,filename是本地文件的名字
(由urllib自动创建),headers包含一些远程文件的信息,如果想要为下载的副本指定文件名,可以
在urlretrieve函数的第二个参数中指出。
from urllib import urlretrieve
urlretrieve('',r'C:\Users\pphacking\Desktop\pythonweb.html')
如果没有指定文件名,文件就会放在临时的位置,用open函数可以打开它,但如果完成了对它的操作,
就可以删除它以节省硬盘空间。要清理临时文件,可以调用urlcleanup函数,但不要提供参数,该函数会负责
清理工作。
[root@web-db pythonjichu]# cat pythonweb.py
#!/usr/local/bin/python


from urllib import urlopen
from urllib import urlretrieve
import re


webpage = urlopen('')
text = webpage.read()
m = re.search('About',text,re.IGNORECASE)
print m.group(1)


localtext = urlopen('file:/root/python/pythonjichu/test')
textcontents = localtext.read()
print textcontents


urlretrieve('','python.html')




3.标准库中一些与网络有关的模块:
asynchat:asyncore的增强版
asyncore:异步套接字处理程序
cgi:基本的CGI支持
Cookie:Cookie对象操作,主要用于服务器
cookielib:客户端cookie支持
email:E-mail消息支持(包括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的客户端支持




4.SocketServer模块是标准库中很多服务器框架的基础,这些服务器框架包括BaseHTTPServer、
SimpleHTTPServer、CGIHTTPServer、SimpleXMLRPCServer和DocXMLRPCServer。
SocketServer包括了4个基本的类:针对TCP套接字流的TCPServer;针对UDP数据包套接字的UDPServer;
以及针对性不强的UnixStreamServer和UnixDatagramServer。


为了编写一个使用SocketServer框架的服务器,大部分代码会在一个请求处理程序(request handler)中。
每当服务器收到一个请求(来自客户端的连接)时,就会实例化一个请求处理程序,并且它的各种处理方法
(handler methods)会在处理请求时被调用。具体调用哪个方法取决于特定的服务器和使用的处理程序类
(handler class),这样可以把他们子类化,使得服务器调用自定义的处理程序集。基本的BaseRequestHandler类
把所有的操作都放到了处理器的一个叫做handler的方法中,这个方法会被服务器调用。然后这个方法就会访问属性
self.request中的客户端套接字。如果使用的是流(如果使用的是TCPServer,这就是可能的),那么可以使用StreamRequestHandler类,
创建了其他两个属性,self.rfile(用于读取)和self.wfile(用于写入)。然后就能使用这些类文件对象和客户机进行通信。




[root@web-db pythonjichu]# cat socketserver.py
#!/usr/local/bin/python


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 connection')
server = TCPServer(('',1234),Handler)
server.serve_forever()
[root@web-db python]# cat client.py
#!/usr/local/bin/python
import socket


s = socket.socket()


host = socket.gethostname()
port = 1234


s.connect((host,port))
print s.recv(1024)




5.处理多连接:
处理多连接的三种方法:分叉(forking)、线程(threading)以及异步I/O(asynchronous I/O)。
通过对SocketServer服务器使用混入类(mix-in class),派生进程和线程很容易处理。


1)分叉:占据资源,并且如果有太多的客户端时分叉不能很好地分叉(尽管如此,对于合理数量的客户端,
分叉在现代UNIX或Linux系统中很高效的,如果有一个多CPU系统,那效率会更高)。
分叉一个进行(一个运行的程序)时,基本是复制了它,并且分叉后的两个进程都从当前的执行点继续运行,
并且每个进程都有自己的内部副本(比如变量)。一个进程(原来的那个)成为了父进程;另一个(复制的)
成为了子进程。分叉操作在时间线(timeline)上创建了一个分支,最后得到了两个独立存在的进程。幸好
进程可以判断哪个是原进程哪个是子进程(通过查看fork函数的返回值)。
在一个使用分叉的服务器中,每一个客户机连接都利用分叉创建了一个子进程。父进程继续监听新的连接,同时子进程处理客户端。
当客户端的请求结束时,子进程就退出了。因为分叉的进程是并行运行的,客户端之间不必互相等待。
[root@web-db pythonjichu]# cat forkserver.py
#!/usr/local/bin/python


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 connection')
server = Server(('',12345),Handler)
server.serve_forever()




因为分叉有点耗费资源(每个分叉出来的进程都需要自己的内存),这就存在了另一个选择:线程。


2)线程是轻量级的进程或子进程,所有的线程都存在于相同的(真正的)进程中,共享内存。资源消耗的下降
伴随着一个缺陷:因为线程共享内存,所以必须确保他们的变量不会冲突,或者是在同一时间修改同一内容,这就会
造成混乱。这些问题都可以归结为同步问题。线程处理能导致同步问题。
在现代操作系统中(除了windows,不支持分叉),分叉实际是很快的,现代的硬件能比以往更好的处理资源消耗。
如果不想被同步问题所困扰,分叉是一个很好的选择。


避免线程和分叉的另外一个方法是转换到Stackless Python(),
一个为了能够在不同的上下文之间快速、方便切换而设计的Python版本。它支持i一个叫做微线程
(microthreads)的类线程的并行形式,微线程比真线程的伸缩性要好。
[root@web-db pythonjichu]# cat threadserver.py
#!/usr/local/bin/python


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 connection')
server = Server(('',12345),Handler)
server.serve_forever()


3)异步I/O在底层的实现有点难度。基本的机制是select模块的select函数,这是非常难处理的。幸好存在更高的
层次处理异步I/O的框架,这就为处理一个强大可伸缩的机制提供了一个简单的、抽象的接口。包含在标准库中的这种
类型的基本框架由asyncore和asynchat模块组成,Twisted是一个非常强大的异步网络编程框架。



带有select和poll的异步I/O
当一个服务器与一个客户端通信时,来自客户端的数据可能是不连续的。如果使用分叉或线程处理,
那就不是问题。当一个程序在等待数据,另一个并行的程序可以继续处理它自己的客户端数据。
另外的处理方法是只处理在给定时间内真正要进行通信的客户端。不需要一直坚挺----只要监听
(或读取)一会,然后把它放到其他客户端的后面。


这是asyncore/asynchat框架和Twisted框架采用的方法,这种功能的基础是select函数,如果poll函数可用,
那也可以是它,这两个函数都来自select模块。这两个函数中,poll的伸缩性更好,但它只能在unix系统中使用
(也就是说,在windows中不可用)。


select函数需要3个序列作为他的必选参数,此外还有一个可选的以秒为单位的超时时间做为第四个参数。
这些序列是文件描述符整数(或者是带有返回这样整数的fileno方法的对象)。这些就是我们等待到鹅连接。
3个序列用于输入、输出以及异常情况(错误以及类似的东西)。如果没有给定超时时间,select会阻塞(也
就是出于等待状态),直到其中的一个文件描述符已经为行动做好了准备;如果给定了超时时间,select最多
阻塞给定的超时时间,如果给定的超时时间是0,那么就给出一个连续的poll(即不阻塞)。select的返回值
是3个序列(也就是一个长度为3的元组),每个代表相应参数的一个活动子集。比如,返回的第一个序列是一个
输入文件描述符的序列,其中有一些可以读取的东西。


序列能包含文件对象(在windows中行不通)或者套接字。下面的代码是一个使用select的为很多连接服务的
服务器(注意:服务器套接字本身被提供给select,这样select就能在准备接受一个新的连接时发出通知)。
服务器是个简单的记录器(logger),他输出(在本地)来自客户机的所有数据。可以使用Telnet(或者写一个
简单的基于套接字的客户机来为他提供数据)连接他来进行测试。


[root@web-db pythonjichu]# cat selectserver.py
#!/usr/local/bin/python


import socket,select


s = socket.socket()


host = socket.gethostname()
port = 12345
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
                        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)
                        else:
                                print data
上述编写的基本套接字服务器是显示的。其中的一些有很清楚的事件循环,用来查找新的连接
和新数据,而基于SocketServer的服务器有一个隐士的循环,在循环中服务器查找连接并为每个
连接创建一个处理程序,但处理程序在要读数据时必须是显示的。Twisted(以及asyncore/asynchat
矿建)使用一个事件甚至多个基于事件的方法。要编写基本的服务器,就要实现处理比如新客户端
连接、新数据到达以及一个客户端断开连接等事件的事件处理程序。具体的类能通过基本类建立
更精练的事件,比如包装“数据到达”事件、收集数据直到新的一行、然后触发“一行数据到达”的事件。


事件处理程序在一个协议(protocol)中定义;在一个新的连接到达时,同样需要一个创建这种协议
对象的工厂(factory),但如果只是想要创建一个通用的协议类的实例,那么就可以使用Twisted
自带的工厂。factory类在twisted.internet.protocol模块中。当编写自己的协议时,要使用和超类
一样的模块中的protocol。得到了一个连接后,事件处理程序connectionMade就会被调用;丢失了一个
连接后,connectionLost就会被调用。来自客户端的数据是通过处理程序dataReceived接收的。当然
不能使用事件处理策略来把数据发回到客户端,如果要实现此功能,可以使用对象self.transport,这个
对象有一个write方法,也有一个包含客户机地址(主机名和端口号)的client属性。


下面的代码是使用Twisted的服务器端代码:实例化factory,还要设置它的protocol属性,这样它在和
客户端通信时就知道使用什么协议(自定义协议)。然后就开始在给定的端口处使用工厂进行监听,
这个工厂要通过实例化协议对象来准备处理连接。程序使用的是reactor中的listenTCP函数来监听,最后
通过调用同一个模块中的run函数启动服务器。
[root@web-db pythonjichu]# cat twistedserver.py
#!/usr/local/bin/python


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()
如果用telnet连接到此服务器并进行测试的话,那么每行可能只输出一个字符取决于缓冲或类似的东西。
当然可以使用sys.stdout.write来代替print。但在很多情况下可能更喜欢每次得到一样,而不是
任意的数据。编写一个处理这种情况的自定义协议很容易,实际上已经有一个县城的类了。
twisted.protocols.basic模块中包含一个有用的预定义协议,时LineReceiver。他实现了dataReceived
并且只要收到了一整行就调用事件处理程序的lineReceived。


如果要在接受数据时做些事情,可以使用由LineReceiver定义的叫做rawDataReceived的事件处理程序。
也可以使用lineReceived,但它依赖于dataReceived的实现LineReceiver。


使用如下代码会看到换行符被去掉了,换句话说,使用print不再提供两倍的换行符。。


[root@web-db pythonjichu]# cat twistedserver.py
#!/usr/local/bin/python


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 lineReceived(self,line):
                print line
factory = Factory()
factory.protocol = SimpleLogger


reactor.listenTCP(1234,factory)
reactor.run()




总结:
套接字是程序(进程)之间进行通信的信息通道,可能会通过网络来通信。socket模块给提供了对客户端和服务器端套接字的低级访问功能。
服务器端套接字会在指定的地址监听客户端的连接,而客户机是直接连接的。


urllib和urllib2,这些模块可以再给出数据源的url时让从不同的服务器读取和下载数据。
urllib模块是一个简单一些的实现,而urllib2是可扩展的,而且很强大。两者都通过urlopen
等简单的函数来工作。


SocketServer框架:是一个同步的网络服务器基类。位于标准库中,使用他可以很容易地编写服务器。
他甚至用CGI支持简单的web服务(http)。如果想同时处理多个连接,可以使用分叉和线程来处理
混入类。


select和poll:这两个函数让你可以考虑一组连接并且能找出已经准备好的读取或写入的连接。
这意味着能为通过时间片轮转来为几个连接提供服务。看起来就像是同时处理几个连接。尽管代码复杂一点,
但在伸缩性和效率上要比线程或分叉要好的多。


Twisted:来自Twisted Maxtrix实验室的框架,支持绝大多数的网络协议,他内容丰富并且很复杂,尽管很庞大,
有的习惯用语却不太容易记,但它的基本用法简单,直观。




Twisted框架是异步的,因此他在伸缩性和效率方面表现的很好。如果能使用Twisted,他可能是很多
自定义网络应用程序的最佳选择。


阅读(1172) | 评论(0) | 转发(0) |
0

上一篇:XML学习连接

下一篇:python程序打包

给主人留下些什么吧!~~