python-socket模块学习

原文地址:http://www.jb51.net/article/19751.htmhtml

 

 

1、网络知识的一些介绍python

 

socket 是网络链接端点。例如当你的Web浏览器请求www.jb51.net上的主页时,你的Web浏览器建立一个socket并命令它去链接 www.jb51.net的Web服务器主机,Web服务器也对来自的请求在一个socket上进行监听。两端使用各自的socket来发送和 接收信息。react

 

在使用的时候,每一个socket都被绑定到一个特定的IP地址和端口。IP地址是一个由4个数组成的序列,这4个数均是范围 0~255中的值(例如,220,176,36,76);端口数值的取值范围是0~65535。端口数小于1024的都是为众所周知的网络服务所保留的 (例如Web服务使用的80端口);最大的保留数被存储在socket模块的IPPORT_RESERVED变量中。你也能够为你的程序使用另外的端口数 值。linux

 

不是全部的IP地址都对世界的其它地方可见。实际上,一些是专门为那些非公共的地址所保留的(好比形如192.168.y.z或10.x.y.z)。地址127.0.0.1是本机地址;它始终指向当前的计算机。程序可使用这个地址来链接运行在同一计算机上的其它程序。web

 

IP地址很差记,你能够花点钱为特定的IP地址注册一个主机名或域名(好比使用www.jb51.net代替222.76.216.16)。域名服务器(DNS)处理名字到IP地址的映射。每一个计算机均可以有一个主机名,即便它没有在官方注册。django

 

多少信息经过一个网络被传送基于许多因素,其中之一就是使用的协议。许多的协议是基于简单的、低级协议以造成一个协议栈。例如HTTP协议,它是用在Web浏览器与Web服务器之间通讯的协议,它是基于TCP协议,而TCP协议又基于IP协议。编程

 

当 在你本身的两个程序间传送信息的时候,你一般选择TCP或UDP协议。TCP协议在两端间创建一个持续的链接,而且你所发送的信息有保证的按顺序到达它们 的目的地。UDP不创建链接,它的速度快但不可靠。你发送的信息也可能到不了另外一端;或它们没有按顺序到达。有时候一个信息的多个复制到达接收端,即便你 只发送了一次。windows

 

2、使用地址和主机名数组

 

socket模块提供了几个函数用于使用主机名和地址来工做。浏览器

 

gethostname()返回运行程序所在的计算机的主机名:

 

>>> import socket
>>> socket.gethostname()
'lenovo'

 

gethostbyname(name) 尝试将给定的主机名解释为一个IP地址。首先将检查当前计算机是否可以解释。若是不能,一个解释请求将发送给一个远程的DNS服务器(远程的DNS服务器 还可能将解释请求转发给另外一个DNS服务器,直到该请求能够被处理)。gethostbyname函数返回这个IP地址或在查找失败后引起一个异常。

 

>>> socket.gethostbyname('lenovo')
'192.168.1.4'
>>> socket.gethostbyname('www.jb51.net')
'222.76.216.16'

 

一个扩展的形式是gethostbyname_ex(name),它返回一个包含三个元素的元组,分别是给定地址的主要的主机名、同一IP地址的可选的主机名的一个列表、关于同一主机的同一接口的其它IP地址的一个列表(列表可能都是空的)。

 

>>> socket.gethostbyname('www.163.com')
'60.191.81.49'
>>> socket.gethostbyname_ex('www.163.com')
('www.cache.split.netease.com', ['www.163.com'], ['60.191.81.48', '60.191.81.49
, '60.191.81.50', '60.191.81.51', '60.191.81.52', '60.191.81.53', '60.191.81.54
, '220.181.28.50', '220.181.28.51', '220.181.28.52', '220.181.28.53', '220.181.
8.54', '220.181.31.182', '220.181.31.183', '220.181.31.184'])

 

gethostbyaddr(address)函数的做用与gethostbyname_ex相同,只是你提供给它的参数是一个IP地址字符串:

 

>>> socket.gethostbyaddr('202.165.102.205')
('homepage.vip.cnb.yahoo.com', ['www.yahoo.com.cn'], ['202.165.102.205'])

 

getservbyname(service,protocol)函数要求一个服务名(如'telnet'或'ftp')和一个协议(如'tcp'或'udp'),返回服务所使用的端口号:

 

>>>socket.getservbyname('http','tcp')
80
>>>socket.getservbyname('telnet','tcp)
23

 

一般,非Python程序以32位字节包的形式存储和使用IP地址。inet_aton(ip_addr)和inet_ntoa(packed)函数在这个形式和IP地址间做转换:

 

>>> socket.inet_aton('222.76.216.16')
'\xdeL\xd8\x10'
>>> socket.inet_ntoa('\xdeL\xd8\x10')
'222.76.216.16'

 

socket 也定义了一些变量来表明保留的IP地址。INADDR_ANY和INADDR_BROADCAST是被保留的IP地址分别表明任意IP地址和广播地 址;INADDR_LOOPBACK表明loopback设备,老是地址127.0.0.1。这些变量是32位字节数字形式的。

 

getfqdn([name])函数返回关于给定主机名的全域名(若是省略,则返回本机的全域名)。

 

3、使用低级的socket通讯

 

尽管Python提供了一些封装,使得使用socket更容易,可是你也能够直接使用socket来工做。

一、建立和销毁socket

 

socket 模块中的socket(family,type[,proto])函数建立一个新的socket对象。family的取值一般是AF_INET。type 的取值一般是SOCK_STREAM(用于定向的链接,可靠的TCP链接)或SOCK_DGRAM(用于UDP):

 

>>> from socket import *
>>> s=socket(AF_INET,SOCK_STREAM)

 

family和type参数暗指了一个协议,可是你可使用socket的第三个可选的参数(proto的取值如IPPROTO_TCP或IPPROTO_RAW)来指定所使用的协议。代替使用IPPROTO_XX变量,你可使用函数getprotobyname:

 

>>> getprotobyname('tcp')
6
>>> IPPROTO_TCP
6

 

fromfd(fd,type[,proto]) 是一个不多被使用的函数,它用来从打开的一个文件描述符建立一个socket对象(文件描述符由文件的fileno()方法返回)。文件描述符与一个真实 的socket链接,而非一个文件。socket对象的fileno()方法返回关于这个socket的文件描述符。

 

当你使用完工 socket对象时,你应调用close()方法显式的关闭socket以尽快释放资源(尽管socket被垃圾回收器回收时将自动被关闭)。另外,你也 可使用shutdown(how)方法来关闭链接一边或两边。参数0阻止socket接收数据,1阻止发送,2阻止接收和发送。

 

二、链接socket

 

当 两个socket链接时(例如使用TCP),一端监听和接收进来的链接,而另外一端发起链接。临听端建立一个socket,调用bind(address) 函数去绑定一个特定的地址和端口,调用listen(backlog)来临听进来的链接,最后调用accept()来接收这个新的,进来的链接,下面是在 服务器端的代码:

 

>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.bind(('127.0.0.1',44444))
>>> s.listen(1)
>>> q,v=s.accept() #返回socket q和地址v

 

注意:上面的代码将一直处于等待直到链接被创建。下面咱们再打开另外一个Python解释器,用做客户端;而后键入以下代码:
>>> from socket import *
>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.connect(('127.0.0.1',44444) #发起链接

 

好了,咱们验证一下链接是否创建了。咱们在服务器端键入如下代码来发送一条信息:

 

>>> q.send('hello,i come from pythontik.com') 注:有时可能出现send() argument 1 must be string or buffer,not str 错误,缘由多是您的机器不支持UTF-8字符集,临时解决方案是q.send(b' hello...')
31 #发送的字节数

 

在客户端键入如下代码来接收信息:
>>> s.recv(1024)
'hello,i come from pythontik.com'

 

你 传递给bind和connect的地址是一个关于AF_INET的socket的元组(ipAddress,port)。代替connect,你也能够调 用connect_ex(address)方法。若是背后对C的connect的调用返回一个错误,那么connect_ex也将返回一个错误(不然返回 0表明成功),代替引起一个异常。

 

当你调用listen时,你给了它一个参数,这个数值表示在等待队列中容许放置的进来的链接总数。当等待队列已满时,若是有更多的链接到达,那么远程端将被告知链接被拒绝。在socket模块中的SOMAXCONN变量代表了等待队列所能容纳的最大量。

 

accept()方法返回形如bind和connect的一个地址,表明远程socket的地址。下面显示变量v的值:

 

>>> v
('127.0.0.1', 1334)

 

UDP是不定向的链接,可是你仍然可使用给定的目的地址和端口来调用connect去关联一个socket。

 

三、发送和接收数据

 

函 数send(string[,flags])发送给定的字符串到远程socket。sendto(string[,flags],address)发送给 定的字符串到一个特定的地址。一般,send方法用于可靠链接的socket,sendto方法用于不可靠链接的socket,可是若是你在一个 UDP socket上调用connect来使它与一个特定的目标创建联系,那么这时你也可使用send方法来代替sendto。

 

send和sendto都返回实际发送的字节数。当你快速发送大量的数据的时候,你可能想去确保所有信息已被发送,那么你可使用以下的一个函数:

 

def safeSend(sock,msg):
sent=0
while msg:
i=sock.send(msg)
if i==-1: #发生了错误
return -1
sent+=i
msg=msg[i:]
time.sleep(25)
return sent

 

recv(bufsize[,flags]) 方法接收一个进来的消息。若是有大量的数据在等待,它只返回前面的bufsize字节数的数据。recvfrom(bufsize[,flags])作同 样的事,除了它使用AF_INET socket的返回值是(data,(ipAddress,port)),这便于你知道消息来自哪儿(这对于非链接的 socket是有用的)。

 

send,sendto,recv和recvfrom方法都有一个可选的参数flags,默认值为0。你能够经过对socket.MSG_*变量进行组合(按位或)来创建flags的值。这些值因平台而有所不一样,可是最通用的值以下所示:

 

MSG_OOB:处理带外数据(既TCP紧急数据)。
MSG_DONTROUTE:不使用路由表;直接发送到接口。
MSG_PEEK:返回等待的数据且不把它们从队列中删除。

 

例如,若是你有一个打开的socket,它有一个消息等待被接收,你能够接收这个消息后并不把它从进来的数据的队列中删除:

 

>>> q.recv(1024,MSG_PEEK)
'hello'
>>> q.recv(1024,MSG_PEEK) #由于没有删除,因此你能够再获得它。
'hello'

 

makefile([mode[,bufsize]]) 方法返回一个文件类对象,其中封装了socket,以便于你之后将它传递给要求参数为一个文件的代码(或许你喜欢使用文件的方法来代替send和 recv)。这个可选的mode和bufsize参数的取值和内建的open函数同样。

 

四、使用socket选项

 

socket对象的getpeername()和 getsockname()方法都返回包含一个IP地址和端口的二元组(这个二元组的形式就像你传递给connect和bind的)。 getpeername返回所链接的远程socket的地址和端口,getsockname返回关于本地socket的相同信息。

 

在默认 状况下,socket是阻塞式的,意思就是socket的方法的调用在任务完成以前是不会返回的。例如,若是存储向外发送的数据的缓存已满,你又企图发送 更多的数据,那么你对send的调用将被阻塞直到它可以将更多的数据放入缓存。你能够经过调用setblocking(flag)方法(其中flag取值 是0,setblocking(0))来改变这个默认行为,以使socket为非阻塞式。当socket为非阻塞式的时候,若是所作的动做将致使阻塞,将 会引发error异常。下面一段代码将试图不断地接受新的链接并使用函数processRequest来处理。若是一个新链接无效,它将间隔半秒再试。另 一方法是在你的监听socket上调用select或poll来检测一个新的链接的到达。

 

别的socket的选项可使用 setsockopt(level,name,value)和getsockopt(level,name[,buflen])方法来设置和获取。 socket表明了一个协议栈的不一样层,level参数指定了选项应用于哪一层。level的取值以SOL_开头(SOL_SOCKET,SOL_TCP 等等)。name代表你涉及的是哪一个选项。对于value,若是该选项要求数值的值,value只能传入数字值。你也能够传递入一个缓存(一个字符串), 但你必须使用正确的格式。对getsockopt,不指定buflen参数意味你要求一个数字值,并返回这个值。若是你提供了 buflen,getsockopt返回表明一个缓存的字符串,它的最大长度是buflen的字节数。下面的例子设置了一个socket的用于发送的缓存 尺寸为64KB:

 

>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.setsockopt(SOL_SOCKET,SO_SNDBUF,65535)

 

要获得一个包在被路由丢弃前所能有的生命周期(TTL)和跳数,你可使用以下代码:

 

>>> s.getsockopt(SOL_IP,IP_TTL)
32

 

五、数值转换

 

因为不一样平台的字节顺序不同,因此当在网络中传输数据时咱们使用标准的网络字节顺序。nthol(x)和ntohs(x)函数要求一个网络字节顺序的数值并把它转换为当前主机字节顺序的相同数值,而htonl(x)和htons(x)则相反:

 

>>> import.socket
>>> socket.htons(20000) #转换为一个16位的值
8270
>>> socket.htonl(20000) #转换为一个32位的值
541982720
>>> socket.ntohl(541982720)
20000

 

 

使用SocketServers

 

SocketServers模块为一组socket服务类定义了一个基类,这组类压缩和隐藏了监听、接受和处理进入的socket链接的细节。

 

一、SocketServers家族
TCPServer和UDPServer都是SocketServer的子类,它们分别处理TCP和UDP信息。
注意:SocketServer也提供UnixStreamServer(TCPServer的子类)和UNIXdatagramServer(UDPServer的子类),它们都如同其父类同样除了在建立监听socket时使用AF_UNIX代替了AF_INET。

 

默 认状况下,socket服务一次处理一个链接,可是你可使用ThreadingMixIN和ForkingMixIn类来建立任一 SocketServer的线程和子进程。实际上,SocketServer模块提供了一些对些有用的类来解决你的麻烦,它们 是:ForkingUDPServer、ForkingTCPServer、ThreadingUDPServer、 ThreadingTCPServer、ThreadingUnixStreamServer和 ThreadingUnixDatagramServer。

 

SocketServer以一般的方法处理进入的链接;要使它更有用,你应该 提供你本身的请求处理器类给它以便它传递一个socket去处理。SocketServer模块中的BaseRequestHandler类是全部请求处 理器的父类。假设,例如你须要写一个多线程的电子邮件服务器,首先你要建立一个MailRequestHandler,它是 BaseRequestHandler的子类,而后把它传递给一个新建立的SocketServer:
import SocketServer
...#建立你的MailRequestHandler
addr=('220.172.20.6',25) #监听的地址和端口
server=SocketServer.ThreadingTCPServer(addr,MailRequestHandler)
server.serve_forever()

 

每 次一个新的链接到来时,这个server建立一个新的MailRequestHandler实例并调用它的handle()方法来处理这个新的请求。由于 server继承自ThreadingTCPServer,对于每一个新的请求它都启动一个单独的线程来处理这个请求,以便于多个请求可以被同时处理。若是 用handle_request()代替server_forever,它将一个一个的处理链接请求。server_forever 只是反复调用 handle_request而已。

 

通常来讲,你只需使用socket服务之一,可是若是你须要建立你本身的子类的话,你能够覆盖咱们下面提到的方法来定制它。

 

当 服务被第一次建立的时候,__init__函数调用server_bind()方法来绑定监听socket(self.socket)到正确的地址 (self.server_address)。而后调用server_activate()来激活这个服务(默认状况下,调用socket的listen 方法)。

 

这个socket服务不作任何事情直到调用了handle_request或serve_forever方法。 handle_request调用get_request()去等待和接收一个新的socket链接,而后调用 verify_request(request,client_address)去看服务是否会处理这个链接(你能够在访问控制中使用这个,默认状况下 verify_request老是返回true)。若是会处理这个请求,handle_request而后调用 process_request(request,client_address),若是 process_request(request,client_address)致使一个异常的话,将调用 handle_error(request,client_address)。默认状况下,process_request简单地调用 finish_request(request,client_address);子进程和线程类覆盖了这个行为去开始一新的进程或线程,而后调用 finish_request。finish_request实例化一个新的请求处理器,请求处理器轮流调用它们的handle()方法。

 

当SocketServer建立一个新的请求处理器时,它传递给这个处理器的__init__函数的self变量,以便于这个处理器可以访问关于这个服务的信息。

 

SocketServer 的fileno()方法返回监听socket的文件描述符。address_family成员变量指定了监听socket的socket族(如 AF_INET),server_address包含了监听socket被绑定到的地址。socket变量包含监听socket自身。

 

二、请求处理器

 

请 求处理器有setup()、handle()和finish()方法,你能够覆盖它们来定制你本身的行为。通常状况下,你只须要覆盖handle方法。 BaseRequestHandler的__init__函数调用setup()方法来作初始化的工做,handle()服务于请求,finish()用 于执行清理工做,若是handle或setup致使一个异常,finish不会被调用。记住,你的请求处理器会为每一个请求建立一个新的实例。

 

request 成员变量有关于流(TCP)服务的最近接受的socket;对于数据报服务,它是一个包含进入消息和监听socket的元组。 client_address包含发送者的地址,server有对SocketServer的一个引用(经过这你能够访问它的成员,如 server_address)。

 

下面的例子实现了一个EchoRequestHandler,这做为一个服务端它将客户端所发送的数据再发送回客户端:

 

>>> import SocketServer
>>> class EchoRequestHandler(SocketServer.BaseRequestHandler):
... def handle(self):
... print 'Got new connection!'
... while 1:
... mesg=self.request.recv(1024)
... if not msg:
... break
... print 'Received:',msg
... self.request.send(msg)
... print 'Done with connection'
>>> server=SocketServer.ThreadingTCPServer(('127.0.0.1',12321),EchoReuestHandler)
>>> server.handle_request() #执行后将等待链接
Got new connection!
Received: Hello!
Received: I like Tuesdays!
Done with connection

 

打开另外一个Python解释器做为客户端,而后执行以下代码:

 

>>> from socket import *
>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.connect(('120.0.0.1',12321))
>>> s.send('Hello!')
6
>>> print s.recv(1024)
Hello!
>>> s.send('I like Tuesdays!')
16
>>> print s.recv(1024)
I like Tuesdays!
>>> s.close()

 

SocketServer 模块也定义了BaseRequestHandler的两个子类:StreamRequestHandler和 DatagramRequestHandler。它们覆盖了setup和finish方法并建立了两个文件对象rfile和wfile,你能够用这两个文 件对象来向客户端读写数据,从而代替使用socket方法。

 

 

socket的阻塞或同步编程

1、使用socket

 

网 络编程中最基本的部分就是socket(套接字)。socket有两种:服务端socket和客户端 socket。在你建立了一个服务端socket之 后,你告诉它去等待链接。而后它将监听某个网络地址(形如:xxx.xxx.xxx.xxx:xxx) 直到客户端链接。而后这两端就能够通讯了。

 

处理客户端socket一般比处理服务端socket要容易一点,由于服务端必须时刻准备处理来自客户端的链接,而且它必须处理多个链接,而客户端只须要简单的链接,而后作点什么,而后断开链接。

 

实 例化一个socket时,能够指定三个参数:地址系列(默认为socket.AF_INET)、流socket(这是个默认 值: socket.SOCK_STREAM)或数据报socket(socket.SOCK_DGRAM)、协议(默认值是0)。对于简单的 socket,你能够不指定任何参数而所有使用默认值。

 

服务端socket在使用bind方法以后调用listen方法去监听一个给定的 地址。而后,客户端socket就能够经过使用connect方法(connect方法所使用的地址参数与bind相同)去链接服务端。listen方法 要求一个参数,这个参数就是等待链接队列中所能包含的链接数。

 

一旦服务端socket调用了listen方法,就进入了临听状态,而后通 常使用一个无限的循环:一、开始接受客房端的链接,这经过调用accept方法来实现。调用了这个方法后将处于阻塞状态(等待客户端发起链接)直到一个客 户端链接,链接后,accept返回形如(client,address)的一个元组,其中client是一个用于与客户端通讯的 socket,address是客户端的形如xxx.xxx.xxx.xxx:xxx的地址;二、而后服务端处理客户端的请求;三、处理完成以后又调用 1。

 

关于传输数据,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)

 

注意:若是你使用Ctrl-C来中止服务端的话,若是再次使用相同的端口可能须要等待一下子。

 

2、使用SocketServer

 

SocketServer模块简单化了编写网络服务器的工做。
它提供了四个基本的服务类:TCPServer(使用TCP协议)、UDPServer(使用数据报)、UnixStreamServer、

 

UnixDatagramServer。UnixStreamServer和UnixDatagramServer用于类Unix平台。
这四个类处理请求都使用同步的方法,也就是说,在下一个请求处理开始以前当前的请求处理必须已完成

 

 

用SocketServer建立一个服务器须要四步:

 

一、经过子类化BaseRequestHandler类和覆盖它的handle()方法来建立一个请求处理器类,用于处理进来

 

的请求;
二、实例化服务类如TCPServer,并传递给它参数:服务器地址和请求处理器类;
三、调用服务实例对象的handle_request()或serve_forever()方法去处理请求。

 

下面使用SocketServer用同步的方法写一个最简单的服务器:

 

from SocketServer import TCPServer, StreamRequestHandler
#第一步。其中StreamRequestHandler类是BaseRequestHandler类的子类,它为流socket定义了
#rfile和wfile方法
class Handler(StreamRequestHandler):
def handle(self):
addr = self.request.getpeername()
print 'Got connection from', addr
self.wfile.write('Thank you for connecting')

 

#第二步。其中''表明运行服务器的主机
server = TCPServer(('', 1234), Handler)
#第三步。serve_forever()致使进入循环状态
server.serve_forever()

 

注意:使用阻塞或同步的方法一次只能链接一个客户端,处理完成后才能链接下一个客户端。

 

 

非阻塞或异步编程

例如,对于一个聊天室来讲,由于有多个链接须要同时被处理,因此很显然,阻塞或同步的方法是不合适的,这就像买票只开了一个窗口,佷多人排队等同样。那么咱们如何解决这个问题呢?主要有三种方法:forking、threading、异步I/O。

Forking和threading的方法很是简单,经过使用SocketServer服务类的min-in类就能够实现。forking只适用于类Unix平台;threading须要注意内存共享的问题。
异步I/O若是底层的方法来实现是有点困难的。要简单点,咱们能够考虑使用标准库中的框架或Twisted(Twisted是一个很是强大的异步网络编程的框架)。

一、用ScoketServer实现Forking和threading

下面咱们使用两个例子来分别建立forking服务器和threading服务器。

Forking 服务器:

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')

server = Server(('', 1234), Handler)
server.serve_forever()

threading服务器:

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')

server = Server(('', 1234), Handler)
server.serve_forever()

二、使用select实现异步I/O

所谓异步I/O,打个比方,就是若是一大群人都想你听他说话,那么你就给他们每人一分钟的时间说,你们轮流说,没说完的待会儿轮到时再继续说。也就是一个时间片的方法。

要实现异步I/O,咱们能够经过使用框架asyncore/asynchat或Twisted,它们都是基于select函数或poll函数(poll只适于类Unix系统)的。select和poll函数都来自select模块。

select 函数要求三个必须序列做为参数和一个可选的以秒为单位的超时值。序列中是表示文件描述符的整数值,它们是咱们要等待的链接。这三个序列是关于输入、输出和 异常条件的。若是超时值没有给出的话,select将处于阻塞状态(也就是等待)直到有文件描述符准备动做。若是超时值给出了,那么select只阻塞给 定的时间。若是超时值是0的话,那么将不阻塞。select返回的值是一个由三个序列组成的元组,它们分别表明相应参数的活动的子集。例如,第一个序列返 回的是用于读的输入文件描述符构成的序列。

序列能够包含文件对象(不适于Windows)或socket。下面这个例子建立一个使用 select去服务几个链接的服务器(注意:服务端的socket自身也提供给了select,以便于它可以在有新的链接准备接受时发出信号通知)。这个 服务器只是简单地打印接受自客户端的数据。你可使用telnet(或写一个基于socket的简单的客户端)来链接测试它。

select server

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
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


三、Twisted

Twisted 是针对Python的一个事件驱动的网络框架,最初是为了网络游戏而开发的,可是如今被应用于各种网络软件。用Twisted,你能够实现事件处理器,非 常相似用GUI工具包(Tk, GTK, Qt, wxWidgets)。这部分我将介绍一些基本的概念和演示如何使用Twisted来作一些相对简单的 网络编程。Twisted是很是强大的框架并提供了大量的支持,如:Web服务器和客户端、 SSH2, SMTP, POP3, IMAP4, AIM, ICQ, IRC, MSN,Jabber, NNTP, DNS等等。

早先咱们所写的基于socket的服务器,它们都有一个显示的事件循环:寻找新的链接和新的数据;基于SocketServer的服务器有一个隐含的循环:寻找链接和为链接建立处理器。但时处理器仍然时显示的读数据。

而 Twisted使用了更多的基于事件的方式。要写一个基本的服务器,你要实现事件处理器,它处理诸如一个新的客户端链接、新的数据到达和客户端链接中断等 状况。在Twisted中,你的事件处理器定义在一个protocol中;你也须要一个factory,当一个新的链接到达时它可以构造这个 protocol对象,可是若是你仅仅想建立一个自定义的Protocol类的实例的话,你可使用来自Twisted的factory,Factory 类在模块twisted.internet.protocol中。当你写你的protocol时,使用 twisted.internet.protocol模块中的Protocol做为你的父类。当你获得一个链接时,事件处理器 connectionMade被调用;当你丢失了一个链接时,connectionLost被调用。从客户端接受数据使用处理器 dataReceived。可是你不能使用事件处理策略向客户端发送数据;要向客户端发送数据,你可使用self.transport,它有一个 write方法。它也有一个client属性,其中包含了客户端的地址(主机名和端口)。

下面这个例子是一个Twisted版的服务器。 其中实例化了Factory并设置了它的protocol属性以便它知道使用哪一个protocol与客户端通讯(这就是所谓的你的自定义 protocol)。而后你使用factory开始监听指定的端口,factory经过实例化的protocol对象处理链接。监听使用reactor模 块中的listenTCP函数。最后,你经过调用reactor模块中的run函数来开始服务器。

from twisted.internet import reactor
from twisted.internet.protocol import Protocol, Factory

# 定义你Protocol类
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()

# 设置factory的protocol属性以便它知道使用哪一个protocol与客户端通讯(这就是所谓的你的自定义
# protocol)

factory.protocol = SimpleLogger

# 监听指定的端口

reactor.listenTCP(1234, factory)

# 开始运行主程序
reactor.run()

为 你的处理目的而写一个自定义的protocol是很容易的。模块twisted.protocols.basic中包含了几个有用的已存在的 protocol,其中的LineReceiver执行dataReceived并在接受到了一个完整的行时调用事件处理器lineReceived。如 果当你在接受数据时除了使用lineReceived,还要作些别的,那么你可使用LineReceiver定义的名为rawDataReceived 事件处理器。下面是一使用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 lineReceived(self, line):
print line

factory = Factory()
factory.protocol = SimpleLogger
reactor.listenTCP(1234, factory)
reactor.run()

urllib和urllib2 (Python3中已经合并成了urllib库)

urllib 和urllib2的工做大同小异,它们让你可以经过网络访问文件,就像访问本身电脑上的同样。经过简单的函数调用,URL所定位的资源就能够被你做为输入 使用到你的程序中。若是再配以re模块,那么你就可以下载Web页面、提取信息、自动建立你所寻找的东西的报告。

urllib2更流行一些。对于简单的下载任务,urllib比较好。若是你须要HTTP验证或cookies,或你想写一些扩展去处理你本身的协议的话,那么urllib2是正确的选择。

1、打开远程文件

打开远程文件的操做和本地差很少,不一样的是只能使用读模式,而且使用urllib模块的urlopen:

>>> from urllib import urlopen
>>> webpage=urlopen('http://www.python.org')

若是你在线的话,变量webpage如今就包含了一个关联Web页:http://www.python.org的文件类对象。
注意:若是你当前没有联网,而你又想练习一下urllib的话,你能够用以下形式访问本地文件:
localpage=urlopen(r'file:c:\test.txt')

由urlopen返回的文件类对象支持close,read,readline,readlines等方法。

下面的代码抽取出了Python官方主页中“Documentation”连接的URL:

>>> import re
>>> text = webpage.read()
>>> m = re.search('<a href="([^"]+)">Documentation</a>', text, re.IGNORECASE)
>>> m.group(1)
'http://docs.python.org/'

2、获取远程文件

urlopen 函数给你一个文件类对象,你能够读取它。若是你使用urlib时只关心下载文件并存储一个复本到本地文件的话,你可使用urlretrieve替而代 之。urlretrieve返回一个元组(filename, headers),filename是本地文件(复本)的名字(它由urllib自动创 建),headers包含关于远程文件的一些信息。
若是你想为复本指定一个名字的话,你能够提供第二个参数:
urlretrieve('http://www.python.org', 'C:\\python_webpage.html')
这 将获取Python官方主页并存储到本地C:\python_webpage.html中。若是你不指定复本的文件名,那么文件将放到一个临时的地方,你 可以使用open函数打开它,若是你要清除这些临时的复本,你能够调用urlcleanup函数而不带任何参数,它将为你完成清除工做。

 

套接字1、套接字 套接字是为特定网络协议(例如TCP/IP,ICMP/IP,UDP/IP等)套件对上的网络应用程序提供者提供当前可移植标准的对象。它们容许程序接受并进行链接,如发送和接受数据。为了创建通讯通道,网络通讯的每一个端点拥有一个套接字对象极为重要。 套接字为BSD UNIX系统核心的一部分,并且他们也被许多其余相似UNIX的操做系统包括Linux所采纳。许多非BSD UNIX系统(如ms-dos,windows,os/2,mac os及大部分主机环境)都以库形式提供对套接字的支持。 三种最流行的套接字类型是:stream,datagram和raw。stream和datagram套接字能够直接与TCP协议进行接口,而raw套接字则接口到IP协议。但套接字并不限于TCP/IP。 2、套接字模块 套接字模块是一个很是简单的基于对象的接口,它提供对低层BSD套接字样式网络的访问。使用该模块能够实现客户机和服务器套接字。要在python 中创建具备TCP和流套接字的简单服务器,须要使用socket模块。利用该模块包含的函数和类定义,可生成经过网络通讯的程序。通常来讲,创建服务器链接须要六个步骤。 第1步是建立socket对象。调用socket构造函数。 socket=socket.socket(familly,type) family的值能够是AF_UNIX(Unix域,用于同一台机器上的进程间通信),也能够是AF_INET(对于IPV4协议的TCP和 UDP),至于type参数,SOCK_STREAM(流套接字)或者 SOCK_DGRAM(数据报文套接字),SOCK_RAW(raw套接字)。 第2步则是将socket绑定(指派)到指定地址上,socket.bind(address) address必须是一个双元素元组,((host,port)),主机名或者ip地址+端口号。若是端口号正在被使用或者保留,或者主机名或ip地址错误,则引起socke.error异常。 第3步,绑定后,必须准备好套接字,以便接受链接请求。 socket.listen(backlog) backlog指定了最多链接数,至少为1,接到链接请求后,这些请求必须排队,若是队列已满,则拒绝请求。 第4步,服务器套接字经过socket的accept方法等待客户请求一个链接: connection,address=socket.accept() 调用accept方法时,socket会进入'waiting'(或阻塞)状态。客户请求链接时,方法创建链接并返回服务器。accept方法返回一个含有俩个元素的元组,形如(connection,address)。第一个元素(connection)是新的socket对象,服务器经过它与客户通讯;第二个元素(address)是客户的internet地址。 第5步是处理阶段,服务器和客户经过send和recv方法通讯(传输数据)。服务器调用send,并采用字符串形式向客户发送信息。send方法返回已发送的字符个数。服务器使用recv方法从客户接受信息。调用recv时,必须指定一个整数来控制本次调用所接受的最大数据量。recv方法在接受数据时会进入'blocket'状态,最后返回一个字符串,用它来表示收到的数据。若是发送的量超过recv所容许,数据会被截断。多余的数据将缓冲于接受端。之后调用recv时,多余的数据会从缓冲区删除。 第6步,传输结束,服务器调用socket的close方法以关闭链接。 创建一个简单客户链接则须要4个步骤。 第1步,建立一个socket以链接服务器 socket=socket.socket(family,type) 第2步,使用socket的connect方法链接服务器 socket.connect((host,port)) 第3步,客户和服务器经过send和recv方法通讯。 第4步,结束后,客户经过调用socket的close方法来关闭链接。 3、一个简单的服务器和客户端通讯的例子 服务器: import socket s=socket.socket() s.bind(('xxx.xxx.xxx.xxx',xxxx)) #ip地址和端口号 s.listen(5) cs,address = s.accept() print 'got connected from',address cs.send('byebye') ra=cs.recv(512) print ra cs.close() 客户端: import socket s=socket.socket() s.connect(('xxx.xxx.xxx.xxx',xxxx)) #与服务器程序ip地址和端口号相同 data=s.recv(512) s.send('hihi') s.close() print 'the data received is',data 运行: 在本机测试(windows环境下,能够将ip地址改成本机ip,端口号在1024以上,windows将1024如下的为保留),运行--CMD--进入命令行模式 先python 服务器程序,后python 客户端程序便可。 或者启动服务器程序后,用telnet ip地址 端口号,也能够获得一样结果。 -------------------------------------------------------------------------------- 让server持续接受链接 server.py import socket s=socket.socket() s.bind(('192.168.43.137',2000)) s.listen(5) while 1: cs,address = s.accept() print 'got connected from',address cs.send('hello I am server,welcome') ra=cs.recv(512) print ra cs.close() 测试两个一个程序中两个socket并存是否可行 client.py import socket s=socket.socket() s.connect(('192.168.43.137',2000)) data=s.recv(512) print 'the data received is\n ',data s.send('hihi I am client') sock2 = socket.socket() sock2.connect(('192.168.43.137',2000)) data2=sock2.recv(512) print 'the data received from server is\n ',data2 sock2.send('client send use sock2') sock2.close() s.close() 网络编程框架2009年04月12日 星期日 上午 10:39twisted是python里面公认的很牛的网络编程框架。学python网络编程的若是不学twisted,估计也就只能算是了解python网络编 程吧,就如同开发网站要用django是同样的,两者都是python下有名的框架。twisted是基于单线程的事件驱动的网络引擎。关于它的学习资料 比较少,并且中文的就更少了,因此学习twisted必定要硬着头皮看英文文档,也就是它的twisted documentation,在这里基本能够找到你所须要的全部基础知识。尤为是core documentation 和example里面都讲了不少示例,这些示例若是都统统的运行一遍,那么你的twisted已经能够算入门了。 我主要是用twisted的工厂和协议框架编写了一个内部的内容分发网络的Tracker服务器,不是基于标准bt协议的,若是要学习,最好仍是按照标准BT协议。前面也给了网址。至于如何使用twisted,我会在后续文章详细介绍。 本文先介绍twisted的两种工做方式,reactor 和 application方式。 The reactor is the core of the event loop within Twisted -- the loop which drives applications using Twisted. The reactor provides basic interfaces to a number of services, including network communications, threading, and event dispatching. reactor是twisted事件循环的核心,它提供了一些服务的基本接口,像网络通讯、线程和事件的分发。 详细的关于reactor的介绍见twisted core documentation里面的Low-Level Twisted一章的第一节Reactor Overview.里面详细介绍了各类reactor的安装和使用。 我所知道的reactor有如下几个 reactor platform Usage IOCPReactor win32 from twisted.internet import iocpreactor iocpreactor.reactor.install() from twisted.internet import reactor selectReactor win32, posix from twisted.internet import reactor pollReactor posix from twisted.internet import pollreactor pollreactor.install() from twisted.internet import reactor epollReactor linux2.6 from twisted.internet import epollreactor epollreactor.install() from twisted.internet import reactor kqueueReactor BSD系列 from twisted.internet import kqreactor kqreactor.install() from twisted.internet import reactor 以上几种就是使用最多的几种reactor了,除了kqueueReactor我没有使用过之外,其余的都使用过了。都能正常工做。建议编程序的时候实现根据不一样的平台选择最佳的reactor。 系统默认使用的是selectreactor。 下面给出一个小例子: from twisted.internet.protocol import Protocol, Factory from twisted.internet import reactor ### Protocol Implementation # This is just about the simplest possible protocol class Echo(Protocol): def dataReceived(self, data): """As soon as any data is received, write it back.""" self.transport.write(data) def main(): f = Factory() f.protocol = Echo reactor.listenTCP(8000, f) reactor.run() if __name__ == '__main__': main()

相关文章
相关标签/搜索