一、网络知识的一些介绍
socket 是网络连接端点。例如当你的Web浏览器请求www.jb51.net上的主页时,你的Web浏览器创建一个socket并命令它去连接 www.jb51.net的Web服务器主机,Web服务器也对来自的请求在一个socket上进行监听。两端使用各自的socket来发送和 接收信息。
在使用的时候,每个socket都被绑定到一个特定的IP地址和端口。IP地址是一个由4个数组成的序列,这4个数均是范围 0~255中的值(例如,220,176,36,76);端口数值的取值范围是0~65535。端口数小于1024的都是为众所周知的网络服务所保留的 (例如Web服务使用的80端口);最大的保留数被存储在socket模块的IPPORT_RESERVED变量中。你也可以为你的程序使用另外的端口数 值。
不是所有的IP地址都对世界的其它地方可见。实际上,一些是专门为那些非公共的地址所保留的(比如形如192.168.y.z或10.x.y.z)。地址127.0.0.1是本机地址;它始终指向当前的计算机。程序可以使用这个地址来连接运行在同一计算机上的其它程序。
IP地址不好记,你可以花点钱为特定的IP地址注册一个主机名或域名(比如使用www.jb51.net代替222.76.216.16)。域名服务器(DNS)处理名字到IP地址的映射。每个计算机都可以有一个主机名,即使它没有在官方注册。
多少信息通过一个网络被传送基于许多因素,其中之一就是使用的协议。许多的协议是基于简单的、低级协议以形成一个协议栈。例如HTTP协议,它是用在Web浏览器与Web服务器之间通信的协议,它是基于TCP协议,而TCP协议又基于IP协议。
当 在你自己的两个程序间传送信息的时候,你通常选择TCP或UDP协议。TCP协议在两端间建立一个持续的连接,并且你所发送的信息有保证的按顺序到达它们 的目的地。UDP不建立连接,它的速度快但不可靠。你发送的信息也可能到不了另一端;或它们没有按顺序到达。有时候一个信息的多个复制到达接收端,即使你 只发送了一次。
二、使用地址和主机名
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')
'xdeLxd8x10'
>>> socket.inet_ntoa('xdeLxd8x10')
'222.76.216.16'
socket 也定义了一些变量来代表保留的IP地址。INADDR_ANY和INADDR_BROADCAST是被保留的IP地址分别代表任意IP地址和广播地 址;INADDR_LOOPBACK代表loopback设备,总是地址127.0.0.1。这些变量是32位字节数字形式的。
getfqdn([name])函数返回关于给定主机名的全域名(如果省略,则返回本机的全域名)。
三、使用低级的socket通信
尽管Python提供了一些封装,使得使用socket更容易,但是你也可以直接使用socket来工作。
1、创建和销毁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阻止接收和发送。
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。
3、发送和接收数据
函 数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函数一样。
4、使用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
5、数值转换
由于不同平台的字节顺序不一样,所以当在网络中传输数据时我们使用标准的网络字节顺序。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连接的细节。
1、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自身。
2、请求处理器
请 求处理器有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的阻塞或同步编程
一、使用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方法,就进入了临听状态,然后通 常使用一个无限的循环:1、开始接受客房端的连接,这通过调用accept方法来实现。调用了这个方法后将处于阻塞状态(等待客户端发起连接)直到一个客 户端连接,连接后,accept返回形如(client,address)的一个元组,其中client是一个用于与客户端通信的 socket,address是客户端的形如xxx.xxx.xxx.xxx:xxx的地址;2、然后服务端处理客户端的请求;3、处理完成之后又调用 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来停止服务端的话,如果再次使用相同的端口可能需要等待一会儿。
二、使用SocketServer
SocketServer模块简单化了编写网络服务器的工作。
它提供了四个基本的服务类:TCPServer(使用TCP协议)、UDPServer(使用数据报)、UnixStreamServer、
UnixDatagramServer。UnixStreamServer和UnixDatagramServer用于类Unix平台。
这四个类处理请求都使用同步的方法,也就是说,在下一个请求处理开始之前当前的请求处理必须已完成
。
用SocketServer创建一个服务器需要四步:
1、通过子类化BaseRequestHandler类和覆盖它的handle()方法来创建一个请求处理器类,用于处理进来
的请求;
2、实例化服务类如TCPServer,并传递给它参数:服务器地址和请求处理器类;
3、调用服务实例对象的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是一个非常强大的异步网络编程的框架)。
1、用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()
2、使用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
3、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是正确的选择。
一、打开远程文件
打开远程文件的操作和本地差不多,不同的是只能使用读模式,并且使用urllib模块的urlopen:
>>> from urllib import urlopen
>>> webpage=urlopen('http://www.python.org')
如果你在线的话,变量webpage现在就包含了一个关联Web页:http://www.python.org的文件类对象。
注意:如果你当前没有联网,而你又想练习一下urllib的话,你可以用如下形式访问本地文件:
localpage=urlopen(r'file:c: est.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/'
二、获取远程文件
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函数而不带任何参数,它将为你完成清除工作。
套接字
一、套接字
套接字是为特定网络协议(例如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。
二、套接字模块
套接字模块是一个非常简单的基于对象的接口,它提供对低层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方法来关闭连接。
三、一个简单的服务器和客户端通信的例子
服务器:
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
',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
',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()